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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -4
  3. data/app/controllers/pay/payments_controller.rb +2 -0
  4. data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
  5. data/app/jobs/pay/customer_sync_job.rb +1 -1
  6. data/app/models/concerns/pay/routing.rb +13 -0
  7. data/{lib → app/models}/pay/braintree/charge.rb +7 -12
  8. data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +33 -71
  9. data/{lib → app/models}/pay/braintree/payment_method.rb +4 -10
  10. data/{lib → app/models}/pay/braintree/subscription.rb +23 -61
  11. data/app/models/pay/charge.rb +16 -45
  12. data/app/models/pay/customer.rb +5 -16
  13. data/app/models/pay/fake_processor/charge.rb +19 -0
  14. data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +28 -38
  15. data/{lib → app/models}/pay/fake_processor/merchant.rb +4 -9
  16. data/app/models/pay/fake_processor/payment_method.rb +13 -0
  17. data/app/models/pay/fake_processor/subscription.rb +70 -0
  18. data/app/models/pay/lemon_squeezy/charge.rb +96 -0
  19. data/app/models/pay/lemon_squeezy/customer.rb +80 -0
  20. data/app/models/pay/lemon_squeezy/payment_method.rb +29 -0
  21. data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
  22. data/app/models/pay/merchant.rb +2 -11
  23. data/{lib → app/models}/pay/paddle_billing/charge.rb +15 -13
  24. data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +20 -35
  25. data/{lib → app/models}/pay/paddle_billing/payment_method.rb +13 -13
  26. data/{lib → app/models}/pay/paddle_billing/subscription.rb +40 -43
  27. data/{lib → app/models}/pay/paddle_classic/charge.rb +15 -18
  28. data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +11 -31
  29. data/{lib → app/models}/pay/paddle_classic/payment_method.rb +3 -11
  30. data/{lib → app/models}/pay/paddle_classic/subscription.rb +17 -37
  31. data/app/models/pay/payment_method.rb +4 -5
  32. data/app/models/pay/stripe/charge.rb +155 -0
  33. data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +78 -111
  34. data/{lib → app/models}/pay/stripe/merchant.rb +5 -20
  35. data/{lib → app/models}/pay/stripe/payment_method.rb +11 -17
  36. data/{lib → app/models}/pay/stripe/subscription.rb +83 -112
  37. data/app/models/pay/subscription.rb +13 -47
  38. data/app/models/pay/webhook.rb +5 -1
  39. data/app/views/pay/user_mailer/payment_action_required.text.erb +9 -0
  40. data/app/views/pay/user_mailer/payment_failed.text.erb +9 -0
  41. data/app/views/pay/user_mailer/receipt.text.erb +20 -0
  42. data/app/views/pay/user_mailer/refund.text.erb +21 -0
  43. data/app/views/pay/user_mailer/subscription_renewing.text.erb +8 -0
  44. data/app/views/pay/user_mailer/subscription_trial_ended.text.erb +8 -0
  45. data/app/views/pay/user_mailer/subscription_trial_will_end.text.erb +8 -0
  46. data/config/locales/en.yml +1 -0
  47. data/config/routes.rb +1 -0
  48. data/db/migrate/20250415151129_add_object_to_pay_models.rb +7 -0
  49. data/db/migrate/2_add_pay_sti_columns.rb +24 -0
  50. data/lib/pay/attributes.rb +16 -8
  51. data/lib/pay/braintree.rb +25 -6
  52. data/lib/pay/engine.rb +2 -0
  53. data/lib/pay/fake_processor.rb +2 -6
  54. data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
  55. data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
  56. data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
  57. data/lib/pay/lemon_squeezy.rb +58 -104
  58. data/lib/pay/nano_id.rb +1 -1
  59. data/lib/pay/paddle_billing.rb +15 -6
  60. data/lib/pay/paddle_classic/webhooks/signature_verifier.rb +1 -1
  61. data/lib/pay/paddle_classic.rb +11 -9
  62. data/lib/pay/receipts.rb +45 -44
  63. data/lib/pay/stripe/webhooks/charge_updated.rb +11 -0
  64. data/lib/pay/stripe/webhooks/customer_updated.rb +13 -9
  65. data/lib/pay/stripe/webhooks/payment_action_required.rb +10 -6
  66. data/lib/pay/stripe/webhooks/payment_failed.rb +6 -4
  67. data/lib/pay/stripe/webhooks/subscription_renewing.rb +9 -4
  68. data/lib/pay/stripe.rb +28 -9
  69. data/lib/pay/version.rb +1 -1
  70. data/lib/pay.rb +19 -1
  71. data/lib/tasks/pay.rake +2 -2
  72. metadata +45 -43
  73. data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
  74. data/lib/pay/braintree/authorization_error.rb +0 -9
  75. data/lib/pay/braintree/error.rb +0 -23
  76. data/lib/pay/fake_processor/charge.rb +0 -21
  77. data/lib/pay/fake_processor/error.rb +0 -6
  78. data/lib/pay/fake_processor/payment_method.rb +0 -21
  79. data/lib/pay/fake_processor/subscription.rb +0 -90
  80. data/lib/pay/lemon_squeezy/billable.rb +0 -90
  81. data/lib/pay/lemon_squeezy/charge.rb +0 -68
  82. data/lib/pay/lemon_squeezy/error.rb +0 -7
  83. data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
  84. data/lib/pay/lemon_squeezy/subscription.rb +0 -185
  85. data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
  86. data/lib/pay/paddle_billing/error.rb +0 -7
  87. data/lib/pay/paddle_classic/error.rb +0 -7
  88. data/lib/pay/stripe/charge.rb +0 -176
  89. 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
