pay 2.7.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pay might be problematic. Click here for more details.

Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -696
  3. data/app/controllers/pay/webhooks/braintree_controller.rb +10 -3
  4. data/app/controllers/pay/webhooks/paddle_controller.rb +7 -8
  5. data/app/controllers/pay/webhooks/stripe_controller.rb +6 -3
  6. data/app/jobs/pay/{email_sync_job.rb → customer_sync_job.rb} +3 -4
  7. data/app/models/pay/application_record.rb +1 -5
  8. data/app/models/pay/charge.rb +31 -18
  9. data/app/models/pay/customer.rb +87 -0
  10. data/app/models/pay/merchant.rb +19 -0
  11. data/app/models/pay/payment_method.rb +41 -0
  12. data/app/models/pay/subscription.rb +42 -30
  13. data/app/models/pay/webhook.rb +36 -0
  14. data/app/views/layouts/pay/application.html.erb +2 -3
  15. data/app/views/pay/payments/show.html.erb +109 -81
  16. data/app/views/pay/user_mailer/receipt.html.erb +2 -2
  17. data/app/views/pay/user_mailer/refund.html.erb +2 -2
  18. data/config/locales/en.yml +1 -1
  19. data/db/migrate/1_create_pay_tables.rb +72 -0
  20. data/lib/generators/active_record/billable_generator.rb +44 -0
  21. data/lib/generators/active_record/merchant_generator.rb +44 -0
  22. data/lib/generators/active_record/templates/billable_migration.rb +17 -0
  23. data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
  24. data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
  25. data/lib/generators/pay/merchant_generator.rb +17 -0
  26. data/lib/generators/pay/orm_helpers.rb +10 -6
  27. data/lib/pay/adapter.rb +9 -0
  28. data/lib/pay/attributes.rb +74 -0
  29. data/lib/pay/billable/sync_customer.rb +30 -0
  30. data/lib/pay/braintree/billable.rb +133 -110
  31. data/lib/pay/braintree/payment_method.rb +42 -0
  32. data/lib/pay/braintree/subscription.rb +9 -12
  33. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -1
  34. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +4 -4
  35. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
  36. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -1
  37. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +2 -2
  38. data/lib/pay/braintree/webhooks/subscription_went_active.rb +1 -1
  39. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +1 -1
  40. data/lib/pay/braintree.rb +3 -2
  41. data/lib/pay/engine.rb +6 -1
  42. data/lib/pay/env.rb +8 -0
  43. data/lib/pay/fake_processor/billable.rb +45 -21
  44. data/lib/pay/fake_processor/payment_method.rb +21 -0
  45. data/lib/pay/fake_processor/subscription.rb +11 -8
  46. data/lib/pay/fake_processor.rb +2 -1
  47. data/lib/pay/nano_id.rb +13 -0
  48. data/lib/pay/paddle/billable.rb +18 -48
  49. data/lib/pay/paddle/charge.rb +5 -5
  50. data/lib/pay/paddle/payment_method.rb +60 -0
  51. data/lib/pay/paddle/response.rb +0 -0
  52. data/lib/pay/paddle/subscription.rb +49 -8
  53. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  54. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  55. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  56. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +26 -28
  57. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  58. data/lib/pay/paddle.rb +7 -3
  59. data/lib/pay/payment.rb +1 -1
  60. data/lib/pay/receipts.rb +35 -7
  61. data/lib/pay/stripe/billable.rb +80 -102
  62. data/lib/pay/stripe/charge.rb +59 -3
  63. data/lib/pay/stripe/merchant.rb +10 -10
  64. data/lib/pay/stripe/payment_method.rb +61 -0
  65. data/lib/pay/stripe/subscription.rb +70 -8
  66. data/lib/pay/stripe/webhooks/account_updated.rb +2 -3
  67. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
  68. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
  69. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +15 -0
  70. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +15 -0
  71. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  72. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  73. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  74. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -14
  75. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  76. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  77. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  78. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -36
  79. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -9
  80. data/lib/pay/stripe/webhooks/subscription_renewing.rb +14 -6
  81. data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -29
  82. data/lib/pay/stripe.rb +12 -3
  83. data/lib/pay/version.rb +1 -1
  84. data/lib/pay/webhooks/delegator.rb +4 -0
  85. data/lib/pay/webhooks/process_job.rb +9 -0
  86. data/lib/pay/webhooks.rb +1 -0
  87. data/lib/pay.rb +7 -78
  88. data/lib/tasks/pay.rake +20 -0
  89. metadata +31 -39
  90. data/app/models/pay.rb +0 -5
  91. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  92. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  93. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  94. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -15
  95. data/db/migrate/20210309004259_add_data_to_pay_billable.rb +0 -19
  96. data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +0 -5
  97. data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +0 -7
  98. data/lib/generators/active_record/pay_generator.rb +0 -58
  99. data/lib/generators/active_record/templates/migration.rb +0 -9
  100. data/lib/pay/billable/sync_email.rb +0 -40
  101. data/lib/pay/billable.rb +0 -172
