pay 7.2.1 → 8.0.0
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/README.md +2 -0
- data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
- data/app/controllers/pay/webhooks/stripe_controller.rb +2 -1
- data/app/jobs/pay/customer_sync_job.rb +1 -1
- data/app/models/concerns/pay/routing.rb +13 -0
- data/{lib → app/models}/pay/braintree/charge.rb +5 -12
- data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +31 -71
- data/{lib → app/models}/pay/braintree/payment_method.rb +1 -9
- data/{lib → app/models}/pay/braintree/subscription.rb +14 -52
- data/app/models/pay/charge.rb +8 -27
- data/app/models/pay/customer.rb +2 -15
- data/app/models/pay/fake_processor/charge.rb +13 -0
- data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +22 -35
- data/{lib → app/models}/pay/fake_processor/merchant.rb +2 -9
- data/app/models/pay/fake_processor/payment_method.rb +11 -0
- data/app/models/pay/fake_processor/subscription.rb +60 -0
- data/app/models/pay/lemon_squeezy/charge.rb +86 -0
- data/app/models/pay/lemon_squeezy/customer.rb +78 -0
- data/app/models/pay/lemon_squeezy/payment_method.rb +27 -0
- data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
- data/app/models/pay/merchant.rb +0 -11
- data/{lib → app/models}/pay/paddle_billing/charge.rb +2 -8
- data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +18 -35
- data/{lib → app/models}/pay/paddle_billing/payment_method.rb +2 -12
- data/{lib → app/models}/pay/paddle_billing/subscription.rb +9 -33
- data/{lib → app/models}/pay/paddle_classic/charge.rb +13 -18
- data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +9 -31
- data/{lib → app/models}/pay/paddle_classic/payment_method.rb +1 -11
- data/{lib → app/models}/pay/paddle_classic/subscription.rb +11 -36
- data/app/models/pay/payment_method.rb +0 -5
- data/{lib → app/models}/pay/stripe/charge.rb +6 -22
- data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +73 -108
- data/{lib → app/models}/pay/stripe/merchant.rb +2 -11
- data/{lib → app/models}/pay/stripe/payment_method.rb +2 -10
- data/{lib → app/models}/pay/stripe/subscription.rb +37 -71
- data/app/models/pay/subscription.rb +7 -37
- data/app/models/pay/webhook.rb +2 -0
- data/config/routes.rb +1 -0
- data/db/migrate/2_add_pay_sti_columns.rb +24 -0
- data/lib/pay/attributes.rb +11 -3
- data/lib/pay/braintree.rb +25 -6
- data/lib/pay/engine.rb +2 -0
- data/lib/pay/fake_processor.rb +2 -6
- data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
- data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
- data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
- data/lib/pay/lemon_squeezy.rb +56 -104
- data/lib/pay/paddle_billing.rb +15 -6
- data/lib/pay/paddle_classic.rb +11 -9
- data/lib/pay/receipts.rb +6 -6
- data/lib/pay/stripe/webhooks/checkout_session_completed.rb +1 -1
- data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
- data/lib/pay/stripe/webhooks/subscription_trial_will_end.rb +1 -1
- data/lib/pay/stripe.rb +21 -7
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +12 -1
- metadata +34 -38
- data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
- data/lib/pay/braintree/authorization_error.rb +0 -9
- data/lib/pay/braintree/error.rb +0 -23
- data/lib/pay/fake_processor/charge.rb +0 -21
- data/lib/pay/fake_processor/error.rb +0 -6
- data/lib/pay/fake_processor/payment_method.rb +0 -21
- data/lib/pay/fake_processor/subscription.rb +0 -90
- data/lib/pay/lemon_squeezy/billable.rb +0 -90
- data/lib/pay/lemon_squeezy/charge.rb +0 -68
- data/lib/pay/lemon_squeezy/error.rb +0 -7
- data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
- data/lib/pay/lemon_squeezy/subscription.rb +0 -185
- data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
- data/lib/pay/paddle_billing/error.rb +0 -7
- data/lib/pay/paddle_classic/error.rb +0 -7
- data/lib/pay/stripe/error.rb +0 -7
@@ -1,36 +1,20 @@
|
|
1
1
|
module Pay
|
2
2
|
module Stripe
|
3
|
-
class
|
4
|
-
include
|
5
|
-
|
6
|
-
attr_reader :pay_customer
|
7
|
-
|
8
|
-
delegate :processor_id,
|
9
|
-
:processor_id?,
|
10
|
-
:email,
|
11
|
-
:customer_name,
|
12
|
-
:payment_method_token,
|
13
|
-
:payment_method_token?,
|
14
|
-
:stripe_account,
|
15
|
-
to: :pay_customer
|
16
|
-
|
17
|
-
def self.default_url_options
|
18
|
-
Rails.application.config.action_mailer.default_url_options || {}
|
19
|
-
end
|
3
|
+
class Customer < Pay::Customer
|
4
|
+
include Pay::Routing
|
20
5
|
|
21
|
-
|
22
|
-
|
23
|
-
|
6
|
+
has_many :charges, dependent: :destroy, class_name: "Pay::Stripe::Charge"
|
7
|
+
has_many :subscriptions, dependent: :destroy, class_name: "Pay::Stripe::Subscription"
|
8
|
+
has_many :payment_methods, dependent: :destroy, class_name: "Pay::Stripe::PaymentMethod"
|
9
|
+
has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::Stripe::PaymentMethod"
|
24
10
|
|
25
11
|
# Returns a hash of attributes for the Stripe::Customer object
|
26
|
-
def
|
27
|
-
owner = pay_customer.owner
|
28
|
-
|
12
|
+
def api_record_attributes
|
29
13
|
attributes = case owner.class.pay_stripe_customer_attributes
|
30
14
|
when Symbol
|
31
|
-
owner.send(owner.class.pay_stripe_customer_attributes,
|
15
|
+
owner.send(owner.class.pay_stripe_customer_attributes, self)
|
32
16
|
when Proc
|
33
|
-
owner.class.pay_stripe_customer_attributes.call(
|
17
|
+
owner.class.pay_stripe_customer_attributes.call(self)
|
34
18
|
end
|
35
19
|
|
36
20
|
# Guard against attributes being returned nil
|
@@ -39,55 +23,30 @@ module Pay
|
|
39
23
|
{email: email, name: customer_name}.merge(attributes)
|
40
24
|
end
|
41
25
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
stripe_customer = if processor_id?
|
52
|
-
::Stripe::Customer.retrieve({id: processor_id, expand: ["tax", "invoice_credit_balance"]}, stripe_options)
|
53
|
-
else
|
54
|
-
sc = ::Stripe::Customer.create(customer_attributes.merge(expand: ["tax"]), stripe_options)
|
55
|
-
pay_customer.update!(processor_id: sc.id, stripe_account: stripe_account)
|
56
|
-
sc
|
57
|
-
end
|
58
|
-
|
59
|
-
if payment_method_token?
|
60
|
-
add_payment_method(payment_method_token, default: true)
|
61
|
-
pay_customer.payment_method_token = nil
|
26
|
+
def api_record(expand: ["tax", "invoice_credit_balance"])
|
27
|
+
with_lock do
|
28
|
+
if processor_id?
|
29
|
+
::Stripe::Customer.retrieve({id: processor_id, expand: expand}, stripe_options)
|
30
|
+
else
|
31
|
+
::Stripe::Customer.create(api_record_attributes.merge(expand: expand), stripe_options).tap do |customer|
|
32
|
+
update!(processor_id: customer.id, stripe_account: stripe_account)
|
33
|
+
end
|
34
|
+
end
|
62
35
|
end
|
63
|
-
|
64
|
-
stripe_customer
|
65
36
|
rescue ::Stripe::StripeError => e
|
66
37
|
raise Pay::Stripe::Error, e
|
67
38
|
end
|
68
39
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
customer unless processor_id?
|
73
|
-
::Stripe::Customer.update(
|
74
|
-
processor_id,
|
75
|
-
customer_attributes.merge(attributes),
|
76
|
-
stripe_options
|
77
|
-
)
|
40
|
+
def update_api_record(**attributes)
|
41
|
+
api_record unless processor_id?
|
42
|
+
::Stripe::Customer.update(processor_id, api_record_attributes.merge(attributes), stripe_options)
|
78
43
|
end
|
79
44
|
|
80
45
|
# Charges an amount to the customer's default payment method
|
81
46
|
def charge(amount, options = {})
|
82
|
-
|
83
|
-
|
84
|
-
payment_method = pay_customer.default_payment_method
|
85
|
-
args = {
|
86
|
-
confirm: true,
|
87
|
-
payment_method: payment_method&.processor_id
|
88
|
-
}.merge(options)
|
89
|
-
|
47
|
+
args = {confirm: true, payment_method: default_payment_method&.processor_id}.merge(options)
|
90
48
|
payment_intent = create_payment_intent(amount, args)
|
49
|
+
|
91
50
|
Pay::Payment.new(payment_intent).validate
|
92
51
|
|
93
52
|
charge = payment_intent.latest_charge
|
@@ -96,24 +55,6 @@ module Pay
|
|
96
55
|
raise Pay::Stripe::Error, e
|
97
56
|
end
|
98
57
|
|
99
|
-
# Creates and returns a Stripe::PaymentIntent
|
100
|
-
def create_payment_intent(amount, options = {})
|
101
|
-
args = {
|
102
|
-
amount: amount,
|
103
|
-
currency: "usd",
|
104
|
-
customer: processor_id,
|
105
|
-
expand: ["latest_charge.refunds"],
|
106
|
-
return_url: root_url
|
107
|
-
}.merge(options)
|
108
|
-
|
109
|
-
::Stripe::PaymentIntent.create(args, stripe_options)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Used for creating Stripe Terminal charges
|
113
|
-
def terminal_charge(amount, options = {})
|
114
|
-
create_payment_intent(amount, options.merge(payment_method_types: ["card_present"], capture_method: "manual"))
|
115
|
-
end
|
116
|
-
|
117
58
|
def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
|
118
59
|
quantity = options.delete(:quantity)
|
119
60
|
opts = {
|
@@ -122,7 +63,7 @@ module Pay
|
|
122
63
|
}.merge(options)
|
123
64
|
|
124
65
|
# Load the Stripe customer to verify it exists and update payment method if needed
|
125
|
-
opts[:customer] =
|
66
|
+
opts[:customer] = processor_id || api_record.id
|
126
67
|
|
127
68
|
# Create subscription on Stripe
|
128
69
|
stripe_sub = ::Stripe::Subscription.create(opts.merge(Pay::Stripe::Subscription.expand_options), stripe_options)
|
@@ -141,7 +82,7 @@ module Pay
|
|
141
82
|
end
|
142
83
|
|
143
84
|
def add_payment_method(payment_method_id, default: false)
|
144
|
-
|
85
|
+
api_record unless processor_id?
|
145
86
|
payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: processor_id}, stripe_options)
|
146
87
|
|
147
88
|
if default
|
@@ -159,50 +100,56 @@ module Pay
|
|
159
100
|
|
160
101
|
# Save the Stripe::PaymentMethod to the database
|
161
102
|
def save_payment_method(payment_method, default:)
|
162
|
-
pay_payment_method =
|
103
|
+
pay_payment_method = payment_methods.where(processor_id: payment_method.id).first_or_initialize
|
163
104
|
|
164
105
|
attributes = Pay::Stripe::PaymentMethod.extract_attributes(payment_method).merge(default: default)
|
165
106
|
|
166
107
|
# Ignore the payment method if it's already in the database
|
167
|
-
|
108
|
+
payment_methods.where.not(id: pay_payment_method.id).update_all(default: false) if default
|
168
109
|
pay_payment_method.update!(attributes)
|
169
110
|
|
170
111
|
# Reload the Rails association
|
171
|
-
|
112
|
+
reload_default_payment_method
|
172
113
|
|
173
114
|
pay_payment_method
|
174
115
|
end
|
175
116
|
|
176
|
-
|
177
|
-
::Stripe::Subscription.retrieve(options.merge(id: subscription_id), stripe_options)
|
178
|
-
end
|
117
|
+
### Stripe extras
|
179
118
|
|
180
|
-
|
181
|
-
|
182
|
-
|
119
|
+
# Creates and returns a Stripe::PaymentIntent
|
120
|
+
def create_payment_intent(amount, options = {})
|
121
|
+
args = {
|
122
|
+
amount: amount,
|
123
|
+
currency: "usd",
|
124
|
+
customer: processor_id || api_record.id,
|
125
|
+
expand: ["latest_charge.refunds"],
|
126
|
+
return_url: root_url
|
127
|
+
}.merge(options)
|
128
|
+
|
129
|
+
::Stripe::PaymentIntent.create(args, stripe_options)
|
183
130
|
end
|
184
131
|
|
185
|
-
|
186
|
-
|
132
|
+
# Used for creating Stripe Terminal charges
|
133
|
+
def terminal_charge(amount, options = {})
|
134
|
+
create_payment_intent(amount, options.merge(payment_method_types: ["card_present"], capture_method: "manual"))
|
187
135
|
end
|
188
136
|
|
189
137
|
def create_setup_intent(options = {})
|
190
|
-
customer
|
191
|
-
::Stripe::SetupIntent.create({
|
192
|
-
customer: processor_id,
|
193
|
-
usage: :off_session
|
194
|
-
}.merge(options), stripe_options)
|
138
|
+
::Stripe::SetupIntent.create({customer: processor_id || api_record.id, usage: :off_session}.merge(options), stripe_options)
|
195
139
|
end
|
196
140
|
|
197
|
-
def
|
198
|
-
|
199
|
-
|
141
|
+
def invoice!(options = {})
|
142
|
+
::Stripe::Invoice.create(options.merge(customer: processor_id || api_record.id), stripe_options).pay
|
143
|
+
end
|
144
|
+
|
145
|
+
def upcoming_invoice
|
146
|
+
::Stripe::Invoice.upcoming({customer: processor_id || api_record.id}, stripe_options)
|
200
147
|
end
|
201
148
|
|
202
149
|
# Syncs a customer's subscriptions from Stripe to the database.
|
203
150
|
# Note that by default canceled subscriptions are NOT returned by Stripe. In order to include them, use `sync_subscriptions(status: "all")`.
|
204
151
|
def sync_subscriptions(**options)
|
205
|
-
subscriptions = ::Stripe::Subscription.list(options.
|
152
|
+
subscriptions = ::Stripe::Subscription.list(options.with_defaults(customer: processor_id), stripe_options)
|
206
153
|
subscriptions.map do |subscription|
|
207
154
|
Pay::Stripe::Subscription.sync(subscription.id)
|
208
155
|
end
|
@@ -221,7 +168,7 @@ module Pay
|
|
221
168
|
# checkout(line_items: "price_12345", allow_promotion_codes: true)
|
222
169
|
#
|
223
170
|
def checkout(**options)
|
224
|
-
|
171
|
+
api_record unless processor_id?
|
225
172
|
args = {
|
226
173
|
customer: processor_id,
|
227
174
|
mode: "payment"
|
@@ -261,7 +208,7 @@ module Pay
|
|
261
208
|
# checkout_charge(amount: 15_00, name: "T-shirt", quantity: 2)
|
262
209
|
#
|
263
210
|
def checkout_charge(amount:, name:, quantity: 1, **options)
|
264
|
-
|
211
|
+
api_record unless processor_id?
|
265
212
|
currency = options.delete(:currency) || "usd"
|
266
213
|
checkout(
|
267
214
|
line_items: {
|
@@ -277,7 +224,7 @@ module Pay
|
|
277
224
|
end
|
278
225
|
|
279
226
|
def billing_portal(**options)
|
280
|
-
|
227
|
+
api_record unless processor_id?
|
281
228
|
args = {
|
282
229
|
customer: processor_id,
|
283
230
|
return_url: options.delete(:return_url) || root_url
|
@@ -285,10 +232,28 @@ module Pay
|
|
285
232
|
::Stripe::BillingPortal::Session.create(args.merge(options), stripe_options)
|
286
233
|
end
|
287
234
|
|
235
|
+
def customer_session(**options)
|
236
|
+
api_record unless processor_id?
|
237
|
+
args = {customer: processor_id}
|
238
|
+
::Stripe::CustomerSession.create(args.merge(options), stripe_options)
|
239
|
+
end
|
240
|
+
|
288
241
|
def authorize(amount, options = {})
|
289
242
|
charge(amount, options.merge(capture_method: :manual))
|
290
243
|
end
|
291
244
|
|
245
|
+
# Creates a meter event to bill for usage
|
246
|
+
#
|
247
|
+
# create_meter_event(:api_request, value: 1)
|
248
|
+
# create_meter_event(:api_request, token: 7)
|
249
|
+
def create_meter_event(event_name, payload: {}, **options)
|
250
|
+
api_record unless processor_id?
|
251
|
+
::Stripe::Billing::MeterEvent.create({
|
252
|
+
event_name: event_name,
|
253
|
+
payload: {stripe_customer_id: processor_id}.merge(payload)
|
254
|
+
}.merge(options))
|
255
|
+
end
|
256
|
+
|
292
257
|
private
|
293
258
|
|
294
259
|
# Options for Stripe requests
|
@@ -299,7 +264,7 @@ module Pay
|
|
299
264
|
# Includes the `session_id` param for Stripe Checkout with existing params (and makes sure the curly braces aren't escaped)
|
300
265
|
def merge_session_id_param(url)
|
301
266
|
uri = URI.parse(url)
|
302
|
-
uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s).to_h.merge("
|
267
|
+
uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s).to_h.merge("stripe_checkout_session_id" => "{CHECKOUT_SESSION_ID}").to_a)
|
303
268
|
uri.to_s.gsub("%7BCHECKOUT_SESSION_ID%7D", "{CHECKOUT_SESSION_ID}")
|
304
269
|
end
|
305
270
|
end
|
@@ -1,15 +1,6 @@
|
|
1
1
|
module Pay
|
2
2
|
module Stripe
|
3
|
-
class Merchant
|
4
|
-
attr_reader :pay_merchant
|
5
|
-
|
6
|
-
delegate :processor_id,
|
7
|
-
to: :pay_merchant
|
8
|
-
|
9
|
-
def initialize(pay_merchant)
|
10
|
-
@pay_merchant = pay_merchant
|
11
|
-
end
|
12
|
-
|
3
|
+
class Merchant < Pay::Merchant
|
13
4
|
def create_account(**options)
|
14
5
|
defaults = {
|
15
6
|
type: "express",
|
@@ -20,7 +11,7 @@ module Pay
|
|
20
11
|
}
|
21
12
|
|
22
13
|
stripe_account = ::Stripe::Account.create(defaults.merge(options))
|
23
|
-
|
14
|
+
update(processor_id: stripe_account.id)
|
24
15
|
stripe_account
|
25
16
|
rescue ::Stripe::StripeError => e
|
26
17
|
raise Pay::Stripe::Error, e
|
@@ -1,14 +1,6 @@
|
|
1
1
|
module Pay
|
2
2
|
module Stripe
|
3
|
-
class PaymentMethod
|
4
|
-
attr_reader :pay_payment_method
|
5
|
-
|
6
|
-
delegate :customer, :processor_id, to: :pay_payment_method
|
7
|
-
|
8
|
-
def initialize(pay_payment_method)
|
9
|
-
@pay_payment_method = pay_payment_method
|
10
|
-
end
|
11
|
-
|
3
|
+
class PaymentMethod < Pay::PaymentMethod
|
12
4
|
# Syncs a PaymentIntent's payment method to the database
|
13
5
|
def self.sync_payment_intent(id, stripe_account: nil)
|
14
6
|
payment_intent = ::Stripe::PaymentIntent.retrieve({id: id, expand: ["payment_method"]}, {stripe_account: stripe_account}.compact)
|
@@ -39,7 +31,7 @@ module Pay
|
|
39
31
|
return
|
40
32
|
end
|
41
33
|
|
42
|
-
default_payment_method_id = pay_customer.
|
34
|
+
default_payment_method_id = pay_customer.api_record.invoice_settings.default_payment_method
|
43
35
|
default = (id == default_payment_method_id)
|
44
36
|
|
45
37
|
attributes = extract_attributes(object).merge(default: default, stripe_account: stripe_account)
|
@@ -1,33 +1,7 @@
|
|
1
1
|
module Pay
|
2
2
|
module Stripe
|
3
|
-
class Subscription
|
4
|
-
|
5
|
-
attr_reader :pay_subscription
|
6
|
-
|
7
|
-
delegate :active?,
|
8
|
-
:canceled?,
|
9
|
-
:ends_at?,
|
10
|
-
:ends_at,
|
11
|
-
:name,
|
12
|
-
:on_trial?,
|
13
|
-
:past_due?,
|
14
|
-
:pause_starts_at,
|
15
|
-
:pause_starts_at?,
|
16
|
-
:processor_id,
|
17
|
-
:processor_plan,
|
18
|
-
:processor_subscription,
|
19
|
-
:prorate,
|
20
|
-
:prorate?,
|
21
|
-
:quantity,
|
22
|
-
:quantity?,
|
23
|
-
:stripe_account,
|
24
|
-
:subscription_items,
|
25
|
-
:trial_ends_at,
|
26
|
-
:pause_behavior,
|
27
|
-
:pause_resumes_at,
|
28
|
-
:current_period_start,
|
29
|
-
:current_period_end,
|
30
|
-
to: :pay_subscription
|
3
|
+
class Subscription < Pay::Subscription
|
4
|
+
attr_writer :api_record
|
31
5
|
|
32
6
|
def self.sync_from_checkout_session(session_id, stripe_account: nil)
|
33
7
|
checkout_session = ::Stripe::Checkout::Session.retrieve({id: session_id}, {stripe_account: stripe_account}.compact)
|
@@ -125,7 +99,7 @@ module Pay
|
|
125
99
|
end
|
126
100
|
|
127
101
|
# Cache the Stripe subscription on the Pay::Subscription that we return
|
128
|
-
pay_subscription.
|
102
|
+
pay_subscription.api_record = object
|
129
103
|
|
130
104
|
# Sync the latest charge if we already have it loaded (like during subscrbe), otherwise, let webhooks take care of creating it
|
131
105
|
if (charge = object.try(:latest_invoice).try(:charge)) && charge.try(:status) == "succeeded"
|
@@ -157,30 +131,20 @@ module Pay
|
|
157
131
|
}
|
158
132
|
end
|
159
133
|
|
160
|
-
def
|
161
|
-
@
|
162
|
-
end
|
163
|
-
|
164
|
-
def subscription(**options)
|
165
|
-
options[:id] = processor_id
|
166
|
-
@stripe_subscription ||= ::Stripe::Subscription.retrieve(options.merge(expand_options), {stripe_account: stripe_account}.compact)
|
167
|
-
end
|
168
|
-
|
169
|
-
def reload!
|
170
|
-
@stripe_subscription = nil
|
134
|
+
def api_record(**options)
|
135
|
+
@api_record ||= ::Stripe::Subscription.retrieve(options.with_defaults(id: processor_id).merge(expand_options), {stripe_account: stripe_account}.compact)
|
171
136
|
end
|
172
137
|
|
173
138
|
# Returns a SetupIntent or PaymentIntent client secret for the subscription
|
174
139
|
def client_secret
|
175
|
-
|
176
|
-
stripe_sub&.pending_setup_intent&.client_secret || stripe_sub&.latest_invoice&.payment_intent&.client_secret
|
140
|
+
api_record&.pending_setup_intent&.client_secret || api_record&.latest_invoice&.payment_intent&.client_secret
|
177
141
|
end
|
178
142
|
|
179
143
|
# Sets the default_payment_method on a subscription
|
180
144
|
# Pass an empty string to unset
|
181
145
|
def update_payment_method(id)
|
182
|
-
@
|
183
|
-
|
146
|
+
@api_record = ::Stripe::Subscription.update(processor_id, {default_payment_method: id}.merge(expand_options), stripe_options)
|
147
|
+
update(payment_method_id: @api_record.default_payment_method&.id)
|
184
148
|
rescue ::Stripe::StripeError => e
|
185
149
|
raise Pay::Stripe::Error, e
|
186
150
|
end
|
@@ -195,8 +159,8 @@ module Pay
|
|
195
159
|
if past_due? && options.fetch(:past_due_cancel_now, true)
|
196
160
|
cancel_now!
|
197
161
|
else
|
198
|
-
@
|
199
|
-
|
162
|
+
@api_record = ::Stripe::Subscription.update(processor_id, {cancel_at_period_end: true}.merge(expand_options), stripe_options)
|
163
|
+
update(ends_at: (on_trial? ? trial_ends_at : Time.at(@api_record.current_period_end)))
|
200
164
|
end
|
201
165
|
rescue ::Stripe::StripeError => e
|
202
166
|
raise Pay::Stripe::Error, e
|
@@ -209,8 +173,8 @@ module Pay
|
|
209
173
|
def cancel_now!(**options)
|
210
174
|
return if canceled? && ends_at.past?
|
211
175
|
|
212
|
-
@
|
213
|
-
|
176
|
+
@api_record = ::Stripe::Subscription.cancel(processor_id, options.merge(expand_options), stripe_options)
|
177
|
+
update(ends_at: Time.current, status: :canceled)
|
214
178
|
rescue ::Stripe::StripeError => e
|
215
179
|
raise Pay::Stripe::Error, e
|
216
180
|
end
|
@@ -223,11 +187,11 @@ module Pay
|
|
223
187
|
subscription_item_id = options.delete(:subscription_item_id) || subscription_items&.first&.dig("id")
|
224
188
|
if subscription_item_id
|
225
189
|
::Stripe::SubscriptionItem.update(subscription_item_id, options.merge(quantity: quantity), stripe_options)
|
226
|
-
@
|
190
|
+
@api_record = nil
|
227
191
|
else
|
228
|
-
@
|
192
|
+
@api_record = ::Stripe::Subscription.update(processor_id, options.merge(quantity: quantity).merge(expand_options), stripe_options)
|
229
193
|
end
|
230
|
-
|
194
|
+
update(quantity: quantity)
|
231
195
|
rescue ::Stripe::StripeError => e
|
232
196
|
raise Pay::Stripe::Error, e
|
233
197
|
end
|
@@ -265,12 +229,12 @@ module Pay
|
|
265
229
|
# https://docs.stripe.com/billing/subscriptions/pause-payment
|
266
230
|
def pause(**options)
|
267
231
|
attributes = {pause_collection: options.reverse_merge(behavior: "void")}
|
268
|
-
@
|
269
|
-
behavior = @
|
270
|
-
|
232
|
+
@api_record = ::Stripe::Subscription.update(processor_id, attributes.merge(expand_options), stripe_options)
|
233
|
+
behavior = @api_record.pause_collection&.behavior
|
234
|
+
update(
|
271
235
|
pause_behavior: behavior,
|
272
|
-
pause_resumes_at: (@
|
273
|
-
pause_starts_at: ((behavior == "void") ? Time.at(@
|
236
|
+
pause_resumes_at: (@api_record.pause_collection&.resumes_at ? Time.at(@api_record.pause_collection&.resumes_at) : nil),
|
237
|
+
pause_starts_at: ((behavior == "void") ? Time.at(@api_record.current_period_end) : nil)
|
274
238
|
)
|
275
239
|
end
|
276
240
|
|
@@ -278,8 +242,8 @@ module Pay
|
|
278
242
|
#
|
279
243
|
# https://docs.stripe.com/billing/subscriptions/pause-payment#unpausing
|
280
244
|
def unpause
|
281
|
-
@
|
282
|
-
|
245
|
+
@api_record = ::Stripe::Subscription.update(processor_id, {pause_collection: ""}.merge(expand_options), stripe_options)
|
246
|
+
update(
|
283
247
|
pause_behavior: nil,
|
284
248
|
pause_resumes_at: nil,
|
285
249
|
pause_starts_at: nil
|
@@ -298,16 +262,14 @@ module Pay
|
|
298
262
|
if paused?
|
299
263
|
unpause
|
300
264
|
else
|
301
|
-
@
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
}.merge(expand_options),
|
308
|
-
stripe_options
|
309
|
-
)
|
265
|
+
@api_record = ::Stripe::Subscription.update(processor_id, {
|
266
|
+
plan: processor_plan,
|
267
|
+
trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
|
268
|
+
cancel_at_period_end: false
|
269
|
+
}.merge(expand_options),
|
270
|
+
stripe_options)
|
310
271
|
end
|
272
|
+
update(ends_at: nil, status: :active)
|
311
273
|
rescue ::Stripe::StripeError => e
|
312
274
|
raise Pay::Stripe::Error, e
|
313
275
|
end
|
@@ -317,7 +279,7 @@ module Pay
|
|
317
279
|
|
318
280
|
proration_behavior = options.delete(:proration_behavior) || (prorate ? "always_invoice" : "none")
|
319
281
|
|
320
|
-
@
|
282
|
+
@api_record = ::Stripe::Subscription.update(
|
321
283
|
processor_id,
|
322
284
|
{
|
323
285
|
cancel_at_period_end: false,
|
@@ -330,11 +292,11 @@ module Pay
|
|
330
292
|
)
|
331
293
|
|
332
294
|
# Validate that swap was successful and handle SCA if needed
|
333
|
-
if (payment_intent = @
|
295
|
+
if (payment_intent = @api_record.latest_invoice.payment_intent)
|
334
296
|
Pay::Payment.new(payment_intent).validate
|
335
297
|
end
|
336
298
|
|
337
|
-
|
299
|
+
sync!(object: @api_record)
|
338
300
|
rescue ::Stripe::StripeError => e
|
339
301
|
raise Pay::Stripe::Error, e
|
340
302
|
end
|
@@ -374,7 +336,7 @@ module Pay
|
|
374
336
|
payment_intent = ::Stripe::PaymentIntent.retrieve({id: payment_intent_id}, stripe_options)
|
375
337
|
|
376
338
|
payment_intent = if payment_intent.status == "requires_payment_method"
|
377
|
-
::Stripe::PaymentIntent.confirm(payment_intent_id, {payment_method:
|
339
|
+
::Stripe::PaymentIntent.confirm(payment_intent_id, {payment_method: customer.default_payment_method.processor_id}, stripe_options)
|
378
340
|
else
|
379
341
|
::Stripe::PaymentIntent.confirm(payment_intent_id, stripe_options)
|
380
342
|
end
|
@@ -390,6 +352,10 @@ module Pay
|
|
390
352
|
end
|
391
353
|
end
|
392
354
|
|
355
|
+
def latest_payment
|
356
|
+
api_record(expand: ["latest_invoice.payment_intent"]).latest_invoice.payment_intent
|
357
|
+
end
|
358
|
+
|
393
359
|
private
|
394
360
|
|
395
361
|
# Options for Stripe requests
|
@@ -9,7 +9,7 @@ module Pay
|
|
9
9
|
|
10
10
|
# Scopes
|
11
11
|
scope :for_name, ->(name) { where(name: name) }
|
12
|
-
scope :on_trial, -> { where(status: ["trialing", "active"]).where("trial_ends_at > ?", Time.current) }
|
12
|
+
scope :on_trial, -> { where(status: ["on_trial", "trialing", "active"]).where("trial_ends_at > ?", Time.current) }
|
13
13
|
scope :canceled, -> { where.not(ends_at: nil) }
|
14
14
|
scope :cancelled, -> { canceled }
|
15
15
|
scope :on_grace_period, -> { where("#{table_name}.ends_at IS NOT NULL AND #{table_name}.ends_at > ?", Time.current) }
|
@@ -40,10 +40,8 @@ module Pay
|
|
40
40
|
validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
|
41
41
|
validates :status, presence: true
|
42
42
|
|
43
|
-
delegate_missing_to :payment_processor
|
44
|
-
|
45
43
|
# Helper methods for payment processors
|
46
|
-
%w[braintree stripe paddle_billing paddle_classic fake_processor].each do |processor_name|
|
44
|
+
%w[braintree stripe paddle_billing paddle_classic lemon_squeezy fake_processor].each do |processor_name|
|
47
45
|
define_method :"#{processor_name}?" do
|
48
46
|
customer.processor == processor_name
|
49
47
|
end
|
@@ -55,16 +53,8 @@ module Pay
|
|
55
53
|
joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
|
56
54
|
end
|
57
55
|
|
58
|
-
def self.pay_processor_for(name)
|
59
|
-
"Pay::#{name.to_s.classify}::Subscription".constantize
|
60
|
-
end
|
61
|
-
|
62
|
-
def payment_processor
|
63
|
-
@payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
|
64
|
-
end
|
65
|
-
|
66
56
|
def sync!(**options)
|
67
|
-
self.class.
|
57
|
+
self.class.sync(processor_id, **options)
|
68
58
|
reload
|
69
59
|
end
|
70
60
|
|
@@ -105,6 +95,10 @@ module Pay
|
|
105
95
|
ends_at? && ends_at <= Time.current
|
106
96
|
end
|
107
97
|
|
98
|
+
def on_grace_period?
|
99
|
+
ends_at? && ends_at > Time.current
|
100
|
+
end
|
101
|
+
|
108
102
|
# If you cancel during a trial, you should still retain access until the end of the trial
|
109
103
|
# Otherwise a subscription is active unless it has ended or is currently paused
|
110
104
|
# Check the subscription status so we don't accidentally consider "incomplete", "unpaid", or other statuses as active
|
@@ -129,35 +123,11 @@ module Pay
|
|
129
123
|
past_due? || incomplete?
|
130
124
|
end
|
131
125
|
|
132
|
-
def change_quantity(quantity, **options)
|
133
|
-
payment_processor.change_quantity(quantity, **options)
|
134
|
-
update(quantity: quantity)
|
135
|
-
end
|
136
|
-
|
137
|
-
def resume
|
138
|
-
payment_processor.resume
|
139
|
-
update(ends_at: nil, status: :active)
|
140
|
-
self
|
141
|
-
end
|
142
|
-
|
143
|
-
def swap(plan, **options)
|
144
|
-
raise ArgumentError, "plan must be a string. Got `#{plan.inspect}` instead." unless plan.is_a?(String)
|
145
|
-
payment_processor.swap(plan, **options)
|
146
|
-
end
|
147
|
-
|
148
126
|
def swap_and_invoice(plan)
|
149
127
|
swap(plan)
|
150
128
|
customer.invoice!(subscription: processor_id)
|
151
129
|
end
|
152
130
|
|
153
|
-
def processor_subscription(**options)
|
154
|
-
payment_processor.subscription(**options)
|
155
|
-
end
|
156
|
-
|
157
|
-
def latest_payment
|
158
|
-
processor_subscription(expand: ["latest_invoice.payment_intent"]).latest_invoice.payment_intent
|
159
|
-
end
|
160
|
-
|
161
131
|
private
|
162
132
|
|
163
133
|
def cancel_if_active
|
data/app/models/pay/webhook.rb
CHANGED
data/config/routes.rb
CHANGED
@@ -6,4 +6,5 @@ Pay::Engine.routes.draw do
|
|
6
6
|
post "webhooks/braintree", to: "pay/webhooks/braintree#create" if Pay::Braintree.enabled?
|
7
7
|
post "webhooks/paddle_billing", to: "pay/webhooks/paddle_billing#create" if Pay::PaddleBilling.enabled?
|
8
8
|
post "webhooks/paddle_classic", to: "pay/webhooks/paddle_classic#create" if Pay::PaddleClassic.enabled?
|
9
|
+
post "webhooks/lemon_squeezy", to: "pay/webhooks/lemon_squeezy#create" if Pay::LemonSqueezy.enabled?
|
9
10
|
end
|