solidus_subscriptions 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +41 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github_changelog_generator +2 -0
  6. data/.gitignore +20 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +12 -0
  9. data/.rubocop_todo.yml +86 -0
  10. data/CHANGELOG.md +191 -0
  11. data/Gemfile +33 -0
  12. data/LICENSE +26 -0
  13. data/README.md +221 -0
  14. data/Rakefile +6 -0
  15. data/app/assets/javascripts/spree/backend/solidus_subscriptions.js +1 -0
  16. data/app/assets/javascripts/spree/backend/solidus_subscriptions/edit_subscription_payment.js +32 -0
  17. data/app/controllers/solidus_subscriptions/api/v1/base_controller.rb +13 -0
  18. data/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb +48 -0
  19. data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +100 -0
  20. data/app/controllers/spree/admin/installments_controller.rb +25 -0
  21. data/app/controllers/spree/admin/subscription_events_controller.rb +37 -0
  22. data/app/controllers/spree/admin/subscription_orders_controller.rb +35 -0
  23. data/app/controllers/spree/admin/subscriptions_controller.rb +100 -0
  24. data/app/controllers/spree/admin/users/subscriptions_controller.rb +17 -0
  25. data/app/decorators/controllers/solidus_subscriptions/spree/api/line_items_controller/create_subscription_line_items.rb +36 -0
  26. data/app/decorators/controllers/solidus_subscriptions/spree/orders_controller/create_subscription_line_items.rb +35 -0
  27. data/app/decorators/models/solidus_subscriptions/spree/line_item/subscription_line_items_association.rb +26 -0
  28. data/app/decorators/models/solidus_subscriptions/spree/order/after_create.rb +19 -0
  29. data/app/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions.rb +23 -0
  30. data/app/decorators/models/solidus_subscriptions/spree/order/installment_details_association.rb +15 -0
  31. data/app/decorators/models/solidus_subscriptions/spree/order/subscription_association.rb +15 -0
  32. data/app/decorators/models/solidus_subscriptions/spree/order/subscription_line_items_association.rb +19 -0
  33. data/app/decorators/models/solidus_subscriptions/spree/product/delegate_subscribable.rb +17 -0
  34. data/app/decorators/models/solidus_subscriptions/spree/user/have_many_subscriptions.rb +30 -0
  35. data/app/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions.rb +20 -0
  36. data/app/decorators/models/solidus_subscriptions/spree/variant/variant_pretty_name.rb +17 -0
  37. data/app/decorators/models/solidus_subscriptions/spree/wallet_payment_source/report_default_change_to_subscriptions.rb +28 -0
  38. data/app/jobs/solidus_subscriptions/process_installment_job.rb +13 -0
  39. data/app/jobs/solidus_subscriptions/process_subscription_job.rb +31 -0
  40. data/app/models/solidus_subscriptions/installment.rb +135 -0
  41. data/app/models/solidus_subscriptions/installment_detail.rb +28 -0
  42. data/app/models/solidus_subscriptions/interval.rb +26 -0
  43. data/app/models/solidus_subscriptions/line_item.rb +42 -0
  44. data/app/models/solidus_subscriptions/promotion/rules/subscription_creation_order.rb +44 -0
  45. data/app/models/solidus_subscriptions/promotion/rules/subscription_installment_order.rb +31 -0
  46. data/app/models/solidus_subscriptions/subscription.rb +392 -0
  47. data/app/models/solidus_subscriptions/subscription_event.rb +11 -0
  48. data/app/overrides/views/admin_subscribable_product_checkbox.rb +8 -0
  49. data/app/overrides/views/admin_subscribable_variant_checkbox.rb +8 -0
  50. data/app/overrides/views/admin_subscriptions_menu_link.rb +10 -0
  51. data/app/overrides/views/admin_users_subscriptions_tab.rb +8 -0
  52. data/app/overrides/views/subscription_line_item_fields.rb +8 -0
  53. data/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb +39 -0
  54. data/app/subscribers/solidus_subscriptions/event_storage_subscriber.rb +64 -0
  55. data/app/views/spree/admin/installments/_state_pill.html.erb +8 -0
  56. data/app/views/spree/admin/installments/index.html.erb +42 -0
  57. data/app/views/spree/admin/products/_subscribable_checkbox.html.erb +8 -0
  58. data/app/views/spree/admin/promotions/rules/_subscription_creation_order.html.erb +0 -0
  59. data/app/views/spree/admin/promotions/rules/_subscription_installment_order.html.erb +0 -0
  60. data/app/views/spree/admin/shared/_subscription_actions.html.erb +35 -0
  61. data/app/views/spree/admin/shared/_subscription_breadcrumbs.html.erb +4 -0
  62. data/app/views/spree/admin/shared/_subscription_sidebar.html.erb +18 -0
  63. data/app/views/spree/admin/shared/_subscription_tab.html.erb +3 -0
  64. data/app/views/spree/admin/shared/_subscription_tabs.html.erb +18 -0
  65. data/app/views/spree/admin/subscription_events/_state_pill.html.erb +8 -0
  66. data/app/views/spree/admin/subscription_events/index.html.erb +42 -0
  67. data/app/views/spree/admin/subscription_orders/index.html.erb +93 -0
  68. data/app/views/spree/admin/subscriptions/_form.html.erb +150 -0
  69. data/app/views/spree/admin/subscriptions/_processing_state_pill.html.erb +9 -0
  70. data/app/views/spree/admin/subscriptions/_state_pill.html.erb +10 -0
  71. data/app/views/spree/admin/subscriptions/edit.html.erb +10 -0
  72. data/app/views/spree/admin/subscriptions/index.html.erb +176 -0
  73. data/app/views/spree/admin/subscriptions/new.html.erb +5 -0
  74. data/app/views/spree/admin/users/_subscription_tab.html.erb +5 -0
  75. data/app/views/spree/admin/users/subscriptions/index.html.erb +44 -0
  76. data/app/views/spree/admin/variants/_subscribable_checkbox.html.erb +6 -0
  77. data/app/views/spree/frontend/products/_subscription_line_item_fields.html.erb +30 -0
  78. data/bin/console +17 -0
  79. data/bin/rails +7 -0
  80. data/bin/rails-engine +13 -0
  81. data/bin/rails-sandbox +16 -0
  82. data/bin/rake +7 -0
  83. data/bin/sandbox +86 -0
  84. data/bin/setup +8 -0
  85. data/config/initializers/permission_sets.rb +11 -0
  86. data/config/initializers/subscribers.rb +9 -0
  87. data/config/locales/en.yml +129 -0
  88. data/config/routes.rb +34 -0
  89. data/db/migrate/20160825164850_create_solidus_subscriptions_subscriptions.rb +11 -0
  90. data/db/migrate/20160825173548_create_solidus_subscriptions_line_items.rb +17 -0
  91. data/db/migrate/20160825202248_create_solidus_subscriptions_installments.rb +23 -0
  92. data/db/migrate/20160825211202_create_solidus_subscriptions_installment_details.rb +22 -0
  93. data/db/migrate/20160825214240_add_subscribable_to_spree_variants.rb +5 -0
  94. data/db/migrate/20160829201653_change_subscription_line_items_installments_to_max_installments.rb +5 -0
  95. data/db/migrate/20160902220242_remove_state_from_solidus_susbscriptions_installment_details.rb +5 -0
  96. data/db/migrate/20160902220604_add_successful_to_solidus_subscriptions_installment_details.rb +5 -0
  97. data/db/migrate/20160902221218_add_message_to_solidus_subscriptions_installment_details.rb +5 -0
  98. data/db/migrate/20160922164101_add_interval_length_and_units_to_subscription_line_items.rb +8 -0
  99. data/db/migrate/20161006191003_add_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -0
  100. data/db/migrate/20161006191127_add_successive_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -0
  101. data/db/migrate/20161014212649_allow_spree_line_item_id_to_be_null.rb +5 -0
  102. data/db/migrate/20161017155749_add_order_id_to_solidus_subscriptions_installment_details.rb +6 -0
  103. data/db/migrate/20161017175509_remove_order_id_from_solidus_subscriptions_installments.rb +5 -0
  104. data/db/migrate/20161017201944_add_subscription_order_to_spree_orders.rb +5 -0
  105. data/db/migrate/20161221155142_add_store_to_solidus_subscriptions_subscriptions.rb +6 -0
  106. data/db/migrate/20161223152905_add_address_id_to_solidus_subscriptions_subscriptions.rb +7 -0
  107. data/db/migrate/20170106224713_change_line_item_max_installments_to_end_date.rb +6 -0
  108. data/db/migrate/20170111224458_change_subscription_actionable_date_to_datetime.rb +5 -0
  109. data/db/migrate/20170111232801_change_inteval_actionable_date_to_datetime.rb +5 -0
  110. data/db/migrate/20170112012407_add_config_options_to_subscriptions.rb +7 -0
  111. data/db/migrate/20200617102749_add_billing_address_to_subscriptions.rb +11 -0
  112. data/db/migrate/20200617155042_add_payment_source_to_subscriptions.rb +6 -0
  113. data/db/migrate/20200618092951_add_payment_method_to_subscriptions.rb +11 -0
  114. data/db/migrate/20200730101242_create_solidus_subscriptions_subscription_events.rb +22 -0
  115. data/db/migrate/20200917072152_add_subscription_reference_to_orders.rb +11 -0
  116. data/db/migrate/20201007140032_add_guest_token_to_subscriptions.rb +6 -0
  117. data/db/migrate/20201123171026_change_actionable_date_to_date.rb +15 -0
  118. data/db/migrate/20210205140422_add_currency_to_subscription.rb +5 -0
  119. data/db/migrate/20210323165714_update_promotion_rule_names.rb +22 -0
  120. data/lib/generators/solidus_subscriptions/install/install_generator.rb +32 -0
  121. data/lib/generators/solidus_subscriptions/install/templates/initializer.rb +99 -0
  122. data/lib/solidus_subscriptions.rb +49 -0
  123. data/lib/solidus_subscriptions/checkout.rb +74 -0
  124. data/lib/solidus_subscriptions/churn_buster/client.rb +48 -0
  125. data/lib/solidus_subscriptions/churn_buster/order_serializer.rb +19 -0
  126. data/lib/solidus_subscriptions/churn_buster/serializer.rb +23 -0
  127. data/lib/solidus_subscriptions/churn_buster/subscription_customer_serializer.rb +28 -0
  128. data/lib/solidus_subscriptions/churn_buster/subscription_payment_method_serializer.rb +37 -0
  129. data/lib/solidus_subscriptions/churn_buster/subscription_serializer.rb +17 -0
  130. data/lib/solidus_subscriptions/configuration.rb +84 -0
  131. data/lib/solidus_subscriptions/dispatcher/base.rb +18 -0
  132. data/lib/solidus_subscriptions/dispatcher/failure_dispatcher.rb +13 -0
  133. data/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher.rb +11 -0
  134. data/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb +19 -0
  135. data/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb +17 -0
  136. data/lib/solidus_subscriptions/engine.rb +56 -0
  137. data/lib/solidus_subscriptions/permission_sets/default_customer.rb +19 -0
  138. data/lib/solidus_subscriptions/permission_sets/subscription_management.rb +12 -0
  139. data/lib/solidus_subscriptions/permitted_attributes.rb +20 -0
  140. data/lib/solidus_subscriptions/processor.rb +17 -0
  141. data/lib/solidus_subscriptions/subscription_generator.rb +77 -0
  142. data/lib/solidus_subscriptions/subscription_line_item_builder.rb +23 -0
  143. data/lib/solidus_subscriptions/testing_support/factories/installment_detail_factory.rb +11 -0
  144. data/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb +29 -0
  145. data/lib/solidus_subscriptions/testing_support/factories/line_item_factory.rb +20 -0
  146. data/lib/solidus_subscriptions/testing_support/factories/spree/line_item_factory.rb +17 -0
  147. data/lib/solidus_subscriptions/testing_support/factories/spree/order_factory.rb +17 -0
  148. data/lib/solidus_subscriptions/testing_support/factories/spree_modification_factory.rb +10 -0
  149. data/lib/solidus_subscriptions/testing_support/factories/subscription_event_factory.rb +8 -0
  150. data/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb +56 -0
  151. data/lib/solidus_subscriptions/version.rb +5 -0
  152. data/lib/tasks/process_subscriptions.rake +8 -0
  153. data/reference/solidus_subscriptions.v1.yaml +290 -0
  154. data/solidus_subscriptions.gemspec +47 -0
  155. data/spec/controllers/spree/admin/subscriptions_controller_spec.rb +202 -0
  156. data/spec/controllers/spree/api/line_items_controller_spec.rb +103 -0
  157. data/spec/controllers/spree/api/orders_controller_spec.rb +57 -0
  158. data/spec/controllers/spree/api/users_controller_spec.rb +48 -0
  159. data/spec/decorators/controllers/solidus_subscriptions/spree/orders_controller/create_subscription_line_items_spec.rb +80 -0
  160. data/spec/decorators/models/solidus_subscriptions/spree/line_item/subscription_line_items_association_spec.rb +10 -0
  161. data/spec/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions_spec.rb +32 -0
  162. data/spec/decorators/models/solidus_subscriptions/spree/order/installment_details_association_spec.rb +9 -0
  163. data/spec/decorators/models/solidus_subscriptions/spree/order/subscription_line_items_association_spec.rb +9 -0
  164. data/spec/decorators/models/solidus_subscriptions/spree/user/have_many_subscriptions_spec.rb +22 -0
  165. data/spec/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions_spec.rb +25 -0
  166. data/spec/features/admin/subscription_orders_spec.rb +35 -0
  167. data/spec/features/admin/subscriptions_spec.rb +63 -0
  168. data/spec/features/admin_users_subscription_tabs_spec.rb +61 -0
  169. data/spec/fixtures/cassettes/churn_buster.yml +229 -0
  170. data/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb +38 -0
  171. data/spec/jobs/solidus_subscriptions/process_subscription_job_spec.rb +83 -0
  172. data/spec/lib/solidus_subscriptions/checkout_spec.rb +125 -0
  173. data/spec/lib/solidus_subscriptions/churn_buster/client_spec.rb +59 -0
  174. data/spec/lib/solidus_subscriptions/dispatcher/failure_dispatcher_spec.rb +29 -0
  175. data/spec/lib/solidus_subscriptions/dispatcher/out_of_stock_dispatcher_spec.rb +15 -0
  176. data/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb +44 -0
  177. data/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb +30 -0
  178. data/spec/lib/solidus_subscriptions/permission_sets/default_customer_spec.rb +95 -0
  179. data/spec/lib/solidus_subscriptions/permission_sets/subscription_management_spec.rb +26 -0
  180. data/spec/lib/solidus_subscriptions/processor_spec.rb +34 -0
  181. data/spec/lib/solidus_subscriptions/promotion/rules/subscription_creation_order_spec.rb +57 -0
  182. data/spec/lib/solidus_subscriptions/promotion/rules/subscription_installment_order_spec.rb +39 -0
  183. data/spec/lib/solidus_subscriptions/subscription_generator_spec.rb +83 -0
  184. data/spec/lib/solidus_subscriptions_spec.rb +30 -0
  185. data/spec/models/solidus_subscriptions/installment_detail_spec.rb +23 -0
  186. data/spec/models/solidus_subscriptions/installment_spec.rb +201 -0
  187. data/spec/models/solidus_subscriptions/line_item_spec.rb +29 -0
  188. data/spec/models/solidus_subscriptions/subscription_spec.rb +814 -0
  189. data/spec/models/spree/variant_spec.rb +16 -0
  190. data/spec/models/spree/wallet_payment_source_spec.rb +20 -0
  191. data/spec/requests/api/v1/line_items_spec.rb +116 -0
  192. data/spec/requests/api/v1/subscriptions_spec.rb +255 -0
  193. data/spec/spec_helper.rb +30 -0
  194. data/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb +76 -0
  195. data/spec/support/active_model_mocks.rb +1 -0
  196. data/spec/support/cancancan.rb +1 -0
  197. data/spec/support/factories.rb +1 -0
  198. data/spec/support/helpers/checkout_infrastructure.rb +18 -0
  199. data/spec/support/helpers/config.rb +13 -0
  200. data/spec/support/shoulda.rb +7 -0
  201. data/spec/support/timecop.rb +1 -0
  202. data/spec/support/vcr.rb +10 -0
  203. data/spec/support/version_cake.rb +8 -0
  204. metadata +498 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::PermissionSets::SubscriptionManagement do
