pay 11.8.0 → 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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/pay/payments_controller.rb +3 -11
  3. data/app/controllers/pay/webhooks/base_controller.rb +50 -0
  4. data/app/controllers/pay/webhooks/braintree_controller.rb +11 -18
  5. data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +8 -26
  6. data/app/controllers/pay/webhooks/paddle_billing_controller.rb +13 -35
  7. data/app/controllers/pay/webhooks/paddle_classic_controller.rb +5 -20
  8. data/app/controllers/pay/webhooks/stripe_controller.rb +13 -29
  9. data/app/models/pay/braintree/charge.rb +6 -12
  10. data/app/models/pay/braintree/payment_method.rb +7 -5
  11. data/app/models/pay/braintree/subscription.rb +39 -44
  12. data/app/models/pay/charge.rb +1 -1
  13. data/app/models/pay/customer.rb +1 -9
  14. data/app/models/pay/lemon_squeezy/charge.rb +63 -61
  15. data/app/models/pay/lemon_squeezy/customer.rb +1 -1
  16. data/app/models/pay/lemon_squeezy/subscription.rb +43 -46
  17. data/app/models/pay/merchant.rb +1 -1
  18. data/app/models/pay/paddle_billing/charge.rb +43 -53
  19. data/app/models/pay/paddle_billing/customer.rb +1 -1
  20. data/app/models/pay/paddle_billing/payment_method.rb +19 -23
  21. data/app/models/pay/paddle_billing/subscription.rb +56 -67
  22. data/app/models/pay/paddle_classic/customer.rb +1 -1
  23. data/app/models/pay/paddle_classic/subscription.rb +42 -44
  24. data/app/models/pay/payment_method.rb +1 -5
  25. data/app/models/pay/stripe/charge.rb +49 -61
  26. data/app/models/pay/stripe/customer.rb +41 -19
  27. data/app/models/pay/stripe/payment_method.rb +25 -30
  28. data/app/models/pay/stripe/subscription.rb +103 -107
  29. data/app/models/pay/subscription.rb +2 -12
  30. data/app/views/layouts/pay/application.html.erb +0 -2
  31. data/app/views/pay/payments/show.html.erb +19 -8
  32. data/lib/pay/braintree/webhooks/subscription.rb +15 -0
  33. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -7
  34. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -7
  35. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +1 -7
  36. data/lib/pay/braintree/webhooks/subscription_went_active.rb +2 -8
  37. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +2 -8
  38. data/lib/pay/braintree.rb +1 -2
  39. data/lib/pay/currency.rb +0 -4
  40. data/lib/pay/engine.rb +4 -0
  41. data/lib/pay/errors.rb +4 -0
  42. data/lib/pay/lemon_squeezy.rb +4 -7
  43. data/lib/pay/paddle_billing.rb +4 -1
  44. data/lib/pay/paddle_classic.rb +4 -1
  45. data/lib/pay/payment.rb +2 -12
  46. data/lib/pay/receipts.rb +0 -1
  47. data/lib/pay/stripe/webhooks/subscription_created.rb +2 -4
  48. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -5
  49. data/lib/pay/stripe.rb +2 -2
  50. data/lib/pay/sync.rb +29 -0
  51. data/lib/pay/version.rb +1 -1
  52. data/lib/pay.rb +7 -5
  53. metadata +5 -3
  54. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +0 -24
@@ -1,52 +1,54 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
3
  class Subscription < Pay::Subscription
4
+ extend Pay::Sync
5
+
4
6
  store_accessor :data, :paddle_update_url
5
7
  store_accessor :data, :paddle_cancel_url
6
8
 
7
9
  def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
