solidus_subscriptions 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +57 -9
  3. data/.github/dependabot.yml +7 -0
  4. data/.rubocop.yml +3 -0
  5. data/CHANGELOG.md +201 -169
  6. data/Gemfile +14 -1
  7. data/README.md +17 -7
  8. data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +24 -0
  9. data/app/controllers/spree/admin/subscriptions_controller.rb +32 -4
  10. data/app/decorators/models/solidus_subscriptions/spree/wallet_payment_source/report_default_change_to_subscriptions.rb +2 -2
  11. data/app/jobs/solidus_subscriptions/create_subscription_job.rb +11 -0
  12. data/app/jobs/solidus_subscriptions/process_installment_job.rb +1 -1
  13. data/app/models/solidus_subscriptions/subscription.rb +75 -31
  14. data/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb +1 -0
  15. data/app/subscribers/solidus_subscriptions/event_storage_subscriber.rb +1 -0
  16. data/app/subscribers/solidus_subscriptions/order_subscriber.rb +16 -0
  17. data/app/views/spree/admin/shared/_subscription_actions.html.erb +27 -8
  18. data/app/views/spree/admin/shared/_subscription_sidebar.html.erb +2 -0
  19. data/app/views/spree/admin/subscriptions/_state_pill.html.erb +3 -2
  20. data/app/views/spree/admin/subscriptions/index.html.erb +50 -13
  21. data/bin/sandbox +1 -6
  22. data/config/locales/en.yml +16 -0
  23. data/config/routes.rb +9 -3
  24. data/db/migrate/20210905145955_add_paused_to_subscriptions.rb +5 -0
  25. data/lib/decorators/api/controllers/solidus_subscriptions/spree/api/line_items_controller/create_subscription_line_items.rb +14 -0
  26. data/lib/generators/solidus_subscriptions/install/install_generator.rb +16 -0
  27. data/lib/generators/solidus_subscriptions/install/templates/app/controllers/concerns/create_subscription.rb +17 -0
  28. data/lib/generators/solidus_subscriptions/install/templates/app/views/cart_line_items/_subscription_fields.html.erb +30 -0
  29. data/lib/generators/solidus_subscriptions/install/templates/initializer.rb +3 -1
  30. data/lib/solidus_subscriptions/configuration.rb +29 -6
  31. data/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb +2 -2
  32. data/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb +2 -2
  33. data/lib/solidus_subscriptions/engine.rb +28 -0
  34. data/lib/solidus_subscriptions/permission_sets/default_customer.rb +1 -1
  35. data/lib/solidus_subscriptions/processing_error_handlers/rails_logger.rb +25 -0
  36. data/lib/solidus_subscriptions/subscription_generator.rb +6 -0
  37. data/lib/solidus_subscriptions/version.rb +1 -1
  38. data/solidus_subscriptions.gemspec +6 -6
  39. data/spec/controllers/spree/api/line_items_controller_spec.rb +63 -28
  40. data/spec/controllers/spree/api/users_controller_spec.rb +3 -0
  41. data/spec/decorators/controllers/solidus_subscriptions/spree/orders_controller/create_subscription_line_items_spec.rb +4 -4
  42. data/spec/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions_spec.rb +2 -2
  43. data/spec/features/admin/subscriptions_spec.rb +13 -3
  44. data/spec/jobs/solidus_subscriptions/create_subscription_job_spec.rb +20 -0
  45. data/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb +17 -0
  46. data/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb +3 -3
  47. data/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb +3 -3
  48. data/spec/lib/solidus_subscriptions/subscription_generator_spec.rb +1 -1
  49. data/spec/models/solidus_subscriptions/installment_spec.rb +1 -1
  50. data/spec/models/solidus_subscriptions/subscription_spec.rb +893 -16
  51. data/spec/models/spree/wallet_payment_source_spec.rb +3 -3
  52. data/spec/requests/api/v1/subscriptions_spec.rb +229 -0
  53. data/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb +8 -8
  54. data/spec/subscribers/solidus_subscriptions/order_subscriber_spec.rb +13 -0
  55. metadata +24 -17
  56. data/app/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions.rb +0 -25
  57. data/config/initializers/subscribers.rb +0 -9
  58. data/spec/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions_spec.rb +0 -32
  59. /data/{app → lib}/views/spree/frontend/products/_subscription_line_item_fields.html.erb +0 -0
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # solidus_subscriptions
2
2
 
