diff options
9 files changed, 109 insertions, 10 deletions
diff --git a/app/models/solidus_subscriptions/subscription.rb b/app/models/solidus_subscriptions/subscription.rb index 74aadd3..c48c616 100644 --- a/app/models/solidus_subscriptions/subscription.rb +++ b/app/models/solidus_subscriptions/subscription.rb @@ -73,7 +73,8 @@ module SolidusSubscriptions # :pending_cancellation state instead of being canceled. Susbcriptions # pending cancellation will still be processed. def can_be_canceled? - true + return true if actionable_date.nil? + (actionable_date - Config.minimum_cancellation_notice).future? end # This method determines if a subscription can be deactivated. A deactivated diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..7b86b56 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,38 @@ +# Configuration +The following options can be added to a rails initializer to modify the +behaviour of the gem: + +```ruby +# config/initializers/subscriptions.rb +# Values set in this example are the defaults + +# The gateway the `ConsolidatedInstallment` will use when charging recurring +# orders. We highly recommend setting this to a specific value +SolidusSubscriptions::Config.default_gateway = my_gateway + +# Defines how long the system will wait before allowing a failed installment to +# be reprocessed by the `Processor` +SolidusSubscriptions::Config.reprocessing_interval = 1.days + +# Notice required to cancel a subscription. A cancellation with insufficient +# notice will result in the subscription being moved to the +# `pending_cancellation` state. Subscriptions pending cancellations will be +# processed an additional one (1) time and then marked as cancelled. +SolidusSubscriptions::Config.minimum_cancellation_notice = 1.day + +# Which queue is responsible for processing subscriptions +mattr_accessor(:processing_queue) { :default } + +# SolidusSubscriptions::LineItem attributes which are allowed to +# be updated from user data +# +# This is useful in the case where certain fields should not be allowed to +# be modified by the user. This locks these attributes from being passed +# in to the orders controller (or the api controller). +SolidusSubscriptions::Config.subscription_line_item_attributes = [ + :quantity, + :subscribable_id, + :interval_length, + :interval_units, + :max_installments +] diff --git a/lib/solidus_subscriptions/config.rb b/lib/solidus_subscriptions/config.rb index d8f89ea..7eabfab 100644 --- a/lib/solidus_subscriptions/config.rb +++ b/lib/solidus_subscriptions/config.rb @@ -5,6 +5,8 @@ module SolidusSubscriptions # retrying to fulfil it mattr_accessor(:reprocessing_interval) { 1.day } + mattr_accessor(:minimum_cancellation_notice) { 1.day } + # Which queue is responsible for processing subscriptions mattr_accessor(:processing_queue) { :default } diff --git a/lib/solidus_subscriptions/processor.rb b/lib/solidus_subscriptions/processor.rb index 41796f9..c035ecf 100644 --- a/lib/solidus_subscriptions/processor.rb +++ b/lib/solidus_subscriptions/processor.rb @@ -80,7 +80,13 @@ module SolidusSubscriptions end def new_installments(user) - subscriptions_by_id.fetch(user.id, []).map { |sub| sub.installments.create! } + subscriptions_by_id.fetch(user.id, []).map do |sub| + ActiveRecord::Base.transaction do + sub.advance_actionable_date + sub.cancel! if sub.pending_cancellation? + sub.installments.create! + end + end end def user_ids diff --git a/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb b/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb index 92ecb77..e4f3cdc 100644 --- a/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb +++ b/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb @@ -23,6 +23,11 @@ FactoryGirl.define do actionable_date { Time.zone.now.tomorrow } end + trait(:pending_cancellation) do + actionable + state { 'pending_cancellation' } + end + trait(:canceled) { state 'canceled' } trait(:inactive) { state 'inactive' } end diff --git a/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb b/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb index 536e5f9..3ad8dcf 100644 --- a/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb +++ b/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb @@ -8,7 +8,15 @@ RSpec.describe SolidusSubscriptions::Api::V1::SubscriptionsController, type: :co shared_examples "an authenticated subscription" do context "when the subscription belongs to user" do - let!(:subscription) { create :subscription, :with_line_item, user: user } + let!(:subscription) do + create( + :subscription, + :with_line_item, + actionable_date: (Date.current + 1.month ), + user: user + ) + end + it { is_expected.to be_success } end diff --git a/spec/lib/solidus_subscriptions/processor_spec.rb b/spec/lib/solidus_subscriptions/processor_spec.rb index 7417905..475dda9 100644 --- a/spec/lib/solidus_subscriptions/processor_spec.rb +++ b/spec/lib/solidus_subscriptions/processor_spec.rb @@ -7,6 +7,10 @@ RSpec.describe SolidusSubscriptions::Processor, :checkout do end let!(:actionable_subscriptions) { create_list(:subscription, 2, :actionable, user: user) } + let!(:pending_cancellation_subscriptions) do + create_list(:subscription, 2, :pending_cancellation, user: user) + end + let!(:future_subscriptions) { create_list(:subscription, 2, :not_actionable) } let!(:canceled_subscriptions) { create_list(:subscription, 2, :canceled) } let!(:inactive) { create_list(:subscription, 2, :inactive) } @@ -27,7 +31,8 @@ RSpec.describe SolidusSubscriptions::Processor, :checkout do 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 } + subs = actionable_subscriptions + pending_cancellation_subscriptions + subs_ids = subs.map { |s| s.line_item.subscribable_id } inst_ids = failed_installments.map { |i| i.subscription.line_item.subscribable_id } subs_ids + inst_ids @@ -41,11 +46,28 @@ RSpec.describe SolidusSubscriptions::Processor, :checkout do subject expect(order_variant_ids).to match_array expected_ids end + + it 'advances the subsription actionable dates' do + subscription = actionable_subscriptions.first + + current_date = subscription.actionable_date + expected_date = subscription.next_actionable_date + + expect { subject }. + to change { subscription.reload.actionable_date }. + from(current_date).to(expected_date) + 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 end describe '.run' do subject { described_class.run } - it_behaves_like 'a subscription order' end diff --git a/spec/models/solidus_subscriptions/subscription_spec.rb b/spec/models/solidus_subscriptions/subscription_spec.rb index b3077bc..37dd31a 100644 --- a/spec/models/solidus_subscriptions/subscription_spec.rb +++ b/spec/models/solidus_subscriptions/subscription_spec.rb @@ -11,9 +11,15 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do describe '#cancel' do subject { subscription.cancel } - let(:subscription) { create :subscription, :with_line_item } + let(:subscription) do + create :subscription, :with_line_item, actionable_date: actionable_date + end + + around { |e| Timecop.freeze { e.run } } context 'the subscription can be canceled' do + let(:actionable_date) { 1.month.from_now } + it 'is canceled' do subject expect(subscription.canceled?).to be_truthy @@ -21,9 +27,7 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do end context 'the subscription cannot be canceled' do - before do - allow(subscription).to receive(:can_be_canceled?).and_return(false) - end + let(:actionable_date) { Date.current } it 'is pending cancelation' do subject diff --git a/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb b/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb index d869e0d..06105c6 100644 --- a/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb +++ b/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb @@ -6,13 +6,26 @@ RSpec.describe "Subscription endpoints", type: :request do before { user.generate_spree_api_key! } describe "#cancel" do - let(:subscription) { create :subscription, user: user } + let(:subscription) do + create :subscription, actionable_date: (Date.current + 1.month), user: user + end it "returns the canceled record", :aggregate_failures do post solidus_subscriptions.cancel_api_v1_subscription_path(subscription), token: user.spree_api_key expect(json_resp["state"]).to eq "canceled" expect(json_resp["actionable_date"]).to be_nil end + + context 'when the miniumum notice has been past' do + let(:subscription) do + create :subscription, actionable_date: Date.current, user: user + end + + it "returns the record pending cancellation", :aggregate_failures do + post solidus_subscriptions.cancel_api_v1_subscription_path(subscription), token: user.spree_api_key + expect(json_resp["state"]).to eq "pending_cancellation" + end + end end describe "#skip" do |