pay 2.6.11 → 3.0.0

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -693
  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 +32 -17
  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 +34 -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 +22 -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 +130 -105
  31. data/lib/pay/braintree/payment_method.rb +33 -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/merchant.rb +37 -0
  48. data/lib/pay/nano_id.rb +13 -0
  49. data/lib/pay/paddle/billable.rb +18 -48
  50. data/lib/pay/paddle/charge.rb +5 -5
  51. data/lib/pay/paddle/payment_method.rb +58 -0
  52. data/lib/pay/paddle/response.rb +0 -0
  53. data/lib/pay/paddle/subscription.rb +49 -8
  54. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  55. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  56. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  57. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +23 -23
  58. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  59. data/lib/pay/paddle.rb +7 -3
  60. data/lib/pay/payment.rb +1 -1
  61. data/lib/pay/receipts.rb +35 -7
  62. data/lib/pay/stripe/billable.rb +82 -93
  63. data/lib/pay/stripe/charge.rb +65 -4
  64. data/lib/pay/stripe/merchant.rb +66 -0
  65. data/lib/pay/stripe/payment_method.rb +61 -0
  66. data/lib/pay/stripe/subscription.rb +91 -24
  67. data/lib/pay/stripe/webhooks/account_updated.rb +16 -0
  68. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
  69. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
  70. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +15 -0
  71. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +15 -0
  72. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  73. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  74. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  75. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -14
  76. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  77. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  78. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  79. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -35
  80. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -9
  81. data/lib/pay/stripe/webhooks/subscription_renewing.rb +14 -6
  82. data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -28
  83. data/lib/pay/stripe.rb +17 -3
  84. data/lib/pay/version.rb +1 -1
  85. data/lib/pay/webhooks/delegator.rb +4 -0
  86. data/lib/pay/webhooks/process_job.rb +9 -0
  87. data/lib/pay/webhooks.rb +1 -0
  88. data/lib/pay.rb +8 -57
  89. metadata +34 -36
  90. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  91. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  92. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  93. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -22
  94. data/db/migrate/20210423235138_add_currency_to_pay_charges.rb +0 -5
  95. data/lib/generators/active_record/pay_generator.rb +0 -58
  96. data/lib/generators/active_record/templates/migration.rb +0 -9
  97. data/lib/pay/billable/sync_email.rb +0 -40
  98. data/lib/pay/billable.rb +0 -168
@@ -3,14 +3,14 @@ module Pay
3
3
  module Webhooks
4
4
  class SubscriptionUpdated
5
5
  def call(event)
6
- subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id: event["subscription_id"])
6
+ subscription = Pay::Subscription.find_by_processor_and_id(:paddle, event["subscription_id"])
7
7
 
8
8
  return if subscription.nil?
9
9
 
10
10
  case event["status"]
11
11
  when "deleted"
12
12
  subscription.status = "canceled"
13
- subscription.ends_at = Time.zone.parse(event["next_bill_date"]) || Time.zone.now if subscription.ends_at.blank?
13
+ subscription.ends_at = Time.zone.parse(event["next_bill_date"]) || Time.current if subscription.ends_at.blank?
14
14
  when "trialing"
15
15
  subscription.status = "trialing"
16
16
  subscription.trial_ends_at = Time.zone.parse(event["next_bill_date"])
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,42 +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,
13
- to: :billable
12
+ :payment_method_token,
13
+ :payment_method_token?,
14
+ :stripe_account,
15
+ to: :pay_customer
14
16
 
15
- class << self
16
- def default_url_options
17
- Rails.application.config.action_mailer.default_url_options || {}
18
- end
17
+ def self.default_url_options
18
+ Rails.application.config.action_mailer.default_url_options || {}
19
19
  end
20
20
 
21
- def initialize(billable)
22
- @billable = billable
21
+ def initialize(pay_customer)
22
+ @pay_customer = pay_customer
23
23
  end
24
24
 
25
- # Handles Billable#customer
26
- #
27
- # Returns Stripe::Customer
28
25
  def customer
29
26
  stripe_customer = if processor_id?
30
- ::Stripe::Customer.retrieve(processor_id)
27
+ ::Stripe::Customer.retrieve({id: processor_id}, stripe_options)
31
28
  else
32
- sc = ::Stripe::Customer.create(email: email, name: customer_name)
33
- billable.update(processor: :stripe, processor_id: sc.id)
29
+ sc = ::Stripe::Customer.create({email: email, name: customer_name}, stripe_options)
30
+ pay_customer.update!(processor_id: sc.id, stripe_account: stripe_account)
34
31
  sc