3
- [![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_subscriptions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_subscriptions)
4
- [![codecov](https://codecov.io/gh/solidusio-contrib/solidus_subscriptions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_subscriptions)
3
+ [![CircleCI](https://circleci.com/gh/solidusio/solidus_subscriptions.svg?style=shield)](https://circleci.com/gh/solidusio/solidus_subscriptions)
4
+ [![codecov](https://codecov.io/gh/solidusio/solidus_subscriptions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio/solidus_subscriptions)
5
5
 
6
6
  A Solidus extension to add subscriptions to your store.
7
7
 
@@ -10,13 +10,26 @@ A Solidus extension to add subscriptions to your store.
10
10
  Add solidus_subscriptions to your Gemfile:
11
11
 
12
12
  ```ruby
13
- gem 'solidus_subscriptions', github: 'solidusio-contrib/solidus_subscriptions'
13
+ gem 'solidus_subscriptions', github: 'solidusio/solidus_subscriptions'
14
14
  ```
15
15
 
16
16
  Bundle your dependencies and run the installation generator:
17
17
 
18
18
  ```shell
19
19
  $ bundle
20
+ $ bin/rails generate solidus_subscriptions:install --frontend=starter
21
+ ```
22
+
23
+ Please, be aware that the starter installation only works with the default
24
+ implementation of the starter frontend. Any customization to the files that
25
+ will be modified by the installer might break the installation procedure.
26
+ If that happens, try to adapt the installed code on top of the customizations
27
+ of the store.
28
+
29
+
30
+ If you are using the legacy `solidus_frontend` gem, please run this command instead:
31
+
32
+ ```shell
20
33
  $ bin/rails generate solidus_subscriptions:install
21
34
  ```
22
35
 
@@ -91,14 +104,11 @@ The task creates ActiveJob jobs which can be fulfilled by your queue library of
91
104
  We suggest using the [Whenever](https://github.com/javan/whenever) gem to schedule the task.
92
105
 
93
106
  ### Promotion rules
107
+
94
108
  This extensions adds the following [Promotion rules](https://guides.solidus.io/developers/promotions/promotion-rules.html):
95
109
  * `SolidusSubscriptions::Promotion::Rules::SubscriptionCreationOrder` which applies if the order is creating a subscription;
96
110
  * `SolidusSubscriptions::Promotion::Rules::SubscriptionInstallmentOrder` which applies if the order is an installment of a subscription.
97
111
 
98
- ### API documentation
99
-
100
- You can find the API documentation [here](https://stoplight.io/p/docs/gh/solidusio-contrib/solidus_subscriptions?group=master).
101
-
102
112
  ### Churn Buster integration
103
113
 
104
114
  This extension optionally integrates with [Churn Buster](https://churnbuster.io) for failed payment
@@ -56,6 +56,26 @@ module SolidusSubscriptions
56
56
  end
57
57
  end
58
58
 
59
+ def pause
60
+ load_subscription
61
+
62
+ if @subscription.pause(actionable_date: actionable_date_param)
63
+ render json: @subscription.to_json
64
+ else
65
+ render json: @subscription.errors.to_json, status: :unprocessable_entity
66
+ end
67
+ end
68
+
69
+ def resume
70
+ load_subscription
71
+
72
+ if @subscription.resume(actionable_date: actionable_date_param)
73
+ render json: @subscription.to_json
74
+ else
75
+ render json: @subscription.errors.to_json, status: :unprocessable_entity
76
+ end
77
+ end
78
+
59
79
  private
60
80
 
61
81
  def load_subscription
@@ -77,6 +97,10 @@ module SolidusSubscriptions
77
97
  ])
78
98
  end
79
99
 
100
+ def actionable_date_param
101
+ params[:subscription].try(:[], :actionable_date)&.presence
102
+ end
103
+
80
104
  def line_item_attributes
81
105
  SolidusSubscriptions.configuration.subscription_line_item_attributes
82
106
  end
@@ -68,10 +68,38 @@ module Spree
68
68
  def skip
69
69
  @subscription.skip(check_skip_limits: false)
70
70
 
71
- notice = I18n.t(
72
- 'spree.admin.subscriptions.successfully_skipped',
73
- date: @subscription.actionable_date
74
- )
71
+ notice = if @subscription.errors.none?
72
+ I18n.t(
73
+ 'spree.admin.subscriptions.successfully_skipped',
74
+ date: @subscription.actionable_date
75
+ )
76
+ else
77
+ @subscription.errors.full_messages.to_sentence
78
+ end
79
+
80
+ redirect_back(fallback_location: spree.admin_subscriptions_path, notice: notice)
81
+ end
82
+
83
+ def pause
84
+ @subscription.pause(actionable_date: nil)
85
+
86
+ notice = if @subscription.errors.none?
87
+ I18n.t('spree.admin.subscriptions.successfully_paused')
88
+ else
89
+ @subscription.errors.full_messages.to_sentence
90
+ end
91
+
92
+ redirect_back(fallback_location: spree.admin_subscriptions_path, notice: notice)
93
+ end
94
+
95
+ def resume
96
+ @subscription.resume(actionable_date: nil)
97
+
98
+ notice = if @subscription.errors.none?
99
+ I18n.t('spree.admin.subscriptions.successfully_resumed')
100
+ else
101
+ @subscription.errors.full_messages.to_sentence
102
+ end
75
103
 
76
104
  redirect_back(fallback_location: spree.admin_subscriptions_path, notice: notice)
77
105
  end
@@ -14,8 +14,8 @@ module SolidusSubscriptions
14
14
  return if !previous_changes.key?('default') || !default?
15
15
 
16
16
  user.subscriptions.with_default_payment_source.each do |subscription|
17
- ::Spree::Event.fire(
18
- 'solidus_subscriptions.subscription_payment_method_changed',
17
+ ::SolidusSupport::LegacyEventCompat::Bus.publish(
18
+ :'solidus_subscriptions.subscription_payment_method_changed',
19
19
  subscription: subscription,
20
20
  )
21
21
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusSubscriptions
4
+ class CreateSubscriptionJob < ApplicationJob
5
+ queue_as { SolidusSubscriptions.configuration.processing_queue }
6
+
7
+ def perform(order)
8
+ SolidusSubscriptions.configuration.subscription_generator_class.call(order)
9
+ end
10
+ end
11
+ end
@@ -7,7 +7,7 @@ module SolidusSubscriptions
7
7
  def perform(installment)
8
8
  Checkout.new(installment).process
9
9
  rescue StandardError => e
10
- SolidusSubscriptions.configuration.processing_error_handler&.call(e)
10
+ SolidusSubscriptions.configuration.processing_error_handler&.call(e, installment)
11
11
  end
12
12
  end
13
13
  end
@@ -38,7 +38,7 @@ module SolidusSubscriptions
38
38
  before_validation :set_currency
39
39
  before_create :generate_guest_token
40
40
  after_create :emit_event_for_creation
41
- before_update :update_actionable_date_if_interval_changed
41
+ before_update :update_actionable_date_if_interval_changed, unless: :paused_changed?
42
42
  after_update :emit_events_for_update
43
43
 
44
44
  # Find all subscriptions that are "actionable"; that is, ones that have an
@@ -83,8 +83,13 @@ module SolidusSubscriptions
83
83
  where(payment_method: nil, payment_source: nil)
84
84
  end)
85
85
 
86
+ # Scope for finding subscription with a specific item
87
+ scope :with_subscribable, (lambda do |id|
88
+ joins(line_items: :subscribable).where(spree_variants: { id: id })
89
+ end)
90
+
86
91
  def self.ransackable_scopes(_auth_object = nil)
87
- [:in_processing_state]
92
+ [:in_processing_state, :with_subscribable]
88
93
  end
89
94
 
90
95
  def self.processing_states
@@ -107,7 +112,7 @@ module SolidusSubscriptions
107
112
  state_machine :state, initial: :active do
108
113
  event :cancel do
109
114
  transition [:active, :pending_cancellation] => :canceled,
110
- if: ->(subscription) { subscription.can_be_canceled? }
115
+ if: ->(subscription) { subscription.can_be_canceled? }
111
116
 
112
117
  transition active: :pending_cancellation
113
118
  end
@@ -122,7 +127,7 @@ module SolidusSubscriptions
122
127
 
123
128
  event :deactivate do
124
129
  transition active: :inactive,
125
- if: ->(subscription) { subscription.can_be_deactivated? }
130
+ if: ->(subscription) { subscription.can_be_deactivated? }
126
131
  end
127
132
 
128
133
  event :activate do
@@ -159,19 +164,21 @@ module SolidusSubscriptions
159
164
  end
160
165
 
161
166
  def skip(check_skip_limits: true)
167
+ check_invalid_skip_states
168
+
162
169
  if check_skip_limits
163
170
  check_successive_skips_exceeded
164
171
  check_total_skips_exceeded
165
-
166
- return if errors.any?
167
172
  end
168
173
 
174
+ return if errors.any?
175
+
169
176
  increment(:skip_count)
170
177
  increment(:successive_skip_count)
171
178
  save!
172
179
 
173
180
  advance_actionable_date.tap do
174
- events.create!(event_type: 'subscription_skipped')
181
+ create_and_emit_event(type: 'subscription_skipped')
175
182
  end
176
183
  end
177
184
 
@@ -206,11 +213,37 @@ module SolidusSubscriptions
206
213
  # @return [Date] The next date after the current actionable_date this
207
214
  # subscription will be eligible to be processed.
208
215
  def advance_actionable_date
209
- update! actionable_date: next_actionable_date
216
+ create_and_emit_event(type: 'subscription_resumed') if paused?
217
+
218
+ update! actionable_date: next_actionable_date, paused: false
210
219
 
211
220
  actionable_date
212
221
  end
213
222
 
223
+ def pause(actionable_date: nil)
224
+ check_invalid_pause_states
225
+ return false if errors.any?
226
+ return true if paused?
227
+
228
+ result = update! paused: true, actionable_date: actionable_date && tomorrow_or_after(actionable_date)
229
+ create_and_emit_event(type: 'subscription_paused') if result
230
+ result
231
+ end
232
+
233
+ def resume(actionable_date: nil)
234
+ check_invalid_resume_states
235
+ return false if errors.any?
236
+ return true unless paused?
237
+
238
+ result = update! paused: false, actionable_date: tomorrow_or_after(actionable_date)
239
+ create_and_emit_event(type: 'subscription_resumed') if result
240
+ result
241
+ end
242
+
243
+ def state_with_pause
244
+ active? && paused? ? 'paused' : state
245
+ end
246
+
214
247
  # The state of the last attempt to process an installment associated to
215
248
  # this subscription
216
249
  #
@@ -300,6 +333,23 @@ module SolidusSubscriptions
300
333
  end
301
334
  end
302
335
 
336
+ def check_invalid_skip_states
337
+ errors.add(:paused, :cannot_skip) if paused?
338
+ errors.add(:state, :cannot_skip) if canceled? || inactive?
339
+ end
340
+
341
+ def check_invalid_pause_states
342
+ errors.add(:paused, :not_active) unless active?
343
+ end
344
+
345
+ alias check_invalid_resume_states check_invalid_pause_states
346
+
347
+ def tomorrow_or_after(date)
348
+ [date.try(:to_date), Time.zone.tomorrow].compact.max
349
+ rescue ::Date::Error
350
+ Time.zone.tomorrow
351
+ end
352
+
303
353
  def update_actionable_date_if_interval_changed
304
354
  if persisted? && (interval_length_previously_changed? || interval_units_previously_changed?)
305
355
  base_date = if installments.any?
@@ -338,13 +388,22 @@ module SolidusSubscriptions
338
388
  end
339
389
  end
340
390
 
341
- def emit_event_for_creation
342
- ::Spree::Event.fire(
343
- 'solidus_subscriptions.subscription_created',
391
+ def emit_event(type:)
392
+ ::SolidusSupport::LegacyEventCompat::Bus.publish(
393
+ :"solidus_subscriptions.#{type}",
344
394
  subscription: self,
345
395
  )
346
396
  end
347
397
 
398
+ def create_and_emit_event(type:)
399
+ events.create!(event_type: type)
400
+ emit_event(type: type)
401
+ end
402
+
403
+ def emit_event_for_creation
404
+ emit_event(type: 'subscription_created')
405
+ end
406
+
348
407
  def emit_event_for_transition
349
408
  event_type = {
350
409
  active: 'subscription_activated',
@@ -353,39 +412,24 @@ module SolidusSubscriptions
353
412
  inactive: 'subscription_ended',
354
413
  }[state.to_sym]
355
414
 
356
- ::Spree::Event.fire(
357
- "solidus_subscriptions.#{event_type}",
358
- subscription: self,
359
- )
415
+ emit_event(type: event_type)
360
416
  end
361
417
 
362
418
  def emit_events_for_update
363
419
  if previous_changes.key?('interval_length') || previous_changes.key?('interval_units')
364
- ::Spree::Event.fire(
365
- 'solidus_subscriptions.subscription_frequency_changed',
366
- subscription: self,
367
- )
420
+ emit_event(type: 'subscription_frequency_changed')
368
421
  end
369
422
 
370
423
  if previous_changes.key?('shipping_address_id')
371
- ::Spree::Event.fire(
372
- 'solidus_subscriptions.subscription_shipping_address_changed',
373
- subscription: self,
374
- )
424
+ emit_event(type: 'subscription_shipping_address_changed')
375
425
  end
376
426
 
377
427
  if previous_changes.key?('billing_address_id')
378
- ::Spree::Event.fire(
379
- 'solidus_subscriptions.subscription_billing_address_changed',
380
- subscription: self,
381
- )
428
+ emit_event(type: 'subscription_billing_address_changed')
382
429
  end
383
430
 
384
431
  if previous_changes.key?('payment_source_id') || previous_changes.key?('payment_source_type') || previous_changes.key?('payment_method_id')
385
- ::Spree::Event.fire(
386
- 'solidus_subscriptions.subscription_payment_method_changed',
387
- subscription: self,
388
- )
432
+ emit_event(type: 'subscription_payment_method_changed')
389
433
  end
390
434
  end
391
435
  end
@@ -3,6 +3,7 @@
3
3
  module SolidusSubscriptions
4
4
  module ChurnBusterSubscriber
5
5
  include ::Spree::Event::Subscriber
6
+ include ::SolidusSupport::LegacyEventCompat::Subscriber
6
7
 
7
8
  event_action :report_subscription_cancellation, event_name: 'solidus_subscriptions.subscription_canceled'
8
9
  event_action :report_subscription_ending, event_name: 'solidus_subscriptions.subscription_ended'
@@ -3,6 +3,7 @@
3
3
  module SolidusSubscriptions
4
4
  module EventStorageSubscriber
5
5
  include ::Spree::Event::Subscriber
6
+ include ::SolidusSupport::LegacyEventCompat::Subscriber
6
7
 
7
8
  event_action :track_subscription_created, event_name: 'solidus_subscriptions.subscription_created'
8
9
  event_action :track_subscription_activated, event_name: 'solidus_subscriptions.subscription_activated'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusSubscriptions
4
+ module OrderSubscriber
5
+ include ::Spree::Event::Subscriber
6
+ include ::SolidusSupport::LegacyEventCompat::Subscriber
7
+
8
+ event_action :create_subscription, event_name: 'order_finalized'
9
+
10
+ private
11
+
12
+ def create_subscription(event)
13
+ SolidusSubscriptions::CreateSubscriptionJob.perform_later(event.payload[:order])
14
+ end
15
+ end
16
+ end
@@ -23,13 +23,32 @@
23
23
  <% end %>
24
24
 
25
25
  <% if subscription.active? %>
26
- <%=
27
- link_to(
28
- t('spree.admin.subscriptions.actions.skip'),
29
- spree.skip_admin_subscription_path(subscription),
30
- method: :post,
31
- class: 'btn btn-default',
32
- )
33
- %>
26
+ <% if subscription.paused? %>
27
+ <%=
28
+ link_to(
29
+ t('spree.admin.subscriptions.actions.resume'),
30
+ spree.resume_admin_subscription_path(subscription),
31
+ method: :post,
32
+ class: 'btn btn-default',
33
+ )
34
+ %>
35
+ <% else %>
36
+ <%=
37
+ link_to(
38
+ t('spree.admin.subscriptions.actions.pause'),
39
+ spree.pause_admin_subscription_path(subscription),
40
+ method: :post,
41
+ class: 'btn btn-default',
42
+ )
43
+ %>
44
+ <%=
45
+ link_to(
46
+ t('spree.admin.subscriptions.actions.skip'),
47
+ spree.skip_admin_subscription_path(subscription),
48
+ method: :post,
49
+ class: 'btn btn-default',
50
+ )
51
+ %>
52
+ <% end %>
34
53
  <% end %>
35
54
  <% end %>
@@ -6,6 +6,8 @@
6
6
  <nav class="menu">
7
7
  <fieldset class="no-border-top no-border-bottom" data-hook="admin_user_lifetime_stats">
8
8
  <dl id="user-lifetime-stats">
9
+ <dt><%= SolidusSubscriptions::Subscription.human_attribute_name(:user) %>:</dt>
10
+ <dd><%= link_to(subscription.user.email, admin_user_path(subscription.user)) %></dd>
9
11
  <dt><%= SolidusSubscriptions::Subscription.human_attribute_name(:created_at) %>:</dt>
10
12
  <dd><%= l(subscription.created_at.to_date) %></dd>
11
13
  <dt><%= SolidusSubscriptions::Subscription.human_attribute_name(:state) %>:</dt>
@@ -3,8 +3,9 @@
3
3
  canceled: 'error',
4
4
  pending_cancellation: 'warning',
5
5
  inactive: 'inactive',
6
- }[subscription.state.to_sym] %>
6
+ paused: 'pending'
7
+ }[subscription.state_with_pause.to_sym] %>
7
8
 
8
9
  <span class="pill pill-<%= state_class %>">
9
- <%= subscription.human_state_name %>
10
+ <%= subscription.state_with_pause.humanize %>
10
11
  </span>
@@ -48,21 +48,21 @@
48
48
  </div>
49
49
 
50
50
  <div class="row">
51
- <div class="field-block col-4">
51
+ <div class="field-block col-3">
52
52
  <div class="field">
53
53
  <%= label_tag :q_user_email_cont, Spree.user_class.human_attribute_name(:email) %>
54
54
  <%= f.text_field :user_email_cont, size: 25 %>
55
55
  </div>
56
56
  </div>
57
57
 
58
- <div class="field-block col-4">
58
+ <div class="field-block col-3">
59
59
  <div class="field">
60
60
  <%= label_tag :q_state_eq, SolidusSubscriptions::Subscription.human_attribute_name(:state) %>
61
61
  <%= f.select :state_eq, SolidusSubscriptions::Subscription.state_machines[:state].states.map {|s| [s.human_name, s.value]}, {include_blank: true}, class: 'select2 fullwidth' %>
62
62
  </div>
63
63
  </div>
64
64
 
65
- <div class="field-block col-4">
65
+ <div class="field-block col-3">
66
66
  <div class="field">
67
67
  <%= label_tag :q_processing_state_eq, SolidusSubscriptions::Subscription.human_attribute_name(:processing_state) %>
68
68
 
@@ -85,10 +85,23 @@
85
85
  %>
86
86
  </div>
87
87
  </div>
88
+ <div class="field-block col-3">
89
+ <div class="field">
90
+ <%= label_tag :q_with_subscribable_eq, SolidusSubscriptions::Subscription.human_attribute_name(:with_subscribable) %>
91
+
92
+ <%=
93
+ f.select(
94
+ :with_subscribable,
95
+ options_for_select(::Spree::Variant.includes(:product).where(id: ::SolidusSubscriptions::LineItem.distinct.pluck(:subscribable_id)).map { |subscribable| [ subscribable.name, subscribable.id ]}, params.dig(:q, :with_subscribable)),
96
+ { include_blank: true },
97
+ class: 'select2 fullwidth'
98
+ )
99
+ %>
100
+ </div>
101
+ </div>
88
102
  </div>
89
103
 
90
104
  <div class="clearfix"></div>
91
-
92
105
  <div class="actions filter-actions">
93
106
  <div data-hook="admin_subscriptions_index_search_buttons">
94
107
  <%= button_tag I18n.t('spree.filter_results'), class: 'btn btn-primary' %>
@@ -110,6 +123,7 @@
110
123
  <th><%= sort_link(@search, :line_item_interval_length, SolidusSubscriptions::Subscription.human_attribute_name(:interval)) %></th>
111
124
  <th><%= sort_link(@search, :state, [:state, 'id asc'], SolidusSubscriptions::Subscription.human_attribute_name(:state)) %></th>
112
125
  <th><%= sort_link(@search, :processing_state, [:processing_state, 'id asc'], SolidusSubscriptions::Subscription.human_attribute_name(:processing_state)) %></th>
126
+ <th><%= SolidusSubscriptions::Subscription.human_attribute_name(:line_items) %></th>
113
127
  <th data-hook="admin_subscriptions_index_header_actions" class="actions"></th>
114
128
  </tr>
115
129
  </thead>
@@ -123,6 +137,7 @@
123
137
  <td><%= subscription.interval.inspect %></td>
124
138
  <td><%= render 'state_pill', subscription: subscription %></td>
125
139
  <td><%= render 'processing_state_pill', subscription: subscription %></td>
140
+ <td><%= subscription.line_items.includes(subscribable: :product).map { |line_item| line_item.subscribable&.name }.join(", ") %></td>
126
141
  <td class="actions">
127
142
  <% if subscription.state_events.include?(:cancel) %>
128
143
  <%=
@@ -150,18 +165,40 @@
150
165
  <% end %>
151
166
 
152
167
  <% if subscription.active? %>
153
- <%=
154
- link_to_with_icon(
155
- :'fast-forward',
156
- t('spree.admin.subscriptions.actions.skip'),
157
- spree.skip_admin_subscription_path(subscription),
158
- no_text: true,
159
- method: :post
160
- )
161
- %>
168
+ <% if subscription.paused? %>
169
+ <%=
170
+ link_to_with_icon(
171
+ :'play-circle',
172
+ t('spree.admin.subscriptions.actions.resume'),
173
+ spree.resume_admin_subscription_path(subscription),
174
+ no_text: true,
175
+ method: :post
176
+ )
177
+ %>
178
+ <% else %>
179
+ <%=
180
+ link_to_with_icon(
181
+ :'pause-circle',
182
+ t('spree.admin.subscriptions.actions.pause'),
183
+ spree.pause_admin_subscription_path(subscription),
184
+ no_text: true,
185
+ method: :post
186
+ )
187
+ %>
188
+ <%=
189
+ link_to_with_icon(
190
+ :'fast-forward',
191
+ t('spree.admin.subscriptions.actions.skip'),
192
+ spree.skip_admin_subscription_path(subscription),
193
+ no_text: true,
194
+ method: :post
195
+ )
196
+ %>
197
+ <% end %>
162
198
  <% end %>
163
199
 
164
200
  <%= link_to_edit(subscription, no_text: true) %>
201
+
165
202
  </td>
166
203
  </tr>
167
204
  <% end %>
data/bin/sandbox CHANGED
@@ -50,7 +50,6 @@ fi
50
50
  cd ./sandbox
51
51
  cat <<RUBY >> Gemfile
52
52
  gem 'solidus', github: 'solidusio/solidus', branch: '$BRANCH'
53
- gem 'solidus_auth_devise', '>= 2.1.0'
54
53
  gem 'rails-i18n'
55
54
  gem 'solidus_i18n'
56
55
 
@@ -69,14 +68,10 @@ unbundled bundle exec rake db:drop db:create
69
68
 
70
69
  unbundled bundle exec rails generate solidus:install \
71
70
  --auto-accept \
72
- --user_class=Spree::User \
73
- --enforce_available_locales=true \
74
- --with-authentication=false \
75
71
  --payment-method=none \
76
72
  $@
77
73
 
78
- unbundled bundle exec rails generate solidus:auth:install
79
- unbundled bundle exec rails generate ${extension_name}:install
74
+ unbundled bundle exec rails generate ${extension_name}:install --frontend=starter --auto-run-migrations=true
80
75
 
81
76
  echo
82
77
  echo "🚀 Sandbox app successfully created for $extension_name!"
@@ -17,6 +17,13 @@ en:
17
17
  failed: This installment could not be processed
18
18
  payment_failed: The payment for this installment failed
19
19
 
20
+ cart_line_items:
21
+ subscription_fields:
22
+ quantity: I want
23
+ quantity_suffix: items
24
+ interval_length: every
25
+ subscription_fields: Subscription Settings
26
+
20
27
  spree:
21
28
  new_subscription: New Subscription
22
29
  admin:
@@ -24,11 +31,15 @@ en:
24
31
  successfully_canceled: Subscription Canceled!
25
32
  successfully_activated: Subscription Activated!
26
33
  successfully_skipped: Subscription delayed until %{date}
34
+ successfully_paused: Subscription Paused
35
+ successfully_resumed: Subscription Resumed
27
36
  actions:
28
37
  cancel: Cancel
29
38
  cancel_alert: "Are you sure you want to cancel this subscription?"
30
39
  activate: Activate
31
40
  skip: Skip One
41
+ pause: Pause
42
+ resume: Resume
32
43
  index:
33
44
  new_subscription: New Subscription
34
45
  edit:
@@ -127,3 +138,8 @@ en:
127
138
  inclusion: "is not a valid currency code"
128
139
  payment_source:
129
140
  not_owned_by_user: "does not belong to the user associated with the subscription"
141
+ paused:
142
+ cannot_skip: "cannot skip a subscription which is paused"
143
+ not_active: "cannot pause/resume a subscription which is not active"
144
+ state:
145
+ cannot_skip: cannot skip a subscription which is canceled or inactive
data/config/routes.rb CHANGED
@@ -8,6 +8,8 @@ SolidusSubscriptions::Engine.routes.draw do
8
8
  member do
9
9
  post :cancel
10
10
  post :skip
11
+ post :pause
12
+ post :resume
11
13
  end
12
14
  end
13
15
  end
@@ -19,9 +21,13 @@ Spree::Core::Engine.routes.draw do
19
21
 
20
22
  namespace :admin do
21
23
  resources :subscriptions, only: [:index, :new, :create, :edit, :update] do
22
- delete :cancel, on: :member
23
- post :activate, on: :member
24
- post :skip, on: :member
24
+ member do
25
+ delete :cancel
26
+ post :activate
27
+ post :skip
28
+ post :pause
29
+ post :resume
30
+ end
25
31
  resources :installments, only: [:index, :show]
26
32
  resources :subscription_events, only: :index
27
33
  resources :subscription_orders, path: :orders, only: :index