solidus_subscriptions 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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