pay 11.7.2 → 12.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 +1 -0
- data/app/controllers/pay/payments_controller.rb +5 -12
- data/app/controllers/pay/webhooks/base_controller.rb +50 -0
- data/app/controllers/pay/webhooks/braintree_controller.rb +11 -18
- data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +8 -26
- data/app/controllers/pay/webhooks/paddle_billing_controller.rb +13 -35
- data/app/controllers/pay/webhooks/paddle_classic_controller.rb +5 -20
- data/app/controllers/pay/webhooks/stripe_controller.rb +13 -29
- data/app/models/pay/braintree/charge.rb +6 -12
- data/app/models/pay/braintree/payment_method.rb +7 -5
- data/app/models/pay/braintree/subscription.rb +39 -44
- data/app/models/pay/charge.rb +1 -1
- data/app/models/pay/customer.rb +1 -9
- data/app/models/pay/lemon_squeezy/charge.rb +63 -61
- data/app/models/pay/lemon_squeezy/customer.rb +1 -1
- data/app/models/pay/lemon_squeezy/subscription.rb +43 -46
- data/app/models/pay/merchant.rb +1 -1
- data/app/models/pay/paddle_billing/charge.rb +43 -53
- data/app/models/pay/paddle_billing/customer.rb +1 -1
- data/app/models/pay/paddle_billing/payment_method.rb +19 -23
- data/app/models/pay/paddle_billing/subscription.rb +56 -67
- data/app/models/pay/paddle_classic/customer.rb +1 -1
- data/app/models/pay/paddle_classic/subscription.rb +42 -44
- data/app/models/pay/payment_method.rb +1 -5
- data/app/models/pay/stripe/charge.rb +54 -59
- data/app/models/pay/stripe/customer.rb +45 -23
- data/app/models/pay/stripe/merchant.rb +1 -1
- data/app/models/pay/stripe/payment_method.rb +27 -29
- data/app/models/pay/stripe/subscription.rb +128 -119
- data/app/models/pay/subscription.rb +2 -12
- data/app/views/layouts/pay/application.html.erb +0 -2
- data/app/views/pay/payments/show.html.erb +19 -8
- data/app/views/pay/user_mailer/payment_action_required.html.erb +1 -1
- data/app/views/pay/user_mailer/payment_action_required.text.erb +1 -1
- data/lib/pay/braintree/webhooks/subscription.rb +15 -0
- data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -7
- data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -7
- data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +1 -7
- data/lib/pay/braintree/webhooks/subscription_went_active.rb +2 -8
- data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +2 -8
- data/lib/pay/braintree.rb +1 -2
- data/lib/pay/currency.rb +0 -4
- data/lib/pay/engine.rb +4 -0
- data/lib/pay/errors.rb +4 -0
- data/lib/pay/lemon_squeezy.rb +4 -7
- data/lib/pay/paddle_billing.rb +4 -1
- data/lib/pay/paddle_classic.rb +4 -1
- data/lib/pay/payment.rb +11 -17
- data/lib/pay/receipts.rb +0 -1
- data/lib/pay/stripe/webhooks/checkout_session_completed.rb +8 -4
- data/lib/pay/stripe/webhooks/invoice_updated.rb +4 -1
- data/lib/pay/stripe/webhooks/payment_action_required.rb +3 -2
- data/lib/pay/stripe/webhooks/subscription_created.rb +2 -4
- data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -5
- data/lib/pay/stripe.rb +3 -3
- data/lib/pay/sync.rb +29 -0
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +7 -5
- metadata +5 -3
- data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +0 -24
data/lib/pay/engine.rb
CHANGED
|
@@ -17,6 +17,10 @@ module Pay
|
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
initializer "pay.deprecator" do |app|
|
|
21
|
+
app.deprecators[:pay] = Pay.deprecator if app.respond_to?(:deprecators)
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
initializer "pay.receipts" do
|
|
21
25
|
if defined?(::Receipts::VERSION)
|
|
22
26
|
raise "[Pay] receipts gem must be version ~> 2" unless Pay::Engine.version_matches?(required: "~> 2", current: ::Receipts::VERSION)
|
data/lib/pay/errors.rb
CHANGED
data/lib/pay/lemon_squeezy.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module Pay
|
|
2
2
|
module LemonSqueezy
|
|
3
3
|
class Error < Pay::Error
|
|
4
|
-
|
|
4
|
+
# Wrapped processor errors report the original message; errors raised with a string keep theirs
|
|
5
|
+
def message
|
|
6
|
+
cause&.message || super
|
|
7
|
+
end
|
|
5
8
|
end
|
|
6
9
|
|
|
7
10
|
module Webhooks
|
|
@@ -38,12 +41,6 @@ module Pay
|
|
|
38
41
|
owner.to_sgid.to_s
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
def self.owner_from_passthrough(passthrough)
|
|
42
|
-
GlobalID::Locator.locate_signed passthrough
|
|
43
|
-
rescue JSON::ParserError
|
|
44
|
-
nil
|
|
45
|
-
end
|
|
46
|
-
|
|
47
44
|
def self.configure_webhooks
|
|
48
45
|
Pay::Webhooks.configure do |events|
|
|
49
46
|
events.subscribe "lemon_squeezy.order_created", Pay::LemonSqueezy::Webhooks::Order.new
|
data/lib/pay/paddle_billing.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module Pay
|
|
2
2
|
module PaddleBilling
|
|
3
3
|
class Error < Pay::Error
|
|
4
|
-
|
|
4
|
+
# Wrapped processor errors report the original message; errors raised with a string keep theirs
|
|
5
|
+
def message
|
|
6
|
+
cause&.message || super
|
|
7
|
+
end
|
|
5
8
|
end
|
|
6
9
|
|
|
7
10
|
module Webhooks
|
data/lib/pay/paddle_classic.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module Pay
|
|
2
2
|
module PaddleClassic
|
|
3
3
|
class Error < Pay::Error
|
|
4
|
-
|
|
4
|
+
# Wrapped processor errors report the original message; errors raised with a string keep theirs
|
|
5
|
+
def message
|
|
6
|
+
cause&.message || super
|
|
7
|
+
end
|
|
5
8
|
end
|
|
6
9
|
|
|
7
10
|
module Webhooks
|
data/lib/pay/payment.rb
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
module Pay
|
|
2
2
|
class Payment
|
|
3
|
-
|
|
3
|
+
# The Stripe Connect account the intent lives on, if any. Needed to look the intent up again
|
|
4
|
+
# and to initialize Stripe.js on the SCA confirmation page.
|
|
5
|
+
attr_reader :intent, :stripe_account
|
|
4
6
|
|
|
5
7
|
delegate :id, :amount, :client_secret, :currency, :customer, :status, :confirm, to: :intent
|
|
6
8
|
|
|
7
|
-
def self.from_id(id)
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
def self.from_id(id, stripe_account: nil)
|
|
10
|
+
options = {stripe_account: stripe_account}.compact
|
|
11
|
+
intent = id.start_with?("seti_") ? ::Stripe::SetupIntent.retrieve(id, options) : ::Stripe::PaymentIntent.retrieve(id, options)
|
|
12
|
+
new(intent, stripe_account: stripe_account)
|
|
13
|
+
rescue ::Stripe::StripeError => e
|
|
14
|
+
raise Pay::Stripe::Error, e
|
|
10
15
|
end
|
|
11
16
|
|
|
12
|
-
def initialize(intent)
|
|
17
|
+
def initialize(intent, stripe_account: nil)
|
|
13
18
|
@intent = intent
|
|
19
|
+
@stripe_account = stripe_account
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def requires_payment_method?
|
|
@@ -25,22 +31,10 @@ module Pay
|
|
|
25
31
|
status == "canceled"
|
|
26
32
|
end
|
|
27
33
|
|
|
28
|
-
def cancelled?
|
|
29
|
-
canceled?
|
|
30
|
-
end
|
|
31
|
-
|
|
32
34
|
def succeeded?
|
|
33
35
|
status == "succeeded"
|
|
34
36
|
end
|
|
35
37
|
|
|
36
|
-
def payment_intent?
|
|
37
|
-
intent.is_a?(::Stripe::PaymentIntent)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def setup_intent?
|
|
41
|
-
intent.is_a?(::Stripe::SetupIntent)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
38
|
def amount_with_currency
|
|
45
39
|
Pay::Currency.format(amount, currency: currency)
|
|
46
40
|
end
|
data/lib/pay/receipts.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Pay
|
|
|
3
3
|
module Webhooks
|
|
4
4
|
class CheckoutSessionCompleted
|
|
5
5
|
def call(event)
|
|
6
|
-
locate_owner(event.data.object)
|
|
6
|
+
locate_owner(event.data.object, stripe_account: event.try(:account))
|
|
7
7
|
|
|
8
8
|
# By the time CheckoutSessionCompleted is fired, we probably missed the original events
|
|
9
9
|
# Instead, we can sync the payment intent or subscription during this event to ensure they're in the database
|
|
@@ -18,11 +18,15 @@ module Pay
|
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
# Associates the Stripe customer from the session with the record referenced by client_reference_id
|
|
22
|
+
#
|
|
23
|
+
# Sessions in payment or setup mode don't always create a Stripe customer (for example Payment Links with
|
|
24
|
+
# customer_creation: if_required), so skip those rather than clearing an existing processor_id
|
|
25
|
+
def locate_owner(object, stripe_account: nil)
|
|
26
|
+
return if object.client_reference_id.nil? || object.customer.nil?
|
|
23
27
|
|
|
24
28
|
owner = Pay::Stripe.find_by_client_reference_id(object.client_reference_id)
|
|
25
|
-
owner&.add_payment_processor(:stripe, processor_id: object.customer)
|
|
29
|
+
owner&.add_payment_processor(:stripe, processor_id: object.customer, stripe_account: stripe_account)
|
|
26
30
|
end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
@@ -19,10 +19,13 @@ module Pay
|
|
|
19
19
|
return unless subscription
|
|
20
20
|
|
|
21
21
|
# Grab the local latest_invoice from the subscription stripe_object
|
|
22
|
+
# It may be an expanded object, a bare ID, or nil for a subscription that hasn't been invoiced yet
|
|
22
23
|
latest_invoice = subscription.stripe_object.try(:latest_invoice)
|
|
24
|
+
latest_invoice_id = latest_invoice.try(:id) || latest_invoice
|
|
25
|
+
return if latest_invoice_id.blank?
|
|
23
26
|
|
|
24
27
|
# Compare the local invoice id to the event invoice id and sync if they are the same
|
|
25
|
-
if
|
|
28
|
+
if latest_invoice_id.to_s == invoice.id.to_s
|
|
26
29
|
Pay::Stripe::Subscription.sync(subscription_id, stripe_account: event.try(:account))
|
|
27
30
|
end
|
|
28
31
|
rescue ::Stripe::StripeError => e
|
|
@@ -13,13 +13,14 @@ module Pay
|
|
|
13
13
|
pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, subscription_id)
|
|
14
14
|
return if pay_subscription.nil? || pay_subscription.status == "incomplete"
|
|
15
15
|
|
|
16
|
-
invoice_payment = ::Stripe::InvoicePayment.list({invoice: invoice.id, status: :open}).first
|
|
16
|
+
invoice_payment = ::Stripe::InvoicePayment.list({invoice: invoice.id, status: :open}, {stripe_account: event.try(:account)}.compact).first
|
|
17
17
|
|
|
18
18
|
if invoice_payment && Pay.send_email?(:payment_action_required, pay_subscription)
|
|
19
19
|
Pay.mailer.with(
|
|
20
20
|
pay_customer: pay_subscription.customer,
|
|
21
21
|
pay_subscription: pay_subscription,
|
|
22
|
-
payment_intent_id: invoice_payment.payment.payment_intent
|
|
22
|
+
payment_intent_id: invoice_payment.payment.payment_intent,
|
|
23
|
+
stripe_account: event.try(:account)
|
|
23
24
|
).payment_action_required.deliver_later
|
|
24
25
|
end
|
|
25
26
|
end
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
module Pay
|
|
2
2
|
module Stripe
|
|
3
3
|
module Webhooks
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Pay::Stripe::Subscription.sync(event.data.object.id, stripe_account: event.try(:account))
|
|
7
|
-
end
|
|
4
|
+
# If a subscription is manually created on Stripe, we want to sync it the same way as an update
|
|
5
|
+
class SubscriptionCreated < SubscriptionUpdated
|
|
8
6
|
end
|
|
9
7
|
end
|
|
10
8
|
end
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
module Pay
|
|
2
2
|
module Stripe
|
|
3
3
|
module Webhooks
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# Canceled subscriptions are still accessible via the API, so we can sync their details
|
|
7
|
-
Pay::Stripe::Subscription.sync(event.data.object.id, stripe_account: event.try(:account))
|
|
8
|
-
end
|
|
4
|
+
# Canceled subscriptions are still accessible via the API, so they sync the same way as an update
|
|
5
|
+
class SubscriptionDeleted < SubscriptionUpdated
|
|
9
6
|
end
|
|
10
7
|
end
|
|
11
8
|
end
|
data/lib/pay/stripe.rb
CHANGED
|
@@ -175,12 +175,12 @@ module Pay
|
|
|
175
175
|
when "subscription"
|
|
176
176
|
Pay::Stripe::Subscription.sync(checkout_session.subscription, stripe_account: stripe_account)
|
|
177
177
|
end
|
|
178
|
-
rescue ::Stripe::InvalidRequestError
|
|
178
|
+
rescue ::Stripe::InvalidRequestError => e
|
|
179
179
|
if try > retries
|
|
180
|
-
raise
|
|
180
|
+
raise Pay::Stripe::Error, e
|
|
181
181
|
else
|
|
182
182
|
try += 1
|
|
183
|
-
sleep 0.15
|
|
183
|
+
sleep 0.15 * try
|
|
184
184
|
retry
|
|
185
185
|
end
|
|
186
186
|
end
|
data/lib/pay/sync.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Pay
|
|
2
|
+
# Shared skeleton for the processor Charge.sync, Subscription.sync, and PaymentMethod.sync class methods
|
|
3
|
+
module Sync
|
|
4
|
+
# Runs the sync block, retrying it when a webhook and an API call race to
|
|
5
|
+
# create the same record. The block retrieves the processor's object itself
|
|
6
|
+
# when the caller didn't pass one in, so each retry gets a fresh read.
|
|
7
|
+
def sync_with_retries(retries: 1)
|
|
8
|
+
try = 0
|
|
9
|
+
begin
|
|
10
|
+
yield
|
|
11
|
+
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
|
|
12
|
+
try += 1
|
|
13
|
+
raise if try > retries
|
|
14
|
+
sleep 0.15 * try
|
|
15
|
+
retry
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The Pay::Customer for this processor, or nil when the ID is blank or the customer isn't in the database
|
|
20
|
+
def find_pay_customer(processor_id)
|
|
21
|
+
Pay::Customer.find_by(processor: pay_processor, processor_id: processor_id) if processor_id.present?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# "stripe" for Pay::Stripe::Charge, "paddle_billing" for Pay::PaddleBilling::Subscription, and so on
|
|
25
|
+
def pay_processor
|
|
26
|
+
name.deconstantize.demodulize.underscore
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/pay/version.rb
CHANGED
data/lib/pay.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Pay
|
|
|
12
12
|
autoload :NanoId, "pay/nano_id"
|
|
13
13
|
autoload :Payment, "pay/payment"
|
|
14
14
|
autoload :Receipts, "pay/receipts"
|
|
15
|
+
autoload :Sync, "pay/sync"
|
|
15
16
|
autoload :Currency, "pay/currency"
|
|
16
17
|
|
|
17
18
|
# Payment processors
|
|
@@ -75,15 +76,16 @@ module Pay
|
|
|
75
76
|
@@emails.subscription_trial_will_end = true
|
|
76
77
|
@@emails.subscription_trial_ended = true
|
|
77
78
|
|
|
79
|
+
mattr_writer :mailer
|
|
78
80
|
@@mailer = "Pay::UserMailer"
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@@
|
|
82
|
+
# Resolved on every call rather than memoized, so code reloading in development returns the current class
|
|
83
|
+
def self.mailer
|
|
84
|
+
@@mailer.constantize
|
|
83
85
|
end
|
|
84
86
|
|
|
85
|
-
def self.
|
|
86
|
-
|
|
87
|
+
def self.deprecator
|
|
88
|
+
@deprecator ||= ActiveSupport::Deprecation.new("13.0", "Pay")
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
mattr_accessor :parent_mailer
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pay
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 12.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jason Charnes
|
|
@@ -42,6 +42,7 @@ files:
|
|
|
42
42
|
- app/assets/stylesheets/pay/application.css
|
|
43
43
|
- app/controllers/pay/application_controller.rb
|
|
44
44
|
- app/controllers/pay/payments_controller.rb
|
|
45
|
+
- app/controllers/pay/webhooks/base_controller.rb
|
|
45
46
|
- app/controllers/pay/webhooks/braintree_controller.rb
|
|
46
47
|
- app/controllers/pay/webhooks/lemon_squeezy_controller.rb
|
|
47
48
|
- app/controllers/pay/webhooks/paddle_billing_controller.rb
|
|
@@ -115,9 +116,9 @@ files:
|
|
|
115
116
|
- lib/pay/attributes.rb
|
|
116
117
|
- lib/pay/billable/sync_customer.rb
|
|
117
118
|
- lib/pay/braintree.rb
|
|
119
|
+
- lib/pay/braintree/webhooks/subscription.rb
|
|
118
120
|
- lib/pay/braintree/webhooks/subscription_canceled.rb
|
|
119
121
|
- lib/pay/braintree/webhooks/subscription_charged_successfully.rb
|
|
120
|
-
- lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb
|
|
121
122
|
- lib/pay/braintree/webhooks/subscription_expired.rb
|
|
122
123
|
- lib/pay/braintree/webhooks/subscription_trial_ended.rb
|
|
123
124
|
- lib/pay/braintree/webhooks/subscription_went_active.rb
|
|
@@ -165,6 +166,7 @@ files:
|
|
|
165
166
|
- lib/pay/stripe/webhooks/subscription_renewing.rb
|
|
166
167
|
- lib/pay/stripe/webhooks/subscription_trial_will_end.rb
|
|
167
168
|
- lib/pay/stripe/webhooks/subscription_updated.rb
|
|
169
|
+
- lib/pay/sync.rb
|
|
168
170
|
- lib/pay/version.rb
|
|
169
171
|
- lib/pay/webhooks.rb
|
|
170
172
|
- lib/pay/webhooks/delegator.rb
|
|
@@ -189,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
189
191
|
- !ruby/object:Gem::Version
|
|
190
192
|
version: '0'
|
|
191
193
|
requirements: []
|
|
192
|
-
rubygems_version: 4.0.
|
|
194
|
+
rubygems_version: 4.0.21
|
|
193
195
|
specification_version: 4
|
|
194
196
|
summary: Payments engine for Ruby on Rails
|
|
195
197
|
test_files: []
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
|
|
2
|
-
|
|
3
|
-
module Pay
|
|
4
|
-
module Braintree
|
|
5
|
-
module Webhooks
|
|
6
|
-
class SubscriptionChargedUnsuccessfully
|
|
7
|
-
def call(event)
|
|
8
|
-
# subscription = event.subscription
|
|
9
|
-
# return if subscription.nil?
|
|
10
|
-
|
|
11
|
-
# pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
|
|
12
|
-
# return unless pay_subscription.present?
|
|
13
|
-
|
|
14
|
-
# pay_customer = pay_subscription.customer
|
|
15
|
-
# pay_charge = Pay::Braintree::Billable.new(pay_customer).save_transaction(subscription.transactions.first)
|
|
16
|
-
|
|
17
|
-
# if Pay.send_emails
|
|
18
|
-
# Pay.mailer.with(pay_customer: pay_charge.customer, charge: pay_charge).receipt.deliver_later
|
|
19
|
-
# end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|