8
- # Passthrough is not return from this API, so we can't use that
9
- object ||= PaddleClassic.client.users.list(subscription_id: subscription_id).data.try(:first)
10
-
11
- pay_customer = Pay::Customer.find_by(processor: :paddle_classic, processor_id: object.user_id)
12
-
13
- # If passthrough exists (only on webhooks) we can use it to create the Pay::Customer
14
- if pay_customer.nil? && object.passthrough
15
- owner = Pay::PaddleClassic.owner_from_passthrough(object.passthrough)
16
- pay_customer = owner&.set_payment_processor(:paddle_classic, processor_id: object.user_id)
17
- end
10
+ sync_with_retries do
11
+ subscription = object || PaddleClassic.client.users.list(subscription_id: subscription_id).data.first
12
+ pay_customer = find_pay_customer(subscription.user_id)
13
+
14
+ # If passthrough exists (only on webhooks) we can use it to create the Pay::Customer
15
+ if pay_customer.nil? && subscription.passthrough
16
+ owner = Pay::PaddleClassic.owner_from_passthrough(subscription.passthrough)
17
+ pay_customer = owner&.set_payment_processor(:paddle_classic, processor_id: subscription.user_id)
18
+ end
18
19
 
19
- return unless pay_customer
20
-
21
- attributes = {
22
- paddle_cancel_url: object.cancel_url,
23
- paddle_update_url: object.update_url,
24
- processor_plan: object.plan_id || object.subscription_plan_id,
25
- quantity: object.quantity || 1,
26
- status: object.state || object.status
27
- }
28
-
29
- case attributes[:status]
30
- when "trialing"
31
- attributes[:trial_ends_at] = Time.zone.parse(object.next_bill_date)
32
- attributes[:ends_at] = nil
33
- when "active", "past_due"
34
- attributes[:trial_ends_at] = nil
35
- attributes[:ends_at] = nil
36
- when "paused", "deleted"
37
- # If paused or delete while on trial, set ends_at to match
38
- attributes[:trial_ends_at] = nil
39
- attributes[:ends_at] = Time.zone.parse(object.next_bill_date)
40
- end
20
+ return unless pay_customer
21
+
22
+ attributes = {
23
+ paddle_cancel_url: subscription.cancel_url,
24
+ paddle_update_url: subscription.update_url,
25
+ processor_plan: subscription.plan_id || subscription.subscription_plan_id,
26
+ quantity: subscription.quantity || 1,
27
+ status: subscription.state || subscription.status
28
+ }
29
+
30
+ case attributes[:status]
31
+ when "trialing"
32
+ attributes[:trial_ends_at] = Time.zone.parse(subscription.next_bill_date)
33
+ attributes[:ends_at] = nil
34
+ when "active", "past_due"
35
+ attributes[:trial_ends_at] = nil
36
+ attributes[:ends_at] = nil
37
+ when "paused", "deleted"
38
+ # If paused or delete while on trial, set ends_at to match
39
+ attributes[:trial_ends_at] = nil
40
+ attributes[:ends_at] = Time.zone.parse(subscription.next_bill_date)
41
+ end
41
42
 
42
- # Update or create the subscription
43
- if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: object.subscription_id))
44
- pay_subscription.with_lock do
45
- pay_subscription.update!(attributes)
43
+ # Update or create the subscription
44
+ if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: subscription.subscription_id))
45
+ pay_subscription.with_lock do
46
+ pay_subscription.update!(attributes)
47
+ end
48
+ pay_subscription
49
+ else
50
+ pay_customer.subscriptions.create!(attributes.merge(name: name, processor_id: subscription.subscription_id))
46
51
  end
47
- pay_subscription
48
- else
49
- pay_customer.subscriptions.create!(attributes.merge(name: name, processor_id: object.subscription_id))
50
52
  end
51
53
  end
52
54
 
@@ -93,7 +95,7 @@ module Pay
93
95
  end
94
96
 
95
97
  def change_quantity(quantity, **options)
96
- raise NotImplementedError, "Paddle does not support setting quantity on subscriptions"
98
+ raise Pay::NotSupportedError, "Paddle does not support setting quantity on subscriptions"
97
99
  end
98
100
 