4
+ it 'is allowed to manage all subscriptions' do
5
+ user = create(:user)
6
+ subscription = create(:subscription)
7
+
8
+ ability = Spree::Ability.new(user)
9
+ permission_set = described_class.new(ability)
10
+ permission_set.activate!
11
+
12
+ expect(ability).to be_able_to(:manage, subscription)
13
+ end
14
+
15
+ it "is allowed to manage all line items" do
16
+ user = create(:user)
17
+ subscription = create(:subscription)
18
+ line_item = create(:subscription_line_item, subscription: subscription)
19
+
20
+ ability = Spree::Ability.new(user)
21
+ permission_set = described_class.new(ability)
22
+ permission_set.activate!
23
+
24
+ expect(ability).to be_able_to(:manage, line_item)
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::Processor do
4
+ it 'schedules the processing of actionable subscriptions' do
5
+ actionable_subscription = create(:subscription, :actionable)
6
+
7
+ described_class.run
8
+
9
+ expect(SolidusSubscriptions::ProcessSubscriptionJob).to have_been_enqueued
10
+ .with(actionable_subscription)
11
+ end
12
+
13
+ it 'schedules the processing of non actionable subscriptions with actionable installments' do
14
+ subscription_with_actionable_installment = create(
15
+ :subscription,
16
+ actionable_date: Time.zone.today + 7.days,
17
+ installments: [create(:installment, :actionable)]
18
+ )
19
+
20
+ described_class.run
21
+
22
+ expect(SolidusSubscriptions::ProcessSubscriptionJob).to have_been_enqueued
23
+ .with(subscription_with_actionable_installment)
24
+ end
25
+
26
+ it 'does not schedule the processing of non actionable subscriptions' do
27
+ non_actionable_subscription = create(:subscription, actionable_date: Time.zone.today + 14.days)
28
+
29
+ described_class.run
30
+
31
+ expect(SolidusSubscriptions::ProcessSubscriptionJob).not_to have_been_enqueued
32
+ .with(non_actionable_subscription)
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusSubscriptions::Promotion::Rules::SubscriptionCreationOrder do
6
+ let(:rule) { described_class.new }
7
+
8
+ describe '#applicable' do
9
+ subject { rule.applicable? promotable }
10
+
11
+ context 'when the promotable is a Spree::Order' do
12
+ let(:promotable) { build_stubbed :order }
13
+
14
+ it { is_expected.to be_truthy }
15
+ end
16
+
17
+ context 'when the promotable is not a Spree::Order' do
18
+ let(:promotable) { build_stubbed :line_item }
19
+
20
+ it { is_expected.to be_falsy }
21
+ end
22
+ end
23
+
24
+ describe '#eligible?' do
25
+ subject { rule.eligible? order }
26
+
27
+ let(:order) { create(:order, line_items: line_items) }
28
+
29
+ context 'when the order contains a line item with a subscription' do
30
+ let(:line_items) { build_list(:line_item, 1, :with_subscription_line_items) }
31
+
32
+ it { is_expected.to be_truthy }
33
+ end
34
+
35
+ context 'when the order does not contain a line item with a subscription' do
36
+ let(:line_items) { build_list(:line_item, 1) }
37
+
38
+ it { is_expected.to be_falsy }
39
+ end
40
+ end
41
+
42
+ describe '#actionable?' do
43
+ subject { rule.actionable? line_item }
44
+
45
+ context 'when the line item has a subscription' do
46
+ let(:line_item) { build_stubbed(:line_item, :with_subscription_line_items) }
47
+
48
+ it { is_expected.to be_truthy }
49
+ end
50
+
51
+ context 'when the line item has no subscription' do
52
+ let(:line_item) { build_stubbed :line_item }
53
+
54
+ it { is_expected.to be_falsy }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusSubscriptions::Promotion::Rules::SubscriptionInstallmentOrder do
6
+ let(:rule) { described_class.new }
7
+
8
+ describe '#applicable' do
9
+ subject { rule.applicable? promotable }
10
+
11
+ context 'when the promotable is a Spree::Order' do
12
+ let(:promotable) { build_stubbed :order }
13
+
14
+ it { is_expected.to be_truthy }
15
+ end
16
+
17
+ context 'when the promotable is not a Spree::Order' do
18
+ let(:promotable) { build_stubbed :line_item }
19
+
20
+ it { is_expected.to be_falsy }
21
+ end
22
+ end
23
+
24
+ describe '#eligible?' do
25
+ subject { rule.eligible? order }
26
+
27
+ context 'when the order fulfills a subscription installment' do
28
+ let(:order) { create(:order, subscription_order: true) }
29
+
30
+ it { is_expected.to be_truthy }
31
+ end
32
+
33
+ context 'when the order contains does not fulfill a subscription installment' do
34
+ let(:order) { create(:order) }
35
+
36
+ it { is_expected.to be_falsy }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusSubscriptions::SubscriptionGenerator do
6
+ describe '.activate' do
7
+ it 'creates the correct number of subscriptions' do
8
+ subscription_line_items = build_list(:subscription_line_item, 2)
9
+
10
+ expect {
11
+ described_class.activate(subscription_line_items)
12
+ }.to change(SolidusSubscriptions::Subscription, :count).by(1)
13
+ end
14
+
15
+ it 'creates subscriptions with the correct attributes', :aggregate_failures do
16
+ subscription_line_items = build_list(:subscription_line_item, 2)
17
+ subscription_line_item = subscription_line_items.first
18
+
19
+ subscription = described_class.activate(subscription_line_items)
20
+
21
+ expect(subscription.line_items).to match_array(subscription_line_items)
22
+ expect(subscription).to have_attributes(
23
+ user: subscription_line_item.order.user,
24
+ shipping_address: subscription_line_item.spree_line_item.order.ship_address,
25
+ billing_address: subscription_line_item.spree_line_item.order.bill_address,
26
+ interval_length: subscription_line_item.interval_length,
27
+ interval_units: subscription_line_item.interval_units,
28
+ end_date: subscription_line_item.end_date,
29
+ store: subscription_line_item.order.store,
30
+ currency: subscription_line_item.order.currency
31
+ )
32
+ end
33
+
34
+ it 'copies the payment method from the order' do
35
+ subscription_line_item = build(:subscription_line_item)
36
+ payment_method = create(:credit_card_payment_method)
37
+ payment_source = create(:credit_card, payment_method: payment_method, user: subscription_line_item.order.user)
38
+ create(:payment,
39
+ order: subscription_line_item.spree_line_item.order,
40
+ source: payment_source,
41
+ payment_method: payment_method,)
42
+
43
+ subscription = described_class.activate([subscription_line_item])
44
+
45
+ expect(subscription).to have_attributes(
46
+ payment_method: payment_method,
47
+ payment_source: payment_source,
48
+ )
49
+ end
50
+
51
+ it 'cleanups the subscription line items fields duplicated on the subscription' do
52
+ attrs = { interval_length: 2, interval_units: :week, end_date: Time.zone.tomorrow }
53
+ subscription_line_item = create(:subscription_line_item, attrs)
54
+
55
+ described_class.activate([subscription_line_item])
56
+
57
+ expect(subscription_line_item.reload).to have_attributes(
58
+ interval_length: nil,
59
+ interval_units: nil,
60
+ end_date: nil
61
+ )
62
+ end
63
+ end
64
+
65
+ describe '.group' do
66
+ it 'groups subscriptions by interval and end date' do
67
+ monthly_subscriptions = build_stubbed_list(:subscription_line_item, 2)
68
+ bimonthly_subscriptions = build_stubbed_list(:subscription_line_item, 2, interval_length: 2)
69
+ weekly_subscriptions = build_stubbed_list(:subscription_line_item, 2, interval_units: :week)
70
+ expiring_subscriptions = build_stubbed_list(:subscription_line_item, 2, end_date: Time.zone.tomorrow)
71
+
72
+ subscriptions = [
73
+ monthly_subscriptions,
74
+ bimonthly_subscriptions,
75
+ weekly_subscriptions,
76
+ expiring_subscriptions,
77
+ ]
78
+ grouping_result = described_class.group(subscriptions.sum)
79
+
80
+ expect(grouping_result).to match_array(subscriptions)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions do
4
+ describe '.churn_buster' do
5
+ context 'when Churn Buster was configured' do
6
+ it 'returns a Churn Buster client instance' do
7
+ allow(described_class.configuration).to receive_messages(
8
+ churn_buster?: true,
9
+ churn_buster_account_id: 'account_id',
10
+ churn_buster_api_key: 'api_key',
11
+ )
12
+ churn_buster = instance_double(SolidusSubscriptions::ChurnBuster::Client)
13
+ allow(SolidusSubscriptions::ChurnBuster::Client).to receive(:new).with(
14
+ account_id: 'account_id',
15
+ api_key: 'api_key',
16
+ ).and_return(churn_buster)
17
+
18
+ expect(described_class.churn_buster).to eq(churn_buster)
19
+ end
20
+ end
21
+
22
+ context 'when Churn Buster was not configured' do
23
+ it 'returns nil' do
24
+ allow(described_class.configuration).to receive_messages(churn_buster?: false)
25
+
26
+ expect(described_class.churn_buster).to be_nil
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusSubscriptions::InstallmentDetail, type: :model do
6
+ it { is_expected.to validate_presence_of :installment }
7
+
8
+ describe '#failed?' do
9
+ subject { build(:installment_detail, success: success).failed? }
10
+
11
+ context 'when the detail was successful' do
12
+ let(:success) { true }
13
+
14
+ it { is_expected.to be_falsy }
15
+ end
16
+
17
+ context 'when the detail was not successfuly' do
18
+ let(:success) { false }
19
+
20
+ it { is_expected.to be_truthy }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusSubscriptions::Installment, type: :model do
6
+ let(:installment) { create :installment }
7
+
8
+ it { is_expected.to validate_presence_of :subscription }
9
+
10
+ describe '#out_of_stock' do
11
+ subject(:out_of_stock) { installment.out_of_stock }
12
+
13
+ let(:expected_date) do
14
+ Time.zone.today + SolidusSubscriptions.configuration.reprocessing_interval
15
+ end
16
+
17
+ it { is_expected.to be_a SolidusSubscriptions::InstallmentDetail }
18
+ it { is_expected.not_to be_successful }
19
+
20
+ it 'has the correct message' do
21
+ expect(out_of_stock).to have_attributes(
22
+ message: I18n.t('solidus_subscriptions.installment_details.out_of_stock')
23
+ )
24
+ end
25
+
26
+ it 'advances the installment actionable_date' do
27
+ out_of_stock
28
+ actionable_date = installment.reload.actionable_date
29
+ expect(actionable_date).to eq expected_date
30
+ end
31
+ end
32
+
33
+ describe '#success!' do
34
+ subject(:success) { installment.success!(order) }
35
+
36
+ let(:order) { create :order }
37
+
38
+ let(:installment) { create :installment, actionable_date: actionable_date }
39
+ let(:actionable_date) { 1.month.from_now.to_date }
40
+
41
+ it 'removes any actionable date if any' do
42
+ expect { success }.
43
+ to change(installment, :actionable_date).
44
+ from(actionable_date).to(nil)
45
+ end
46
+
47
+ it 'creates a new installment detail' do
48
+ expect { success }.
49
+ to change { SolidusSubscriptions::InstallmentDetail.count }.
50
+ by(1)
51
+ end
52
+
53
+ it 'creates a successful installment detail' do
54
+ success
55
+ expect(installment.details.last).to be_successful && have_attributes(
56
+ order: order,
57
+ message: I18n.t('solidus_subscriptions.installment_details.success')
58
+ )
59
+ end
60
+ end
61
+
62
+ describe '#failed!' do
63
+ subject(:failed) { installment.failed!(order) }
64
+
65
+ let(:order) { create :order }
66
+
67
+ let(:expected_date) do
68
+ Time.zone.today + SolidusSubscriptions.configuration.reprocessing_interval
69
+ end
70
+
71
+ it { is_expected.to be_a SolidusSubscriptions::InstallmentDetail }
72
+ it { is_expected.not_to be_successful }
73
+
74
+ it 'has the correct message' do
75
+ expect(failed).to have_attributes(
76
+ message: I18n.t('solidus_subscriptions.installment_details.failed'),
77
+ order: order
78
+ )
79
+ end
80
+
81
+ it 'advances the installment actionable_date' do
82
+ failed
83
+ actionable_date = installment.reload.actionable_date
84
+ expect(actionable_date).to eq expected_date
85
+ end
86
+
87
+ context 'when the reprocessing interval is set to nil' do
88
+ before { stub_config(reprocessing_interval: nil) }
89
+
90
+ it 'does not advance the installment actionable_date' do
91
+ failed
92
+ actionable_date = installment.reload.actionable_date
93
+ expect(actionable_date).to be_nil
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#unfulfilled?' do
99
+ subject { installment.unfulfilled? }
100
+
101
+ let(:installment) { create(:installment, details: details) }
102
+
103
+ context 'when the installment has an associated successful detail' do
104
+ let(:details) { create_list :installment_detail, 1, success: true }
105
+
106
+ it { is_expected.to be_falsy }
107
+ end
108
+
109
+ context 'when the installment has no associated successful detail' do
110
+ let(:details) { create_list :installment_detail, 1 }
111
+
112
+ it { is_expected.to be_truthy }
113
+ end
114
+ end
115
+
116
+ describe '#fulfilled' do
117
+ subject { installment.fulfilled? }
118
+
119
+ let(:installment) { create(:installment, details: details) }
120
+
121
+ context 'when the installment has an associated completed order' do
122
+ let(:details) { create_list :installment_detail, 1, success: true }
123
+
124
+ it { is_expected.to be_truthy }
125
+ end
126
+
127
+ context 'when the installment has no associated completed order' do
128
+ let(:details) { create_list :installment_detail, 1 }
129
+
130
+ it { is_expected.to be_falsy }
131
+ end
132
+ end
133
+
134
+ describe '#payment_failed!' do
135
+ context 'when the maximum reprocessing time has been reached' do
136
+ it 'creates a new installment detail' do
137
+ subscription = create(:subscription)
138
+ allow(subscription).to receive(:maximum_reprocessing_time_reached?).and_return(true)
139
+
140
+ current_installment = create(:installment, subscription: subscription)
141
+ current_installment.payment_failed!(create(:order))
142
+
143
+ expect(current_installment.details.count).to eq(1)
144
+ end
145
+
146
+ it 'sets the actionable_date to nil' do
147
+ subscription = create(:subscription)
148
+ allow(subscription).to receive(:maximum_reprocessing_time_reached?).and_return(true)
149
+
150
+ current_installment = create(:installment, subscription: subscription)
151
+ current_installment.payment_failed!(create(:order))
152
+
153
+ expect(current_installment.actionable_date).to eq(nil)
154
+ end
155
+
156
+ it 'cancels the subscription' do
157
+ subscription = create(:subscription)
158
+ allow(subscription).to receive(:maximum_reprocessing_time_reached?).and_return(true)
159
+
160
+ current_installment = create(:installment, subscription: subscription)
161
+ current_installment.payment_failed!(create(:order))
162
+
163
+ expect(current_installment.subscription.state).to eq('canceled')
164
+ end
165
+ end
166
+
167
+ context 'when the maximum reprocessing time has not been reached' do
168
+ it 'creates a new installment detail' do
169
+ subscription = create(:subscription)
170
+ allow(subscription).to receive(:maximum_reprocessing_time_reached?).and_return(false)
171
+
172
+ current_installment = create(:installment, subscription: subscription)
173
+ current_installment.payment_failed!(create(:order))
174
+ current_installment.payment_failed!(create(:order))
175
+
176
+ expect(current_installment.details.count).to eq(2)
177
+ end
178
+
179
+ it "advances the installment's actionable_date" do
180
+ subscription = create(:subscription)
181
+ allow(subscription).to receive(:maximum_reprocessing_time_reached?).and_return(false)
182
+ stub_config(reprocessing_interval: 2.days)
183
+
184
+ current_installment = create(:installment, subscription: subscription)
185
+ current_installment.payment_failed!(create(:order))
186
+
187
+ expect(current_installment.actionable_date).to eq(Time.zone.today + 2.days)
188
+ end
189
+
190
+ it 'does not cancel the subscription' do
191
+ subscription = create(:subscription)
192
+ allow(subscription).to receive(:maximum_reprocessing_time_reached?).and_return(false)
193
+
194
+ current_installment = create(:installment, subscription: subscription)
195
+ current_installment.payment_failed!(create(:order))
196
+
197
+ expect(subscription.state).to eq('active')
198
+ end
199
+ end
200
+ end
201
+ end