data/lib/pay/paddle.rb CHANGED
@@ -2,8 +2,9 @@ module Pay
2
2
  module Paddle
3
3
  autoload :Billable, "pay/paddle/billable"
4
4
  autoload :Charge, "pay/paddle/charge"
5
- autoload :Subscription, "pay/paddle/subscription"
6
5
  autoload :Error, "pay/paddle/error"
6
+ autoload :PaymentMethod, "pay/paddle/payment_method"
7
+ autoload :Subscription, "pay/paddle/subscription"
7
8
 
8
9
  module Webhooks
9
10
  autoload :SignatureVerifier, "pay/paddle/webhooks/signature_verifier"
@@ -44,9 +45,12 @@ module Pay
44
45
  options.merge(owner_sgid: owner.to_sgid.to_s).to_json
45
46
  end
46
47
 
48
+ def self.parse_passthrough(passthrough)
49
+ JSON.parse(passthrough)
50
+ end
51
+
47
52
  def self.owner_from_passthrough(passthrough)
48
- passthrough_json = JSON.parse(passthrough)
49
- GlobalID::Locator.locate_signed(passthrough_json["owner_sgid"])
53
+ GlobalID::Locator.locate_signed parse_passthrough(passthrough)["owner_sgid"]
50
54
  rescue JSON::ParserError
51
55
  nil
52
56
  end
data/lib/pay/payment.rb CHANGED
@@ -2,7 +2,7 @@ module Pay
2
2
  class Payment
3
3
  attr_reader :intent
4
4
 
5
- delegate :id, :amount, :client_secret, :status, :confirm, to: :intent
5
+ delegate :id, :amount, :client_secret, :customer, :status, :confirm, to: :intent
6
6
 
7
7
  def self.from_id(id)
8
8
  intent = id.start_with?("seti_") ? ::Stripe::SetupIntent.retrieve(id) : ::Stripe::PaymentIntent.retrieve(id)
data/lib/pay/receipts.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  module Pay
2
2
  module Receipts
3
- def filename
4
- "receipt-#{created_at.strftime("%Y-%m-%d")}.pdf"
5
- end
6
-
7
3
  def product
8
4
  Pay.application_name
9
5
  end
10
6
 
11
- # Must return a file object
7
+ def receipt_filename
8
+ "receipt-#{created_at.strftime("%Y-%m-%d")}.pdf"
9
+ end
10
+ alias_method :filename, :receipt_filename
11
+
12
12
  def receipt
13
13
  receipt_pdf.render
14
14
  end
@@ -26,15 +26,43 @@ module Pay
26
26
  )
27
27
  end
28
28
 
29
+ def invoice_filename
30
+ "invoice-#{created_at.strftime("%Y-%m-%d")}.pdf"
31
+ end
32
+
33
+ def invoice
34
+ invoice_pdf.render
35
+ end
36
+
37
+ def invoice_pdf
38
+ ::Receipts::Invoice.new(
39
+ id: id,
40
+ issue_date: created_at,
41
+ due_date: created_at,
42
+ status: "<b><color rgb='#5eba7d'>PAID</color></b>",
43
+ bill_to: [
44
+ customer.customer_name,
45
+ customer.email
46
+ ].compact,
47
+ product: product,
48
+ company: {
49
+ name: Pay.business_name,
50
+ address: Pay.business_address,
51
+ email: Pay.support_email
52
+ },
53
+ line_items: line_items
54
+ )
55
+ end
56
+
29
57
  def line_items
30
58
  line_items = [
31
59
  [I18n.t("receipt.date"), created_at.to_s],
32
- [I18n.t("receipt.account_billed"), "#{owner.name} (#{owner.email})"],
60
+ [I18n.t("receipt.account_billed"), "#{customer.customer_name} (#{customer.email})"],
33
61
  [I18n.t("receipt.product"), product],
34
62
  [I18n.t("receipt.amount"), ActionController::Base.helpers.number_to_currency(amount / 100.0)],
35
63
  [I18n.t("receipt.charged_to"), charged_to]
36
64
  ]