99
101
  # A subscription could be set to cancel or pause in the future
@@ -139,10 +141,6 @@ module Pay
139
141
  rescue ::Paddle::Error => e
140
142
  raise Pay::PaddleClassic::Error, e
141
143
  end
142
-
143
- # Retries the latest invoice for a Past Due subscription
144
- def retry_failed_payment
145
- end
146
144
  end
147
145
  end
148
146
  end
@@ -12,15 +12,11 @@ module Pay
12
12
  store_accessor :data, :username
13
13
  store_accessor :data, :bank
14
14
 
15
- validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
15
+ validates :processor_id, presence: true
16
16
 
17
17
  def self.find_by_processor_and_id(processor, processor_id)
18
18
  joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
19
19
  end
20
-
21
- def self.pay_processor_for(name)
22
- "Pay::#{name.to_s.classify}::PaymentMethod".constantize
23
- end
24
20
  end
25
21
  end
26
22
 
@@ -1,6 +1,8 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  class Charge < Pay::Charge
4
+ extend Pay::Sync
5
+
4
6
  EXPAND = ["balance_transaction", "payment_intent", "refunds.data.balance_transaction"]
5
7
 
6
8
  delegate :amount_captured, :payment_intent, to: :stripe_object, allow_nil: true
@@ -11,73 +13,59 @@ module Pay
11
13
  def self.sync_payment_intent(id, stripe_account: nil)
12
14
  payment_intent = ::Stripe::PaymentIntent.retrieve({id: id}, {stripe_account: stripe_account}.compact)
13
15
  sync(payment_intent.latest_charge, stripe_account: stripe_account)
16
+ rescue ::Stripe::StripeError => e
17
+ raise Pay::Stripe::Error, e
14
18
  end
15
19
 
