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