summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessandro Desantis <desa.alessandro@gmail.com>2021-02-05 13:30:47 +0100
committerGitHub <noreply@github.com>2021-02-05 13:30:47 +0100
commit3a3657b79bd5d98025136da4b39751e6a43b7b9f (patch)
treeb7a57b183641d64d05e5e70e281fd78cb5e37ebd
parent69f0ca038b66d0ca36971405e51c0d3aa916cf19 (diff)
parent4717d24c0e7203210efe8c23b55467c3e728cdc5 (diff)
Merge pull request #172 from solidusio-contrib/aldesantis/refactoring
Processor and Checkout refactoring
-rw-r--r--.rubocop.yml3
-rw-r--r--README.md3
-rw-r--r--app/jobs/solidus_subscriptions/process_installment_job.rb11
-rw-r--r--app/jobs/solidus_subscriptions/process_installments_job.rb24
-rw-r--r--app/models/solidus_subscriptions/installment.rb7
-rw-r--r--app/models/solidus_subscriptions/line_item.rb29
-rw-r--r--app/models/solidus_subscriptions/subscription.rb9
-rw-r--r--app/services/solidus_subscriptions/checkout.rb155
-rw-r--r--app/services/solidus_subscriptions/dispatcher.rb23
-rw-r--r--app/services/solidus_subscriptions/failure_dispatcher.rb14
-rw-r--r--app/services/solidus_subscriptions/line_item_builder.rb36
-rw-r--r--app/services/solidus_subscriptions/order_builder.rb42
-rw-r--r--app/services/solidus_subscriptions/out_of_stock_dispatcher.rb10
-rw-r--r--app/services/solidus_subscriptions/payment_failed_dispatcher.rb20
-rw-r--r--app/services/solidus_subscriptions/success_dispatcher.rb18
-rw-r--r--app/services/solidus_subscriptions/user_mismatch_error.rb17
-rw-r--r--app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb4
-rw-r--r--lib/generators/solidus_subscriptions/install/templates/initializer.rb8
-rw-r--r--lib/solidus_subscriptions.rb8
-rw-r--r--lib/solidus_subscriptions/checkout.rb73
-rw-r--r--lib/solidus_subscriptions/configuration.rb8
-rw-r--r--lib/solidus_subscriptions/dispatcher/base.rb18
-rw-r--r--lib/solidus_subscriptions/dispatcher/failure_dispatcher.rb13
-rw-r--r--lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher.rb11
-rw-r--r--lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb19
-rw-r--r--lib/solidus_subscriptions/dispatcher/success_dispatcher.rb17
-rw-r--r--lib/solidus_subscriptions/processor.rb110
-rw-r--r--lib/solidus_subscriptions/subscription_generator.rb (renamed from app/services/solidus_subscriptions/subscription_generator.rb)0
-rw-r--r--lib/solidus_subscriptions/subscription_line_item_builder.rb (renamed from app/services/solidus_subscriptions/subscription_line_item_builder.rb)0
-rw-r--r--lib/solidus_subscriptions/testing_support/factories/installment_factory.rb4
-rw-r--r--spec/jobs/solidus_subscriptions/process_installment_job_spec.rb11
-rw-r--r--spec/jobs/solidus_subscriptions/process_installments_job_spec.rb28
-rw-r--r--spec/lib/solidus_subscriptions/checkout_spec.rb122
-rw-r--r--spec/lib/solidus_subscriptions/dispatcher/failure_dispatcher_spec.rb27
-rw-r--r--spec/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher_spec.rb13
-rw-r--r--spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb42
-rw-r--r--spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb28
-rw-r--r--spec/lib/solidus_subscriptions/processor_spec.rb202
-rw-r--r--spec/lib/solidus_subscriptions/subscription_generator_spec.rb (renamed from spec/services/solidus_subscriptions/subscription_generator_spec.rb)0
-rw-r--r--spec/lib/solidus_subscriptions/subscription_order_promotion_rule_spec.rb (renamed from spec/services/solidus_subscriptions/subscription_order_promotion_rule_spec.rb)0
-rw-r--r--spec/lib/solidus_subscriptions/subscription_promotion_rule_spec.rb (renamed from spec/services/solidus_subscriptions/subscription_promotion_rule_spec.rb)0
-rw-r--r--spec/models/solidus_subscriptions/installment_spec.rb9
-rw-r--r--spec/models/solidus_subscriptions/line_item_spec.rb86
-rw-r--r--spec/models/solidus_subscriptions/subscription_spec.rb10
-rw-r--r--spec/services/solidus_subscriptions/checkout_spec.rb389
-rw-r--r--spec/services/solidus_subscriptions/dispatcher_spec.rb11
-rw-r--r--spec/services/solidus_subscriptions/failure_dispatcher_spec.rb27
-rw-r--r--spec/services/solidus_subscriptions/line_item_builder_spec.rb33
-rw-r--r--spec/services/solidus_subscriptions/order_builder_spec.rb53
-rw-r--r--spec/services/solidus_subscriptions/out_of_stock_dispatcher_spec.rb12
-rw-r--r--spec/services/solidus_subscriptions/payment_failed_dispatcher_spec.rb42
-rw-r--r--spec/services/solidus_subscriptions/success_dispatcher_spec.rb28
-rw-r--r--spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb12
53 files changed, 537 insertions, 1362 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index fcd4bc4..97c9838 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -7,3 +7,6 @@ RSpec/DescribeClass:
Exclude:
- spec/requests/**/*
- spec/features/**/*
+
+Rails/SkipsModelValidations:
+ Enabled: false
diff --git a/README.md b/README.md
index 2705c2d..6c3ed60 100644
--- a/README.md
+++ b/README.md
@@ -62,8 +62,7 @@ This will associate a `SolidusSubscriptions::LineItem` to the line item being ad
The customer will not be charged for the subscription until it is processed. The subscription line
items should be shown to the user on the cart page by looping over
-`Spree::Order#subscription_line_items`. `SolidusSubscriptions::LineItem#dummy_line_item` may be
-useful to help you display the subscription line item with your existing cart infrastructure.
+`Spree::Order#subscription_line_items`.
When the order is finalized, a `SolidusSubscriptions::Subscription` will be created for each group
of subscription line items which can be fulfilled by a single subscription.
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/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)
diff --git a/lib/generators/solidus_subscriptions/install/templates/initializer.rb b/lib/generators/solidus_subscriptions/install/templates/initializer.rb
index faf03f4..31e44b5 100644
--- a/lib/generators/solidus_subscriptions/install/templates/initializer.rb
+++ b/lib/generators/solidus_subscriptions/install/templates/initializer.rb
@@ -32,16 +32,16 @@ SolidusSubscriptions.configure do |config|
# subclass from the the dispatcher you are replacing and call `super` from within `#dispatch`.
# This handler is called when a subscription order is successfully placed.
- # config.success_dispatcher_class = 'SolidusSubscriptions::SuccessDispatcher'
+ # config.success_dispatcher_class = 'SolidusSubscriptions::Dispatcher::SuccessDispatcher'
# This handler is called when a group of installments fails to be processed.
- # config.failure_dispatcher_class = 'SolidusSubscriptions::FailureDispatcher'
+ # config.failure_dispatcher_class = 'SolidusSubscriptions::Dispatcher::FailureDispatcher'
# This handler is called when a payment fails on a subscription order.
- # config.payment_failed_dispatcher_class = 'SolidusSubscriptions::PaymentFailedDispatcher'
+ # config.payment_failed_dispatcher_class = 'SolidusSubscriptions::Dispatcher::PaymentFailedDispatcher'
# This handler is called when there isn't enough stock to fulfill an installment.
- # config.out_of_stock_dispatcher = 'SolidusSubscriptions::OutOfStockDispatcher'
+ # config.out_of_stock_dispatcher = 'SolidusSubscriptions::Dispatcher::OutOfStockDispatcher'
# ===================================== Permitted attributes =====================================
#
diff --git a/lib/solidus_subscriptions.rb b/lib/solidus_subscriptions.rb
index e9dae93..d5e3d73 100644
--- a/lib/solidus_subscriptions.rb
+++ b/lib/solidus_subscriptions.rb
@@ -18,6 +18,14 @@ require 'solidus_subscriptions/churn_buster/subscription_customer_serializer'
require 'solidus_subscriptions/churn_buster/subscription_payment_method_serializer'
require 'solidus_subscriptions/churn_buster/subscription_serializer'
require 'solidus_subscriptions/churn_buster/order_serializer'
+require 'solidus_subscriptions/checkout'
+require 'solidus_subscriptions/subscription_generator'
+require 'solidus_subscriptions/subscription_line_item_builder'
+require 'solidus_subscriptions/dispatcher/base'
+require 'solidus_subscriptions/dispatcher/failure_dispatcher'
+require 'solidus_subscriptions/dispatcher/out_of_stock_dispatcher'
+require 'solidus_subscriptions/dispatcher/payment_failed_dispatcher'
+require 'solidus_subscriptions/dispatcher/success_dispatcher'
module SolidusSubscriptions
class << self
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/configuration.rb b/lib/solidus_subscriptions/configuration.rb
index 52c8fd4..536aad3 100644
--- a/lib/solidus_subscriptions/configuration.rb
+++ b/lib/solidus_subscriptions/configuration.rb
@@ -15,22 +15,22 @@ module SolidusSubscriptions
)
def success_dispatcher_class
- @success_dispatcher_class ||= 'SolidusSubscriptions::SuccessDispatcher'
+ @success_dispatcher_class ||= 'SolidusSubscriptions::Dispatcher::SuccessDispatcher'
@success_dispatcher_class.constantize
end
def failure_dispatcher_class
- @failure_dispatcher_class ||= 'SolidusSubscriptions::FailureDispatcher'
+ @failure_dispatcher_class ||= 'SolidusSubscriptions::Dispatcher::FailureDispatcher'
@failure_dispatcher_class.constantize
end
def payment_failed_dispatcher_class
- @payment_failed_dispatcher_class ||= 'SolidusSubscriptions::PaymentFailedDispatcher'
+ @payment_failed_dispatcher_class ||= 'SolidusSubscriptions::Dispatcher::PaymentFailedDispatcher'
@payment_failed_dispatcher_class.constantize
end
def out_of_stock_dispatcher_class
- @out_of_stock_dispatcher_class ||= 'SolidusSubscriptions::OutOfStockDispatcher'
+ @out_of_stock_dispatcher_class ||= 'SolidusSubscriptions::Dispatcher::OutOfStockDispatcher'
@out_of_stock_dispatcher_class.constantize
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/processor.rb b/lib/solidus_subscriptions/processor.rb
index d56816a..ffc274b 100644
--- a/lib/solidus_subscriptions/processor.rb
+++ b/lib/solidus_subscriptions/processor.rb
@@ -1,116 +1,36 @@
# frozen_string_literal: true
-# This class is responsible for finding subscriptions and installments
-# which need to be processed. It will group them together by user and attempts
-# to process them together. Subscriptions will also be grouped by their
-# shiping address id.
-#
-# This class passes the reponsibility of actually creating the order off onto
-# the consolidated installment class.
-#
-# This class generates `ProcessInstallmentsJob`s
module SolidusSubscriptions
class Processor
class << self
- # Find all actionable subscriptions and intallments, group them together
- # by user, and schedule a processing job for the group as a batch
def run
- batched_users_to_be_processed.each { |batch| new(batch).build_jobs }
+ SolidusSubscriptions::Subscription.actionable.find_each(&method(:process_subscription))
+ SolidusSubscriptions::Installment.actionable.find_each(&method(:process_installment))
end
private
- def batched_users_to_be_processed
- subscriptions = SolidusSubscriptions::Subscription.arel_table
- installments = SolidusSubscriptions::Installment.arel_table
+ def process_subscription(subscription)
+ ActiveRecord::Base.transaction do
+ subscription.successive_skip_count = 0
+ subscription.advance_actionable_date
- ::Spree.user_class.
- joins(:subscriptions).
- joins(
- subscriptions.
- join(installments, Arel::Nodes::OuterJoin).
- on(subscriptions[:id].eq(installments[:subscription_id])).
- join_sources
- ).
- where(
- SolidusSubscriptions::Subscription.actionable.arel.constraints.reduce(:and).
- or(SolidusSubscriptions::Installment.actionable.with_active_subscription.arel.constraints.reduce(:and))
- ).
- distinct.
- find_in_batches
- end
- end
-
- # @return [Array<Spree.user_class>]
- attr_reader :users
-
- # Get a new instance of the SolidusSubscriptions::Processor
- #
- # @param users [Array<Spree.user_class>] A list of users with actionable
- # subscriptions or installments
- #
- # @return [SolidusSubscriptions::Processor]
- def initialize(users)
- @users = users
- @installments = {}
- end
-
- # Create `ProcessInstallmentsJob`s for the users used to initalize the
- # instance
- def build_jobs
- users.map do |user|
- installemts_by_address_and_user = installments(user).group_by do |i|
- [i.subscription.shipping_address_id, i.subscription.billing_address_id]
- end
-
- installemts_by_address_and_user.each_value do |grouped_installments|
- ProcessInstallmentsJob.perform_later grouped_installments.map(&:id)
- end
- end
- end
+ subscription.cancel! if subscription.pending_cancellation?
+ subscription.deactivate! if subscription.can_be_deactivated?
- private
-
- def subscriptions_by_id
- @subscriptions_by_id ||= Subscription.
- actionable.
- includes(:line_items, :user).
- where(user_id: user_ids).
- group_by(&:user_id)
- end
-
- def retry_installments
- @failed_installments ||= Installment.
- actionable.
- includes(:subscription).
- where(solidus_subscriptions_subscriptions: { user_id: user_ids }).
- group_by { |i| i.subscription.user_id }
- end
-
- def installments(user)
- @installments[user.id] ||= retry_installments.fetch(user.id, []) + new_installments(user)
- end
-
- def new_installments(user)
- ActiveRecord::Base.transaction do
- subscriptions_by_id.fetch(user.id, []).map do |sub|
- sub.successive_skip_count = 0
- sub.advance_actionable_date
- sub.cancel! if sub.pending_cancellation?
- sub.deactivate! if sub.can_be_deactivated?
if SolidusSubscriptions.configuration.clear_past_installments
- sub.installments.unfulfilled.each do |installment|
- installment.actionable_date = nil
- installment.save!
+ subscription.installments.unfulfilled.actionable.each do |installment|
+ installment.update!(actionable_date: nil)
end
end
- sub.installments.create!
+
+ subscription.installments.create!(actionable_date: Time.zone.now)
end
end
- end
- def user_ids
- @user_ids ||= users.map(&:id)
+ def process_installment(installment)
+ ProcessInstallmentJob.perform_later(installment)
+ end
end
end
end
diff --git a/app/services/solidus_subscriptions/subscription_generator.rb b/lib/solidus_subscriptions/subscription_generator.rb
index 8153912..8153912 100644
--- a/app/services/solidus_subscriptions/subscription_generator.rb
+++ b/lib/solidus_subscriptions/subscription_generator.rb
diff --git a/app/services/solidus_subscriptions/subscription_line_item_builder.rb b/lib/solidus_subscriptions/subscription_line_item_builder.rb
index 7354102..7354102 100644
--- a/app/services/solidus_subscriptions/subscription_line_item_builder.rb
+++ b/lib/solidus_subscriptions/subscription_line_item_builder.rb
diff --git a/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb b/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb
index 2eb5069..20dcc04 100644
--- a/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb
+++ b/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb
@@ -21,5 +21,9 @@ FactoryBot.define do
[association(:installment_detail, :success, installment: @instance, order: order)]
end
end
+
+ trait :actionable do
+ actionable_date { Time.zone.now }
+ end
end
end
diff --git a/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb b/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb
new file mode 100644
index 0000000..4a33819
--- /dev/null
+++ b/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb
@@ -0,0 +1,11 @@
+RSpec.describe SolidusSubscriptions::ProcessInstallmentJob do
+ it 'processes checkout for the installment' do
+ installment = build_stubbed(:installment)
+ checkout = instance_spy(SolidusSubscriptions::Checkout)
+ allow(SolidusSubscriptions::Checkout).to receive(:new).with(installment).and_return(checkout)
+
+ described_class.perform_now(installment)
+
+ expect(checkout).to have_received(:process)
+ end
+end
diff --git a/spec/jobs/solidus_subscriptions/process_installments_job_spec.rb b/spec/jobs/solidus_subscriptions/process_installments_job_spec.rb
deleted file mode 100644
index 111fc05..0000000
--- a/spec/jobs/solidus_subscriptions/process_installments_job_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe SolidusSubscriptions::ProcessInstallmentsJob do
- let(:root_order) { create :completed_order_with_pending_payment }
- let(:installments) do
- traits = {
- subscription_traits: [{
- user: root_order.user,
- line_item_traits: [{
- spree_line_item: root_order.line_items.first
- }]
- }]
- }
-
- create_list(:installment, 2, traits)
- end
-
- describe '#perform' do
- subject { described_class.new.perform(installments) }
-
- it 'processes the consolidated installment' do
- expect_any_instance_of(SolidusSubscriptions::Checkout).
- to receive(:process).once
-
- subject
- end
- end
-end
diff --git a/spec/lib/solidus_subscriptions/checkout_spec.rb b/spec/lib/solidus_subscriptions/checkout_spec.rb
new file mode 100644
index 0000000..2a83d9b
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/checkout_spec.rb
@@ -0,0 +1,122 @@
+RSpec.describe SolidusSubscriptions::Checkout, :checkout do
+ context 'when the order can be created and paid' do
+ it 'creates and finalizes a new order for the installment' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable)
+
+ order = described_class.new(installment).process
+
+ expect(order).to be_complete
+ expect(order).to be_paid
+ end
+
+ # rubocop:disable RSpec/MultipleExpectations
+ it 'copies basic information from the subscription' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable)
+ subscription = installment.subscription
+
+ order = described_class.new(installment).process
+
+ expect(order.ship_address.value_attributes).to eq(subscription.shipping_address_to_use.value_attributes)
+ expect(order.bill_address.value_attributes).to eq(subscription.billing_address_to_use.value_attributes)
+ expect(order.payments.first.payment_method).to eq(subscription.payment_method_to_use)
+ expect(order.payments.first.source).to eq(subscription.payment_source_to_use)
+ expect(order.user).to eq(subscription.user)
+ expect(order.email).to eq(subscription.user.email)
+ end
+ # rubocop:enable RSpec/MultipleExpectations
+
+ it 'marks the order as a subscription order' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable)
+ subscription = installment.subscription
+
+ order = described_class.new(installment).process
+
+ expect(order.subscription).to eq(subscription)
+ expect(order.subscription_order).to eq(true)
+ end
+
+ it 'matches the total on the subscription' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable)
+ subscription = installment.subscription
+
+ order = described_class.new(installment).process
+
+ expect(order.item_total).to eq(subscription.line_items.first.subscribable.price)
+ end
+
+ it 'calls the success dispatcher' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable)
+ success_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::SuccessDispatcher, installment)
+
+ described_class.new(installment).process
+
+ expect(success_dispatcher).to have_received(:dispatch)
+ end
+ end
+
+ context 'when payment of the order fails' do
+ it 'calls the payment failed dispatcher' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable).tap do |i|
+ i.subscription.update!(payment_source: create(:credit_card, number: '4111123412341234'))
+ end
+ payment_failed_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::PaymentFailedDispatcher, installment)
+
+ described_class.new(installment).process
+
+ expect(payment_failed_dispatcher).to have_received(:dispatch)
+ end
+ end
+
+ context 'when an item is out of stock' do
+ it 'calls the out of stock dispatcher' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable).tap do |i|
+ i.subscription.line_items.first.subscribable.stock_items.each do |stock_item|
+ stock_item.update!(backorderable: false)
+ end
+ end
+ out_of_stock_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::OutOfStockDispatcher, installment)
+
+ described_class.new(installment).process
+
+ expect(out_of_stock_dispatcher).to have_received(:dispatch)
+ end
+ end
+
+ context 'when a generic transition error happens during checkout' do
+ it 'calls the failure dispatcher' do
+ stub_spree_preferences(auto_capture: true)
+ installment = create(:installment, :actionable)
+ failure_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::FailureDispatcher, installment)
+ # rubocop:disable RSpec/AnyInstance
+ allow_any_instance_of(Spree::Order).to receive(:next!)
+ .and_raise(StateMachines::InvalidTransition.new(
+ Spree::Order.new,
+ Spree::Order.state_machines[:state],
+ :next,
+ ))
+ # rubocop:enable RSpec/AnyInstance
+
+ described_class.new(installment).process
+
+ expect(failure_dispatcher).to have_received(:dispatch)
+ end
+ end
+
+ private
+
+ def stub_dispatcher(klass, installment)
+ instance_spy(klass).tap do |dispatcher|
+ allow(klass).to receive(:new).with(
+ installment,
+ an_instance_of(Spree::Order)
+ ).and_return(dispatcher)
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/dispatcher/failure_dispatcher_spec.rb b/spec/lib/solidus_subscriptions/dispatcher/failure_dispatcher_spec.rb
new file mode 100644
index 0000000..f247237
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/dispatcher/failure_dispatcher_spec.rb
@@ -0,0 +1,27 @@
+RSpec.describe SolidusSubscriptions::Dispatcher::FailureDispatcher do
+ describe '#dispatch' do
+ it 'marks the installment as failed' do
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(installment).to have_received(:failed!).with(order)
+ end
+
+ it 'cancels the order' do
+ if Spree.solidus_gem_version > Gem::Version.new('2.10')
+ skip 'Orders in `cart` state cannot be canceled starting from Solidus 2.11.'
+ end
+
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(order.state).to eq('canceled')
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher_spec.rb b/spec/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher_spec.rb
new file mode 100644
index 0000000..abb8343
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher_spec.rb
@@ -0,0 +1,13 @@
+RSpec.describe SolidusSubscriptions::Dispatcher::OutOfStockDispatcher do
+ describe '#dispatch' do
+ it 'marks the installment as out of stock' do
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = build_stubbed(:order)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(installment).to have_received(:out_of_stock)
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb b/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb
new file mode 100644
index 0000000..0a222d4
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb
@@ -0,0 +1,42 @@
+RSpec.describe SolidusSubscriptions::Dispatcher::PaymentFailedDispatcher do
+ describe '#dispatch' do
+ it 'marks the installment as payment_failed' do
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(installment).to have_received(:payment_failed!).with(order)
+ end
+
+ it 'cancels the order' do
+ if Spree.solidus_gem_version > Gem::Version.new('2.10')
+ skip 'Orders in `cart` state cannot be canceled starting from Solidus 2.11.'
+ end
+
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(order.state).to eq('canceled')
+ end
+
+ it 'fires an installments_failed_payment event' do
+ stub_const('Spree::Event', class_spy(Spree::Event))
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(Spree::Event).to have_received(:fire).with(
+ 'solidus_subscriptions.installment_failed_payment',
+ installment: installment,
+ order: order,
+ )
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb b/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb
new file mode 100644
index 0000000..6a71800
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb
@@ -0,0 +1,28 @@
+RSpec.describe SolidusSubscriptions::Dispatcher::SuccessDispatcher do
+ describe '#dispatch' do
+ it 'marks the installment as success' do
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(installment).to have_received(:success!).with(order)
+ end
+
+ it 'fires an installments_succeeded event' do
+ stub_const('Spree::Event', class_spy(Spree::Event))
+ installment = instance_spy(SolidusSubscriptions::Installment)
+ order = create(:order_with_line_items)
+
+ dispatcher = described_class.new(installment, order)
+ dispatcher.dispatch
+
+ expect(Spree::Event).to have_received(:fire).with(
+ 'solidus_subscriptions.installment_succeeded',
+ installment: installment,
+ order: order,
+ )
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/processor_spec.rb b/spec/lib/solidus_subscriptions/processor_spec.rb
index d3bb268..e82c0e9 100644
--- a/spec/lib/solidus_subscriptions/processor_spec.rb
+++ b/spec/lib/solidus_subscriptions/processor_spec.rb
@@ -1,156 +1,124 @@
-require 'spec_helper'
-
RSpec.describe SolidusSubscriptions::Processor, :checkout do
- include ActiveJob::TestHelper
- around { |e| perform_enqueued_jobs { e.run } }
-
- let!(:user) { create(:user, :subscription_user) }
- let!(:credit_card) {
- card = create(:credit_card, gateway_customer_profile_id: 'BGS-123', user: user)
- wallet_payment_source = user.wallet.add(card)
- user.wallet.default_wallet_payment_source = wallet_payment_source
- user.save
- card
- }
-
- let!(:actionable_subscriptions) { create_list(:subscription, 2, :actionable, user: user) }
- let!(:pending_cancellation_subscriptions) do
- create_list(:subscription, 2, :pending_cancellation, user: user)
- end
+ shared_examples 'processes the subscription' do
+ it 'resets the successive_skip_count' do
+ subscription
+ subscription.update_columns(successive_skip_count: 3)
- let!(:expiring_subscriptions) do
- create_list(
- :subscription,
- 2,
- :actionable,
- :with_line_item,
- user: user,
- end_date: Date.current.tomorrow,
- )
- end
+ described_class.run
- let!(:future_subscriptions) { create_list(:subscription, 2, :not_actionable) }
- let!(:canceled_subscriptions) { create_list(:subscription, 2, :canceled) }
- let!(:inactive) { create_list(:subscription, 2, :inactive) }
-
- let!(:successful_installments) { create_list :installment, 2, :success }
- let!(:failed_installments) do
- create_list(
- :installment,
- 2,
- :failed,
- subscription_traits: [{ user: user }]
- )
- end
+ expect(subscription.reload.successive_skip_count).to eq(0)
+ end
- # all subscriptions and previously failed installments belong to the same user
- let(:expected_orders) { 1 }
+ context 'with clear_past_installments set to true' do
+ it 'clears any past unfulfilled installments' do
+ stub_config(clear_past_installments: true)
+ subscription
+ installment = create(:installment, :actionable, subscription: subscription)
- shared_examples 'a subscription order' do
- let(:order_variant_ids) { Spree::Order.last.variant_ids }
- let(:expected_ids) do
- subs = actionable_subscriptions + pending_cancellation_subscriptions + expiring_subscriptions
- subs_ids = subs.flat_map { |s| s.line_items.pluck(:subscribable_id) }
- inst_ids = failed_installments.flat_map { |i| i.subscription.line_items.pluck(:subscribable_id) }
+ described_class.run
- subs_ids + inst_ids
+ expect(installment.reload.actionable_date).to eq(nil)
+ end
end
- it 'creates the correct number of orders' do
- expect { subject }.to change { Spree::Order.complete.count }.by expected_orders
- end
+ context 'with clear_past_installments set to false' do
+ it 'does not clear any past unfulfilled installments' do
+ stub_config(clear_past_installments: false)
+ subscription
+ installment = create(:installment, :actionable, subscription: subscription)
- it 'creates the correct order' do
- subject
- expect(order_variant_ids).to match_array expected_ids
+ described_class.run
+
+ expect(installment.reload.actionable_date).not_to be_nil
+ end
end
- it 'advances the subscription actionable dates' do
- subscription = actionable_subscriptions.first
+ it 'creates a new installment' do
+ subscription
- current_date = subscription.actionable_date
- expected_date = subscription.next_actionable_date
+ described_class.run
- expect { subject }.
- to change { subscription.reload.actionable_date }.
- from(current_date).to(expected_date)
+ expect(subscription.installments.count).to eq(1)
end
- it 'cancels subscriptions pending cancellation' do
- subs = pending_cancellation_subscriptions.first
- expect { subject }.
- to change { subs.reload.state }.
- from('pending_cancellation').to('canceled')
- end
+ it 'schedules the newly created installment for processing' do
+ subscription
+
+ described_class.run
- it 'resets the subscription successive skip count' do
- subs = pending_cancellation_subscriptions.first
- expect { subject }.
- to change { subs.reload.state }.
- from('pending_cancellation').to('canceled')
+ expect(SolidusSubscriptions::ProcessInstallmentJob).to have_been_enqueued
+ .with(subscription.installments.last)
end
- it 'deactivates expired subscriptions' do
- sub = expiring_subscriptions.first
+ it 'schedules other actionable installments for processing' do
+ actionable_installment = create(:installment, :actionable)
- expect { subject }.
- to change { sub.reload.state }.
- from('active').to('inactive')
+ described_class.run
+
+ expect(SolidusSubscriptions::ProcessInstallmentJob).to have_been_enqueued
+ .with(actionable_installment)
end
+ end
- context 'the subscriptions have different shipping addresses' do
- let!(:sub_to_different_address) do
- create(:subscription, :actionable, :with_shipping_address, user: user)
- end
+ shared_examples 'schedules the subscription for reprocessing' do
+ it 'advances the actionable_date' do
+ subscription
+ subscription.update_columns(interval_length: 1, interval_units: 'week')
+ old_actionable_date = subscription.actionable_date
- it 'creates an order for each shipping address' do
- expect { subject }.to change { Spree::Order.complete.count }.by 2
- end
+ described_class.run
+
+ expect(subscription.reload.actionable_date.to_date).to eq((old_actionable_date + 1.week).to_date)
end
+ end
- context "when the config 'clear_past_installments' is enabled" do
- it 'clears the past failed installments' do
- stub_config(clear_past_installments: true)
+ context 'with a regular subscription' do
+ let(:subscription) { create(:subscription, :actionable) }
- subject
+ include_examples 'processes the subscription'
+ include_examples 'schedules the subscription for reprocessing'
+ end
- failed_installments.each do |fi|
- expect(fi.reload.actionable_date).to eq(nil)
- end
- end
+ context 'with a subscription that is pending deactivation' do
+ let(:subscription) do
+ create(
+ :subscription,
+ :actionable,
+ interval_units: 'week',
+ interval_length: 2,
+ end_date: 3.days.from_now,
+ )
end
- context 'the subscriptions have different billing addresses' do
- let!(:sub_to_different_address) do
- create(:subscription, :actionable, :with_billing_address, user: user)
- end
+ include_examples 'processes the subscription'
+ include_examples 'schedules the subscription for reprocessing'
- it 'creates an order for each billing address' do
- expect { subject }.to change { Spree::Order.complete.count }.by 2
- end
- end
+ it 'deactivates the subscription' do
+ subscription
- context 'the subscription is cancelled with pending installments' do
- let!(:cancelled_installment) do
- installment = create(:installment, actionable_date: Time.zone.today)
- installment.subscription.cancel!
- end
+ described_class.run
- it 'does not process the installment' do
- expect { subject }.to change { Spree::Order.complete.count }.by expected_orders
- end
+ expect(subscription.reload.state).to eq('inactive')
end
end
- describe '.run' do
- subject { described_class.run }
+ context 'with a subscription that is pending cancellation' do
+ let(:subscription) do
+ create(
+ :subscription,
+ :actionable,
+ :pending_cancellation,
+ )
+ end
- it_behaves_like 'a subscription order'
- end
+ include_examples 'processes the subscription'
+
+ it 'cancels the subscription' do
+ subscription
- describe '#build_jobs' do
- subject { described_class.new([user]).build_jobs }
+ described_class.run
- it_behaves_like 'a subscription order'
+ expect(subscription.reload.state).to eq('canceled')
+ end
end
end
diff --git a/spec/services/solidus_subscriptions/subscription_generator_spec.rb b/spec/lib/solidus_subscriptions/subscription_generator_spec.rb
index b7d73b8..b7d73b8 100644
--- a/spec/services/solidus_subscriptions/subscription_generator_spec.rb
+++ b/spec/lib/solidus_subscriptions/subscription_generator_spec.rb
diff --git a/spec/services/solidus_subscriptions/subscription_order_promotion_rule_spec.rb b/spec/lib/solidus_subscriptions/subscription_order_promotion_rule_spec.rb
index bacbef2..bacbef2 100644
--- a/spec/services/solidus_subscriptions/subscription_order_promotion_rule_spec.rb
+++ b/spec/lib/solidus_subscriptions/subscription_order_promotion_rule_spec.rb
diff --git a/spec/services/solidus_subscriptions/subscription_promotion_rule_spec.rb b/spec/lib/solidus_subscriptions/subscription_promotion_rule_spec.rb
index f4c65cc..f4c65cc 100644
--- a/spec/services/solidus_subscriptions/subscription_promotion_rule_spec.rb
+++ b/spec/lib/solidus_subscriptions/subscription_promotion_rule_spec.rb
diff --git a/spec/models/solidus_subscriptions/installment_spec.rb b/spec/models/solidus_subscriptions/installment_spec.rb
index 4e4831e..399a7e6 100644
--- a/spec/models/solidus_subscriptions/installment_spec.rb
+++ b/spec/models/solidus_subscriptions/installment_spec.rb
@@ -5,15 +5,6 @@ RSpec.describe SolidusSubscriptions::Installment, type: :model do
it { is_expected.to validate_presence_of :subscription }
- describe '#line_item_builder' do
- subject { installment.line_item_builder }
-
- let(:line_items) { installment.subscription.line_items }
-
- it { is_expected.to be_a SolidusSubscriptions::LineItemBuilder }
- it { is_expected.to have_attributes(subscription_line_items: line_items) }
- end
-
describe '#out_of_stock' do
subject { installment.out_of_stock }
diff --git a/spec/models/solidus_subscriptions/line_item_spec.rb b/spec/models/solidus_subscriptions/line_item_spec.rb
index ad80384..aaa24a5 100644
--- a/spec/models/solidus_subscriptions/line_item_spec.rb
+++ b/spec/models/solidus_subscriptions/line_item_spec.rb
@@ -24,90 +24,4 @@ RSpec.describe SolidusSubscriptions::LineItem, type: :model do
expect(subject.from_now).to eq Date.parse("2016-10-22")
end
end
-
- describe '#as_json' do
- subject { line_item.as_json }
-
- around { |e| Timecop.freeze { e.run } }
-
- let(:line_item) { create(:subscription_line_item, :with_subscription) }
-
- let(:expected_hash) do
- hash = {
- "id" => line_item.id,
- "spree_line_item_id" => line_item.spree_line_item.id,
- "subscription_id" => line_item.subscription_id,
- "quantity" => line_item.quantity,
- "end_date" => line_item.end_date,
- "subscribable_id" => line_item.subscribable_id,
- "created_at" => line_item.created_at,
- "updated_at" => line_item.updated_at,
- "interval_units" => line_item.interval_units,
- "interval_length" => line_item.interval_length
- }
- Rails.gem_version >= Gem::Version.new('6.0.0') ? hash.as_json : hash
- end
-
- it 'includes the attribute values' do
- expect(subject).to match a_hash_including(expected_hash)
- end
-
- it 'includes the dummy lineitem' do
- expect(subject).to have_key('dummy_line_item')
- end
- end
-
- describe '#dummy_line_item' do
- subject { line_item.dummy_line_item }
-
- let(:line_item) { create(:subscription_line_item, :with_subscription) }
-
- it { is_expected.to be_a Spree::LineItem }
- it { is_expected.to be_frozen }
-
- it 'has the correct variant' do
- expect(subject.variant_id).to eq line_item.subscribable_id
- end
-
- context 'with no spree line item' do
- let(:line_item) { create(:subscription_line_item, :with_subscription, spree_line_item: nil) }
-
- it { is_expected.to be_a Spree::LineItem }
- it { is_expected.to be_frozen }
-
- it 'has the correct variant' do
- expect(subject.variant_id).to eq line_item.subscribable_id
- end
- end
-
- context 'with an associated subscription' do
- context 'the associated subscription has a shipping address' do
- let(:line_item) do
- create(:subscription_line_item, :with_subscription, subscription_traits: [:with_shipping_address])
- end
-
- it 'uses the subscription shipping address' do
- expect(subject.order.ship_address).to eq line_item.subscription.shipping_address
- end
-
- it 'uses the subscription users billing address' do
- expect(subject.order.bill_address).to eq line_item.subscription.user.bill_address
- end
- end
-
- context 'the associated subscription has a billing address' do
- let(:line_item) do
- create(:subscription_line_item, :with_subscription, subscription_traits: [:with_billing_address])
- end
-
- it 'uses the subscription users shipping address' do
- expect(subject.order.ship_address).to eq line_item.subscription.user.ship_address
- end
-
- it 'uses the subscription billing address' do
- expect(subject.order.bill_address).to eq line_item.subscription.billing_address
- end
- end
- end
- end
end
diff --git a/spec/models/solidus_subscriptions/subscription_spec.rb b/spec/models/solidus_subscriptions/subscription_spec.rb
index 5d855db..f305580 100644
--- a/spec/models/solidus_subscriptions/subscription_spec.rb
+++ b/spec/models/solidus_subscriptions/subscription_spec.rb
@@ -348,16 +348,6 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
end
end
- describe '#line_item_builder' do
- subject { subscription.line_item_builder }
-
- let(:subscription) { create :subscription, :with_line_item }
- let(:line_items) { subscription.line_items }
-
- it { is_expected.to be_a SolidusSubscriptions::LineItemBuilder }
- it { is_expected.to have_attributes(subscription_line_items: line_items) }
- end
-
describe '#processing_state' do
subject { subscription.processing_state }
diff --git a/spec/services/solidus_subscriptions/checkout_spec.rb b/spec/services/solidus_subscriptions/checkout_spec.rb
deleted file mode 100644
index 4b408dc..0000000
--- a/spec/services/solidus_subscriptions/checkout_spec.rb
+++ /dev/null
@@ -1,389 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe SolidusSubscriptions::Checkout do
- let(:checkout) { described_class.new(installments) }
- let(:root_order) { create :completed_order_with_pending_payment, user: subscription_user }
- let(:subscription_user) { create(:user, :subscription_user) }
- let!(:credit_card) {
- card = create(:credit_card, user: subscription_user, gateway_customer_profile_id: 'BGS-123', payment_method: payment_method)
- wallet_payment_source = subscription_user.wallet.add(card)
- subscription_user.wallet.default_wallet_payment_source = wallet_payment_source
- card
- }
- let(:payment_method) { create(:payment_method) }
- let(:installments) { create_list(:installment, 2, installment_traits) }
-
- let(:installment_traits) do
- {
- subscription_traits: [{
- user: subscription_user,
- line_item_traits: [{
- spree_line_item: root_order.line_items.first
- }]
- }]
- }
- end
-
- before do
- Spree::Variant.all.each { |v| v.update(subscribable: true) }
- end
-
- context 'initialized with installments belonging to multiple users' do
- subject { checkout }
-
- let(:installments) { build_stubbed_list :installment, 2 }
-
- it 'raises an error' do
- expect { subject }.
- to raise_error SolidusSubscriptions::UserMismatchError, /must have the same user/
- end
- end
-
- describe '#process', :checkout do
- subject(:order) { checkout.process }
-
- let(:subscription_line_item) { installments.first.subscription.line_items.first }
-
- shared_examples 'a completed checkout' do
- it { is_expected.to be_a Spree::Order }
-
- let(:total) { 49.98 }
- let(:quantity) { installments.length }
-
- it 'has the correct number of line items' do
- count = order.line_items.length
- expect(count).to eq quantity
- end
-
- it 'the line items have the correct values' do
- line_item = order.line_items.first
- expect(line_item).to have_attributes(
- quantity: subscription_line_item.quantity,
- variant_id: subscription_line_item.subscribable_id
- )
- end
-
- it 'has a shipment' do
- expect(order.shipments).to be_present
- end
-
- it 'has a payment' do
- expect(order.payments.valid).to be_present
- end
-
- it 'has the correct totals' do
- expect(order).to have_attributes(
- total: total,
- shipment_total: 10
- )
- end
-
- it { is_expected.to be_complete }
-
- it 'associates the order to the installment detail' do
- order
- installment_orders = installments.flat_map { |i| i.details.map(&:order) }.compact
- expect(installment_orders).to all eq order
- end
-
- it 'creates an installment detail for each installment' do
- expect { subject }.
- to change { SolidusSubscriptions::InstallmentDetail.count }.
- by(installments.count)
- end
- end
-
- context 'no line items get added to the cart' do
- before do
- installments
- Spree::StockItem.update_all(count_on_hand: 0, backorderable: false)
- end
-
- it 'creates two failed installment details' do
- expect { order }.
- to change { SolidusSubscriptions::InstallmentDetail.count }.
- by(installments.length)
-
- details = SolidusSubscriptions::InstallmentDetail.last(installments.length)
- expect(details).to all be_failed
- end
-
- it { is_expected.to be_nil }
-
- it 'creates no order' do
- expect { subject }.not_to change { Spree::Order.count }
- end
- end
-
- if Gem::Specification.find_by_name('solidus').version >= Gem::Version.new('1.4.0')
- context 'Altered checkout flow' do
- before do
- @old_checkout_flow = Spree::Order.checkout_flow
- Spree::Order.remove_checkout_step(:delivery)
- end
-
- after do
- Spree::Order.checkout_flow(&@old_checkout_flow)
- end
-
- it 'has a payment' do
- expect(order.payments.valid).to be_present
- end
-
- it 'has the correct totals' do
- expect(order).to have_attributes(
- total: 39.98,
- shipment_total: 0
- )
- end
-
- it { is_expected.to be_complete }
- end
- end
-
- context 'the variant is out of stock' do
- let(:subscription_line_item) { installments.last.subscription.line_items.first }
- let(:expected_date) { Time.zone.today + SolidusSubscriptions.configuration.reprocessing_interval }
-
- # Remove stock for 1 variant in the consolidated installment
- before do
- subscribable_id = installments.first.subscription.line_items.first.subscribable_id
- variant = Spree::Variant.find(subscribable_id)
- variant.stock_items.update_all(count_on_hand: 0, backorderable: false)
- end
-
- it 'creates a failed installment detail' do
- subject
- detail = installments.first.details.last
-
- expect(detail).not_to be_successful
- expect(detail.message).
- to eq I18n.t('solidus_subscriptions.installment_details.out_of_stock')
- end
-
- it 'removes the installment from the list of installments' do
- expect { subject }.
- to change { checkout.installments.length }.
- by(-1)
- end
-
- it_behaves_like 'a completed checkout' do
- let(:total) { 29.99 }
- let(:quantity) { installments.length - 1 }
- end
- end
-
- context 'the payment fails' do
- let(:payment_method) { create(:payment_method) }
- let!(:credit_card) {
- card = create(:credit_card, user: checkout.user, payment_method: payment_method)
- wallet_payment_source = checkout.user.wallet.add(card)
- checkout.user.wallet.default_wallet_payment_source = wallet_payment_source
- card
- }
- let(:expected_date) { Time.zone.today + SolidusSubscriptions.configuration.reprocessing_interval }
-
- it { is_expected.to be_nil }
-
- it 'marks all of the installments as failed' do
- subject
-
- details = installments.map do |installments|
- installments.details.reload.last
- end
-
- expect(details).to all be_failed && have_attributes(
- message: I18n.t('solidus_subscriptions.installment_details.payment_failed')
- )
- end
-
- it 'marks the installment to be reprocessed' do
- subject
- actionable_dates = installments.map do |installment|
- installment.reload.actionable_date
- end
-
- expect(actionable_dates).to all eq expected_date
- end
- end
-
- context 'when there are cart promotions' do
- let!(:promo) do
- create(
- :promotion,
- :with_item_total_rule,
- :with_order_adjustment,
- promo_params
- )
- end
-
- # Promotions require the :apply_automatically flag to be auto applied in
- # solidus versions greater than 1.0
- let(:promo_params) do
- {}.tap do |params|
- if Spree::Promotion.new.respond_to?(:apply_automatically)
- params[:apply_automatically] = true
- end
- end
- end
-
- it_behaves_like 'a completed checkout' do
- let(:total) { 39.98 }
- end
-
- it 'applies the correct adjustments' do
- expect(subject.adjustments).to be_present
- end
- end
-
- context 'there is an aribitrary failure' do
- let(:expected_date) { Time.zone.today + SolidusSubscriptions.configuration.reprocessing_interval }
-
- before do
- allow(checkout).to receive(:populate).and_raise('arbitrary runtime error')
- end
-
- it 'advances the installment actionable dates', :aggregate_failures do
- expect { subject }.to raise_error('arbitrary runtime error')
-
- actionable_dates = installments.map do |installment|
- installment.reload.actionable_date
- end
-
- expect(actionable_dates).to all eq expected_date
- end
- end
-
- context 'the user has store credit' do
- let!(:store_credit) { create :store_credit, user: subscription_user }
- let!(:store_credit_payment_method) { create :store_credit_payment_method }
-
- it_behaves_like 'a completed checkout'
-
- it 'has a valid store credit payment' do
- expect(order.payments.valid.store_credits).to be_present
- end
- end
-
- context 'the subscription has a shipping address' do
- let(:installment_traits) do
- {
- subscription_traits: [{
- shipping_address: shipping_address,
- user: subscription_user,
- line_item_traits: [{ spree_line_item: root_order.line_items.first }]
- }]
- }
- end
- let(:shipping_address) { create :address }
-
- it_behaves_like 'a completed checkout'
-
- it 'ships to the subscription address' do
- expect(subject.ship_address).to eq shipping_address
- end
- end
-
- context 'the subscription has a billing address' do
- let(:installment_traits) do
- {
- subscription_traits: [{
- billing_address: billing_address,
- user: subscription_user,
- line_item_traits: [{ spree_line_item: root_order.line_items.first }]
- }]
- }
- end
- let(:billing_address) { create :address }
-
- it_behaves_like 'a completed checkout'
-
- it 'bills to the subscription address' do
- expect(subject.bill_address).to eq billing_address
- end
- end
-
- context 'the subscription has a payment method' do
- let(:installment_traits) do
- {
- subscription_traits: [{
- payment_method: payment_method,
- user: subscription_user,
- line_item_traits: [{ spree_line_item: root_order.line_items.first }]
- }]
- }
- end
- let(:payment_method) { create :check_payment_method }
-
- it_behaves_like 'a completed checkout'
-
- it 'pays with the payment method' do
- expect(subject.payments.valid.first.payment_method).to eq payment_method
- end
- end
-
- context 'the subscription has a payment method and a source' do
- let(:installment_traits) do
- {
- subscription_traits: [{
- payment_method: payment_method,
- payment_source: payment_source,
- user: subscription_user,
- line_item_traits: [{ spree_line_item: root_order.line_items.first }]
- }]
- }
- end
- let(:payment_source) { create :credit_card, payment_method: payment_method, user: subscription_user }
- let(:payment_method) { create :credit_card_payment_method }
-
- it_behaves_like 'a completed checkout'
-
- it 'pays with the payment method' do
- expect(subject.payments.valid.first.payment_method).to eq payment_method
- end
-
- it 'pays with the payment source' do
- expect(subject.payments.valid.first.source).to eq payment_source
- end
- end
-
- context 'there are multiple associated subscritpion line items' do
- it_behaves_like 'a completed checkout' do
- let(:quantity) { subscription_line_items.length }
- end
-
- let(:installments) { create_list(:installment, 1, installment_traits) }
- let(:subscription_line_items) { create_list(:subscription_line_item, 2, quantity: 1) }
-
- let(:installment_traits) do
- {
- subscription_traits: [{
- user: subscription_user,
- line_items: subscription_line_items
- }]
- }
- end
- end
- end
-
- describe '#order' do
- subject { checkout.order }
-
- let(:user) { installments.first.subscription.user }
-
- it { is_expected.to be_a Spree::Order }
-
- it 'has the correct attributes' do
- expect(subject).to have_attributes(
- user: user,
- email: user.email,
- store: installments.first.subscription.store
- )
- end
-
- it 'is the same instance any time its called' do
- order = checkout.order
- expect(subject).to equal order
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/dispatcher_spec.rb b/spec/services/solidus_subscriptions/dispatcher_spec.rb
deleted file mode 100644
index 49c3f4b..0000000
--- a/spec/services/solidus_subscriptions/dispatcher_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-RSpec.describe SolidusSubscriptions::Dispatcher do
- describe '#dispatch' do
- it 'raises a NotImplementedError' do
- dispatcher = described_class.new([])
-
- expect {
- dispatcher.dispatch
- }.to raise_error(NotImplementedError)
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/failure_dispatcher_spec.rb b/spec/services/solidus_subscriptions/failure_dispatcher_spec.rb
deleted file mode 100644
index a7c18ea..0000000
--- a/spec/services/solidus_subscriptions/failure_dispatcher_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-RSpec.describe SolidusSubscriptions::FailureDispatcher do
- describe '#dispatch' do
- it 'marks all the installments as failed' do
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(installments).to all(have_received(:failed!).with(order).once)
- end
-
- it 'cancels the order' do
- if Spree.solidus_gem_version > Gem::Version.new('2.10')
- skip 'Orders in `cart` state cannot be canceled starting from Solidus 2.11.'
- end
-
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(order.state).to eq('canceled')
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/line_item_builder_spec.rb b/spec/services/solidus_subscriptions/line_item_builder_spec.rb
deleted file mode 100644
index d1e041c..0000000
--- a/spec/services/solidus_subscriptions/line_item_builder_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe SolidusSubscriptions::LineItemBuilder do
- let(:builder) { described_class.new subscription_line_items }
- let(:variant) { create(:variant, subscribable: true) }
- let(:subscription_line_item) { subscription_line_items.first }
- let(:subscription_line_items) do
- build_stubbed_list(:subscription_line_item, 1, subscribable_id: variant.id)
- end
-
- describe '#spree_line_items' do
- subject { builder.spree_line_items }
-
- let(:expected_attributes) do
- {
- variant_id: subscription_line_item.subscribable_id,
- quantity: subscription_line_item.quantity
- }
- end
-
- it { is_expected.to be_a Array }
-
- it 'contains a line item with the correct attributes' do
- expect(subject.first).to have_attributes expected_attributes
- end
-
- context 'the variant is out of stock' do
- before { create :stock_location, backorderable_default: false }
-
- it { is_expected.to be_empty }
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/order_builder_spec.rb b/spec/services/solidus_subscriptions/order_builder_spec.rb
deleted file mode 100644
index 3463bd1..0000000
--- a/spec/services/solidus_subscriptions/order_builder_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe SolidusSubscriptions::OrderBuilder do
- let(:builder) { described_class.new order }
-
- describe '#add_line_items' do
- subject { builder.add_line_items([line_item]) }
-
- let(:variant) { create :variant, subscribable: true }
- let(:order) do
- create :order, line_items_attributes: line_items_attributes
- end
-
- let(:line_item) { create(:line_item, quantity: 4, variant: variant) }
-
- context 'the line item doesnt already exist on the order' do
- let(:line_items_attributes) { [] }
-
- it 'adds a new line item to the order' do
- expect { subject }.
- to change { order.line_items.count }.
- from(0).to(1)
- end
-
- it 'has a line item with the correct values' do
- subject
- line_item = order.line_items.last
-
- expect(line_item).to have_attributes(
- variant_id: variant.id,
- quantity: line_item.quantity
- )
- end
- end
-
- context 'the line item already exists on the order' do
- let(:line_items_attributes) do
- [{
- variant: variant,
- quantity: 3
- }]
- end
-
- it 'increases the correct line item by the correct amount' do
- existing_line_item = order.line_items.first
-
- expect { subject }.
- to change { existing_line_item.reload.quantity }.
- by(line_item.quantity)
- end
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/out_of_stock_dispatcher_spec.rb b/spec/services/solidus_subscriptions/out_of_stock_dispatcher_spec.rb
deleted file mode 100644
index 6f3825c..0000000
--- a/spec/services/solidus_subscriptions/out_of_stock_dispatcher_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-RSpec.describe SolidusSubscriptions::OutOfStockDispatcher do
- describe '#dispatch' do
- it 'marks all the installments as out of stock' do
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
-
- dispatcher = described_class.new(installments)
- dispatcher.dispatch
-
- expect(installments).to all(have_received(:out_of_stock).once)
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/payment_failed_dispatcher_spec.rb b/spec/services/solidus_subscriptions/payment_failed_dispatcher_spec.rb
deleted file mode 100644
index a6a8a0c..0000000
--- a/spec/services/solidus_subscriptions/payment_failed_dispatcher_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-RSpec.describe SolidusSubscriptions::PaymentFailedDispatcher do
- describe '#dispatch' do
- it 'marks all the installments as payment_failed' do
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(installments).to all(have_received(:payment_failed!).with(order).once)
- end
-
- it 'cancels the order' do
- if Spree.solidus_gem_version > Gem::Version.new('2.10')
- skip 'Orders in `cart` state cannot be canceled starting from Solidus 2.11.'
- end
-
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(order.state).to eq('canceled')
- end
-
- it 'fires an installments_failed_payment event' do
- stub_const('Spree::Event', class_spy(Spree::Event))
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(Spree::Event).to have_received(:fire).with(
- 'solidus_subscriptions.installments_failed_payment',
- installments: installments,
- order: order,
- )
- end
- end
-end
diff --git a/spec/services/solidus_subscriptions/success_dispatcher_spec.rb b/spec/services/solidus_subscriptions/success_dispatcher_spec.rb
deleted file mode 100644
index ce41638..0000000
--- a/spec/services/solidus_subscriptions/success_dispatcher_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-RSpec.describe SolidusSubscriptions::SuccessDispatcher do
- describe '#dispatch' do
- it 'marks all the installments as success' do
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(installments).to all(have_received(:success!).with(order).once)
- end
-
- it 'fires an installments_succeeded event' do
- stub_const('Spree::Event', class_spy(Spree::Event))
- installments = Array.new(2) { instance_spy(SolidusSubscriptions::Installment) }
- order = create(:order_with_line_items)
-
- dispatcher = described_class.new(installments, order)
- dispatcher.dispatch
-
- expect(Spree::Event).to have_received(:fire).with(
- 'solidus_subscriptions.installments_succeeded',
- installments: installments,
- order: order,
- )
- end
- end
-end
diff --git a/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb b/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb
index 572913b..11ada2c 100644
--- a/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb
+++ b/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb
@@ -29,10 +29,10 @@ RSpec.describe SolidusSubscriptions::ChurnBusterSubscriber do
allow(SolidusSubscriptions).to receive(:churn_buster).and_return(churn_buster)
order = build_stubbed(:order)
- installments = build_list(:installment, 2)
+ installment = build_stubbed(:installment)
Spree::Event.fire(
- 'solidus_subscriptions.installments_succeeded',
- installments: installments,
+ 'solidus_subscriptions.installment_succeeded',
+ installment: installment,
order: order,
)
@@ -46,10 +46,10 @@ RSpec.describe SolidusSubscriptions::ChurnBusterSubscriber do
allow(SolidusSubscriptions).to receive(:churn_buster).and_return(churn_buster)
order = build_stubbed(:order)
- installments = build_list(:installment, 2)
+ installment = build_stubbed(:installment)
Spree::Event.fire(
- 'solidus_subscriptions.installments_failed_payment',
- installments: installments,
+ 'solidus_subscriptions.installment_failed_payment',
+ installment: installment,
order: order,
)