16
- def self.sync(charge_id, object: nil, stripe_account: nil, try: 0, retries: 1)
17
- # Skip loading the latest charge details from the API if we already have it
18
- object ||= ::Stripe::Charge.retrieve({id: charge_id, expand: EXPAND}, {stripe_account: stripe_account}.compact)
19
- if object.customer.blank?
20
- Rails.logger.debug "Stripe Charge #{object.id} does not have a customer"
21
- return
22
- end
23
-
24
- pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
25
- if pay_customer.blank?
26
- Rails.logger.debug "Pay::Customer #{object.customer} is not in the database while syncing Stripe Charge #{object.id}"
27
- return
28
- end
29
-
30
- # Requests for the rest of the sync should go to the same Stripe Connect account as the customer
31
- stripe_account ||= pay_customer.stripe_account
32
-
33
- payment_method = object.payment_method_details.try(object.payment_method_details.type)
34
- attrs = {
35
- object: object.to_hash,
36
- amount: object.amount,
37
- amount_refunded: object.amount_refunded,
38
- application_fee_amount: object.application_fee_amount,
39
- bank: payment_method.try(:bank_name) || payment_method.try(:bank), # eps, fpx, ideal, p24, acss_debit, etc
40
- brand: payment_method.try(:brand)&.capitalize,
41
- created_at: Time.at(object.created),
42
- currency: object.currency,
43
- exp_month: payment_method.try(:exp_month).to_s,
44
- exp_year: payment_method.try(:exp_year).to_s,
45
- last4: payment_method.try(:last4).to_s,
46
- metadata: object.metadata,
47
- payment_method_type: object.payment_method_details.type,
48
- stripe_account: stripe_account,
49
- stripe_receipt_url: object.receipt_url
50
- }
51
-
52
- # Associate charge with subscription if we can
53
- if object.payment_intent.present?
54
- invoice_payments = ::Stripe::InvoicePayment.list({payment: {type: :payment_intent, payment_intent: object.payment_intent}, status: :paid}, {stripe_account: stripe_account}.compact)
55
- if invoice_payments.any?
56
- invoice = ::Stripe::Invoice.retrieve({id: invoice_payments.first.invoice, expand: ["total_discount_amounts.discount.source.coupon"]}, {stripe_account: stripe_account}.compact)
57
- attrs[:stripe_invoice] = invoice.to_hash
58
- attrs[:subtotal] = invoice.subtotal
59
- attrs[:tax] = invoice.total - invoice.total_excluding_tax.to_i
60
- if (subscription = invoice.parent.try(:subscription_details).try(:subscription))
61
- attrs[:subscription] = pay_customer.subscriptions.find_by(processor_id: subscription)
20
+ def self.sync(charge_id, object: nil, stripe_account: nil, retries: 1)
21
+ sync_with_retries(retries: retries) do
22
+ charge = object || ::Stripe::Charge.retrieve({id: charge_id, expand: EXPAND}, {stripe_account: stripe_account}.compact)
23
+ return unless (pay_customer = find_pay_customer(charge.customer))
24
+ stripe_account ||= pay_customer.stripe_account
25
+
26
+ payment_method = charge.payment_method_details.try(charge.payment_method_details.type)
27
+ attrs = {
28
+ object: charge.to_hash,
29
+ amount: charge.amount,
30
+ amount_refunded: charge.amount_refunded,
31
+ application_fee_amount: charge.application_fee_amount,
32
+ bank: payment_method.try(:bank_name) || payment_method.try(:bank), # eps, fpx, ideal, p24, acss_debit, etc
33
+ brand: payment_method.try(:brand)&.capitalize,
34
+ created_at: Time.at(charge.created),
35
+ currency: charge.currency,
36
+ exp_month: payment_method.try(:exp_month).to_s,
37
+ exp_year: payment_method.try(:exp_year).to_s,
38
+ last4: payment_method.try(:last4).to_s,
39
+ metadata: charge.metadata,
40
+ payment_method_type: charge.payment_method_details.type,
41
+ stripe_account: stripe_account,
42
+ stripe_receipt_url: charge.receipt_url
43
+ }
44
+
45
+ # Associate charge with subscription if we can
46
+ if charge.payment_intent.present?
47
+ invoice_payments = ::Stripe::InvoicePayment.list({payment: {type: :payment_intent, payment_intent: charge.payment_intent}, status: :paid}, {stripe_account: stripe_account}.compact)
48
+ if invoice_payments.any?
49
+ invoice = ::Stripe::Invoice.retrieve({id: invoice_payments.first.invoice, expand: ["total_discount_amounts.discount.source.coupon"]}, {stripe_account: stripe_account}.compact)
50
+ attrs[:stripe_invoice] = invoice.to_hash
51
+ attrs[:subtotal] = invoice.subtotal
52
+ attrs[:tax] = invoice.total - invoice.total_excluding_tax.to_i
53
+ if (subscription = invoice.parent.try(:subscription_details).try(:subscription))
54
+ attrs[:subscription] = pay_customer.subscriptions.find_by(processor_id: subscription)
55
+ end
62
56
  end
63
57
  end
64
- end
65
58
 
66
- # Update or create the charge
67
- if (pay_charge = find_by(customer: pay_customer, processor_id: object.id))
68
- pay_charge.with_lock { pay_charge.update!(attrs) }
69
- pay_charge
70
- else
71
- create!(attrs.merge(customer: pay_customer, processor_id: object.id))
72
- end
73
- rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
74
- if try > retries
75
- raise
76
- else
77
- try += 1
78
- sleep 0.15 * try
79
- retry
59
+ # Update or create the charge
60
+ if (pay_charge = find_by(customer: pay_customer, processor_id: charge.id))
61
+ pay_charge.with_lock { pay_charge.update!(attrs) }
62
+ pay_charge
63
+ else
64
+ create!(attrs.merge(customer: pay_customer, processor_id: charge.id))
65
+ end
80
66
  end
67
+ rescue ::Stripe::StripeError => e
68
+ raise Pay::Stripe::Error, e
81
69
  end
82
70
 
83
71
  def api_record
