diff options
-rw-r--r-- | lib/solidus_subscriptions/processor.rb | 90 | ||||
-rw-r--r-- | spec/lib/solidus_subscriptions/processor_spec.rb | 56 |
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 |