solidus_subscriptions 1.0.0.rc1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 -163
- data/Gemfile +14 -1
- data/README.md +17 -7
- data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +25 -1
- data/app/controllers/spree/admin/subscriptions_controller.rb +32 -4
- data/app/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions.rb +2 -2
- 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/{app/decorators → 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 +16 -1
- data/lib/solidus_subscriptions/checkout.rb +2 -9
- data/lib/solidus_subscriptions/configuration.rb +37 -8
- 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/order_creator.rb +29 -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/lib/solidus_subscriptions.rb +1 -0
- 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 +31 -23
- data/app/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions.rb +0 -23
- data/config/initializers/subscribers.rb +0 -9
- data/spec/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions_spec.rb +0 -32
- /data/{app/decorators → lib/decorators/frontend}/controllers/solidus_subscriptions/spree/orders_controller/create_subscription_line_items.rb +0 -0
- /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
|
4
|
-
[![codecov](https://codecov.io/gh/solidusio
|
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
|
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,8 +97,12 @@ 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
|
-
SolidusSubscriptions.configuration.subscription_line_item_attributes
|
105
|
+
SolidusSubscriptions.configuration.subscription_line_item_attributes
|
82
106
|
end
|
83
107
|
|
84
108
|
def update_payment_attributes(attributes)
|
@@ -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
|
data/app/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions.rb
CHANGED
@@ -12,9 +12,9 @@ module SolidusSubscriptions
|
|
12
12
|
def remove_from_subscriptions
|
13
13
|
SolidusSubscriptions::LineItem.where(subscribable: self).delete_all
|
14
14
|
end
|
15
|
+
|
16
|
+
::Spree::Variant.prepend self
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
19
|
-
|
20
|
-
Spree::Variant.prepend(SolidusSubscriptions::Spree::Variant::AutoDeleteFromSubscriptions)
|
@@ -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
|