@@ -38,8 +38,9 @@ module Pay
38
38
  end
39
39
 
40
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)
41
+ ::Stripe::Customer.update(stripe_customer_id, api_record_attributes.merge(attributes), stripe_options)
42
+ rescue ::Stripe::StripeError => e
43
+ raise Pay::Stripe::Error, e
43
44
  end
44
45
 
45
46
  # Charges an amount to the customer's default payment method
@@ -62,7 +63,7 @@ module Pay
62
63
  }.merge(options)
63
64
 
64
65
  # Load the Stripe customer to verify it exists and update payment method if needed
65
- opts[:customer] = processor_id || api_record.id
66
+ opts[:customer] = stripe_customer_id
66
67
 
67
68
  # Create subscription on Stripe
68
69
  stripe_sub = ::Stripe::Subscription.create(opts.merge(Pay::Stripe::Subscription.expand_options), stripe_options)
@@ -82,11 +83,10 @@ module Pay
82
83
  end
83
84
 
84
85
  def add_payment_method(payment_method_id, default: false)
85
- api_record unless processor_id?
86
- payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: processor_id}, stripe_options)
86
+ payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: stripe_customer_id}, stripe_options)
87
87
 
88
88
  if default
89
- ::Stripe::Customer.update(processor_id, {
89
+ ::Stripe::Customer.update(stripe_customer_id, {
90
90
  invoice_settings: {
91
91
  default_payment_method: payment_method.id
92
92
  }
@@ -121,12 +121,14 @@ module Pay
121
121
  args = {
122
122
  amount: amount,
123
123
  currency: "usd",
124
- customer: processor_id || api_record.id,
124
+ customer: stripe_customer_id,
125
125
  expand: Pay::Stripe::Charge::EXPAND.map { |option| "latest_charge.#{option}" },
126
126
  return_url: root_url
127
127
  }.merge(options)
128
128
 
129
129
  ::Stripe::PaymentIntent.create(args, stripe_options)
130
+ rescue ::Stripe::StripeError => e
131
+ raise Pay::Stripe::Error, e
130
132
  end
131
133
 
132
134
  # Used for creating Stripe Terminal charges
@@ -135,15 +137,21 @@ module Pay
135
137
  end
136
138
 
137
139
  def create_setup_intent(options = {})
138
- ::Stripe::SetupIntent.create({customer: processor_id || api_record.id, usage: :off_session}.merge(options), stripe_options)
140
+ ::Stripe::SetupIntent.create({customer: stripe_customer_id, usage: :off_session}.merge(options), stripe_options)
141
+ rescue ::Stripe::StripeError => e
142
+ raise Pay::Stripe::Error, e
139
143
  end
140
144
 
141
145
  def invoice!(options = {})
142
- ::Stripe::Invoice.create(options.merge(customer: processor_id || api_record.id), stripe_options).pay
146
+ ::Stripe::Invoice.create(options.merge(customer: stripe_customer_id), stripe_options).pay
147
+ rescue ::Stripe::StripeError => e
148
+ raise Pay::Stripe::Error, e
143
149
  end
144
150
 
145
151
  def preview_invoice(**options)
146
- ::Stripe::Invoice.create_preview(options.merge(customer: processor_id || api_record.id), stripe_options)
152
+ ::Stripe::Invoice.create_preview(options.merge(customer: stripe_customer_id), stripe_options)
153
+ rescue ::Stripe::StripeError => e
154
+ raise Pay::Stripe::Error, e
147
155
  end
148
156
 
149
157
  # Syncs a customer's subscriptions from Stripe to the database.
@@ -168,9 +176,8 @@ module Pay
168
176
  # checkout(line_items: "price_12345", allow_promotion_codes: true)
169
177
  #
170
178
  def checkout(**options)
171
- api_record unless processor_id?
172
179
  args = {
173
- customer: processor_id,
180
+ customer: stripe_customer_id,
174
181
  mode: "payment"
175
182
  }
176
183
 
@@ -201,6 +208,8 @@ module Pay
201
208
  end
202
209
 
203
210
  ::Stripe::Checkout::Session.create(args.merge(options), stripe_options)
211
+ rescue ::Stripe::StripeError => e
212
+ raise Pay::Stripe::Error, e
204
213
  end
205
214
 
206
215
  # https://stripe.com/docs/api/checkout/sessions/create
@@ -208,7 +217,6 @@ module Pay
208
217
  # checkout_charge(amount: 15_00, name: "T-shirt", quantity: 2)
209
218
  #
210
219
  def checkout_charge(amount:, name:, quantity: 1, **options)
211
- api_record unless processor_id?
212
220
  currency = options.delete(:currency) || "usd"
213
221
  checkout(
214
222
  line_items: {
@@ -224,38 +232,52 @@ module Pay
224
232
  end
225
233
 
226
234
  def billing_portal(**options)
227
- api_record unless processor_id?
228
235
  args = {
229
- customer: processor_id,
236
+ customer: stripe_customer_id,
230
237
  return_url: options.delete(:return_url) || root_url
231
238
  }
232
239
  ::Stripe::BillingPortal::Session.create(args.merge(options), stripe_options)
240
+ rescue ::Stripe::StripeError => e
241
+ raise Pay::Stripe::Error, e
233
242
  end
234
243
 
235
244
  def customer_session(**options)
236
- api_record unless processor_id?
237
- args = {customer: processor_id}
245
+ args = {customer: stripe_customer_id}
238
246
  ::Stripe::CustomerSession.create(args.merge(options), stripe_options)
247
+ rescue ::Stripe::StripeError => e
248
+ raise Pay::Stripe::Error, e
239
249
  end
240
250
 
241
251
  def authorize(amount, options = {})
242
252
  charge(amount, options.merge(capture_method: :manual))
243
253
  end
244
254
 
255
+ # Attempts to pay all past_due subscription invoices to bring them back to active state
256
+ # Pass in `status: []` to include other subscription statuses
257
+ def retry_past_due_subscriptions!(status: [:past_due])
258
+ subscriptions.where(status: Array.wrap(status)).each(&:pay_open_invoices)
259
+ end
260
+
245
261
  # Creates a meter event to bill for usage
246
262
  #
247
263
  # create_meter_event(:api_request, value: 1)
248
264
  # create_meter_event(:api_request, token: 7)
249
265
  def create_meter_event(event_name, payload: {}, **options)
250
- api_record unless processor_id?
251
266
  ::Stripe::Billing::MeterEvent.create({
252
267
  event_name: event_name,
253
- payload: {stripe_customer_id: processor_id}.merge(payload)
268
+ payload: {stripe_customer_id: stripe_customer_id}.merge(payload)
254
269
  }.merge(options), stripe_options)
270
+ rescue ::Stripe::StripeError => e
271
+ raise Pay::Stripe::Error, e
255
272
  end
256
273
 
257
274
  private
258
275
 
276
+ # The Stripe::Customer ID, creating the customer on Stripe first if this record doesn't have one yet
277
+ def stripe_customer_id
278
+ processor_id || api_record.id
279
+ end
280
+
259
281
  # Options for Stripe requests
260
282
  def stripe_options
261
283
  {stripe_account: stripe_account}.compact
@@ -1,12 +1,16 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  class PaymentMethod < Pay::PaymentMethod
4
+ extend Pay::Sync
5
+
4
6
  # Syncs a PaymentIntent's payment method to the database
5
7
  def self.sync_payment_intent(id, stripe_account: nil)
6
8
  payment_intent = ::Stripe::PaymentIntent.retrieve({id: id, expand: ["payment_method"]}, {stripe_account: stripe_account}.compact)
7
9
  payment_method = payment_intent.payment_method
8
10
  return unless payment_method
9
11
  Pay::Stripe::PaymentMethod.sync(payment_method.id, object: payment_method, stripe_account: stripe_account)
12
+ rescue ::Stripe::StripeError => e
13
+ raise Pay::Stripe::Error, e
10
14
  end
11
15
 
12
16
  # Syncs a SetupIntent's payment method to the database
@@ -15,42 +19,29 @@ module Pay
15
19
  payment_method = setup_intent.payment_method
16
20
  return unless payment_method
17
21
  Pay::Stripe::PaymentMethod.sync(payment_method.id, object: payment_method, stripe_account: stripe_account)
22
+ rescue ::Stripe::StripeError => e
23
+ raise Pay::Stripe::Error, e
18
24
  end
19
25
 
20
26
  # Syncs PaymentMethod objects from Stripe
21
- def self.sync(id, object: nil, stripe_account: nil, try: 0, retries: 1)
22
- object ||= ::Stripe::PaymentMethod.retrieve(id, {stripe_account: stripe_account}.compact)
23
- if object.customer.blank?
24
- Rails.logger.debug "Stripe PaymentMethod #{object.id} does not have a customer"
25
- return
26
- end
27
-
28
- pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
29
- if pay_customer.blank?
30
- Rails.logger.debug "Pay::Customer #{object.customer} is not in the database while syncing Stripe PaymentMethod #{object.id}"
31
- return
32
- end
33
-
34
- # Record the same Stripe Connect account as the customer when one wasn't given
35
- stripe_account ||= pay_customer.stripe_account
27
+ def self.sync(id, object: nil, stripe_account: nil, retries: 1)
28
+ sync_with_retries(retries: retries) do
29
+ payment_method = object || ::Stripe::PaymentMethod.retrieve(id, {stripe_account: stripe_account}.compact)
30
+ return unless (pay_customer = find_pay_customer(payment_method.customer))
31
+ stripe_account ||= pay_customer.stripe_account
36
32
 
37
- default_payment_method_id = pay_customer.api_record.invoice_settings&.default_payment_method
38
- default = (id == default_payment_method_id)
33
+ default_payment_method_id = pay_customer.api_record.invoice_settings&.default_payment_method
34
+ default = (id == default_payment_method_id)
39
35
 
40
- attributes = extract_attributes(object).merge(default: default, stripe_account: stripe_account)
36
+ attributes = extract_attributes(payment_method).merge(default: default, stripe_account: stripe_account)
41
37
 
42
- where(customer: pay_customer).update_all(default: false) if default
43
- pay_payment_method = where(customer: pay_customer, processor_id: object.id).first_or_initialize
44
- pay_payment_method.update!(attributes)
45
- pay_payment_method
46
- rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
47
- if try > retries
48
- raise
49
- else
50
- try += 1
51
- sleep 0.15 * try
52
- retry
38
+ where(customer: pay_customer).update_all(default: false) if default
39
+ pay_payment_method = where(customer: pay_customer, processor_id: payment_method.id).first_or_initialize
40
+ pay_payment_method.update!(attributes)
41
+ pay_payment_method
53
42
  end
43
+ rescue ::Stripe::StripeError => e
44
+ raise Pay::Stripe::Error, e
54
45
  end
55
46
 
56
47
  # Extracts payment method details from a Stripe::PaymentMethod object
@@ -76,11 +67,15 @@ module Pay
76
67
 
77
68
  customer.payment_methods.update_all(default: false)
78
69
  update!(default: true)
70
+ rescue ::Stripe::StripeError => e
71
+ raise Pay::Stripe::Error, e
79
72
  end
80
73
 
81
74
  # Remove payment method
82
75
  def detach
83
- ::Stripe::PaymentMethod.detach(processor_id, stripe_options)
76
+ ::Stripe::PaymentMethod.detach(processor_id, {}, stripe_options)
77
+ rescue ::Stripe::StripeError => e
78
+ raise Pay::Stripe::Error, e
84
79
  end
85
80
 
86
81
  private