pay 7.3.0 → 8.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
- 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 +15 -53
- 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} +20 -37
- 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 +3 -1
- 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/customer_updated.rb +1 -1
- data/lib/pay/stripe.rb +16 -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
@@ -21,6 +21,8 @@ module Pay
|
|
21
21
|
to_recursive_ostruct(event["data"])
|
22
22
|
when "paddle_classic"
|
23
23
|
to_recursive_ostruct(event)
|
24
|
+
when "lemon_squeezy"
|
25
|
+
Pay::LemonSqueezy.construct_from_webhook_event(event)
|
24
26
|
when "stripe"
|
25
27
|
::Stripe::Event.construct_from(event)
|
26
28
|
else
|
@@ -30,7 +32,7 @@ module Pay
|
|
30
32
|
|
31
33
|
def to_recursive_ostruct(obj)
|
32
34
|
if obj.is_a?(Hash)
|
33
|
-
|
35
|
+
ActiveSupport::InheritableOptions.new(obj.map { |key, val| [key.to_sym, to_recursive_ostruct(val)] }.to_h)
|
34
36
|
elsif obj.is_a?(Array)
|
35
37
|
obj.map { |o| to_recursive_ostruct(o) }
|
36
38
|
else # Assumed to be a primitive value
|
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
|