diff options
Diffstat (limited to 'app')
17 files changed, 13 insertions, 494 deletions
diff --git a/app/jobs/solidus_subscriptions/process_installment_job.rb b/app/jobs/solidus_subscriptions/process_installment_job.rb new file mode 100644 index 0000000..127c787 --- /dev/null +++ b/app/jobs/solidus_subscriptions/process_installment_job.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusSubscriptions + class ProcessInstallmentJob < ApplicationJob + queue_as { SolidusSubscriptions.configuration.processing_queue } + + def perform(installment) + Checkout.new(installment).process + end + end +end diff --git a/app/jobs/solidus_subscriptions/process_installments_job.rb b/app/jobs/solidus_subscriptions/process_installments_job.rb deleted file mode 100644 index e7e1f63..0000000 --- a/app/jobs/solidus_subscriptions/process_installments_job.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -# This job is responsible for creating a consolidated installment from a -# list of installments and processing it. - -module SolidusSubscriptions - class ProcessInstallmentsJob < ApplicationJob - queue_as SolidusSubscriptions.configuration.processing_queue - - # Process a collection of installments - # - # @param installment_ids [Array<Integer>] The ids of the - # installments to be processed together and fulfilled by the same order - # - # @return [Spree::Order] The order which fulfills the list of installments - def perform(installment_ids) - return if installment_ids.empty? - - installments = SolidusSubscriptions::Installment.where(id: installment_ids). - includes(subscription: [:line_items, :user]) - Checkout.new(installments).process - end - end -end diff --git a/app/models/solidus_subscriptions/installment.rb b/app/models/solidus_subscriptions/installment.rb index 0b5f627..aed1f62 100644 --- a/app/models/solidus_subscriptions/installment.rb +++ b/app/models/solidus_subscriptions/installment.rb @@ -31,13 +31,6 @@ module SolidusSubscriptions unfulfilled.where("#{table_name}.actionable_date <= ?", Time.zone.today) end) - # Get the builder for the subscription_line_item. This will be an - # object that can generate the appropriate line item for the subscribable - # object - # - # @return [SolidusSubscriptions::LineItemBuilder] - delegate :line_item_builder, to: :subscription - # Mark this installment as out of stock. # # @return [SolidusSubscriptions::InstallmentDetail] The record of the failed diff --git a/app/models/solidus_subscriptions/line_item.rb b/app/models/solidus_subscriptions/line_item.rb index 64e6fb0..2583ecc 100644 --- a/app/models/solidus_subscriptions/line_item.rb +++ b/app/models/solidus_subscriptions/line_item.rb @@ -38,34 +38,5 @@ module SolidusSubscriptions validates :subscribable_id, presence: true validates :quantity, numericality: { greater_than: 0 } validates :interval_length, numericality: { greater_than: 0 }, unless: -> { subscription } - - def as_json(**options) - options[:methods] ||= [:dummy_line_item] - super(options) - end - - # Get a placeholder line item for calculating the values of future - # subscription orders. It is frozen and cannot be saved - def dummy_line_item - li = LineItemBuilder.new([self]).spree_line_items.first - return unless li - - li.order = dummy_order - li.validate - li.freeze - end - - private - - # Get a placeholder order for calculating the values of future - # subscription orders. It is a frozen duplicate of the current order and - # cannot be saved - def dummy_order - order = spree_line_item ? spree_line_item.order.dup : ::Spree::Order.create - order.ship_address = subscription.shipping_address || subscription.user.ship_address if subscription - order.bill_address = subscription.billing_address || subscription.user.bill_address if subscription - - order.freeze - end end end diff --git a/app/models/solidus_subscriptions/subscription.rb b/app/models/solidus_subscriptions/subscription.rb index 4168973..15424d8 100644 --- a/app/models/solidus_subscriptions/subscription.rb +++ b/app/models/solidus_subscriptions/subscription.rb @@ -203,15 +203,6 @@ module SolidusSubscriptions actionable_date end - # Get the builder for the subscription_line_item. This will be an - # object that can generate the appropriate line item for the subscribable - # object - # - # @return [SolidusSubscriptions::LineItemBuilder] - def line_item_builder - LineItemBuilder.new(line_items) - end - # The state of the last attempt to process an installment associated to # this subscription # diff --git a/app/services/solidus_subscriptions/checkout.rb b/app/services/solidus_subscriptions/checkout.rb deleted file mode 100644 index c40849a..0000000 --- a/app/services/solidus_subscriptions/checkout.rb +++ /dev/null @@ -1,155 +0,0 @@ -# frozen_string_literal: true - -# This class takes a collection of installments and populates a new spree -# order with the correct contents based on the subscriptions associated to the -# intallments. This is to group together subscriptions being -# processed on the same day for a specific user -module SolidusSubscriptions - class Checkout - # @return [Array<Installment>] The collection of installments to be used - # when generating a new order - attr_reader :installments - - delegate :user, to: :subscription - - # Get a new instance of a Checkout - # - # @param installments [Array<Installment>] The collection of installments - # to be used when generating a new order - def initialize(installments) - @installments = installments - raise UserMismatchError.new(installments) if different_owners? - end - - # Generate a new Spree::Order based on the information associated to the - # installments - # - # @return [Spree::Order] - def process - populate - - # Installments are removed and set for future processing if they are - # out of stock. If there are no line items left there is nothing to do - return if installments.empty? - - if checkout - SolidusSubscriptions.configuration.success_dispatcher_class.new(installments, order).dispatch - return order - end - - # A new order will only have 1 payment that we created - if order.payments.any?(&:failed?) - SolidusSubscriptions.configuration.payment_failed_dispatcher_class.new(installments, order).dispatch - installments.clear - nil - end - ensure - # Any installments that failed to be processed will be reprocessed - unfulfilled_installments = installments.select(&:unfulfilled?) - if unfulfilled_installments.any? - SolidusSubscriptions.configuration.failure_dispatcher_class. - new(unfulfilled_installments, order).dispatch - end - end - - # The order fulfilling the consolidated installment - # - # @return [Spree::Order] - def order - @order ||= ::Spree::Order.create( - user: user, - email: user.email, - store: subscription.store || ::Spree::Store.default, - subscription_order: true, - subscription: subscription - ) - end - - private - - def checkout - order.recalculate - apply_promotions - - order.checkout_steps[0...-1].each do - case order.state - when "address" - order.ship_address = ship_address - order.bill_address = bill_address - when "payment" - create_payment - end - - order.next! - end - - # Do this as a separate "quiet" transition so that it returns true or - # false rather than raising a failed transition error - order.complete - end - - def populate - unfulfilled_installments = [] - - order_line_items = installments.flat_map do |installment| - line_items = installment.line_item_builder.spree_line_items - - unfulfilled_installments.push(installment) if line_items.empty? - - line_items - end - - # Remove installments which had no stock from the active list - # They will be reprocessed later - @installments -= unfulfilled_installments - if unfulfilled_installments.any? - SolidusSubscriptions.configuration.out_of_stock_dispatcher_class.new(unfulfilled_installments).dispatch - end - - return if installments.empty? - - order_builder.add_line_items(order_line_items) - end - - def order_builder - @order_builder ||= OrderBuilder.new(order) - end - - def subscription - installments.first.subscription - end - - def ship_address - subscription.shipping_address_to_use - end - - def bill_address - subscription.billing_address_to_use - end - - def payment_source - subscription.payment_source_to_use - end - - def payment_method - subscription.payment_method_to_use - end - - def create_payment - order.payments.create( - source: payment_source, - amount: order.total, - payment_method: payment_method, - ) - end - - def apply_promotions - ::Spree::PromotionHandler::Cart.new(order).activate - order.updater.update # reload totals - end - - def different_owners? - installments.map { |i| i.subscription.user }.uniq.length > 1 - end - end -end diff --git a/app/services/solidus_subscriptions/dispatcher.rb b/app/services/solidus_subscriptions/dispatcher.rb deleted file mode 100644 index 0472f79..0000000 --- a/app/services/solidus_subscriptions/dispatcher.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module SolidusSubscriptions - class Dispatcher - attr_reader :installments, :order - - # Returns a new instance of this dispatcher. - # - # @param installments [Array<SolidusSubscriptions::Installment>] The installments to process - # with this dispatcher - # @param order [Spree::Order] The order that was generated as a result of these installments - # - # @return [SolidusSubscriptions::Dispatcher] - def initialize(installments, order = nil) - @installments = installments - @order = order - end - - def dispatch - raise NotImplementedError - end - end -end diff --git a/app/services/solidus_subscriptions/failure_dispatcher.rb b/app/services/solidus_subscriptions/failure_dispatcher.rb deleted file mode 100644 index c77d4b0..0000000 --- a/app/services/solidus_subscriptions/failure_dispatcher.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -# Handles failed installments. -module SolidusSubscriptions - class FailureDispatcher < Dispatcher - def dispatch - order.touch(:completed_at) - order.cancel - installments.each do |installment| - installment.failed!(order) - end - end - end -end diff --git a/app/services/solidus_subscriptions/line_item_builder.rb b/app/services/solidus_subscriptions/line_item_builder.rb deleted file mode 100644 index c9aedb9..0000000 --- a/app/services/solidus_subscriptions/line_item_builder.rb +++ /dev/null @@ -1,36 +0,0 @@ -# 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/app/services/solidus_subscriptions/order_builder.rb b/app/services/solidus_subscriptions/order_builder.rb deleted file mode 100644 index a577e98..0000000 --- a/app/services/solidus_subscriptions/order_builder.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -# This class is responsible for adding line items to order without going -# through order contents. -module SolidusSubscriptions - class OrderBuilder - attr_reader :order - - # Get a new instance of an OrderBuilder - # - # @param order [Spree::Order] The order to be built - # - # @return [SolidusSubscriptions::OrderBuilder] - def initialize(order) - @order = order - end - - # Add line items to an order. If the order already - # has a line item for a given variant_id, update the quantity. Otherwise - # add the line item to the order. - # - # @param items [Array<Spree::LineItem>] The order to add the line item to - # @return [Array<Spree::LineItem] The collection that was passed in - def add_line_items(items) - items.map { |item| add_item_to_order(item) } - end - - private - - def add_item_to_order(new_item) - line_item = order.line_items.detect do |li| - li.variant_id == new_item.variant_id - end - - if line_item - line_item.increment!(:quantity, new_item.quantity) - else - order.line_items << new_item - end - end - end -end diff --git a/app/services/solidus_subscriptions/out_of_stock_dispatcher.rb b/app/services/solidus_subscriptions/out_of_stock_dispatcher.rb deleted file mode 100644 index 05484f4..0000000 --- a/app/services/solidus_subscriptions/out_of_stock_dispatcher.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -# Handles installments that cannot be processed for lack of stock. -module SolidusSubscriptions - class OutOfStockDispatcher < Dispatcher - def dispatch - installments.each(&:out_of_stock) - end - end -end diff --git a/app/services/solidus_subscriptions/payment_failed_dispatcher.rb b/app/services/solidus_subscriptions/payment_failed_dispatcher.rb deleted file mode 100644 index 29eb291..0000000 --- a/app/services/solidus_subscriptions/payment_failed_dispatcher.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -# Handles payment failures for subscription installments. -module SolidusSubscriptions - class PaymentFailedDispatcher < Dispatcher - def dispatch - order.touch(:completed_at) - order.cancel - installments.each do |installment| - installment.payment_failed!(order) - end - - ::Spree::Event.fire( - 'solidus_subscriptions.installments_failed_payment', - installments: installments, - order: order, - ) - end - end -end diff --git a/app/services/solidus_subscriptions/subscription_generator.rb b/app/services/solidus_subscriptions/subscription_generator.rb deleted file mode 100644 index 8153912..0000000 --- a/app/services/solidus_subscriptions/subscription_generator.rb +++ /dev/null @@ -1,65 +0,0 @@ -# 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/app/services/solidus_subscriptions/subscription_line_item_builder.rb b/app/services/solidus_subscriptions/subscription_line_item_builder.rb deleted file mode 100644 index 7354102..0000000 --- a/app/services/solidus_subscriptions/subscription_line_item_builder.rb +++ /dev/null @@ -1,23 +0,0 @@ -# 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 diff --git a/app/services/solidus_subscriptions/success_dispatcher.rb b/app/services/solidus_subscriptions/success_dispatcher.rb deleted file mode 100644 index ce55266..0000000 --- a/app/services/solidus_subscriptions/success_dispatcher.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -# Handles installments that are processed successfully. -module SolidusSubscriptions - class SuccessDispatcher < Dispatcher - def dispatch - installments.each do |installment| - installment.success!(order) - end - - ::Spree::Event.fire( - 'solidus_subscriptions.installments_succeeded', - installments: installments, - order: order, - ) - end - end -end diff --git a/app/services/solidus_subscriptions/user_mismatch_error.rb b/app/services/solidus_subscriptions/user_mismatch_error.rb deleted file mode 100644 index 1d227ca..0000000 --- a/app/services/solidus_subscriptions/user_mismatch_error.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module SolidusSubscriptions - class UserMismatchError < StandardError - def initialize(installments) - @installments = installments - end - - def to_s - <<-MSG.squish - Installments must have the same user to be processed as a consolidated - installment. Could not process installments: - #{@installments.map(&:id).join(', ')} - MSG - end - end -end diff --git a/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb b/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb index aa9dfa4..3454003 100644 --- a/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb +++ b/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb @@ -6,8 +6,8 @@ module SolidusSubscriptions event_action :report_subscription_cancellation, event_name: 'solidus_subscriptions.subscription_canceled' event_action :report_subscription_ending, event_name: 'solidus_subscriptions.subscription_ended' - event_action :report_payment_success, event_name: 'solidus_subscriptions.installments_succeeded' - event_action :report_payment_failure, event_name: 'solidus_subscriptions.installments_failed_payment' + event_action :report_payment_success, event_name: 'solidus_subscriptions.installment_succeeded' + event_action :report_payment_failure, event_name: 'solidus_subscriptions.installment_failed_payment' event_action :report_payment_method_change, event_name: 'solidus_subscriptions.subscription_payment_method_changed' def report_subscription_cancellation(event) |