diff options
Diffstat (limited to 'lib/solidus_subscriptions')
9 files changed, 275 insertions, 0 deletions
diff --git a/lib/solidus_subscriptions/checkout.rb b/lib/solidus_subscriptions/checkout.rb new file mode 100644 index 0000000..83fc35c --- /dev/null +++ b/lib/solidus_subscriptions/checkout.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + class Checkout + attr_reader :installment + + def initialize(installment) + @installment = installment + end + + def process + order = create_order + + begin + populate_order(order) + finalize_order(order) + + SolidusSubscriptions.configuration.success_dispatcher_class.new(installment, order).dispatch + rescue StateMachines::InvalidTransition + if order.payments.any?(&:failed?) + SolidusSubscriptions.configuration.payment_failed_dispatcher_class.new(installment, order).dispatch + else + SolidusSubscriptions.configuration.failure_dispatcher_class.new(installment, order).dispatch + end + rescue ::Spree::Order::InsufficientStock + SolidusSubscriptions.configuration.out_of_stock_dispatcher_class.new(installment, order).dispatch + end + + order + end + + private + + def create_order + ::Spree::Order.create( + user: installment.subscription.user, + email: installment.subscription.user.email, + store: installment.subscription.store || ::Spree::Store.default, + subscription_order: true, + subscription: installment.subscription + ) + end + + def populate_order(order) + installment.subscription.line_items.each do |line_item| + order.contents.add(line_item.subscribable, line_item.quantity) + end + end + + def finalize_order(order) + ::Spree::PromotionHandler::Cart.new(order).activate + order.recalculate + + order.checkout_steps[0...-1].each do + case order.state + when 'address' + order.ship_address = installment.subscription.shipping_address_to_use + order.bill_address = installment.subscription.billing_address_to_use + when 'payment' + order.payments.create( + payment_method: installment.subscription.payment_method_to_use, + source: installment.subscription.payment_source_to_use, + amount: order.total, + ) + end + + order.next! + end + + order.complete! + end + end +end diff --git a/lib/solidus_subscriptions/dispatcher/base.rb b/lib/solidus_subscriptions/dispatcher/base.rb new file mode 100644 index 0000000..30c5034 --- /dev/null +++ b/lib/solidus_subscriptions/dispatcher/base.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + module Dispatcher + class Base + attr_reader :installment, :order + + def initialize(installment, order) + @installment = installment + @order = order + end + + def dispatch + raise NotImplementedError + end + end + end +end diff --git a/lib/solidus_subscriptions/dispatcher/failure_dispatcher.rb b/lib/solidus_subscriptions/dispatcher/failure_dispatcher.rb new file mode 100644 index 0000000..54f8f2b --- /dev/null +++ b/lib/solidus_subscriptions/dispatcher/failure_dispatcher.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + module Dispatcher + class FailureDispatcher < Base + def dispatch + order.touch(:completed_at) + order.cancel + installment.failed!(order) + end + end + end +end diff --git a/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher.rb b/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher.rb new file mode 100644 index 0000000..fe3e701 --- /dev/null +++ b/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + module Dispatcher + class OutOfStockDispatcher < Base + def dispatch + installment.out_of_stock + end + end + end +end diff --git a/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb b/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb new file mode 100644 index 0000000..a56332e --- /dev/null +++ b/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + module Dispatcher + class PaymentFailedDispatcher < Base + def dispatch + order.touch(:completed_at) + order.cancel + installment.payment_failed!(order) + + ::Spree::Event.fire( + 'solidus_subscriptions.installment_failed_payment', + installment: installment, + order: order, + ) + end + end + end +end diff --git a/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb b/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb new file mode 100644 index 0000000..0ae71dc --- /dev/null +++ b/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + module Dispatcher + class SuccessDispatcher < Base + def dispatch + installment.success!(order) + + ::Spree::Event.fire( + 'solidus_subscriptions.installment_succeeded', + installment: installment, + order: order, + ) + end + end + end +end diff --git a/lib/solidus_subscriptions/line_item_builder.rb b/lib/solidus_subscriptions/line_item_builder.rb new file mode 100644 index 0000000..c9aedb9 --- /dev/null +++ b/lib/solidus_subscriptions/line_item_builder.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# This class is responsible for taking SubscriptionLineItems and building +# them into Spree::LineItems which can be added to an order +module SolidusSubscriptions + class LineItemBuilder + attr_reader :subscription_line_items + + # Get a new instance of a LineItemBuilder + # + # @param subscription_line_items[Array<SolidusSubscriptions::LineItem>] The + # subscription line item to be converted into a Spree::LineItem + # + # @return [SolidusSubscriptions::LineItemBuilder] + def initialize(subscription_line_items) + @subscription_line_items = subscription_line_items + end + + # Get a new (unpersisted) Spree::LineItem which matches the details of + # :subscription_line_item + # + # @return [Array<Spree::LineItem>] + def spree_line_items + line_items = subscription_line_items.map do |subscription_line_item| + variant = subscription_line_item.subscribable + + next unless variant.can_supply?(subscription_line_item.quantity) + + ::Spree::LineItem.new(variant: variant, quantity: subscription_line_item.quantity) + end + + # Either all line items for an installment are fulfilled or none are + line_items.all? ? line_items : [] + end + end +end diff --git a/lib/solidus_subscriptions/subscription_generator.rb b/lib/solidus_subscriptions/subscription_generator.rb new file mode 100644 index 0000000..8153912 --- /dev/null +++ b/lib/solidus_subscriptions/subscription_generator.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# This module is responsible for taking SolidusSubscriptions::LineItem +# objects and creating SolidusSubscriptions::Subscription Objects +module SolidusSubscriptions + module SubscriptionGenerator + extend self + + SubscriptionConfiguration = Struct.new(:interval_length, :interval_units, :end_date) + + # Create and persist a subscription for a collection of subscription + # line items + # + # @param subscription_line_items [Array<SolidusSubscriptions::LineItem>] The + # subscription_line_items to be activated + # + # @return [SolidusSubscriptions::Subscription] + def activate(subscription_line_items) + return if subscription_line_items.empty? + + order = subscription_line_items.first.order + configuration = subscription_configuration(subscription_line_items.first) + + subscription_attributes = { + user: order.user, + line_items: subscription_line_items, + store: order.store, + shipping_address: order.ship_address, + billing_address: order.bill_address, + payment_source: order.payments.valid.last&.payment_source, + payment_method: order.payments.valid.last&.payment_method, + **configuration.to_h + } + + Subscription.create!(subscription_attributes) do |sub| + sub.actionable_date = sub.next_actionable_date + end + end + + # Group a collection of line items by common subscription configuration + # options. Grouped subscription_line_items can belong to a single + # subscription. + # + # @param subscription_line_items [Array<SolidusSubscriptions::LineItem>] The + # subscription_line_items to be grouped. + # + # @return [Array<Array<SolidusSubscriptions::LineItem>>] + def group(subscription_line_items) + subscription_line_items.group_by do |li| + subscription_configuration(li) + end. + values + end + + private + + def subscription_configuration(subscription_line_item) + SubscriptionConfiguration.new( + subscription_line_item.interval_length, + subscription_line_item.interval_units, + subscription_line_item.end_date + ) + end + end +end diff --git a/lib/solidus_subscriptions/subscription_line_item_builder.rb b/lib/solidus_subscriptions/subscription_line_item_builder.rb new file mode 100644 index 0000000..7354102 --- /dev/null +++ b/lib/solidus_subscriptions/subscription_line_item_builder.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + module SubscriptionLineItemBuilder + private + + def create_subscription_line_item(line_item) + SolidusSubscriptions::LineItem.create!( + subscription_params.merge(spree_line_item: line_item) + ) + + # Rerun the promotion handler to pickup subscription promotions + ::Spree::PromotionHandler::Cart.new(line_item.order).activate + line_item.order.recalculate + end + + def subscription_params + params.require(:subscription_line_item).permit( + SolidusSubscriptions.configuration.subscription_line_item_attributes + ) + end + end +end |