37
- line_items << [I18n.t("receipt.additional_info"), owner.extra_billing_info] if owner.extra_billing_info?
65
+ line_items << [I18n.t("receipt.additional_info"), customer.owner.extra_billing_info] if customer.owner.extra_billing_info?
38
66
  line_items
39
67
  end
40
68
  end
@@ -3,43 +3,40 @@ module Pay
3
3
  class Billable
4
4
  include Rails.application.routes.url_helpers
5
5
 
6
- attr_reader :billable
6
+ attr_reader :pay_customer
7
7
 
8
8
  delegate :processor_id,
9
9
  :processor_id?,
10
10
  :email,
11
11
  :customer_name,
12
- :card_token,
12
+ :payment_method_token,
13
+ :payment_method_token?,
13
14
  :stripe_account,
14
- to: :billable
15
+ to: :pay_customer
15
16
 
16
- class << self
17
- def default_url_options
18
- Rails.application.config.action_mailer.default_url_options || {}
19
- end
17
+ def self.default_url_options
18
+ Rails.application.config.action_mailer.default_url_options || {}
20
19
  end
21
20
 
22
- def initialize(billable)
23
- @billable = billable
21
+ def initialize(pay_customer)
22
+ @pay_customer = pay_customer
24
23
  end
25
24
 
26
- # Handles Billable#customer
27
- #
28
- # Returns Stripe::Customer
29
25
  def customer
30
26
  stripe_customer = if processor_id?
31
- ::Stripe::Customer.retrieve(processor_id, {stripe_account: stripe_account})
27
+ ::Stripe::Customer.retrieve({id: processor_id}, stripe_options)
32
28
  else
33
- sc = ::Stripe::Customer.create({email: email, name: customer_name}, {stripe_account: stripe_account})
34
- billable.update(processor: :stripe, processor_id: sc.id, stripe_account: stripe_account)
29
+ sc = ::Stripe::Customer.create({email: email, name: customer_name}, stripe_options)
30
+ pay_customer.update!(processor_id: sc.id, stripe_account: stripe_account)
35
31
  sc
36
32
  end
37
33
 
38
- # Update the user's card on file if a token was passed in
39
- if card_token.present?
40
- payment_method = ::Stripe::PaymentMethod.attach(card_token, {customer: stripe_customer.id}, {stripe_account: stripe_account})
41
- stripe_customer = ::Stripe::Customer.update(stripe_customer.id, {invoice_settings: {default_payment_method: payment_method.id}}, {stripe_account: stripe_account})
42
- update_card_on_file(payment_method.card)
34
+ if payment_method_token?
35
+ payment_method = ::Stripe::PaymentMethod.attach(payment_method_token, {customer: stripe_customer.id}, stripe_options)
36
+ pay_payment_method = save_payment_method(payment_method, default: false)
37
+ pay_payment_method.make_default!
38
+
39
+ pay_customer.payment_method_token = nil
43
40
  end
44
41
 
45
42
  stripe_customer
@@ -47,36 +44,32 @@ module Pay
47
44
  raise Pay::Stripe::Error, e
48
45
  end
49
46
 
50
- # Handles Billable#charge
51
- #
52
- # Returns Pay::Charge
53
47
  def charge(amount, options = {})
54
- stripe_customer = customer
48
+ add_payment_method(payment_method_token, default: true) if payment_method_token?
49
+
50
+ payment_method = pay_customer.default_payment_method
55
51
  args = {
56
52
  amount: amount,
57
53
  confirm: true,
58
54
  confirmation_method: :automatic,
59
55
  currency: "usd",
60
- customer: stripe_customer.id,
61
- payment_method: stripe_customer.invoice_settings.default_payment_method
56
+ customer: processor_id,
57
+ payment_method: payment_method&.processor_id
62
58
  }.merge(options)
63
59
 
64
- payment_intent = ::Stripe::PaymentIntent.create(args, {stripe_account: stripe_account})
60
+ payment_intent = ::Stripe::PaymentIntent.create(args, stripe_options)
65
61
  Pay::Payment.new(payment_intent).validate
66
62
 
