pay 7.3.0 → 11.2.2
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 +8 -4
- data/app/controllers/pay/payments_controller.rb +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 +7 -12
- data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +33 -71
- data/{lib → app/models}/pay/braintree/payment_method.rb +4 -10
- data/{lib → app/models}/pay/braintree/subscription.rb +23 -61
- data/app/models/pay/charge.rb +16 -45
- data/app/models/pay/customer.rb +5 -16
- data/app/models/pay/fake_processor/charge.rb +19 -0
- data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +28 -38
- data/{lib → app/models}/pay/fake_processor/merchant.rb +4 -9
- data/app/models/pay/fake_processor/payment_method.rb +13 -0
- data/app/models/pay/fake_processor/subscription.rb +70 -0
- data/app/models/pay/lemon_squeezy/charge.rb +96 -0
- data/app/models/pay/lemon_squeezy/customer.rb +80 -0
- data/app/models/pay/lemon_squeezy/payment_method.rb +29 -0
- data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
- data/app/models/pay/merchant.rb +2 -11
- data/{lib → app/models}/pay/paddle_billing/charge.rb +15 -13
- data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +20 -35
- data/{lib → app/models}/pay/paddle_billing/payment_method.rb +13 -13
- data/{lib → app/models}/pay/paddle_billing/subscription.rb +40 -43
- data/{lib → app/models}/pay/paddle_classic/charge.rb +15 -18
- data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +11 -31
- data/{lib → app/models}/pay/paddle_classic/payment_method.rb +3 -11
- data/{lib → app/models}/pay/paddle_classic/subscription.rb +17 -37
- data/app/models/pay/payment_method.rb +4 -5
- data/app/models/pay/stripe/charge.rb +155 -0
- data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +78 -111
- data/{lib → app/models}/pay/stripe/merchant.rb +5 -20
- data/{lib → app/models}/pay/stripe/payment_method.rb +11 -17
- data/{lib → app/models}/pay/stripe/subscription.rb +83 -112
- data/app/models/pay/subscription.rb +13 -47
- data/app/models/pay/webhook.rb +5 -1
- data/app/views/pay/user_mailer/payment_action_required.text.erb +9 -0
- data/app/views/pay/user_mailer/payment_failed.text.erb +9 -0
- data/app/views/pay/user_mailer/receipt.text.erb +20 -0
- data/app/views/pay/user_mailer/refund.text.erb +21 -0
- data/app/views/pay/user_mailer/subscription_renewing.text.erb +8 -0
- data/app/views/pay/user_mailer/subscription_trial_ended.text.erb +8 -0
- data/app/views/pay/user_mailer/subscription_trial_will_end.text.erb +8 -0
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20250415151129_add_object_to_pay_models.rb +7 -0
- data/db/migrate/2_add_pay_sti_columns.rb +24 -0
- data/lib/pay/attributes.rb +16 -8
- 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 +58 -104
- data/lib/pay/nano_id.rb +1 -1
- data/lib/pay/paddle_billing.rb +15 -6
- data/lib/pay/paddle_classic/webhooks/signature_verifier.rb +1 -1
- data/lib/pay/paddle_classic.rb +11 -9
- data/lib/pay/receipts.rb +45 -44
- data/lib/pay/stripe/webhooks/charge_updated.rb +11 -0
- data/lib/pay/stripe/webhooks/customer_updated.rb +13 -9
- data/lib/pay/stripe/webhooks/payment_action_required.rb +10 -6
- data/lib/pay/stripe/webhooks/payment_failed.rb +6 -4
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +9 -4
- data/lib/pay/stripe.rb +28 -9
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +19 -1
- data/lib/tasks/pay.rake +2 -2
- metadata +45 -43
- 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/charge.rb +0 -176
- data/lib/pay/stripe/error.rb +0 -7
data/lib/pay/receipts.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
module Pay
|
2
2
|
module Receipts
|
3
|
-
def product
|
4
|
-
Pay.application_name
|
5
|
-
end
|
6
|
-
|
7
3
|
def receipt_filename
|
8
4
|
"receipt-#{created_at.strftime("%Y-%m-%d")}.pdf"
|
9
5
|
end
|
@@ -21,6 +17,10 @@ module Pay
|
|
21
17
|
]
|
22
18
|
end
|
23
19
|
|
20
|
+
def pdf_product_name
|
21
|
+
Pay.application_name
|
22
|
+
end
|
23
|
+
|
24
24
|
def pdf_line_items
|
25
25
|
items = [
|
26
26
|
[
|
@@ -31,60 +31,61 @@ module Pay
|
|
31
31
|
]
|
32
32
|
]
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
Array.wrap(li["discounts"]).each do |discount_id|
|
42
|
-
if (discount = total_discount_amounts.find { |d| d.dig("discount", "id") == discount_id })
|
43
|
-
items << [discount_description(discount), nil, nil, Pay::Currency.format(-discount["amount"], currency: currency)]
|
44
|
-
end
|
34
|
+
if try(:stripe_invoice)
|
35
|
+
stripe_invoice.lines.auto_paging_each do |line|
|
36
|
+
items << [line.description, line.quantity, Pay::Currency.format(line.pricing.unit_amount_decimal, currency: line.currency), Pay::Currency.format(line.amount, currency: line.currency)]
|
37
|
+
|
38
|
+
line.discounts.each do |discount_id|
|
39
|
+
discount = stripe_invoice.total_discount_amounts.find { |d| d.discount.id == discount_id }
|
40
|
+
items << [discount_description(discount), nil, nil, Pay::Currency.format(-discount.amount, currency: currency)]
|
45
41
|
end
|
46
42
|
end
|
47
43
|
else
|
48
|
-
items << [
|
44
|
+
items << [pdf_product_name, 1, Pay::Currency.format(amount, currency: currency), Pay::Currency.format(amount, currency: currency)]
|
49
45
|
end
|
50
46
|
|
51
47
|
# If no subtotal, we will display the total
|
52
|
-
items << [nil, nil, I18n.t("pay.line_items.subtotal"), Pay::Currency.format(subtotal || amount, currency: currency)]
|
48
|
+
items << [nil, nil, I18n.t("pay.line_items.subtotal"), Pay::Currency.format(try(:stripe_invoice)&.subtotal || amount, currency: currency)]
|
53
49
|
|
54
50
|
# Discounts on the invoice
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
51
|
+
try(:stripe_invoice)&.discounts&.each do |discount_id|
|
52
|
+
discount = stripe_invoice.total_discount_amounts.find { |d| d.discount.id == discount_id }
|
53
|
+
items << [nil, nil, discount_description(discount), Pay::Currency.format(-discount.amount, currency: currency)]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Total excluding tax
|
57
|
+
if try(:stripe_invoice)
|
58
|
+
items << [nil, nil, I18n.t("pay.line_items.total"), Pay::Currency.format(stripe_invoice.total_excluding_tax, currency: currency)]
|
59
59
|
end
|
60
60
|
|
61
61
|
# Tax rates
|
62
|
-
|
63
|
-
next if
|
64
|
-
|
62
|
+
try(:stripe_invoice)&.total_taxes&.each do |tax|
|
63
|
+
next if tax.amount.zero?
|
64
|
+
# tax_rate = ::Stripe::TaxRate.retrieve(tax.tax_rate_details.tax_rate)
|
65
|
+
items << [nil, nil, I18n.t("pay.line_items.tax"), Pay::Currency.format(tax.amount, currency: currency)]
|
65
66
|
end
|
66
67
|
|
68
|
+
# Total
|
67
69
|
items << [nil, nil, I18n.t("pay.line_items.total"), Pay::Currency.format(amount, currency: currency)]
|
68
70
|
items
|
69
71
|
end
|
70
72
|
|
71
73
|
def discount_description(discount)
|
72
|
-
coupon = discount.
|
73
|
-
name = coupon.
|
74
|
+
coupon = discount.discount.coupon
|
75
|
+
name = coupon.name
|
74
76
|
|
75
|
-
if (percent = coupon
|
77
|
+
if (percent = coupon.percent_off)
|
76
78
|
I18n.t("pay.line_items.percent_discount", name: name, percent: ActiveSupport::NumberHelper.number_to_rounded(percent, strip_insignificant_zeros: true))
|
77
79
|
else
|
78
|
-
I18n.t("pay.line_items.amount_discount", name: name, amount: Pay::Currency.format(coupon
|
80
|
+
I18n.t("pay.line_items.amount_discount", name: name, amount: Pay::Currency.format(coupon.amount_off, currency: coupon.currency))
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
82
|
-
def tax_description(
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
84
|
+
# def tax_description(tax_rate)
|
85
|
+
# percent = "#{ActiveSupport::NumberHelper.number_to_rounded(tax_rate.percentage, strip_insignificant_zeros: true)}%"
|
86
|
+
# percent += " inclusive" if tax_rate.inclusive
|
87
|
+
# "#{tax_rate.display_name} - #{tax_rate.jurisdiction} (#{percent})"
|
88
|
+
# end
|
88
89
|
|
89
90
|
def receipt_line_items
|
90
91
|
line_items = pdf_line_items
|
@@ -94,15 +95,15 @@ module Pay
|
|
94
95
|
|
95
96
|
if refunded?
|
96
97
|
# If we have a list of individual refunds, add each entry
|
97
|
-
if refunds&.any?
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
else
|
104
|
-
|
105
|
-
end
|
98
|
+
# if refunds&.any?
|
99
|
+
# refunds.each do |refund|
|
100
|
+
# next unless refund["status"] == "succeeded"
|
101
|
+
# refunded_at = Time.at(refund["created"]).to_date
|
102
|
+
# line_items << [nil, nil, I18n.t("pay.receipt.refunded_on", date: I18n.l(refunded_at, format: :long)), Pay::Currency.format(refund["amount"], currency: refund["currency"])]
|
103
|
+
# end
|
104
|
+
# else
|
105
|
+
line_items << [nil, nil, I18n.t("pay.receipt.refunded"), Pay::Currency.format(amount_refunded, currency: currency)]
|
106
|
+
# end
|
106
107
|
end
|
107
108
|
|
108
109
|
line_items
|
@@ -155,7 +156,7 @@ module Pay
|
|
155
156
|
company: {
|
156
157
|
name: Pay.business_name,
|
157
158
|
address: Pay.business_address,
|
158
|
-
email: Pay.support_email
|
159
|
+
email: Pay.support_email&.address,
|
159
160
|
logo: Pay.business_logo
|
160
161
|
},
|
161
162
|
line_items: pdf_line_items
|
@@ -9,7 +9,19 @@ module Pay
|
|
9
9
|
# Couldn't find user, we can skip
|
10
10
|
return unless pay_customer.present?
|
11
11
|
|
12
|
-
stripe_customer = pay_customer.
|
12
|
+
stripe_customer = pay_customer.api_record
|
13
|
+
|
14
|
+
attributes = {
|
15
|
+
object: stripe_customer.to_hash
|
16
|
+
}
|
17
|
+
|
18
|
+
# Sync invoice credit balance and currency
|
19
|
+
if stripe_customer.invoice_credit_balance.present?
|
20
|
+
attributes[:invoice_credit_balance] = stripe_customer.invoice_credit_balance
|
21
|
+
attributes[:currency] = stripe_customer.currency
|
22
|
+
end
|
23
|
+
|
24
|
+
pay_customer.update(attributes)
|
13
25
|
|
14
26
|
# Sync default card
|
15
27
|
if (payment_method_id = stripe_customer.invoice_settings.default_payment_method)
|
@@ -19,14 +31,6 @@ module Pay
|
|
19
31
|
# No default payment method set
|
20
32
|
pay_customer.payment_methods.update_all(default: false)
|
21
33
|
end
|
22
|
-
|
23
|
-
# Sync invoice credit balance and currency
|
24
|
-
if stripe_customer.invoice_credit_balance.present?
|
25
|
-
pay_customer.update(
|
26
|
-
invoice_credit_balance: stripe_customer.invoice_credit_balance,
|
27
|
-
currency: stripe_customer.currency
|
28
|
-
)
|
29
|
-
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -6,16 +6,20 @@ module Pay
|
|
6
6
|
# Event is of type "invoice" see:
|
7
7
|
# https://stripe.com/docs/api/invoices/object
|
8
8
|
|
9
|
-
|
9
|
+
invoice = event.data.object
|
10
|
+
subscription_id = invoice.parent.try(:subscription_details).try(:subscription)
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
# Don't send email on incomplete Stripe subscriptions since they're just getting created and the JavaScript will handle SCA
|
13
|
+
pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, subscription_id)
|
14
|
+
return if pay_subscription.nil? || pay_subscription.status == "incomplete"
|
13
15
|
|
14
|
-
|
16
|
+
invoice_payment = ::Stripe::InvoicePayment.list({invoice: invoice.id, status: :open}).first
|
17
|
+
|
18
|
+
if invoice_payment && Pay.send_email?(:payment_action_required, pay_subscription)
|
15
19
|
Pay.mailer.with(
|
16
20
|
pay_customer: pay_subscription.customer,
|
17
|
-
|
18
|
-
|
21
|
+
pay_subscription: pay_subscription,
|
22
|
+
payment_intent_id: invoice_payment.payment.payment_intent
|
19
23
|
).payment_action_required.deliver_later
|
20
24
|
end
|
21
25
|
end
|
@@ -6,15 +6,17 @@ module Pay
|
|
6
6
|
# Event is of type "invoice" see:
|
7
7
|
# https://stripe.com/docs/api/invoices/object
|
8
8
|
|
9
|
-
|
9
|
+
invoice = event.data.object
|
10
|
+
subscription_id = invoice.parent.try(:subscription_details).try(:subscription)
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
# Don't send email on incomplete Stripe subscriptions since they're just getting created and the JavaScript will handle SCA
|
13
|
+
pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, subscription_id)
|
14
|
+
return if pay_subscription.nil? || pay_subscription.status == "incomplete"
|
13
15
|
|
14
16
|
if Pay.send_email?(:payment_failed, pay_subscription)
|
15
17
|
Pay.mailer.with(
|
16
18
|
pay_customer: pay_subscription.customer,
|
17
|
-
stripe_invoice:
|
19
|
+
stripe_invoice: invoice
|
18
20
|
).payment_failed.deliver_now
|
19
21
|
end
|
20
22
|
end
|
@@ -6,19 +6,24 @@ module Pay
|
|
6
6
|
# Occurs X number of days before a subscription is scheduled to create an invoice that is automatically charged—where X is determined by your subscriptions settings. Note: The received Invoice object will not have an invoice ID.
|
7
7
|
|
8
8
|
def call(event)
|
9
|
+
invoice = event.data.object
|
10
|
+
subscription_id = invoice.parent.try(:subscription_details).try(:subscription)
|
11
|
+
|
9
12
|
# Event is of type "invoice" see:
|
10
13
|
# https://stripe.com/docs/api/invoices/object
|
11
|
-
pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe,
|
14
|
+
pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, subscription_id)
|
12
15
|
return unless pay_subscription
|
13
16
|
|
14
17
|
# Stripe subscription items all have the same interval
|
15
|
-
price =
|
18
|
+
price = ::Stripe::Price.retrieve({id: invoice.lines.first.pricing.price_details.price}, {stripe_account: event.try(:account)}.compact)
|
16
19
|
|
17
|
-
|
20
|
+
# For collection_method=send_invoice, Stripe will send an email and next_payment_attempt will be null
|
21
|
+
# https://docs.stripe.com/api/invoices/object#invoice_object-collection_method
|
22
|
+
if Pay.send_email?(:subscription_renewing, pay_subscription, price) && (next_payment_attempt = invoice.next_payment_attempt)
|
18
23
|
Pay.mailer.with(
|
19
24
|
pay_customer: pay_subscription.customer,
|
20
25
|
pay_subscription: pay_subscription,
|
21
|
-
date: Time.zone.at(
|
26
|
+
date: Time.zone.at(next_payment_attempt)
|
22
27
|
).subscription_renewing.deliver_later
|
23
28
|
end
|
24
29
|
end
|
data/lib/pay/stripe.rb
CHANGED
@@ -1,16 +1,13 @@
|
|
1
1
|
module Pay
|
2
2
|
module Stripe
|
3
|
-
|
4
|
-
|
5
|
-
autoload :Error, "pay/stripe/error"
|
6
|
-
autoload :Merchant, "pay/stripe/merchant"
|
7
|
-
autoload :PaymentMethod, "pay/stripe/payment_method"
|
8
|
-
autoload :Subscription, "pay/stripe/subscription"
|
3
|
+
class Error < Pay::Error
|
4
|
+
end
|
9
5
|
|
10
6
|
module Webhooks
|
11
7
|
autoload :AccountUpdated, "pay/stripe/webhooks/account_updated"
|
12
8
|
autoload :ChargeRefunded, "pay/stripe/webhooks/charge_refunded"
|
13
9
|
autoload :ChargeSucceeded, "pay/stripe/webhooks/charge_succeeded"
|
10
|
+
autoload :ChargeUpdated, "pay/stripe/webhooks/charge_updated"
|
14
11
|
autoload :CheckoutSessionCompleted, "pay/stripe/webhooks/checkout_session_completed"
|
15
12
|
autoload :CheckoutSessionAsyncPaymentSucceeded, "pay/stripe/webhooks/checkout_session_async_payment_succeeded"
|
16
13
|
autoload :CustomerDeleted, "pay/stripe/webhooks/customer_deleted"
|
@@ -30,7 +27,7 @@ module Pay
|
|
30
27
|
|
31
28
|
extend Env
|
32
29
|
|
33
|
-
REQUIRED_VERSION = "~>
|
30
|
+
REQUIRED_VERSION = "~> 15"
|
34
31
|
|
35
32
|
# A list of database model names that include Pay
|
36
33
|
# Used for safely looking up models with client_reference_id
|
@@ -68,7 +65,7 @@ module Pay
|
|
68
65
|
|
69
66
|
def self.webhook_receive_test_events
|
70
67
|
value = find_value_by_name(:stripe, :webhook_receive_test_events)
|
71
|
-
value.blank?
|
68
|
+
value.blank? || ActiveModel::Type::Boolean.new.cast(value)
|
72
69
|
end
|
73
70
|
|
74
71
|
def self.configure_webhooks
|
@@ -76,8 +73,9 @@ module Pay
|
|
76
73
|
# Listen to the charge event to make sure we get non-subscription
|
77
74
|
# purchases as well. Invoice is only for subscriptions and manual creation
|
78
75
|
# so it does not include individual charges.
|
79
|
-
events.subscribe "stripe.charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
|
80
76
|
events.subscribe "stripe.charge.refunded", Pay::Stripe::Webhooks::ChargeRefunded.new
|
77
|
+
events.subscribe "stripe.charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
|
78
|
+
events.subscribe "stripe.charge.updated", Pay::Stripe::Webhooks::ChargeUpdated.new
|
81
79
|
|
82
80
|
events.subscribe "stripe.payment_intent.succeeded", Pay::Stripe::Webhooks::PaymentIntentSucceeded.new
|
83
81
|
|
@@ -143,5 +141,26 @@ module Pay
|
|
143
141
|
Rails.logger.error "[Pay] Unable to locate record with: #{client_reference_id}"
|
144
142
|
nil
|
145
143
|
end
|
144
|
+
|
145
|
+
# Subscriptions aren't always immediately associated, so we want to retry by default
|
146
|
+
def self.sync_checkout_session(session_id, stripe_account: nil, try: 0, retries: 5)
|
147
|
+
checkout_session = ::Stripe::Checkout::Session.retrieve({id: session_id, expand: ["payment_intent.latest_charge"]}, {stripe_account: stripe_account}.compact)
|
148
|
+
case checkout_session.mode
|
149
|
+
when "payment"
|
150
|
+
if (id = checkout_session.payment_intent.try(:latest_charge)&.id)
|
151
|
+
Pay::Stripe::Charge.sync(id, stripe_account: stripe_account, retries: 5)
|
152
|
+
end
|
153
|
+
when "subscription"
|
154
|
+
Pay::Stripe::Subscription.sync(checkout_session.subscription, stripe_account: stripe_account)
|
155
|
+
end
|
156
|
+
rescue ::Stripe::InvalidRequestError
|
157
|
+
if try > retries
|
158
|
+
raise
|
159
|
+
else
|
160
|
+
try += 1
|
161
|
+
sleep 0.15**try
|
162
|
+
retry
|
163
|
+
end
|
164
|
+
end
|
146
165
|
end
|
147
166
|
end
|
data/lib/pay/version.rb
CHANGED
data/lib/pay.rb
CHANGED
@@ -19,6 +19,7 @@ module Pay
|
|
19
19
|
autoload :FakeProcessor, "pay/fake_processor"
|
20
20
|
autoload :PaddleBilling, "pay/paddle_billing"
|
21
21
|
autoload :PaddleClassic, "pay/paddle_classic"
|
22
|
+
autoload :LemonSqueezy, "pay/lemon_squeezy"
|
22
23
|
autoload :Stripe, "pay/stripe"
|
23
24
|
|
24
25
|
autoload :Webhooks, "pay/webhooks"
|
@@ -56,7 +57,7 @@ module Pay
|
|
56
57
|
@@routes_path = "/pay"
|
57
58
|
|
58
59
|
mattr_accessor :enabled_processors
|
59
|
-
@@enabled_processors = [:stripe, :braintree, :paddle_billing, :paddle_classic]
|
60
|
+
@@enabled_processors = [:stripe, :braintree, :paddle_billing, :paddle_classic, :lemon_squeezy]
|
60
61
|
|
61
62
|
mattr_accessor :send_emails
|
62
63
|
@@send_emails = true
|
@@ -130,4 +131,21 @@ module Pay
|
|
130
131
|
option
|
131
132
|
end
|
132
133
|
end
|
134
|
+
|
135
|
+
SYNC_HANDLERS = {
|
136
|
+
lemon_squeezy_order_id: ->(param) { Pay::LemonSqueezy.sync_order(param) },
|
137
|
+
paddle_billing_transaction_id: ->(param) { Pay::PaddleBilling.sync_transaction(param) },
|
138
|
+
stripe_checkout_session_id: ->(param) { Pay::Stripe.sync_checkout_session(param) },
|
139
|
+
transaction_id: ->(param) { Pay::PaddleBilling.sync_transaction(param) },
|
140
|
+
session_id: ->(param) { Pay::Stripe.sync_checkout_session(param) }
|
141
|
+
}
|
142
|
+
|
143
|
+
def self.sync(params)
|
144
|
+
SYNC_HANDLERS.each do |param_name, handler|
|
145
|
+
if (param = params[param_name])
|
146
|
+
return handler.call(param)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
nil
|
150
|
+
end
|
133
151
|
end
|
data/lib/tasks/pay.rake
CHANGED
@@ -15,10 +15,10 @@ def sync_default_payment_method(pay_customer, retries: 2)
|
|
15
15
|
puts "Syncing Pay::Customer ##{pay_customer.id} attempt #{try + 1}: #{pay_customer.processor.titleize} #{pay_customer.processor_id}"
|
16
16
|
case pay_customer.processor
|
17
17
|
when "braintree"
|
18
|
-
payment_method = pay_customer.
|
18
|
+
payment_method = pay_customer.api_record.payment_methods.find(&:default?)
|
19
19
|
Pay::Braintree::PaymentMethod.sync(payment_method.token, object: payment_method) if payment_method
|
20
20
|
when "stripe"
|
21
|
-
payment_method_id = pay_customer.
|
21
|
+
payment_method_id = pay_customer.api_record.invoice_settings.default_payment_method
|
22
22
|
Pay::Stripe::PaymentMethod.sync(payment_method_id) if payment_method_id
|
23
23
|
when "paddle_classic"
|
24
24
|
Pay::PaddleClassic::PaymentMethod.sync(pay_customer: pay_customer)
|
metadata
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 11.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Charnes
|
8
8
|
- Chris Oliver
|
9
9
|
- Collin Jilbert
|
10
|
-
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: rails
|
@@ -18,14 +17,14 @@ dependencies:
|
|
18
17
|
requirements:
|
19
18
|
- - ">="
|
20
19
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
20
|
+
version: 7.0.0
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
24
|
requirements:
|
26
25
|
- - ">="
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
27
|
+
version: 7.0.0
|
29
28
|
description: Stripe, Paddle, and Braintree payments for Ruby on Rails apps
|
30
29
|
email:
|
31
30
|
- jason@thecharnes.com
|
@@ -44,6 +43,7 @@ files:
|
|
44
43
|
- app/controllers/pay/application_controller.rb
|
45
44
|
- app/controllers/pay/payments_controller.rb
|
46
45
|
- app/controllers/pay/webhooks/braintree_controller.rb
|
46
|
+
- app/controllers/pay/webhooks/lemon_squeezy_controller.rb
|
47
47
|
- app/controllers/pay/webhooks/paddle_billing_controller.rb
|
48
48
|
- app/controllers/pay/webhooks/paddle_classic_controller.rb
|
49
49
|
- app/controllers/pay/webhooks/stripe_controller.rb
|
@@ -52,27 +52,62 @@ files:
|
|
52
52
|
- app/jobs/pay/customer_sync_job.rb
|
53
53
|
- app/mailers/pay/application_mailer.rb
|
54
54
|
- app/mailers/pay/user_mailer.rb
|
55
|
+
- app/models/concerns/pay/routing.rb
|
55
56
|
- app/models/pay/application_record.rb
|
57
|
+
- app/models/pay/braintree/charge.rb
|
58
|
+
- app/models/pay/braintree/customer.rb
|
59
|
+
- app/models/pay/braintree/payment_method.rb
|
60
|
+
- app/models/pay/braintree/subscription.rb
|
56
61
|
- app/models/pay/charge.rb
|
57
62
|
- app/models/pay/customer.rb
|
63
|
+
- app/models/pay/fake_processor/charge.rb
|
64
|
+
- app/models/pay/fake_processor/customer.rb
|
65
|
+
- app/models/pay/fake_processor/merchant.rb
|
66
|
+
- app/models/pay/fake_processor/payment_method.rb
|
67
|
+
- app/models/pay/fake_processor/subscription.rb
|
68
|
+
- app/models/pay/lemon_squeezy/charge.rb
|
69
|
+
- app/models/pay/lemon_squeezy/customer.rb
|
70
|
+
- app/models/pay/lemon_squeezy/payment_method.rb
|
71
|
+
- app/models/pay/lemon_squeezy/subscription.rb
|
58
72
|
- app/models/pay/merchant.rb
|
73
|
+
- app/models/pay/paddle_billing/charge.rb
|
74
|
+
- app/models/pay/paddle_billing/customer.rb
|
75
|
+
- app/models/pay/paddle_billing/payment_method.rb
|
76
|
+
- app/models/pay/paddle_billing/subscription.rb
|
77
|
+
- app/models/pay/paddle_classic/charge.rb
|
78
|
+
- app/models/pay/paddle_classic/customer.rb
|
79
|
+
- app/models/pay/paddle_classic/payment_method.rb
|
80
|
+
- app/models/pay/paddle_classic/subscription.rb
|
59
81
|
- app/models/pay/payment_method.rb
|
82
|
+
- app/models/pay/stripe/charge.rb
|
83
|
+
- app/models/pay/stripe/customer.rb
|
84
|
+
- app/models/pay/stripe/merchant.rb
|
85
|
+
- app/models/pay/stripe/payment_method.rb
|
86
|
+
- app/models/pay/stripe/subscription.rb
|
60
87
|
- app/models/pay/subscription.rb
|
61
88
|
- app/models/pay/webhook.rb
|
62
89
|
- app/views/layouts/pay/application.html.erb
|
63
90
|
- app/views/pay/payments/show.html.erb
|
64
|
-
- app/views/pay/stripe/_checkout_button.html.erb
|
65
91
|
- app/views/pay/user_mailer/payment_action_required.html.erb
|
92
|
+
- app/views/pay/user_mailer/payment_action_required.text.erb
|
66
93
|
- app/views/pay/user_mailer/payment_failed.html.erb
|
94
|
+
- app/views/pay/user_mailer/payment_failed.text.erb
|
67
95
|
- app/views/pay/user_mailer/receipt.html.erb
|
96
|
+
- app/views/pay/user_mailer/receipt.text.erb
|
68
97
|
- app/views/pay/user_mailer/refund.html.erb
|
98
|
+
- app/views/pay/user_mailer/refund.text.erb
|
69
99
|
- app/views/pay/user_mailer/subscription_renewing.html.erb
|
100
|
+
- app/views/pay/user_mailer/subscription_renewing.text.erb
|
70
101
|
- app/views/pay/user_mailer/subscription_trial_ended.html.erb
|
102
|
+
- app/views/pay/user_mailer/subscription_trial_ended.text.erb
|
71
103
|
- app/views/pay/user_mailer/subscription_trial_will_end.html.erb
|
104
|
+
- app/views/pay/user_mailer/subscription_trial_will_end.text.erb
|
72
105
|
- config/currencies/iso.json
|
73
106
|
- config/locales/en.yml
|
74
107
|
- config/routes.rb
|
75
108
|
- db/migrate/1_create_pay_tables.rb
|
109
|
+
- db/migrate/20250415151129_add_object_to_pay_models.rb
|
110
|
+
- db/migrate/2_add_pay_sti_columns.rb
|
76
111
|
- lib/generators/pay/email_views_generator.rb
|
77
112
|
- lib/generators/pay/views_generator.rb
|
78
113
|
- lib/pay.rb
|
@@ -80,12 +115,6 @@ files:
|
|
80
115
|
- lib/pay/attributes.rb
|
81
116
|
- lib/pay/billable/sync_customer.rb
|
82
117
|
- lib/pay/braintree.rb
|
83
|
-
- lib/pay/braintree/authorization_error.rb
|
84
|
-
- lib/pay/braintree/billable.rb
|
85
|
-
- lib/pay/braintree/charge.rb
|
86
|
-
- lib/pay/braintree/error.rb
|
87
|
-
- lib/pay/braintree/payment_method.rb
|
88
|
-
- lib/pay/braintree/subscription.rb
|
89
118
|
- lib/pay/braintree/webhooks/subscription_canceled.rb
|
90
119
|
- lib/pay/braintree/webhooks/subscription_charged_successfully.rb
|
91
120
|
- lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb
|
@@ -98,35 +127,15 @@ files:
|
|
98
127
|
- lib/pay/env.rb
|
99
128
|
- lib/pay/errors.rb
|
100
129
|
- lib/pay/fake_processor.rb
|
101
|
-
- lib/pay/fake_processor/billable.rb
|
102
|
-
- lib/pay/fake_processor/charge.rb
|
103
|
-
- lib/pay/fake_processor/error.rb
|
104
|
-
- lib/pay/fake_processor/merchant.rb
|
105
|
-
- lib/pay/fake_processor/payment_method.rb
|
106
|
-
- lib/pay/fake_processor/subscription.rb
|
107
130
|
- lib/pay/lemon_squeezy.rb
|
108
|
-
- lib/pay/lemon_squeezy/
|
109
|
-
- lib/pay/lemon_squeezy/charge.rb
|
110
|
-
- lib/pay/lemon_squeezy/error.rb
|
111
|
-
- lib/pay/lemon_squeezy/payment_method.rb
|
112
|
-
- lib/pay/lemon_squeezy/subscription.rb
|
131
|
+
- lib/pay/lemon_squeezy/webhooks/order.rb
|
113
132
|
- lib/pay/lemon_squeezy/webhooks/subscription.rb
|
114
|
-
- lib/pay/lemon_squeezy/webhooks/
|
133
|
+
- lib/pay/lemon_squeezy/webhooks/subscription_payment.rb
|
115
134
|
- lib/pay/nano_id.rb
|
116
135
|
- lib/pay/paddle_billing.rb
|
117
|
-
- lib/pay/paddle_billing/billable.rb
|
118
|
-
- lib/pay/paddle_billing/charge.rb
|
119
|
-
- lib/pay/paddle_billing/error.rb
|
120
|
-
- lib/pay/paddle_billing/payment_method.rb
|
121
|
-
- lib/pay/paddle_billing/subscription.rb
|
122
136
|
- lib/pay/paddle_billing/webhooks/subscription.rb
|
123
137
|
- lib/pay/paddle_billing/webhooks/transaction_completed.rb
|
124
138
|
- lib/pay/paddle_classic.rb
|
125
|
-
- lib/pay/paddle_classic/billable.rb
|
126
|
-
- lib/pay/paddle_classic/charge.rb
|
127
|
-
- lib/pay/paddle_classic/error.rb
|
128
|
-
- lib/pay/paddle_classic/payment_method.rb
|
129
|
-
- lib/pay/paddle_classic/subscription.rb
|
130
139
|
- lib/pay/paddle_classic/webhooks/signature_verifier.rb
|
131
140
|
- lib/pay/paddle_classic/webhooks/subscription_cancelled.rb
|
132
141
|
- lib/pay/paddle_classic/webhooks/subscription_created.rb
|
@@ -136,15 +145,10 @@ files:
|
|
136
145
|
- lib/pay/payment.rb
|
137
146
|
- lib/pay/receipts.rb
|
138
147
|
- lib/pay/stripe.rb
|
139
|
-
- lib/pay/stripe/billable.rb
|
140
|
-
- lib/pay/stripe/charge.rb
|
141
|
-
- lib/pay/stripe/error.rb
|
142
|
-
- lib/pay/stripe/merchant.rb
|
143
|
-
- lib/pay/stripe/payment_method.rb
|
144
|
-
- lib/pay/stripe/subscription.rb
|
145
148
|
- lib/pay/stripe/webhooks/account_updated.rb
|
146
149
|
- lib/pay/stripe/webhooks/charge_refunded.rb
|
147
150
|
- lib/pay/stripe/webhooks/charge_succeeded.rb
|
151
|
+
- lib/pay/stripe/webhooks/charge_updated.rb
|
148
152
|
- lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb
|
149
153
|
- lib/pay/stripe/webhooks/checkout_session_completed.rb
|
150
154
|
- lib/pay/stripe/webhooks/customer_deleted.rb
|
@@ -169,7 +173,6 @@ homepage: https://github.com/pay-rails/pay
|
|
169
173
|
licenses:
|
170
174
|
- MIT
|
171
175
|
metadata: {}
|
172
|
-
post_install_message:
|
173
176
|
rdoc_options: []
|
174
177
|
require_paths:
|
175
178
|
- lib
|
@@ -184,8 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
187
|
- !ruby/object:Gem::Version
|
185
188
|
version: '0'
|
186
189
|
requirements: []
|
187
|
-
rubygems_version: 3.
|
188
|
-
signing_key:
|
190
|
+
rubygems_version: 3.7.1
|
189
191
|
specification_version: 4
|
190
192
|
summary: Payments engine for Ruby on Rails
|
191
193
|
test_files: []
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<%= button_tag title,
|
2
|
-
id: "checkout-#{session.id}",
|
3
|
-
class: local_assigns[:class],
|
4
|
-
style: (local_assigns[:class] || local_assigns[:style]) ? local_assigns[:style] : 'background-color:#6772E5;color:#FFF;padding:8px 12px;border:0;border-radius:4px;font-size:1em'
|
5
|
-
%>
|
6
|
-
<%= tag.div id: "error-for-#{session.id}" %>
|
7
|
-
|
8
|
-
<script>
|
9
|
-
(() => {
|
10
|
-
const checkoutButton = document.getElementById("checkout-<%= session.id %>");
|
11
|
-
checkoutButton.addEventListener('click', function () {
|
12
|
-
Stripe("<%= Pay::Stripe.public_key %>").redirectToCheckout({
|
13
|
-
sessionId: '<%= session.id %>'
|
14
|
-
}).then(function (result) {
|
15
|
-
if (result.error) {
|
16
|
-
document.getElementById("error-for-#{session.id}").innerText = result.error.message;
|
17
|
-
}
|
18
|
-
});
|
19
|
-
});
|
20
|
-
})()
|
21
|
-
</script>
|
data/lib/pay/braintree/error.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module Pay
|
2
|
-
module Braintree
|
3
|
-
class Error < Pay::Error
|
4
|
-
# For any manually raised Braintree error results (for failure responses)
|
5
|
-
# we can raise this exception manually but treat it as if we wrapped an exception
|
6
|
-
|
7
|
-
attr_reader :result
|
8
|
-
|
9
|
-
def initialize(result)
|
10
|
-
if result.is_a?(::Braintree::ErrorResult)
|
11
|
-
super(result.message)
|
12
|
-
@result = result
|
13
|
-
else
|
14
|
-
super
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def cause
|
19
|
-
super || result
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|