35
32
  end
36
33
 
37
- # Update the user's card on file if a token was passed in
38
- if card_token.present?
39
- payment_method = ::Stripe::PaymentMethod.attach(card_token, customer: stripe_customer.id)
40
- stripe_customer = ::Stripe::Customer.update(stripe_customer.id, invoice_settings: {default_payment_method: payment_method.id})
41
- 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
42
40
  end
43
41
 
44
42
  stripe_customer
@@ -46,36 +44,32 @@ module Pay
46
44
  raise Pay::Stripe::Error, e
47
45
  end
48
46
 
49
- # Handles Billable#charge
50
- #
51
- # Returns Pay::Charge
52
47
  def charge(amount, options = {})
53
- 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
54
51
  args = {
55
52
  amount: amount,
56
53
  confirm: true,
57
54
  confirmation_method: :automatic,
58
55
  currency: "usd",
59
- customer: stripe_customer.id,
60
- payment_method: stripe_customer.invoice_settings.default_payment_method
56
+ customer: processor_id,
57
+ payment_method: payment_method&.processor_id
61
58
  }.merge(options)
62
59
 
63
- payment_intent = ::Stripe::PaymentIntent.create(args)
60
+ payment_intent = ::Stripe::PaymentIntent.create(args, stripe_options)
64
61
  Pay::Payment.new(payment_intent).validate
65
62
 
66
- # Create a new charge object
67
- save_pay_charge(payment_intent.charges.first)
63
+ charge = payment_intent.charges.first
64
+ Pay::Stripe::Charge.sync(charge.id, object: charge)
68
65
  rescue ::Stripe::StripeError => e
69
66
  raise Pay::Stripe::Error, e
70
67
  end
71
68
 
72
- # Handles Billable#subscribe
73
- #
74
- # Returns Pay::Subscription
75
69
  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
76
70
  quantity = options.delete(:quantity) || 1
77
71
  opts = {
78
- expand: ["pending_setup_intent", "latest_invoice.payment_intent"],
72
+ expand: ["pending_setup_intent", "latest_invoice.payment_intent", "latest_invoice.charge.invoice"],
79
73
  items: [plan: plan, quantity: quantity],
80
74
  off_session: true
81
75
  }.merge(options)
@@ -83,19 +77,18 @@ module Pay
83
77
  # Inherit trial from plan unless trial override was specified
84
78
  opts[:trial_from_plan] = true unless opts[:trial_period_days]
85
79
 
86
- # 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
87
81
  opts[:customer] = customer.id
88
82
 
89
- stripe_sub = ::Stripe::Subscription.create(opts)
90
- subscription = billable.create_pay_subscription(stripe_sub, "stripe", name, plan, status: stripe_sub.status, quantity: quantity)
83
+ # Create subscription on Stripe
84
+ stripe_sub = ::Stripe::Subscription.create(opts, stripe_options)
91
85
 
92
- # 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
93
90
  if subscription.incomplete?
94
91
  Pay::Payment.new(stripe_sub.latest_invoice.payment_intent).validate
95
-
96
- # Trial, card requires SCA
97
- elsif subscription.on_trial? && stripe_sub.pending_setup_intent
98
- Pay::Payment.new(stripe_sub.pending_setup_intent).validate
99
92
  end
100
93
 
101
94
  subscription
@@ -103,51 +96,57 @@ module Pay
103
96
  raise Pay::Stripe::Error, e
104
97
  end
105
98
 
106
- # Handles Billable#update_card
107
- #
108
- # Returns true if successful
109
- def update_card(payment_method_id)
110
- stripe_customer = customer
111
-
112
- 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)
113
102
 
114
- payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, customer: stripe_customer.id)
115
- ::Stripe::Customer.update(stripe_customer.id, invoice_settings: {default_payment_method: payment_method.id})
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
116
110
 
117
- update_card_on_file(payment_method.card)
118
- true
111
+ save_payment_method(payment_method, default: default)
119
112
  rescue ::Stripe::StripeError => e
120
113
  raise Pay::Stripe::Error, e
121
114
  end
122
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
+
123
131
  def update_email!
124
- ::Stripe::Customer.update(processor_id, {email: email, name: customer_name})
132
+ ::Stripe::Customer.update(processor_id, {email: email, name: customer_name}, stripe_options)
125
133
  end
126
134
 
127
135
  def processor_subscription(subscription_id, options = {})