67
- # Create a new charge object
68
- save_pay_charge(payment_intent.charges.first)
63
+ charge = payment_intent.charges.first
64
+ Pay::Stripe::Charge.sync(charge.id, object: charge)
69
65
  rescue ::Stripe::StripeError => e
70
66
  raise Pay::Stripe::Error, e
71
67
  end
72
68
 
73
- # Handles Billable#subscribe
74
- #
75
- # Returns Pay::Subscription
76
69
  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
77
70
  quantity = options.delete(:quantity) || 1
78
71
  opts = {
79
- expand: ["pending_setup_intent", "latest_invoice.payment_intent"],
72
+ expand: ["pending_setup_intent", "latest_invoice.payment_intent", "latest_invoice.charge.invoice"],
80
73
  items: [plan: plan, quantity: quantity],
81
74
  off_session: true
82
75
  }.merge(options)
@@ -84,19 +77,18 @@ module Pay
84
77
  # Inherit trial from plan unless trial override was specified
85
78
  opts[:trial_from_plan] = true unless opts[:trial_period_days]
86
79
 
87
- # Load the Stripe customer to verify it exists and update card if needed
80
+ # Load the Stripe customer to verify it exists and update payment method if needed
88
81
  opts[:customer] = customer.id
89
82
 
90
- stripe_sub = ::Stripe::Subscription.create(opts, {stripe_account: stripe_account})
91
- subscription = billable.create_pay_subscription(stripe_sub, "stripe", name, plan, status: stripe_sub.status, quantity: quantity, stripe_account: stripe_account, application_fee_percent: stripe_sub.application_fee_percent)
83
+ # Create subscription on Stripe
84
+ stripe_sub = ::Stripe::Subscription.create(opts, stripe_options)
92
85
 
93
- # No trial, card requires SCA
86
+ # Save Pay::Subscription
87
+ subscription = Pay::Stripe::Subscription.sync(stripe_sub.id, object: stripe_sub, name: name)
88
+
89
+ # No trial, payment method requires SCA
94
90
  if subscription.incomplete?
95
91
  Pay::Payment.new(stripe_sub.latest_invoice.payment_intent).validate
96
-
97
- # Trial, card requires SCA
98
- elsif subscription.on_trial? && stripe_sub.pending_setup_intent
99
- Pay::Payment.new(stripe_sub.pending_setup_intent).validate
100
92
  end
101
93
 
102
94
  subscription
@@ -104,51 +96,57 @@ module Pay
104
96
  raise Pay::Stripe::Error, e
105
97
  end
106
98
 
107
- # Handles Billable#update_card
108
- #
109
- # Returns true if successful
110
- def update_card(payment_method_id)
111
- stripe_customer = customer
112
-
113
- return true if payment_method_id == stripe_customer.invoice_settings.default_payment_method
99
+ def add_payment_method(payment_method_id, default: false)
100
+ customer unless processor_id?
101
+ payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: processor_id}, stripe_options)
114
102
 
115
- payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: stripe_customer.id}, {stripe_account: stripe_account})
116
- ::Stripe::Customer.update(stripe_customer.id, {invoice_settings: {default_payment_method: payment_method.id}}, {stripe_account: stripe_account})
103
+ if default
104
+ ::Stripe::Customer.update(processor_id, {
105
+ invoice_settings: {
106
+ default_payment_method: payment_method.id
107
+ }
108
+ }, stripe_options)
109
+ end
117
110
 
118
- update_card_on_file(payment_method.card)
119
- true
111
+ save_payment_method(payment_method, default: default)
120
112
  rescue ::Stripe::StripeError => e
121
113
  raise Pay::Stripe::Error, e
122
114
  end
123
115
 
116
+ # Save the Stripe::PaymentMethod to the database
117
+ def save_payment_method(payment_method, default:)
118
+ pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.id).first_or_initialize
119
+
120
+ attributes = Pay::Stripe::PaymentMethod.extract_attributes(payment_method).merge(default: default)
121
+
122
+ pay_customer.payment_methods.update_all(default: false) if default
123
+ pay_payment_method.update!(attributes)
124
+
125
+ # Reload the Rails association
126
+ pay_customer.reload_default_payment_method if default
127
+
128
+ pay_payment_method
129
+ end
130
+
124
131
  def update_email!
125
- ::Stripe::Customer.update(processor_id, {email: email, name: customer_name}, {stripe_account: stripe_account})
132
+ ::Stripe::Customer.update(processor_id, {email: email, name: customer_name}, stripe_options)
126
133
  end
