summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/solidus_subscriptions/processor.rb90
-rw-r--r--spec/lib/solidus_subscriptions/processor_spec.rb56
2 files changed, 146 insertions, 0 deletions
diff --git a/lib/solidus_subscriptions/processor.rb b/lib/solidus_subscriptions/processor.rb
new file mode 100644
index 0000000..97b39df
--- /dev/null
+++ b/lib/solidus_subscriptions/processor.rb
@@ -0,0 +1,90 @@
+# 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.
+#
+# 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 }
+ end
+
+ private
+
+ def batched_users_to_be_processed
+ subscriptions = Subscription.arel_table
+ installments = Installment.arel_table
+
+ Spree::User.
+ joins(:subscriptions).
+ joins(
+ subscriptions.
+ join(installments, Arel::Nodes::OuterJoin).
+ on(subscriptions[:id].eq(installments[:subscription_id])).
+ join_sources
+ ).
+ where(
+ Subscription.actionable.arel.constraints.reduce(:and).
+ or(Installment.actionable.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 { |user| ProcessInstallmentsJob.perform_later installments(user) }
+ end
+
+ private
+
+ def subscriptions_by_id
+ @subscriptions_by_id ||= Subscription.
+ actionable.
+ 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)
+ subscriptions_by_id.fetch(user.id, []).map { |sub| sub.installments.create! }
+ end
+
+ def user_ids
+ @user_ids ||= users.map(&:id)
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/processor_spec.rb b/spec/lib/solidus_subscriptions/processor_spec.rb
new file mode 100644
index 0000000..7417905
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/processor_spec.rb
@@ -0,0 +1,56 @@
+require 'rails_helper'
+
+RSpec.describe SolidusSubscriptions::Processor, :checkout do
+ let(:user) do
+ ccs = build_list(:credit_card, 1, gateway_customer_profile_id: 'BGS-123', default: true)
+ build :user, credit_cards: ccs
+ end
+
+ let!(:actionable_subscriptions) { create_list(:subscription, 2, :actionable, user: user) }
+ 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
+
+ # all subscriptions and previously failed installments belong to the same user
+ let(:expected_orders) { 1 }
+
+ shared_examples 'a subscription order' do
+ let(:order_variant_ids) { Spree::Order.last.variant_ids }
+ let(:expected_ids) do
+ subs_ids = actionable_subscriptions.map { |s| s.line_item.subscribable_id }
+ inst_ids = failed_installments.map { |i| i.subscription.line_item.subscribable_id }
+
+ subs_ids + inst_ids
+ end
+
+ it 'creates the correct number of orders' do
+ expect { subject }.to change { Spree::Order.complete.count }.by expected_orders
+ end
+
+ it 'creates the correct order' do
+ subject
+ expect(order_variant_ids).to match_array expected_ids
+ end
+ end
+
+ describe '.run' do
+ subject { described_class.run }
+
+ it_behaves_like 'a subscription order'
+ end
+
+ describe '#build_jobs' do
+ subject { described_class.new([user]).build_jobs }
+ it_behaves_like 'a subscription order'
+ end
+end