- # Unit price is stored with the line item
35
- # Negative amounts shouldn't display quantity
36
- # Sort by line_items by period_end? oldest to newest
37
- if line_items.any?
38
- line_items.each do |li|
39
- items << [li["description"], li["quantity"], Pay::Currency.format(li["unit_amount"], currency: currency), Pay::Currency.format(li["amount"], currency: currency)]
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 << [product, 1, Pay::Currency.format(amount, currency: currency), Pay::Currency.format(amount, currency: currency)]
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
- Array.wrap(discounts).each do |discount_id|
56
- if (discount = total_discount_amounts.find { |d| d.dig("discount", "id") == discount_id })
57
- items << [nil, nil, discount_description(discount), Pay::Currency.format(-discount["amount"], currency: currency)]
58
- end
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
- Array.wrap(total_tax_amounts).each do |tax_amount|
63
- next if tax_amount["amount"].zero?
64
- items << [nil, nil, tax_description(tax_amount), Pay::Currency.format(tax_amount["amount"], currency: currency)]
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.dig("discount", "coupon")
73
- name = coupon.dig("name")
74
+ coupon = discount.discount.coupon
75
+ name = coupon.name
74
76
 
75
- if (percent = coupon["percent_off"])
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["amount_off"], currency: coupon["currency"]))
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(tax_amount)
83
- tax_rate = tax_amount["tax_rate"]
84
- percent = "#{ActiveSupport::NumberHelper.number_to_rounded(tax_rate["percentage"], strip_insignificant_zeros: true)}%"
85
- percent += " inclusive" if tax_rate["inclusive"]
86
- "#{tax_rate["display_name"]} - #{tax_rate["jurisdiction"]} (#{percent})"
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
- refunds.each do |refund|
99
- next unless refund["status"] == "succeeded"
100
- refunded_at = Time.at(refund["created"]).to_date
101
- 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"])]
102
- end
103
- else
104
- line_items << [nil, nil, I18n.t("pay.receipt.refunded"), Pay::Currency.format(amount_refunded, currency: currency)]
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.address,
159
+ email: Pay.support_email&.address,
159
160
  logo: Pay.business_logo
160
161
  },
161
162
  line_items: pdf_line_items
@@ -0,0 +1,11 @@
1
+ module Pay
2
+ module Stripe
3
+ module Webhooks
4
+ class ChargeUpdated
5
+ def call(event)
6
+ Pay::Stripe::Charge.sync(event.data.object.id, stripe_account: event.try(:account))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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.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
- object = event.data.object
9
+ invoice = event.data.object
10
+ subscription_id = invoice.parent.try(:subscription_details).try(:subscription)
10
11
 
11
- pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, object.subscription)
12
- return if pay_subscription.nil?
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
- if Pay.send_email?(:payment_action_required, pay_subscription)
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
- payment_intent_id: event.data.object.payment_intent,
18
- pay_subscription: pay_subscription
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
- object = event.data.object
9
+ invoice = event.data.object
10
+ subscription_id = invoice.parent.try(:subscription_details).try(:subscription)
10
11
 
11
- pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, object.subscription)
12
- return if pay_subscription.nil?
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: object
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, event.data.object.subscription)
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 = event.data.object.lines.data.first.price
18
+ price = ::Stripe::Price.retrieve({id: invoice.lines.first.pricing.price_details.price}, {stripe_account: event.try(:account)}.compact)
16
19
 
17
- if Pay.send_email?(:subscription_renewing, pay_subscription, price)
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(event.data.object.next_payment_attempt)
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
- autoload :Billable, "pay/stripe/billable"
4
- autoload :Charge, "pay/stripe/charge"
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 = "~> 12"
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? ? true : ActiveModel::Type::Boolean.new.cast(value)
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
@@ -1,3 +1,3 @@
1
1
  module Pay
2
- VERSION = "7.3.0"
2
+ VERSION = "11.2.2"
3
3
  end
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.customer.payment_methods.find(&:default?)
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.customer.invoice_settings.default_payment_method
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: 7.3.0
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: 2024-07-09 00:00:00.000000000 Z
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: 6.0.0
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: 6.0.0
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/billable.rb
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/transaction_completed.rb
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.5.13
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>
@@ -1,9 +0,0 @@
1
- module Pay
2
- module Braintree
3
- class AuthorizationError < Braintree::Error
4
- def message
5
- I18n.t("pay.errors.braintree.authorization")
6
- end
7
- end
8
- end
9
- end
@@ -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