127
134
 
128
135
  def processor_subscription(subscription_id, options = {})
129
- ::Stripe::Subscription.retrieve(options.merge(id: subscription_id), {stripe_account: stripe_account})
136
+ ::Stripe::Subscription.retrieve(options.merge(id: subscription_id), stripe_options)
130
137
  end
131
138
 
132
139
  def invoice!(options = {})
133
140
  return unless processor_id?
134
- ::Stripe::Invoice.create(options.merge(customer: processor_id), {stripe_account: stripe_account}).pay
141
+ ::Stripe::Invoice.create(options.merge(customer: processor_id), stripe_options).pay
135
142
  end
136
143
 
137
144
  def upcoming_invoice
138
- ::Stripe::Invoice.upcoming({customer: processor_id}, {stripe_account: stripe_account})
139
- end
140
-
141
- # Used by webhooks when the customer or source changes
142
- def sync_card_from_stripe
143
- if (payment_method_id = customer.invoice_settings.default_payment_method)
144
- update_card_on_file ::Stripe::PaymentMethod.retrieve(payment_method_id, {stripe_account: stripe_account}).card
145
- else
146
- billable.update(card_type: nil, card_last4: nil)
147
- end
145
+ ::Stripe::Invoice.upcoming({customer: processor_id}, stripe_options)
148
146
  end
149
147
 
150
148
  def create_setup_intent
151
- ::Stripe::SetupIntent.create({customer: processor_id, usage: :off_session}, {stripe_account: stripe_account})
149
+ ::Stripe::SetupIntent.create({customer: processor_id, usage: :off_session}, stripe_options)
152
150
  end
153
151
 
154
152
  def trial_end_date(stripe_sub)
@@ -156,41 +154,14 @@ module Pay
156
154
  stripe_sub.trial_end.present? ? Time.at(stripe_sub.trial_end) : nil
157
155
  end
158
156
 
159
- # Save the card to the database as the user's current card
160
- def update_card_on_file(card)
161
- billable.update!(
162
- card_type: card.brand.capitalize,
163
- card_last4: card.last4,
164
- card_exp_month: card.exp_month,
165
- card_exp_year: card.exp_year
166
- )
167
-
168
- billable.card_token = nil
169
- end
170
-
171
- def save_pay_charge(object)
172
- charge = billable.charges.find_or_initialize_by(processor: :stripe, processor_id: object.id)
173
-
174
- attrs = {
175
- amount: object.amount,
176
- card_last4: object.payment_method_details.card.last4,
177
- card_type: object.payment_method_details.card.brand,
178
- card_exp_month: object.payment_method_details.card.exp_month,
179
- card_exp_year: object.payment_method_details.card.exp_year,
180
- created_at: Time.zone.at(object.created),
181
- currency: object.currency,
182
- stripe_account: stripe_account,
183
- application_fee_amount: object.application_fee_amount
184
- }
185
-
186
- # Associate charge with subscription if we can
187
- if object.invoice
188
- invoice = (object.invoice.is_a?(::Stripe::Invoice) ? object.invoice : ::Stripe::Invoice.retrieve(object.invoice))
189
- attrs[:subscription] = Pay::Subscription.find_by(processor: :stripe, processor_id: invoice.subscription)
157
+ # Syncs a customer's subscriptions from Stripe to the database
158
+ def sync_subscriptions
159
+ subscriptions = ::Stripe::Subscription.list({customer: customer}, stripe_options)
160
+ subscriptions.map do |subscription|
161
+ Pay::Stripe::Subscription.sync(subscription.id)
190
162
  end
191
-
192
- charge.update(attrs)
193
- charge
163
+ rescue ::Stripe::StripeError => e
164
+ raise Pay::Stripe::Error, e
194
165
  end
195
166
 
196
167
  # https://stripe.com/docs/api/checkout/sessions/create
@@ -224,7 +195,7 @@ module Pay
224
195
  }
225
196
  end
226
197
 
227
- ::Stripe::Checkout::Session.create(args.merge(options), {stripe_account: stripe_account})
198
+ ::Stripe::Checkout::Session.create(args.merge(options), stripe_options)
228
199
  end
229
200
 
230
201
  # https://stripe.com/docs/api/checkout/sessions/create
@@ -251,7 +222,14 @@ module Pay
251
222
  customer: processor_id,
252
223
  return_url: options.delete(:return_url) || root_url
253
224
  }
