solidus_subscriptions 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +57 -9
- data/.github/dependabot.yml +7 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +201 -169
- data/Gemfile +14 -1
- data/README.md +17 -7
- data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +24 -0
- data/app/controllers/spree/admin/subscriptions_controller.rb +32 -4
- data/app/decorators/models/solidus_subscriptions/spree/wallet_payment_source/report_default_change_to_subscriptions.rb +2 -2
- data/app/jobs/solidus_subscriptions/create_subscription_job.rb +11 -0
- data/app/jobs/solidus_subscriptions/process_installment_job.rb +1 -1
- data/app/models/solidus_subscriptions/subscription.rb +75 -31
- data/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb +1 -0
- data/app/subscribers/solidus_subscriptions/event_storage_subscriber.rb +1 -0
- data/app/subscribers/solidus_subscriptions/order_subscriber.rb +16 -0
- data/app/views/spree/admin/shared/_subscription_actions.html.erb +27 -8
- data/app/views/spree/admin/shared/_subscription_sidebar.html.erb +2 -0
- data/app/views/spree/admin/subscriptions/_state_pill.html.erb +3 -2
- data/app/views/spree/admin/subscriptions/index.html.erb +50 -13
- data/bin/sandbox +1 -6
- data/config/locales/en.yml +16 -0
- data/config/routes.rb +9 -3
- data/db/migrate/20210905145955_add_paused_to_subscriptions.rb +5 -0
- data/lib/decorators/api/controllers/solidus_subscriptions/spree/api/line_items_controller/create_subscription_line_items.rb +14 -0
- data/lib/generators/solidus_subscriptions/install/install_generator.rb +16 -0
- data/lib/generators/solidus_subscriptions/install/templates/app/controllers/concerns/create_subscription.rb +17 -0
- data/lib/generators/solidus_subscriptions/install/templates/app/views/cart_line_items/_subscription_fields.html.erb +30 -0
- data/lib/generators/solidus_subscriptions/install/templates/initializer.rb +3 -1
- data/lib/solidus_subscriptions/configuration.rb +29 -6
- data/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb +2 -2
- data/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb +2 -2
- data/lib/solidus_subscriptions/engine.rb +28 -0
- data/lib/solidus_subscriptions/permission_sets/default_customer.rb +1 -1
- data/lib/solidus_subscriptions/processing_error_handlers/rails_logger.rb +25 -0
- data/lib/solidus_subscriptions/subscription_generator.rb +6 -0
- data/lib/solidus_subscriptions/version.rb +1 -1
- data/solidus_subscriptions.gemspec +6 -6
- data/spec/controllers/spree/api/line_items_controller_spec.rb +63 -28
- data/spec/controllers/spree/api/users_controller_spec.rb +3 -0
- data/spec/decorators/controllers/solidus_subscriptions/spree/orders_controller/create_subscription_line_items_spec.rb +4 -4
- data/spec/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions_spec.rb +2 -2
- data/spec/features/admin/subscriptions_spec.rb +13 -3
- data/spec/jobs/solidus_subscriptions/create_subscription_job_spec.rb +20 -0
- data/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb +17 -0
- data/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb +3 -3
- data/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb +3 -3
- data/spec/lib/solidus_subscriptions/subscription_generator_spec.rb +1 -1
- data/spec/models/solidus_subscriptions/installment_spec.rb +1 -1
- data/spec/models/solidus_subscriptions/subscription_spec.rb +893 -16
- data/spec/models/spree/wallet_payment_source_spec.rb +3 -3
- data/spec/requests/api/v1/subscriptions_spec.rb +229 -0
- data/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb +8 -8
- data/spec/subscribers/solidus_subscriptions/order_subscriber_spec.rb +13 -0
- metadata +24 -17
- data/app/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions.rb +0 -25
- data/config/initializers/subscribers.rb +0 -9
- data/spec/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions_spec.rb +0 -32
- /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
|
-
[](https://circleci.com/gh/solidusio/solidus_subscriptions)
|
4
|
+
[](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
|
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 =
|
72
|
-
|
73
|
-
|
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
|
-
::
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
342
|
-
::
|
343
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
6
|
+
paused: 'pending'
|
7
|
+
}[subscription.state_with_pause.to_sym] %>
|
7
8
|
|
8
9
|
<span class="pill pill-<%= state_class %>">
|
9
|
-
<%= subscription.
|
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-
|
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-
|
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-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
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!"
|
data/config/locales/en.yml
CHANGED
@@ -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
|
-
|
23
|
-
|
24
|
-
|
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
|