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,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::ProcessInstallmentJob do
4
+ it 'processes checkout for the installment' do
5
+ installment = build_stubbed(:installment)
6
+ checkout = instance_spy(SolidusSubscriptions::Checkout)
7
+ allow(SolidusSubscriptions::Checkout).to receive(:new).with(installment).and_return(checkout)
8
+
9
+ described_class.perform_now(installment)
10
+
11
+ expect(checkout).to have_received(:process)
12
+ end
13
+
14
+ context 'when handling #perform errors' do
15
+ it 'swallows error when a proc is not configured' do
16
+ checkout = instance_double(SolidusSubscriptions::Checkout).tap do |c|
17
+ allow(c).to receive(:process).and_raise('test error')
18
+ end
19
+ allow(SolidusSubscriptions::Checkout).to receive(:new).and_return(checkout)
20
+
21
+ expect {
22
+ described_class.perform_now(build_stubbed(:installment))
23
+ }.not_to raise_error
24
+ end
25
+
26
+ it 'runs proc when a proc is configured' do
27
+ stub_config(processing_error_handler: proc { |e| raise e } )
28
+ checkout = instance_double(SolidusSubscriptions::Checkout).tap do |c|
29
+ allow(c).to receive(:process).and_raise('test error')
30
+ end
31
+ allow(SolidusSubscriptions::Checkout).to receive(:new).and_return(checkout)
32
+
33
+ expect {
34
+ described_class.perform_now(build_stubbed(:installment))
35
+ }.to raise_error(/test error/)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::ProcessSubscriptionJob do
4
+ context 'when clear_past_installments is enabled' do
5
+ it 'voids the actionable date of the unfulfilled installments' do
6
+ stub_config(clear_past_installments: true)
7
+ subscription = create(:subscription)
8
+ unfulfilled_installment = create(:installment, :failed, subscription: subscription)
9
+
10
+ described_class.perform_now(subscription)
11
+
12
+ expect(unfulfilled_installment.reload.actionable_date).to eq(nil)
13
+ end
14
+ end
15
+
16
+ context 'when the subscription is actionable' do
17
+ it 'resets the successive_skip_count' do
18
+ subscription = create(:subscription, :actionable, successive_skip_count: 3)
19
+
20
+ described_class.perform_now(subscription)
21
+
22
+ expect(subscription.reload.successive_skip_count).to eq(0)
23
+ end
24
+
25
+ it 'creates a new installment' do
26
+ subscription = create(:subscription, :actionable)
27
+
28
+ described_class.perform_now(subscription)
29
+
30
+ expect(subscription.installments.count).to eq(1)
31
+ end
32
+
33
+ it 'advances the actionable date' do
34
+ subscription = create(:subscription, :actionable)
35
+ subscription.update_columns(interval_length: 1, interval_units: 'week')
36
+ old_actionable_date = subscription.reload.actionable_date
37
+
38
+ described_class.perform_now(subscription)
39
+
40
+ expect(subscription.reload.actionable_date.to_date).to eq((old_actionable_date + 1.week).to_date)
41
+ end
42
+ end
43
+
44
+ context 'when the subscription is pending cancellation' do
45
+ it 'cancels the subscription' do
46
+ subscription = create(
47
+ :subscription,
48
+ :actionable,
49
+ :pending_cancellation,
50
+ )
51
+ described_class.perform_now(subscription)
52
+
53
+ expect(subscription.reload.state).to eq('canceled')
54
+ end
55
+ end
56
+
57
+ context 'when the subscription is pending of deactivation' do
58
+ it 'deactivates the subscription' do
59
+ subscription = create(
60
+ :subscription,
61
+ :actionable,
62
+ interval_units: 'week',
63
+ interval_length: 2,
64
+ end_date: 3.days.from_now,
65
+ )
66
+ described_class.perform_now(subscription)
67
+
68
+ expect(subscription.reload.state).to eq('inactive')
69
+ end
70
+ end
71
+
72
+ it 'schedules all the subscription actionable installments for processing' do
73
+ subscription = create(:subscription, :actionable)
74
+ unfulfilled_installment = create(:installment, :failed, subscription: subscription)
75
+
76
+ described_class.perform_now(subscription)
77
+
78
+ new_installment = subscription.reload.installments.last
79
+ [unfulfilled_installment, new_installment].each do |installment|
80
+ expect(SolidusSubscriptions::ProcessInstallmentJob).to have_been_enqueued.with(installment)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::Checkout, :checkout do
4
+ context 'when the order can be created and paid' do
5
+ # rubocop:disable RSpec/MultipleExpectations
6
+ it 'creates and finalizes a new order for the installment' do
7
+ stub_spree_preferences(auto_capture: true)
8
+ installment = create(:installment, :actionable)
9
+
10
+ order = described_class.new(installment).process
11
+
12
+ expect(order).to be_complete
13
+ expect(order).to be_paid
14
+ end
15
+
16
+ it 'copies basic information from the subscription' do
17
+ stub_spree_preferences(auto_capture: true)
18
+ installment = create(:installment, :actionable)
19
+ subscription = installment.subscription
20
+
21
+ order = described_class.new(installment).process
22
+
23
+ expect(order.ship_address.value_attributes).to eq(subscription.shipping_address_to_use.value_attributes)
24
+ expect(order.bill_address.value_attributes).to eq(subscription.billing_address_to_use.value_attributes)
25
+ expect(order.payments.first.payment_method).to eq(subscription.payment_method_to_use)
26
+ expect(order.payments.first.source).to eq(subscription.payment_source_to_use)
27
+ expect(order.currency).to eq(subscription.currency)
28
+ expect(order.user).to eq(subscription.user)
29
+ expect(order.email).to eq(subscription.user.email)
30
+ end
31
+
32
+ it 'marks the order as a subscription order' do
33
+ stub_spree_preferences(auto_capture: true)
34
+ installment = create(:installment, :actionable)
35
+ subscription = installment.subscription
36
+
37
+ order = described_class.new(installment).process
38
+
39
+ expect(order.subscription).to eq(subscription)
40
+ expect(order.subscription_order).to eq(true)
41
+ end
42
+ # rubocop:enable RSpec/MultipleExpectations
43
+
44
+ it 'matches the total on the subscription' do
45
+ stub_spree_preferences(auto_capture: true)
46
+ installment = create(:installment, :actionable)
47
+ subscription = installment.subscription
48
+
49
+ order = described_class.new(installment).process
50
+
51
+ expect(order.item_total).to eq(subscription.line_items.first.subscribable.price)
52
+ end
53
+
54
+ it 'calls the success dispatcher' do
55
+ stub_spree_preferences(auto_capture: true)
56
+ installment = create(:installment, :actionable)
57
+ success_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::SuccessDispatcher, installment)
58
+
59
+ described_class.new(installment).process
60
+
61
+ expect(success_dispatcher).to have_received(:dispatch)
62
+ end
63
+ end
64
+
65
+ context 'when payment of the order fails' do
66
+ it 'calls the payment failed dispatcher' do
67
+ stub_spree_preferences(auto_capture: true)
68
+ installment = create(:installment, :actionable).tap do |i|
69
+ i.subscription.update!(payment_source: create(:credit_card, number: '4111123412341234', user: i.subscription.user))
70
+ end
71
+ payment_failed_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::PaymentFailedDispatcher, installment)
72
+
73
+ described_class.new(installment).process
74
+
75
+ expect(payment_failed_dispatcher).to have_received(:dispatch)
76
+ end
77
+ end
78
+
79
+ context 'when an item is out of stock' do
80
+ it 'calls the out of stock dispatcher' do
81
+ stub_spree_preferences(auto_capture: true)
82
+ installment = create(:installment, :actionable).tap do |i|
83
+ i.subscription.line_items.first.subscribable.stock_items.each do |stock_item|
84
+ stock_item.update!(backorderable: false)
85
+ end
86
+ end
87
+ out_of_stock_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::OutOfStockDispatcher, installment)
88
+
89
+ described_class.new(installment).process
90
+
91
+ expect(out_of_stock_dispatcher).to have_received(:dispatch)
92
+ end
93
+ end
94
+
95
+ context 'when a generic transition error happens during checkout' do
96
+ it 'calls the failure dispatcher' do
97
+ stub_spree_preferences(auto_capture: true)
98
+ installment = create(:installment, :actionable)
99
+ failure_dispatcher = stub_dispatcher(SolidusSubscriptions::Dispatcher::FailureDispatcher, installment)
100
+ # rubocop:disable RSpec/AnyInstance
101
+ allow_any_instance_of(Spree::Order).to receive(:next!)
102
+ .and_raise(StateMachines::InvalidTransition.new(
103
+ Spree::Order.new,
104
+ Spree::Order.state_machines[:state],
105
+ :next,
106
+ ))
107
+ # rubocop:enable RSpec/AnyInstance
108
+
109
+ described_class.new(installment).process
110
+
111
+ expect(failure_dispatcher).to have_received(:dispatch)
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def stub_dispatcher(klass, installment)
118
+ instance_spy(klass).tap do |dispatcher|
119
+ allow(klass).to receive(:new).with(
120
+ installment,
121
+ an_instance_of(Spree::Order)
122
+ ).and_return(dispatcher)
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::ChurnBuster::Client, vcr: { cassette_name: 'churn_buster', record: :new_episodes } do
4
+ describe '#report_failed_payment' do
5
+ it 'reports the failed payment to Churn Buster' do
6
+ client = described_class.new(
7
+ account_id: 'test_account_id',
8
+ api_key: 'test_api_key',
9
+ )
10
+
11
+ order = create(:order, subscription: create(:subscription))
12
+ response = client.report_failed_payment(order)
13
+
14
+ expect(response).to be_success
15
+ end
16
+ end
17
+
18
+ describe '#report_successful_payment' do
19
+ it 'reports the successful payment to Churn Buster' do
20
+ client = described_class.new(
21
+ account_id: 'test_account_id',
22
+ api_key: 'test_api_key',
23
+ )
24
+
25
+ order = create(:order, subscription: create(:subscription))
26
+ response = client.report_successful_payment(order)
27
+
28
+ expect(response).to be_success
29
+ end
30
+ end
31
+
32
+ describe '#report_subscription_cancellation' do
33
+ it 'reports the failed payment to Churn Buster' do
34
+ client = described_class.new(
35
+ account_id: 'test_account_id',
36
+ api_key: 'test_api_key',
37
+ )
38
+
39
+ subscription = create(:subscription)
40
+ response = client.report_subscription_cancellation(subscription)
41
+
42
+ expect(response).to be_success
43
+ end
44
+ end
45
+
46
+ describe '#report_payment_method_change' do
47
+ it 'reports the payment method change to Churn Buster' do
48
+ client = described_class.new(
49
+ account_id: 'test_account_id',
50
+ api_key: 'test_api_key',
51
+ )
52
+
53
+ subscription = create(:subscription)
54
+ response = client.report_payment_method_change(subscription)
55
+
56
+ expect(response).to be_success
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::Dispatcher::FailureDispatcher do
4
+ describe '#dispatch' do
5
+ it 'marks the installment as failed' do
6
+ installment = instance_spy(SolidusSubscriptions::Installment)
7
+ order = create(:order_with_line_items)
8
+
9
+ dispatcher = described_class.new(installment, order)
10
+ dispatcher.dispatch
11
+
12
+ expect(installment).to have_received(:failed!).with(order)
13
+ end
14
+
15
+ it 'cancels the order' do
16
+ if Spree.solidus_gem_version > Gem::Version.new('2.10')
17
+ skip 'Orders in `cart` state cannot be canceled starting from Solidus 2.11.'
18
+ end
19
+
20
+ installment = instance_spy(SolidusSubscriptions::Installment)
21
+ order = create(:order_with_line_items)
22
+
23
+ dispatcher = described_class.new(installment, order)
24
+ dispatcher.dispatch
25
+
26
+ expect(order.state).to eq('canceled')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::Dispatcher::OutOfStockDispatcher do
4
+ describe '#dispatch' do
5
+ it 'marks the installment as out of stock' do
6
+ installment = instance_spy(SolidusSubscriptions::Installment)
7
+ order = build_stubbed(:order)
8
+
9
+ dispatcher = described_class.new(installment, order)
10
+ dispatcher.dispatch
11
+
12
+ expect(installment).to have_received(:out_of_stock)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::Dispatcher::PaymentFailedDispatcher do
4
+ describe '#dispatch' do
5
+ it 'marks the installment as payment_failed' do
6
+ installment = instance_spy(SolidusSubscriptions::Installment)
7
+ order = create(:order_with_line_items)
8
+
9
+ dispatcher = described_class.new(installment, order)
10
+ dispatcher.dispatch
11
+
12
+ expect(installment).to have_received(:payment_failed!).with(order)
13
+ end
14
+
15
+ it 'cancels the order' do
16
+ if Spree.solidus_gem_version > Gem::Version.new('2.10')
17
+ skip 'Orders in `cart` state cannot be canceled starting from Solidus 2.11.'
18
+ end
19
+
20
+ installment = instance_spy(SolidusSubscriptions::Installment)
21
+ order = create(:order_with_line_items)
22
+
23
+ dispatcher = described_class.new(installment, order)
24
+ dispatcher.dispatch
25
+
26
+ expect(order.state).to eq('canceled')
27
+ end
28
+
29
+ it 'fires an installments_failed_payment event' do
30
+ stub_const('Spree::Event', class_spy(Spree::Event))
31
+ installment = instance_spy(SolidusSubscriptions::Installment)
32
+ order = create(:order_with_line_items)
33
+
34
+ dispatcher = described_class.new(installment, order)
35
+ dispatcher.dispatch
36
+
37
+ expect(Spree::Event).to have_received(:fire).with(
38
+ 'solidus_subscriptions.installment_failed_payment',
39
+ installment: installment,
40
+ order: order,
41
+ )
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::Dispatcher::SuccessDispatcher do
4
+ describe '#dispatch' do
5
+ it 'marks the installment as success' do
6
+ installment = instance_spy(SolidusSubscriptions::Installment)
7
+ order = create(:order_with_line_items)
8
+
9
+ dispatcher = described_class.new(installment, order)
10
+ dispatcher.dispatch
11
+
12
+ expect(installment).to have_received(:success!).with(order)
13
+ end
14
+
15
+ it 'fires an installments_succeeded event' do
16
+ stub_const('Spree::Event', class_spy(Spree::Event))
17
+ installment = instance_spy(SolidusSubscriptions::Installment)
18
+ order = create(:order_with_line_items)
19
+
20
+ dispatcher = described_class.new(installment, order)
21
+ dispatcher.dispatch
22
+
23
+ expect(Spree::Event).to have_received(:fire).with(
24
+ 'solidus_subscriptions.installment_succeeded',
25
+ installment: installment,
26
+ order: order,
27
+ )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe SolidusSubscriptions::PermissionSets::DefaultCustomer do
4
+ context 'when the user is authenticated' do
5
+ it 'is allowed to display and update their subscriptions' do
6
+ user = create(:user)
7
+ subscription = create(:subscription, user: user)
8
+
9
+ ability = Spree::Ability.new(user)
10
+ permission_set = described_class.new(ability)
11
+ permission_set.activate!
12
+
13
+ expect(ability).to be_able_to([:show, :display, :update], subscription)
14
+ end
15
+
16
+ it "is not allowed to display or update someone else's subscriptions" do
17
+ user = create(:user)
18
+ subscription = create(:subscription)
19
+
20
+ ability = Spree::Ability.new(user)
21
+ permission_set = described_class.new(ability)
22
+ permission_set.activate!
23
+
24
+ expect(ability).not_to be_able_to([:show, :display, :update], subscription)
25
+ end
26
+
27
+ it 'is allowed to display and update line items on their subscriptions' do
28
+ user = create(:user)
29
+ subscription = create(:subscription, user: user)
30
+ line_item = create(:subscription_line_item, subscription: subscription)
31
+
32
+ ability = Spree::Ability.new(user)
33
+ permission_set = described_class.new(ability)
34
+ permission_set.activate!
35
+
36
+ expect(ability).to be_able_to([:show, :display, :update], line_item)
37
+ end
38
+
39
+ it "is not allowed to display or update line items on someone else's subscriptions" do
40
+ user = create(:user)
41
+ subscription = create(:subscription)
42
+ line_item = create(:subscription_line_item, subscription: subscription)
43
+
44
+ ability = Spree::Ability.new(user)
45
+ permission_set = described_class.new(ability)
46
+ permission_set.activate!
47
+
48
+ expect(ability).not_to be_able_to([:show, :display, :update], line_item)
49
+ end
50
+ end
51
+
52
+ context 'when the user provides a guest token' do
53
+ it 'is allowed to display and update their subscriptions' do
54
+ subscription = create(:subscription)
55
+
56
+ ability = Spree::Ability.new(nil)
57
+ permission_set = described_class.new(ability)
58
+ permission_set.activate!
59
+
60
+ expect(ability).to be_able_to([:show, :display, :update], subscription, subscription.guest_token)
61
+ end
62
+
63
+ it "is not allowed to display or update someone else's subscriptions" do
64
+ subscription = create(:subscription)
65
+
66
+ ability = Spree::Ability.new(nil)
67
+ permission_set = described_class.new(ability)
68
+ permission_set.activate!
69
+
70
+ expect(ability).not_to be_able_to([:show, :display, :update], subscription, 'invalid')
71
+ end
72
+
73
+ it 'is allowed to display and update line items on their subscriptions' do
74
+ subscription = create(:subscription)
75
+ line_item = create(:subscription_line_item, subscription: subscription)
76
+
77
+ ability = Spree::Ability.new(nil)
78
+ permission_set = described_class.new(ability)
79
+ permission_set.activate!
80
+
81
+ expect(ability).to be_able_to([:show, :display, :update], line_item, subscription.guest_token)
82
+ end
83
+
84
+ it "is not allowed to display or update line items on someone else's subscriptions" do
85
+ subscription = create(:subscription)
86
+ line_item = create(:subscription_line_item, subscription: subscription)
87
+
88
+ ability = Spree::Ability.new(nil)
89
+ permission_set = described_class.new(ability)
90
+ permission_set.activate!
91
+
92
+ expect(ability).not_to be_able_to([:show, :display, :update], line_item, 'invalid')
93
+ end
94
+ end
95
+ end