254
- ::Stripe::BillingPortal::Session.create(args.merge(options), {stripe_account: stripe_account})
225
+ ::Stripe::BillingPortal::Session.create(args.merge(options), stripe_options)
226
+ end
227
+
228
+ private
229
+
230
+ # Options for Stripe requests
231
+ def stripe_options
232
+ {stripe_account: stripe_account}.compact
255
233
  end
256
234
  end
257
235
  end
@@ -3,14 +3,63 @@ module Pay
3
3
  class Charge
4
4
  attr_reader :pay_charge
5
5
 
6
- delegate :processor_id, :owner, :stripe_account, to: :pay_charge
6
+ delegate :processor_id, :stripe_account, to: :pay_charge
7
+
8
+ def self.sync(charge_id, object: nil, stripe_account: nil, try: 0, retries: 1)
9
+ # Skip loading the latest charge details from the API if we already have it
10
+ object ||= ::Stripe::Charge.retrieve(charge_id, {stripe_account: stripe_account}.compact)
11
+
12
+ pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
13
+ return unless pay_customer
14
+
15
+ payment_method = object.payment_method_details.send(object.payment_method_details.type)
16
+ attrs = {
17
+ amount: object.amount,
18
+ amount_refunded: object.amount_refunded,
19
+ application_fee_amount: object.application_fee_amount,
20
+ created_at: Time.at(object.created),
21
+ currency: object.currency,
22
+ stripe_account: pay_customer.stripe_account,
23
+ metadata: object.metadata,
24
+ payment_method_type: object.payment_method_details.type,
25
+ brand: payment_method.try(:brand)&.capitalize,
26
+ last4: payment_method.try(:last4).to_s,
27
+ exp_month: payment_method.try(:exp_month).to_s,
28
+ exp_year: payment_method.try(:exp_year).to_s,
29
+ bank: payment_method.try(:bank_name) || payment_method.try(:bank) # eps, fpx, ideal, p24, acss_debit, etc
30
+ }
31
+
32
+ # Associate charge with subscription if we can
33
+ if object.invoice
34
+ invoice = (object.invoice.is_a?(::Stripe::Invoice) ? object.invoice : ::Stripe::Invoice.retrieve(object.invoice, {stripe_account: stripe_account}.compact))
35
+ attrs[:subscription] = pay_customer.subscriptions.find_by(processor_id: invoice.subscription)
36
+ end
37
+
38
+ # Update or create the charge
39
+ if (pay_charge = pay_customer.charges.find_by(processor_id: object.id))
40
+ pay_charge.with_lock do
41
+ pay_charge.update!(attrs)
42
+ end
43
+ pay_charge
44
+ else
45
+ pay_customer.charges.create!(attrs.merge(processor_id: object.id))
46
+ end
47
+ rescue ActiveRecord::RecordInvalid
48
+ try += 1
49
+ if try <= retries
50
+ sleep 0.1
51
+ retry
52
+ else
53
+ raise
54
+ end
55
+ end
7
56
 
8
57
  def initialize(pay_charge)
9
58
  @pay_charge = pay_charge
10
59
  end
11
60
 
12
61
  def charge
13
- ::Stripe::Charge.retrieve({id: processor_id, expand: ["customer", "invoice.subscription"]}, {stripe_account: stripe_account})
62
+ ::Stripe::Charge.retrieve({id: processor_id, expand: ["customer", "invoice.subscription"]}, stripe_options)
14
63
  rescue ::Stripe::StripeError => e
15
64
  raise Pay::Stripe::Error, e
16
65
  end
@@ -21,11 +70,18 @@ module Pay
21
70
  # refund!(5_00)
22
71
  # refund!(5_00, refund_application_fee: true)
23
72
  def refund!(amount_to_refund, **options)
24
- ::Stripe::Refund.create(options.merge(charge: processor_id, amount: amount_to_refund), {stripe_account: stripe_account})
73
+ ::Stripe::Refund.create(options.merge(charge: processor_id, amount: amount_to_refund), stripe_options)
25
74
  pay_charge.update(amount_refunded: amount_to_refund)
26
75
  rescue ::Stripe::StripeError => e
27
76
  raise Pay::Stripe::Error, e
28
77
  end
78
+
79
+ private
80
+
81
+ # Options for Stripe requests
82
+ def stripe_options
83
+ {stripe_account: stripe_account}.compact
84
+ end
29
85
  end
30
86
  end
31
87
  end