128
- ::Stripe::Subscription.retrieve(options.merge(id: subscription_id))
136
+ ::Stripe::Subscription.retrieve(options.merge(id: subscription_id), stripe_options)
129
137
  end
130
138
 
131
139
  def invoice!(options = {})
132
140
  return unless processor_id?
133
- ::Stripe::Invoice.create(options.merge(customer: processor_id)).pay
141
+ ::Stripe::Invoice.create(options.merge(customer: processor_id), stripe_options).pay
134
142
  end
135
143
 
136
144
  def upcoming_invoice
137
- ::Stripe::Invoice.upcoming(customer: processor_id)
138
- end
139
-
140
- # Used by webhooks when the customer or source changes
141
- def sync_card_from_stripe
142
- if (payment_method_id = customer.invoice_settings.default_payment_method)
143
- update_card_on_file ::Stripe::PaymentMethod.retrieve(payment_method_id).card
144
- else
145
- billable.update(card_type: nil, card_last4: nil)
146
- end
145
+ ::Stripe::Invoice.upcoming({customer: processor_id}, stripe_options)
147
146
  end
148
147
 
149
148
  def create_setup_intent
150
- ::Stripe::SetupIntent.create(customer: processor_id, usage: :off_session)
149
+ ::Stripe::SetupIntent.create({customer: processor_id, usage: :off_session}, stripe_options)
151
150
  end
152
151
 
153
152
  def trial_end_date(stripe_sub)
@@ -155,31 +154,14 @@ module Pay
155
154
  stripe_sub.trial_end.present? ? Time.at(stripe_sub.trial_end) : nil
156
155
  end
157
156
 
158
- # Save the card to the database as the user's current card
159
- def update_card_on_file(card)
160
- billable.update!(
161
- card_type: card.brand.capitalize,
162
- card_last4: card.last4,
163
- card_exp_month: card.exp_month,
164
- card_exp_year: card.exp_year
165
- )
166
-
167
- billable.card_token = nil
168
- end
169
-
170
- def save_pay_charge(object)
171
- charge = billable.charges.find_or_initialize_by(processor: :stripe, processor_id: object.id)
172
-
173
- charge.update(
174
- amount: object.amount,
175
- card_last4: object.payment_method_details.card.last4,
176
- card_type: object.payment_method_details.card.brand,
177
- card_exp_month: object.payment_method_details.card.exp_month,
178
- card_exp_year: object.payment_method_details.card.exp_year,
179
- created_at: Time.zone.at(object.created)
180
- )
181
-
182
- charge
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)
162
+ end
163
+ rescue ::Stripe::StripeError => e
164
+ raise Pay::Stripe::Error, e
183
165
  end
184
166
 
185
167
  # https://stripe.com/docs/api/checkout/sessions/create
@@ -213,7 +195,7 @@ module Pay
213
195
  }
214
196
  end
215
197
 
216
- ::Stripe::Checkout::Session.create(args.merge(options))
198
+ ::Stripe::Checkout::Session.create(args.merge(options), stripe_options)
217
199
  end
218
200
 
219
201
  # https://stripe.com/docs/api/checkout/sessions/create
@@ -240,7 +222,14 @@ module Pay
240
222
  customer: processor_id,
241
223
  return_url: options.delete(:return_url) || root_url
242
224
  }
243
- ::Stripe::BillingPortal::Session.create(args.merge(options))
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
244
233
  end
245
234
  end
246
235
  end
@@ -3,24 +3,85 @@ module Pay
3
3
  class Charge
4
4
  attr_reader :pay_charge
5
5
 
6
- delegate :processor_id, :owner, 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"])
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
17
66
 
18
- def refund!(amount_to_refund)
19
- ::Stripe::Refund.create(charge: processor_id, amount: amount_to_refund)
67
+ # https://stripe.com/docs/api/refunds/create
68
+ #
69
+ # refund!
70
+ # refund!(5_00)
71
+ # refund!(5_00, refund_application_fee: true)
72
+ def refund!(amount_to_refund, **options)
73
+ ::Stripe::Refund.create(options.merge(charge: processor_id, amount: amount_to_refund), stripe_options)
20
74
  pay_charge.update(amount_refunded: amount_to_refund)
21
75
  rescue ::Stripe::StripeError => e
22
76
  raise Pay::Stripe::Error, e
23
77
  end
78
+
79
+ private
80
+
81
+ # Options for Stripe requests
82
+ def stripe_options
83
+ {stripe_account: stripe_account}.compact
84
+ end
24
85
  end
25
86
  end
26
87
  end