summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/models/solidus_subscriptions/subscription.rb3
-rw-r--r--docs/configuration.md38
-rw-r--r--lib/solidus_subscriptions/config.rb2
-rw-r--r--lib/solidus_subscriptions/processor.rb8
-rw-r--r--lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb5
-rw-r--r--spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb10
-rw-r--r--spec/lib/solidus_subscriptions/processor_spec.rb26
-rw-r--r--spec/models/solidus_subscriptions/subscription_spec.rb12
-rw-r--r--spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb15
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