solidus_subscriptions-alpha 0.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 +7 -0
- data/LICENSE +26 -0
- data/README.md +128 -0
- data/Rakefile +28 -0
- data/app/assets/javascripts/spree/backend/solidus_subscriptions.js +2 -0
- data/app/assets/javascripts/spree/frontend/solidus_subscriptions.js +2 -0
- data/app/assets/stylesheets/spree/backend/solidus_subscriptions.css +4 -0
- data/app/assets/stylesheets/spree/frontend/solidus_subscriptions.css +4 -0
- data/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb +35 -0
- data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +44 -0
- data/app/controllers/spree/admin/subscriptions_controller.rb +66 -0
- data/app/decorators/spree/controllers/api/line_items/create_subscription_line_items.rb +28 -0
- data/app/decorators/spree/controllers/orders/create_subscription_line_items.rb +33 -0
- data/app/decorators/spree/line_items/subscription_line_items_association.rb +22 -0
- data/app/decorators/spree/orders/after_create.rb +15 -0
- data/app/decorators/spree/orders/finalize_creates_subscriptions.rb +19 -0
- data/app/decorators/spree/orders/subscription_line_items_association.rb +15 -0
- data/app/decorators/spree/users/have_many_subscriptions.rb +18 -0
- data/app/decorators/spree/variant_pretty_name.rb +13 -0
- data/app/jobs/solidus_subscriptions/process_installments_job.rb +22 -0
- data/app/models/solidus_subscriptions/checkout.rb +137 -0
- data/app/models/solidus_subscriptions/dispatcher.rb +32 -0
- data/app/models/solidus_subscriptions/failure_dispatcher.rb +19 -0
- data/app/models/solidus_subscriptions/installment.rb +126 -0
- data/app/models/solidus_subscriptions/installment_detail.rb +23 -0
- data/app/models/solidus_subscriptions/interval.rb +24 -0
- data/app/models/solidus_subscriptions/line_item.rb +98 -0
- data/app/models/solidus_subscriptions/line_item_builder.rb +44 -0
- data/app/models/solidus_subscriptions/order_builder.rb +40 -0
- data/app/models/solidus_subscriptions/out_of_stock_dispatcher.rb +19 -0
- data/app/models/solidus_subscriptions/payment_failed_dispatcher.rb +23 -0
- data/app/models/solidus_subscriptions/subscription.rb +217 -0
- data/app/models/solidus_subscriptions/subscription_generator.rb +60 -0
- data/app/models/solidus_subscriptions/subscription_line_item_builder.rb +21 -0
- data/app/models/solidus_subscriptions/subscription_order_promotion_rule.rb +25 -0
- data/app/models/solidus_subscriptions/subscription_promotion_rule.rb +38 -0
- data/app/models/solidus_subscriptions/success_dispatcher.rb +16 -0
- data/app/models/solidus_subscriptions/unsubscribable_error.rb +17 -0
- data/app/models/solidus_subscriptions/user_mismatch_error.rb +15 -0
- data/app/overrides/views/admin_subscribable_checkbox.rb +6 -0
- data/app/overrides/views/admin_subscriptions_menu_link.rb +8 -0
- data/app/overrides/views/subscription_line_item_fields.rb +6 -0
- data/app/views/spree/admin/promotions/rules/_subscription_order_promotion_rule.html.erb +0 -0
- data/app/views/spree/admin/promotions/rules/_subscription_promotion_rule.html.erb +0 -0
- data/app/views/spree/admin/shared/_no_objects_found.html.erb +4 -0
- data/app/views/spree/admin/shared/_subscription_tab.html.erb +3 -0
- data/app/views/spree/admin/solidus_subscriptions/subscriptions/_subscription.html.erb +66 -0
- data/app/views/spree/admin/subscriptions/_form.html.erb +81 -0
- data/app/views/spree/admin/subscriptions/_legacy_form.html.erb +81 -0
- data/app/views/spree/admin/subscriptions/_legacy_sidebar.html.erb +28 -0
- data/app/views/spree/admin/subscriptions/edit.html.erb +21 -0
- data/app/views/spree/admin/subscriptions/index.html.erb +119 -0
- data/app/views/spree/admin/subscriptions/new.html.erb +9 -0
- data/app/views/spree/admin/variants/_subscribable_checkbox.html.erb +6 -0
- data/app/views/spree/frontend/products/_subscription_line_item_fields.html.erb +30 -0
- data/config/locales/en.yml +91 -0
- data/config/routes.rb +25 -0
- data/db/migrate/20160825164850_create_solidus_subscriptions_subscriptions.rb +11 -0
- data/db/migrate/20160825173548_create_solidus_subscriptions_line_items.rb +17 -0
- data/db/migrate/20160825202248_create_solidus_subscriptions_installments.rb +23 -0
- data/db/migrate/20160825211202_create_solidus_subscriptions_installment_details.rb +22 -0
- data/db/migrate/20160825214240_add_subscribable_to_spree_variants.rb +5 -0
- data/db/migrate/20160829201653_change_subscription_line_items_installments_to_max_installments.rb +5 -0
- data/db/migrate/20160902220242_remove_state_from_solidus_susbscriptions_installment_details.rb +5 -0
- data/db/migrate/20160902220604_add_successful_to_solidus_subscriptions_installment_details.rb +5 -0
- data/db/migrate/20160902221218_add_message_to_solidus_subscriptions_installment_details.rb +5 -0
- data/db/migrate/20160922164101_add_interval_length_and_units_to_subscription_line_items.rb +8 -0
- data/db/migrate/20161006191003_add_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -0
- data/db/migrate/20161006191127_add_successive_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -0
- data/db/migrate/20161014212649_allow_spree_line_item_id_to_be_null.rb +5 -0
- data/db/migrate/20161017155749_add_order_id_to_solidus_subscriptions_installment_details.rb +6 -0
- data/db/migrate/20161017175509_remove_order_id_from_solidus_subscriptions_installments.rb +6 -0
- data/db/migrate/20161017201944_add_subscription_order_to_spree_orders.rb +5 -0
- data/db/migrate/20161221155142_add_store_to_solidus_subscriptions_subscriptions.rb +6 -0
- data/db/migrate/20161223152905_add_address_id_to_solidus_subscriptions_subscriptions.rb +7 -0
- data/db/migrate/20170106224713_change_line_item_max_installments_to_end_date.rb +6 -0
- data/db/migrate/20170111224458_change_subscription_actionable_date_to_datetime.rb +5 -0
- data/db/migrate/20170111232801_change_inteval_actionable_date_to_datetime.rb +5 -0
- data/db/migrate/20170112012407_add_config_options_to_subscriptions.rb +7 -0
- data/lib/generators/solidus_subscriptions/install/install_generator.rb +30 -0
- data/lib/solidus_subscriptions.rb +6 -0
- data/lib/solidus_subscriptions/ability.rb +19 -0
- data/lib/solidus_subscriptions/config.rb +97 -0
- data/lib/solidus_subscriptions/engine.rb +56 -0
- data/lib/solidus_subscriptions/permitted_attributes.rb +36 -0
- data/lib/solidus_subscriptions/processor.rb +108 -0
- data/lib/solidus_subscriptions/testing_support/factories.rb +5 -0
- data/lib/solidus_subscriptions/testing_support/factories/installment_detail_factory.rb +7 -0
- data/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb +21 -0
- data/lib/solidus_subscriptions/testing_support/factories/line_item_factory.rb +18 -0
- data/lib/solidus_subscriptions/testing_support/factories/spree/line_item_factory.rb +17 -0
- data/lib/solidus_subscriptions/testing_support/factories/spree/order_factory.rb +18 -0
- data/lib/solidus_subscriptions/testing_support/factories/spree_modification_factory.rb +8 -0
- data/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb +43 -0
- data/lib/solidus_subscriptions/version.rb +3 -0
- data/lib/tasks/process_subscriptions.rake +6 -0
- metadata +460 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Once an order is finalized its subscriptions line items should be converted
|
|
2
|
+
# into active subscritptions. This hooks into Spree::Order#finalize! and
|
|
3
|
+
# passes all subscription_line_items present on the order to the Subscription
|
|
4
|
+
# generator which will build and persist the subscriptions
|
|
5
|
+
module Spree
|
|
6
|
+
module Orders
|
|
7
|
+
module FinalizeCreatesSubscriptions
|
|
8
|
+
def finalize!
|
|
9
|
+
SolidusSubscriptions::SubscriptionGenerator.group(subscription_line_items).each do |line_items|
|
|
10
|
+
SolidusSubscriptions::SubscriptionGenerator.activate(line_items)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Spree::Order.prepend Spree::Orders::FinalizeCreatesSubscriptions
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Spree::Orders may contain many subscription_line_items. When the order is
|
|
2
|
+
# finalized these subscription_line_items are converted into subscritpions.
|
|
3
|
+
# The order needs to be able to get a list of associated subscription_line_items
|
|
4
|
+
# to be able to populate the full subscriptions.
|
|
5
|
+
module Spree
|
|
6
|
+
module Orders
|
|
7
|
+
module SubscriptionLineItemsAssociation
|
|
8
|
+
def self.prepended(base)
|
|
9
|
+
base.has_many :subscription_line_items, through: :line_items
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Spree::Order.prepend Spree::Orders::SubscriptionLineItemsAssociation
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Spree::Users maintain a list of the subscriptions associated with them
|
|
2
|
+
module Spree
|
|
3
|
+
module Users
|
|
4
|
+
module HaveManySubscritptions
|
|
5
|
+
def self.prepended(base)
|
|
6
|
+
base.has_many(
|
|
7
|
+
:subscriptions,
|
|
8
|
+
class_name: 'SolidusSubscriptions::Subscription',
|
|
9
|
+
foreign_key: 'user_id'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
base.accepts_nested_attributes_for :subscriptions
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Spree.user_class.prepend(Spree::Users::HaveManySubscritptions)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# This job is responsible for creating a consolidated installment from a
|
|
2
|
+
# list of installments and processing it.
|
|
3
|
+
|
|
4
|
+
module SolidusSubscriptions
|
|
5
|
+
class ProcessInstallmentsJob < ActiveJob::Base
|
|
6
|
+
queue_as Config.processing_queue
|
|
7
|
+
|
|
8
|
+
# Process a collection of installments
|
|
9
|
+
#
|
|
10
|
+
# @param installment_ids [Array<Integer>] The ids of the
|
|
11
|
+
# installments to be processed together and fulfilled by the same order
|
|
12
|
+
#
|
|
13
|
+
# @return [Spree::Order] The order which fulfills the list of installments
|
|
14
|
+
def perform(installment_ids)
|
|
15
|
+
return if installment_ids.empty?
|
|
16
|
+
|
|
17
|
+
installments = SolidusSubscriptions::Installment.where(id: installment_ids).
|
|
18
|
+
includes(subscription: [:line_items, :user])
|
|
19
|
+
Checkout.new(installments).process
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# This class takes a collection of installments and populates a new spree
|
|
2
|
+
# order with the correct contents based on the subscriptions associated to the
|
|
3
|
+
# intallments. This is to group together subscriptions being
|
|
4
|
+
# processed on the same day for a specific user
|
|
5
|
+
module SolidusSubscriptions
|
|
6
|
+
class Checkout
|
|
7
|
+
# @return [Array<Installment>] The collection of installments to be used
|
|
8
|
+
# when generating a new order
|
|
9
|
+
attr_reader :installments
|
|
10
|
+
|
|
11
|
+
delegate :user, to: :subscription
|
|
12
|
+
|
|
13
|
+
# Get a new instance of a Checkout
|
|
14
|
+
#
|
|
15
|
+
# @param installments [Array<Installment>] The collection of installments
|
|
16
|
+
# to be used when generating a new order
|
|
17
|
+
def initialize(installments)
|
|
18
|
+
@installments = installments
|
|
19
|
+
raise UserMismatchError.new(installments) if different_owners?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Generate a new Spree::Order based on the information associated to the
|
|
23
|
+
# installments
|
|
24
|
+
#
|
|
25
|
+
# @return [Spree::Order]
|
|
26
|
+
def process
|
|
27
|
+
populate
|
|
28
|
+
|
|
29
|
+
# Installments are removed and set for future processing if they are
|
|
30
|
+
# out of stock. If there are no line items left there is nothing to do
|
|
31
|
+
return if installments.empty?
|
|
32
|
+
|
|
33
|
+
if checkout
|
|
34
|
+
Config.success_dispatcher_class.new(installments, order).dispatch
|
|
35
|
+
return order
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# A new order will only have 1 payment that we created
|
|
39
|
+
if order.payments.any?(&:failed?)
|
|
40
|
+
Config.payment_failed_dispatcher_class.new(installments, order).dispatch
|
|
41
|
+
installments.clear
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
ensure
|
|
45
|
+
# Any installments that failed to be processed will be reprocessed
|
|
46
|
+
unfulfilled_installments = installments.select(&:unfulfilled?)
|
|
47
|
+
if unfulfilled_installments.any?
|
|
48
|
+
Config.failure_dispatcher_class.
|
|
49
|
+
new(unfulfilled_installments, order).dispatch
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The order fulfilling the consolidated installment
|
|
54
|
+
#
|
|
55
|
+
# @return [Spree::Order]
|
|
56
|
+
def order
|
|
57
|
+
@order ||= Spree::Order.create(
|
|
58
|
+
user: user,
|
|
59
|
+
email: user.email,
|
|
60
|
+
store: subscription.store || Spree::Store.default,
|
|
61
|
+
subscription_order: true
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def checkout
|
|
68
|
+
order.update!
|
|
69
|
+
apply_promotions
|
|
70
|
+
|
|
71
|
+
order.checkout_steps[0...-1].each do
|
|
72
|
+
order.ship_address = ship_address if order.state == "address"
|
|
73
|
+
create_payment if order.state == "payment"
|
|
74
|
+
order.next!
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Do this as a separate "quiet" transition so that it returns true or
|
|
78
|
+
# false rather than raising a failed transition error
|
|
79
|
+
order.complete
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def populate
|
|
83
|
+
unfulfilled_installments = []
|
|
84
|
+
|
|
85
|
+
order_line_items = installments.flat_map do |installment|
|
|
86
|
+
line_items = installment.line_item_builder.spree_line_items
|
|
87
|
+
|
|
88
|
+
unfulfilled_installments.push(installment) if line_items.empty?
|
|
89
|
+
|
|
90
|
+
line_items
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Remove installments which had no stock from the active list
|
|
94
|
+
# They will be reprocessed later
|
|
95
|
+
@installments -= unfulfilled_installments
|
|
96
|
+
if unfulfilled_installments.any?
|
|
97
|
+
Config.out_of_stock_dispatcher_class.new(unfulfilled_installments).dispatch
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
return if installments.empty?
|
|
101
|
+
order_builder.add_line_items(order_line_items)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def order_builder
|
|
105
|
+
@order_builder ||= OrderBuilder.new(order)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def subscription
|
|
109
|
+
installments.first.subscription
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def ship_address
|
|
113
|
+
subscription.shipping_address || user.ship_address
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def active_card
|
|
117
|
+
user.credit_cards.default.last
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def create_payment
|
|
121
|
+
order.payments.create(
|
|
122
|
+
source: active_card,
|
|
123
|
+
amount: order.total,
|
|
124
|
+
payment_method: Config.default_gateway
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def apply_promotions
|
|
129
|
+
Spree::PromotionHandler::Cart.new(order).activate
|
|
130
|
+
order.updater.update # reload totals
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def different_owners?
|
|
134
|
+
installments.map { |i| i.subscription.user }.uniq.length > 1
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SolidusSubscriptions
|
|
2
|
+
class Dispatcher
|
|
3
|
+
attr_reader :installments, :order
|
|
4
|
+
|
|
5
|
+
# Get a new instance of the FailureDispatcher
|
|
6
|
+
#
|
|
7
|
+
# @param installments [Array<SolidusSubscriptions::Installment>] The
|
|
8
|
+
# installments which have failed to be fulfilled
|
|
9
|
+
#
|
|
10
|
+
# @return [SolidusSubscriptions::FailureDispatcher]
|
|
11
|
+
def initialize(installments, order = nil)
|
|
12
|
+
@installments = installments
|
|
13
|
+
@order = order
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def dispatch
|
|
17
|
+
notify
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def notify
|
|
23
|
+
Rails.logger.tagged('Event') do
|
|
24
|
+
Rails.logger.info message.squish.tr("\n", ' ')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def message
|
|
29
|
+
raise 'A message should be set in subclasses of Dispatcher'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# A handler for behaviour that should happen after installments are marked as
|
|
2
|
+
# failures
|
|
3
|
+
module SolidusSubscriptions
|
|
4
|
+
class FailureDispatcher < Dispatcher
|
|
5
|
+
def dispatch
|
|
6
|
+
order.touch :completed_at
|
|
7
|
+
order.cancel!
|
|
8
|
+
installments.each { |i| i.failed!(order) }
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def message
|
|
13
|
+
"
|
|
14
|
+
Something went wrong processing installments: #{installments.map(&:id).join(', ')}.
|
|
15
|
+
They have been marked for reprocessing.
|
|
16
|
+
"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# This class represents a single iteration of a subscription. It is fulfulled
|
|
2
|
+
# by a conmpleted order and maintains an association which tracks all attempts
|
|
3
|
+
# successful or othewise at fulfulling this installment
|
|
4
|
+
module SolidusSubscriptions
|
|
5
|
+
class Installment < ActiveRecord::Base
|
|
6
|
+
has_many :details, class_name: 'SolidusSubscriptions::InstallmentDetail'
|
|
7
|
+
belongs_to(
|
|
8
|
+
:subscription,
|
|
9
|
+
class_name: 'SolidusSubscriptions::Subscription',
|
|
10
|
+
inverse_of: :installments
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
validates :subscription, presence: true
|
|
14
|
+
|
|
15
|
+
scope :fulfilled, (lambda do
|
|
16
|
+
joins(:details).where(InstallmentDetail.table_name => { success: true }).distinct
|
|
17
|
+
end)
|
|
18
|
+
|
|
19
|
+
scope :unfulfilled, (lambda do
|
|
20
|
+
fulfilled_ids = fulfilled.pluck(:id)
|
|
21
|
+
where.not(id: fulfilled_ids).distinct
|
|
22
|
+
end)
|
|
23
|
+
|
|
24
|
+
scope :actionable, (lambda do
|
|
25
|
+
unfulfilled.where("#{table_name}.actionable_date <= ?", Time.zone.now)
|
|
26
|
+
end)
|
|
27
|
+
|
|
28
|
+
# Get the builder for the subscription_line_item. This will be an
|
|
29
|
+
# object that can generate the appropriate line item for the subscribable
|
|
30
|
+
# object
|
|
31
|
+
#
|
|
32
|
+
# @return [SolidusSubscriptions::LineItemBuilder]
|
|
33
|
+
def line_item_builder
|
|
34
|
+
subscription.line_item_builder
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Mark this installment as out of stock.
|
|
38
|
+
#
|
|
39
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the failed
|
|
40
|
+
# processing attempt
|
|
41
|
+
def out_of_stock
|
|
42
|
+
advance_actionable_date!
|
|
43
|
+
|
|
44
|
+
details.create!(
|
|
45
|
+
success: false,
|
|
46
|
+
message: I18n.t('solidus_subscriptions.installment_details.out_of_stock')
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Mark this installment as a success
|
|
51
|
+
#
|
|
52
|
+
# @param order [Spree::Order] The order generated for this processing
|
|
53
|
+
# attempt
|
|
54
|
+
#
|
|
55
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
56
|
+
# successful processing attempt
|
|
57
|
+
def success!(order)
|
|
58
|
+
update!(actionable_date: nil)
|
|
59
|
+
|
|
60
|
+
details.create!(
|
|
61
|
+
success: true,
|
|
62
|
+
order: order,
|
|
63
|
+
message: I18n.t('solidus_subscriptions.installment_details.success')
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Mark this installment as a failure
|
|
68
|
+
#
|
|
69
|
+
# @param order [Spree::Order] The order generated for this processing
|
|
70
|
+
# attempt
|
|
71
|
+
#
|
|
72
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
73
|
+
# failed processing attempt
|
|
74
|
+
def failed!(order)
|
|
75
|
+
advance_actionable_date!
|
|
76
|
+
|
|
77
|
+
details.create!(
|
|
78
|
+
success: false,
|
|
79
|
+
order: order,
|
|
80
|
+
message: I18n.t('solidus_subscriptions.installment_details.failed')
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Does this installment still need to be fulfilled by a completed order
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def unfulfilled?
|
|
88
|
+
!fulfilled?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Had this installment been fulfilled by a completed order
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
def fulfilled?
|
|
95
|
+
details.where(success: true).exists?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Mark this installment as having a failed payment
|
|
99
|
+
#
|
|
100
|
+
# @param order [Spree::Order] The order generated for this processing
|
|
101
|
+
# attempt
|
|
102
|
+
#
|
|
103
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
104
|
+
# failed processing attempt
|
|
105
|
+
def payment_failed!(order)
|
|
106
|
+
advance_actionable_date!
|
|
107
|
+
|
|
108
|
+
details.create!(
|
|
109
|
+
success: false,
|
|
110
|
+
order: order,
|
|
111
|
+
message: I18n.t('solidus_subscriptions.installment_details.payment_failed')
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def advance_actionable_date!
|
|
118
|
+
update!(actionable_date: next_actionable_date)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def next_actionable_date
|
|
122
|
+
return if Config.reprocessing_interval.nil?
|
|
123
|
+
(DateTime.current + Config.reprocessing_interval).beginning_of_minute
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# This class represents a single attempt to fulfill an installment. It will
|
|
2
|
+
# indicate the result of that attept.
|
|
3
|
+
module SolidusSubscriptions
|
|
4
|
+
class InstallmentDetail < ActiveRecord::Base
|
|
5
|
+
belongs_to(
|
|
6
|
+
:installment,
|
|
7
|
+
class_name: 'SolidusSubscriptions::Installment',
|
|
8
|
+
inverse_of: :details
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
belongs_to(:order, class_name: 'Spree::Order')
|
|
12
|
+
|
|
13
|
+
validates :installment, presence: true
|
|
14
|
+
alias_attribute :successful, :success
|
|
15
|
+
|
|
16
|
+
# Was the attempt at fulfilling this installment a failure?
|
|
17
|
+
#
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def failed?
|
|
20
|
+
!success
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|