1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# frozen_string_literal: true
# This class represents a single iteration of a subscription. It is fulfilled
# by a completed order and maintains an association which tracks all attempts
# successful or otherwise at fulfilling this installment
module SolidusSubscriptions
class Installment < ApplicationRecord
has_many :details, class_name: 'SolidusSubscriptions::InstallmentDetail'
belongs_to(
:subscription,
class_name: 'SolidusSubscriptions::Subscription',
inverse_of: :installments,
)
validates :subscription, presence: true
scope :fulfilled, (lambda do
joins(:details).where(InstallmentDetail.table_name => { success: true }).distinct
end)
scope :unfulfilled, (lambda do
fulfilled_ids = fulfilled.pluck(:id)
where.not(id: fulfilled_ids).distinct
end)
scope :with_active_subscription, (lambda do
joins(:subscription).where.not(Subscription.table_name => { state: "canceled" })
end)
scope :actionable, (lambda do
unfulfilled.where("#{table_name}.actionable_date <= ?", Time.zone.today)
end)
# Mark this installment as out of stock.
#
# @return [SolidusSubscriptions::InstallmentDetail] The record of the failed
# processing attempt
def out_of_stock
advance_actionable_date!
details.create!(
success: false,
message: I18n.t('solidus_subscriptions.installment_details.out_of_stock')
)
end
# Mark this installment as a success
#
# @param order [Spree::Order] The order generated for this processing
# attempt
#
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
# successful processing attempt
def success!(order)
update!(actionable_date: nil)
details.create!(
success: true,
order: order,
message: I18n.t('solidus_subscriptions.installment_details.success')
)
end
# Mark this installment as a failure
#
# @param order [Spree::Order] The order generated for this processing
# attempt
#
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
# failed processing attempt
def failed!(order)
advance_actionable_date!
details.create!(
success: false,
order: order,
message: I18n.t('solidus_subscriptions.installment_details.failed')
)
end
# Does this installment still need to be fulfilled by a completed order
#
# @return [Boolean]
def unfulfilled?
!fulfilled?
end
# Had this installment been fulfilled by a completed order
#
# @return [Boolean]
def fulfilled?
details.exists?(success: true)
end
# Returns the state of this fulfillment
#
# @return [Symbol] :fulfilled/:unfulfilled
def state
fulfilled? ? :fulfilled : :unfulfilled
end
# Mark this installment as having a failed payment
#
# @param order [Spree::Order] The order generated for this processing
# attempt
#
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
# failed processing attempt
def payment_failed!(order)
details.create!(
success: false,
order: order,
message: I18n.t('solidus_subscriptions.installment_details.payment_failed')
)
if subscription.maximum_reprocessing_time_reached? && !subscription.canceled?
subscription.force_cancel!
update!(actionable_date: nil)
else
advance_actionable_date!
end
end
private
def advance_actionable_date!
update!(actionable_date: next_actionable_date)
end
def next_actionable_date
return if SolidusSubscriptions.configuration.reprocessing_interval.nil?
(DateTime.current + SolidusSubscriptions.configuration.reprocessing_interval).beginning_of_minute
end
end
end
|