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,130 +1,116 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  class Subscription < Pay::Subscription
4
+ extend Pay::Sync
5
+
4
6
  attr_writer :api_record
5
7
 
6
8
  def self.sync_from_checkout_session(session_id, stripe_account: nil)
7
- checkout_session = ::Stripe::Checkout::Session.retrieve({id: session_id}, {stripe_account: stripe_account}.compact)
8
- sync(checkout_session.subscription, stripe_account: stripe_account)
9
+ Pay.deprecator.warn "Pay::Stripe::Subscription.sync_from_checkout_session is deprecated, use Pay::Stripe.sync_checkout_session instead"
10
+ Pay::Stripe.sync_checkout_session(session_id, stripe_account: stripe_account)
9
11
  end
10
12
 
11
- def self.sync(subscription_id, object: nil, name: nil, stripe_account: nil, try: 0, retries: 1)
12
- # Skip loading the latest subscription details from the API if we already have it
13
- object ||= ::Stripe::Subscription.retrieve({id: subscription_id}.merge(expand_options), {stripe_account: stripe_account}.compact)
14
- if object.customer.blank?
15
- Rails.logger.debug "Stripe Subscription #{object.id} does not have a customer"
16
- return
17
- end
18
-
19
- pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
20
- if pay_customer.blank?
21
- Rails.logger.debug "Pay::Customer #{object.customer} is not in the database while syncing Stripe Subscription #{object.id}"
22
- return
23
- end
24
-
25
- # Requests for the rest of the sync should go to the same Stripe Connect account as the customer
26
- stripe_account ||= pay_customer.stripe_account
27
-
28
- attributes = {
29
- object: object.to_hash,
30
- application_fee_percent: object.application_fee_percent,
31
- created_at: Time.at(object.created),
32
- processor_plan: object.items.first.price.id,
33
- quantity: object.items.first.try(:quantity) || 0,
34
- status: object.status,
35
- stripe_account: stripe_account,
36
- metadata: object.metadata,
37
- metered: false,
38
- pause_behavior: object.pause_collection&.behavior,
39
- pause_resumes_at: (object.pause_collection&.resumes_at ? Time.at(object.pause_collection&.resumes_at) : nil),
40
- current_period_start: (object.items.first.current_period_start ? Time.at(object.items.first.current_period_start) : nil),
41
- current_period_end: (object.items.first.current_period_end ? Time.at(object.items.first.current_period_end) : nil)
42
- }
43
-
44
- # Subscriptions that have ended should have their trial ended at the
45
- # same time if they were still on trial (if you cancel a
46
- # subscription, your are cancelling your trial as well at the same
47
- # instant). This avoids canceled subscriptions responding `true`
48
- # to #on_trial? due to the `trial_ends_at` being left set in the
49
- # future.
50
- if object.trial_end
51
- trial_ended_at = [object.ended_at, object.trial_end].compact.min
52
- attributes[:trial_ends_at] = Time.at(trial_ended_at)
53
- else
54
- attributes[:trial_ends_at] = nil
55
- end
56
-
57
- object.items.auto_paging_each do |subscription_item|
58
- next if attributes[:metered]
59
- attributes[:metered] = true if subscription_item.price.try(:recurring).try(:usage_type) == "metered"
60
- end
13
+ def self.sync(subscription_id, object: nil, name: nil, stripe_account: nil, retries: 1)
14
+ sync_with_retries(retries: retries) do
15
+ subscription = object || ::Stripe::Subscription.retrieve({id: subscription_id}.merge(expand_options), {stripe_account: stripe_account}.compact)
16
+ return unless (pay_customer = find_pay_customer(subscription.customer))
17
+ stripe_account ||= pay_customer.stripe_account
18
+
19
+ attributes = {
20
+ object: subscription.to_hash,
21
+ application_fee_percent: subscription.application_fee_percent,
22
+ created_at: Time.at(subscription.created),
23
+ processor_plan: subscription.items.first.price.id,
24
+ quantity: subscription.items.first.try(:quantity) || 0,
25
+ status: subscription.status,
26
+ stripe_account: stripe_account,
27
+ metadata: subscription.metadata,
28
+ metered: false,
29
+ pause_behavior: subscription.pause_collection&.behavior,
30
+ pause_resumes_at: (subscription.pause_collection&.resumes_at ? Time.at(subscription.pause_collection&.resumes_at) : nil),
31
+ current_period_start: (subscription.items.first.current_period_start ? Time.at(subscription.items.first.current_period_start) : nil),
32
+ current_period_end: (subscription.items.first.current_period_end ? Time.at(subscription.items.first.current_period_end) : nil)
33
+ }
34
+
35
+ # Subscriptions that have ended should have their trial ended at the
36
+ # same time if they were still on trial (if you cancel a
37
+ # subscription, your are cancelling your trial as well at the same
38
+ # instant). This avoids canceled subscriptions responding `true`
39
+ # to #on_trial? due to the `trial_ends_at` being left set in the
40
+ # future.
41
+ if subscription.trial_end
42
+ trial_ended_at = [subscription.ended_at, subscription.trial_end].compact.min
43
+ attributes[:trial_ends_at] = Time.at(trial_ended_at)
44
+ else
45
+ attributes[:trial_ends_at] = nil
46
+ end
61
47
 
62
- attributes[:ends_at] = if object.ended_at
63
- # Fully cancelled subscription
64
- Time.at(object.ended_at)
65
- elsif object.cancel_at
66
- # subscription cancelling in the future
67
- Time.at(object.cancel_at)
68
- elsif object.cancel_at_period_end
69
- # Subscriptions cancelling in the future
70
- Time.at(object.items.first.current_period_end)
71
- end
48
+ subscription.items.auto_paging_each do |subscription_item|
49
+ next if attributes[:metered]
50
+ attributes[:metered] = true if subscription_item.price.try(:recurring).try(:usage_type) == "metered"
51
+ end
72
52
 
73
- # Sync payment method if directly attached to subscription
74
- if object.default_payment_method
75
- if object.default_payment_method.is_a? String
76
- Pay::Stripe::PaymentMethod.sync(object.default_payment_method, stripe_account: stripe_account)
77
- attributes[:payment_method_id] = object.default_payment_method
78
- else
79
- Pay::Stripe::PaymentMethod.sync(object.default_payment_method.id, object: object.default_payment_method, stripe_account: stripe_account)
80
- attributes[:payment_method_id] = object.default_payment_method.id
53
+ attributes[:ends_at] = if subscription.ended_at
54
+ # Fully cancelled subscription
55
+ Time.at(subscription.ended_at)
56
+ elsif subscription.cancel_at
57
+ # subscription cancelling in the future
58
+ Time.at(subscription.cancel_at)
59
+ elsif subscription.cancel_at_period_end
60
+ # Subscriptions cancelling in the future
61
+ Time.at(subscription.items.first.current_period_end)
81
62
  end
82
- end
83
63
 
84
- # Update or create the subscription
85
- pay_subscription = find_by(customer: pay_customer, processor_id: object.id)
86
- if pay_subscription
87
- # If pause behavior is changing to `void`, record the pause start date
88
- # Any other pause status (or no pause at all) should have nil for start
89
- if pay_subscription.pause_behavior != attributes[:pause_behavior]
90
- attributes[:pause_starts_at] = if attributes[:pause_behavior] == "void"
91
- Time.at(object.items.first.current_period_end)
64
+ # Sync payment method if directly attached to subscription
65
+ if subscription.default_payment_method
66
+ if subscription.default_payment_method.is_a? String
67
+ Pay::Stripe::PaymentMethod.sync(subscription.default_payment_method, stripe_account: stripe_account)
68
+ attributes[:payment_method_id] = subscription.default_payment_method
69
+ else
70
+ Pay::Stripe::PaymentMethod.sync(subscription.default_payment_method.id, object: subscription.default_payment_method, stripe_account: stripe_account)
71
+ attributes[:payment_method_id] = subscription.default_payment_method.id
92
72
  end
93
73
  end
94
74
 
95
- pay_subscription.with_lock { pay_subscription.update!(attributes) }
96
- else
97
- # Allow setting the subscription name in metadata, otherwise use the default
98
- name ||= object.metadata["pay_name"] || Pay.default_product_name
99
- pay_subscription = create!(attributes.merge(customer: pay_customer, name: name, processor_id: object.id))
100
- end
75
+ # Update or create the subscription
76
+ pay_subscription = find_by(customer: pay_customer, processor_id: subscription.id)
77
+ if pay_subscription
78
+ # If pause behavior is changing to `void`, record the pause start date
79
+ # Any other pause status (or no pause at all) should have nil for start
80
+ if pay_subscription.pause_behavior != attributes[:pause_behavior]
81
+ attributes[:pause_starts_at] = if attributes[:pause_behavior] == "void"
82
+ Time.at(subscription.items.first.current_period_end)
83
+ end
84
+ end
101
85
 
102
- # Cache the Stripe subscription on the Pay::Subscription that we return
103
- pay_subscription.api_record = object
86
+ pay_subscription.with_lock { pay_subscription.update!(attributes) }
87
+ else
88
+ # Allow setting the subscription name in metadata, otherwise use the default
89
+ name ||= subscription.metadata["pay_name"] || Pay.default_product_name
90
+ pay_subscription = create!(attributes.merge(customer: pay_customer, name: name, processor_id: subscription.id))
91
+ end
104
92
 
105
- # Sync the latest charge if we already have it loaded (like during subscrbe), otherwise, let webhooks take care of creating it
106
- if (invoice = object.try(:latest_invoice))
107
- Array(invoice.try(:payments)).each do |invoice_payment|
108
- next unless invoice_payment.status == "paid"
93
+ # Cache the Stripe subscription on the Pay::Subscription that we return
94
+ pay_subscription.api_record = subscription
109
95
 
110
- case invoice_payment.payment.type
111
- when "payment_intent"
112
- Pay::Stripe::Charge.sync_payment_intent(invoice_payment.payment.payment_intent, stripe_account: stripe_account)
113
- when "charge"
114
- Pay::Stripe::Charge.sync(invoice_payment.payment.charge, stripe_account: stripe_account)
96
+ # Sync the latest charge if we already have it loaded (like during subscrbe), otherwise, let webhooks take care of creating it
97
+ if (invoice = subscription.try(:latest_invoice))
98
+ Array(invoice.try(:payments)).each do |invoice_payment|
99
+ next unless invoice_payment.status == "paid"
100
+
101
+ case invoice_payment.payment.type
102
+ when "payment_intent"
103
+ Pay::Stripe::Charge.sync_payment_intent(invoice_payment.payment.payment_intent, stripe_account: stripe_account)
104
+ when "charge"
105
+ Pay::Stripe::Charge.sync(invoice_payment.payment.charge, stripe_account: stripe_account)
106
+ end
115
107
  end
116
108
  end
117
- end
118
109
 
119
- pay_subscription
120
- rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
121
- try += 1
122
- if try <= retries
123
- sleep 0.1
124
- retry
125
- else
126
- raise
110
+ pay_subscription
127
111
  end
112
+ rescue ::Stripe::StripeError => e
113
+ raise Pay::Stripe::Error, e
128
114
  end
129
115
 
130
116
  # Common expand options for all requests that create, retrieve, or update a Stripe Subscription
@@ -153,6 +139,8 @@ module Pay
153
139
 
154
140
  def api_record(**options)
155
141
  @api_record ||= ::Stripe::Subscription.retrieve(options.with_defaults(id: processor_id).merge(expand_options), {stripe_account: stripe_account}.compact)
142
+ rescue ::Stripe::StripeError => e
143
+ raise Pay::Stripe::Error, e
156
144
  end
157
145
 
158
146
  # Returns a SetupIntent or PaymentIntent client secret for the subscription
@@ -256,6 +244,8 @@ module Pay
256
244
  pause_resumes_at: (@api_record.pause_collection&.resumes_at ? Time.at(@api_record.pause_collection&.resumes_at) : nil),
257
245
  pause_starts_at: ((behavior == "void") ? Time.at(@api_record.items.first.current_period_end) : nil)
258
246
  )
247
+ rescue ::Stripe::StripeError => e
248
+ raise Pay::Stripe::Error, e
259
249
  end
260
250
 
261
251
  # Unpauses a subscription
@@ -268,6 +258,8 @@ module Pay
268
258
  pause_resumes_at: nil,
269
259
  pause_starts_at: nil
270
260
  )
261
+ rescue ::Stripe::StripeError => e
262
+ raise Pay::Stripe::Error, e
271
263
  end
272
264
 
273
265
  def resumable?
@@ -296,7 +288,7 @@ module Pay
296
288
  def swap(plan, **options)
297
289
  raise ArgumentError, "plan must be a string" unless plan.is_a?(String)
298
290
 
299
- prorate = options.fetch(:prorate) { true }
291
+ prorate = options.delete(:prorate) { true }
300
292
  proration_behavior = options.delete(:proration_behavior) || (prorate ? "always_invoice" : "none")
301
293
 
302
294
  @api_record = ::Stripe::Subscription.update(
@@ -333,6 +325,8 @@ module Pay
333
325
 
334
326
  def preview_invoice(**options)
335
327
  ::Stripe::Invoice.create_preview(options.merge(subscription: processor_id), stripe_options)
328
+ rescue ::Stripe::StripeError => e
329
+ raise Pay::Stripe::Error, e
336
330
  end
337
331
 
338
332
  # Retries the latest invoice for a Past Due subscription and attempts to pay it
@@ -346,7 +340,7 @@ module Pay
346
340
 
347
341
  ::Stripe::PaymentIntent.confirm(payment_intent_id, {payment_method: payment_method.processor_id}, stripe_options)
348
342
  else
349
- ::Stripe::PaymentIntent.confirm(payment_intent_id, stripe_options)
343
+ ::Stripe::PaymentIntent.confirm(payment_intent_id, {}, stripe_options)
350
344
  end
351
345
  Pay::Payment.new(payment_intent).validate
352
346
  rescue ::Stripe::StripeError => e
@@ -361,6 +355,8 @@ module Pay
361
355
  payment_intent_id = invoice.payments.first&.payment&.payment_intent
362
356
  retry_failed_payment(payment_intent_id: payment_intent_id) if payment_intent_id
363
357
  end
358
+ rescue ::Stripe::StripeError => e
359
+ raise Pay::Stripe::Error, e
364
360
  end
365
361
 
366
362
  # Returns the Stripe::PaymentIntent for the latest invoice, if there is one
@@ -1,7 +1,5 @@
1
1
  module Pay
2
2
  class Subscription < Pay::ApplicationRecord
3
- STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
4
-
5
3
  # Associations
6
4
  belongs_to :customer, touch: true
7
5
  belongs_to :payment_method, optional: true, primary_key: :processor_id
@@ -9,7 +7,7 @@ module Pay
9
7
 
10
8
  # Scopes
11
9
  scope :for_name, ->(name) { where(name: name) }
12
- scope :on_trial, -> { where(status: ["on_trial", "trialing", "active"]).where("trial_ends_at > ?", Time.current) }
10
+ scope :on_trial, -> { where(status: ["trialing", "active"]).where("trial_ends_at > ?", Time.current) }
13
11
  scope :canceled, -> { where.not(ends_at: nil) }
14
12
  scope :cancelled, -> { canceled }
15
13
  scope :on_grace_period, -> { where("#{table_name}.ends_at IS NOT NULL AND #{table_name}.ends_at > ?", Time.current) }
@@ -29,7 +27,7 @@ module Pay
29
27
 
30
28
  # Validations
31
29
  validates :name, presence: true
32
- validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
30
+ validates :processor_id, presence: true
33
31
  validates :processor_plan, presence: true
34
32
  validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
35
33
  validates :status, presence: true
@@ -54,18 +52,10 @@ module Pay
54
52
  reload
55
53
  end
56
54
 
57
- def skip_trial
58
- self.trial_ends_at = nil
59
- end
60
-
61
55
  def generic_trial?
62
56
  fake_processor? && trial_ends_at?
63
57
  end
64
58
 
65
- def has_trial?
66
- trial_ends_at?
67
- end
68
-
69
59
  # Does not include the last second of the trial
70
60
  def on_trial?
71
61
  return false if ended?
@@ -4,8 +4,6 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Payment Confirmation - <%= Pay.business_name %></title>
7
- <%#= stylesheet_link_tag "pay/application", media: "all" %>
8
- <%#= javascript_include_tag "pay/application" %>
9
7
  <%= csrf_meta_tags %>
10
8
 
11
9
  <link href="https://unpkg.com/tailwindcss@^2.2/dist/tailwind.min.css" rel="stylesheet">
@@ -1,8 +1,12 @@
1
1
  <div
2
2
  data-controller="payment-intent"
3
+ data-payment-intent-public-key-value="<%= Pay::Stripe.public_key %>"
4
+ data-payment-intent-stripe-account-value="<%= @payment.stripe_account %>"
3
5
  data-payment-intent-client-secret-value="<%= @payment.client_secret %>"
4
6
  data-payment-intent-status-value="<%= @payment.status %>"
5
7
  data-payment-intent-customer-value="<%= @payment.customer %>"
8
+ data-payment-intent-name-missing-message-value="<%= t("pay.requires_action.name_missing") %>"
9
+ data-payment-intent-confirmed-message-value="<%= t("pay.requires_action.success") %>"
6
10
  class="h-screen md:flex md:justify-center md:items-center">
7
11
  <div class="w-full max-w-lg">
8
12
  <!-- Status Messages -->
@@ -54,7 +58,7 @@
54
58
  </div>
55
59
  <% end %>
56
60
 
57
- <%= sanitize link_to(t("pay.back"), @redirect_to, class: "inline-block w-full px-4 py-3 bg-gray-100 hover:bg-gray-200 text-center text-gray-600 rounded-lg") %>
61
+ <%= link_to t("pay.back"), @redirect_to, class: "inline-block w-full px-4 py-3 bg-gray-100 hover:bg-gray-200 text-center text-gray-600 rounded-lg" %>
58
62
  </div>
59
63
 
60
64
  <p class="text-center text-gray-500 text-sm">
@@ -64,26 +68,33 @@
64
68
  </div>
65
69
 
66
70
  <script type="module">
67
- window.stripe = Stripe('<%= Pay::Stripe.public_key %>', <%= {stripeAccount: @payment.stripe_account}.compact.to_json.html_safe %>)
68
-
69
71
  import { Application, Controller } from 'https://unpkg.com/@hotwired/stimulus/dist/stimulus.js'
70
72
  const application = Application.start()
71
73
 
72
74
  application.register('payment-intent', class extends Controller {
73
75
  static targets = ["error", "errorMessage", "success", "successMessage", "form", "button", "name", "card", "cardFields"]
74
76
  static values = {
77
+ publicKey: String,
78
+ stripeAccount: String,
75
79
  clientSecret: String,
76
80
  customer: String,
77
81
  status: String,
82
+ nameMissingMessage: String,
83
+ confirmedMessage: String,
78
84
  errorMessage: String,
79
85
  successMessage: String,
80
86
  complete: Boolean,
81
87
  processing: Boolean
82
88
  }
83
89
 
90
+ // Value callbacks run before connect(), so Stripe.js must be ready here
91
+ initialize() {
92
+ this.stripe = Stripe(this.publicKeyValue, this.stripeAccountValue ? {stripeAccount: this.stripeAccountValue} : {})
93
+ }
94
+
84
95
  connect() {
85
96
  if (this.hasCardTarget) {
86
- this.elements = stripe.elements({clientSecret: this.clientSecretValue})
97
+ this.elements = this.stripe.elements({clientSecret: this.clientSecretValue})
87
98
  this.payment = this.elements.create('payment')
88
99
  this.payment.mount(this.cardTarget)
89
100
  }
@@ -93,7 +104,7 @@
93
104
  switch(this.statusValue) {
94
105
  case "requires_action":
95
106
  this.processingValue = true
96
- stripe.confirmPayment({clientSecret: this.clientSecretValue, confirmParams: { return_url: document.location.href }}).then(this.handleConfirmResult.bind(this))
107
+ this.stripe.confirmPayment({clientSecret: this.clientSecretValue, confirmParams: { return_url: document.location.href }}).then(this.handleConfirmResult.bind(this))
97
108
  break
98
109
  case "requires_payment_method":
99
110
  this.cardFieldsTarget.classList.toggle("hidden", false)
@@ -103,7 +114,7 @@
103
114
 
104
115
  confirmPayment() {
105
116
  this.processingValue = true
106
- stripe.confirmPayment({elements: this.elements, confirmParams: { return_url: document.location.href }}).then(this.handleConfirmResult.bind(this))
117
+ this.stripe.confirmPayment({elements: this.elements, confirmParams: { return_url: document.location.href }}).then(this.handleConfirmResult.bind(this))
107
118
  }
108
119
 
109
120
  handleConfirmResult(result) {
@@ -112,14 +123,14 @@
112
123
  if (result.error) {
113
124
  if (result.error.code === 'parameter_invalid_empty' &&
114
125
  result.error.param === 'payment_method_data[billing_details][name]') {
115
- this.errorMessageValue = '<%=t "pay.requires_action.name_missing" %>'
126
+ this.errorMessageValue = this.nameMissingMessageValue
116
127
  } else {
117
128
  this.errorMessageValue = result.error.message
118
129
  this.statusValue = result.error.payment_intent.status
119
130
  }
120
131
  } else {
121
132
  this.completeValue = true
122
- this.successMessageValue = '<%=t "pay.requires_action.success" %>'
133
+ this.successMessageValue = this.confirmedMessageValue
123
134
  }
124
135
  }
125
136
 
@@ -0,0 +1,15 @@
1
+ module Pay
2
+ module Braintree
3
+ module Webhooks
4
+ # Every Braintree subscription event is handled the same way: sync the subscription
5
+ class Subscription
6
+ def call(event)
7
+ subscription = event.subscription
8
+ return if subscription.nil?
9
+
10
+ Pay::Braintree::Subscription.sync(subscription.id)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,13 +3,7 @@
3
3
  module Pay
4
4
  module Braintree
5
5
  module Webhooks
6
- class SubscriptionCanceled
7
- def call(event)
8
- subscription = event.subscription
9
- return if subscription.nil?
10
-
11
- Pay::Braintree::Subscription.sync(subscription.id)
12
- end
6
+ class SubscriptionCanceled < Subscription
13
7
  end
14
8
  end
15
9
  end
@@ -3,13 +3,7 @@
3
3
  module Pay
4
4
  module Braintree
5
5
  module Webhooks
6
- class SubscriptionExpired
7
- def call(event)
8
- subscription = event.subscription
9
- return if subscription.nil?
10
-
11
- Pay::Braintree::Subscription.sync(subscription.id)
12
- end
6
+ class SubscriptionExpired < Subscription
13
7
  end
14
8
  end
15
9
  end
@@ -3,13 +3,7 @@
3
3
  module Pay
4
4
  module Braintree
5
5
  module Webhooks
6
- class SubscriptionTrialEnded
7
- def call(event)
8
- subscription = event.subscription
9
- return if subscription.nil?
10
-
11
- Pay::Braintree::Subscription.sync(subscription.id)
12
- end
6
+ class SubscriptionTrialEnded < Subscription
13
7
  end
14
8
  end
15
9
  end
@@ -1,15 +1,9 @@
1
- # A subscription's first authorized transaction is created, or a successful transaction moves a subscription from the Past Due status to the Active status. Subscriptions with trial periods will not trigger this notification when they move from the trial period into the first billing cycle.
1
+ # A subscription's first authorized transaction is created, or a successful transaction moves a subscription from the Past Due status to the Active status.
2
2
 
3
3
  module Pay
4
4
  module Braintree
5
5
  module Webhooks
6
- class SubscriptionWentActive
7
- def call(event)
8
- subscription = event.subscription
9
- return if subscription.nil?
10
-
11
- Pay::Braintree::Subscription.sync(subscription.id)
12
- end
6
+ class SubscriptionWentActive < Subscription
13
7
  end
14
8
  end
15
9
  end
@@ -1,15 +1,9 @@
1
- # A subscription has moved from the Active status to the Past Due status. This will only be triggered when the initial transaction in a billing cycle is declined. Once the status moves to past due, it will not be triggered again in that billing cycle.
1
+ # A subscription already exists and fails to create a successful charge.
2
2
 
3
3
  module Pay
4
4
  module Braintree
5
5
  module Webhooks
6
- class SubscriptionWentPastDue
7
- def call(event)
8
- subscription = event.subscription
9
- return if subscription.nil?
10
-
11
- Pay::Braintree::Subscription.sync(subscription.id)
12
- end
6
+ class SubscriptionWentPastDue < Subscription
13
7
  end
14
8
  end
15
9
  end
data/lib/pay/braintree.rb CHANGED
@@ -27,9 +27,9 @@ module Pay
27
27
  end
28
28
 
29
29
  module Webhooks
30
+ autoload :Subscription, "pay/braintree/webhooks/subscription"
30
31
  autoload :SubscriptionCanceled, "pay/braintree/webhooks/subscription_canceled"
31
32
  autoload :SubscriptionChargedSuccessfully, "pay/braintree/webhooks/subscription_charged_successfully"
32
- autoload :SubscriptionChargedUnsuccessfully, "pay/braintree/webhooks/subscription_charged_unsuccessfully"
33
33
  autoload :SubscriptionExpired, "pay/braintree/webhooks/subscription_expired"
34
34
  autoload :SubscriptionTrialEnded, "pay/braintree/webhooks/subscription_trial_ended"
35
35
  autoload :SubscriptionWentActive, "pay/braintree/webhooks/subscription_went_active"
@@ -73,7 +73,6 @@ module Pay
73
73
  Pay::Webhooks.configure do |events|
74
74
  events.subscribe "braintree.subscription_canceled", Pay::Braintree::Webhooks::SubscriptionCanceled.new
75
75
  events.subscribe "braintree.subscription_charged_successfully", Pay::Braintree::Webhooks::SubscriptionChargedSuccessfully.new
76
- events.subscribe "braintree.subscription_charged_unsuccessfully", Pay::Braintree::Webhooks::SubscriptionChargedUnsuccessfully.new
77
76
  events.subscribe "braintree.subscription_expired", Pay::Braintree::Webhooks::SubscriptionExpired.new
78
77
  events.subscribe "braintree.subscription_trial_ended", Pay::Braintree::Webhooks::SubscriptionTrialEnded.new
79
78
  events.subscribe "braintree.subscription_went_active", Pay::Braintree::Webhooks::SubscriptionWentActive.new
data/lib/pay/currency.rb CHANGED
@@ -65,10 +65,6 @@ module Pay
65
65
  attributes["format"]
66
66
  end
67
67
 
68
- def subunit?
69
- subunit.blank?
70
- end
71
-
72
68
  def subunit
73
69
  attributes["subunit"]
74
70
  end
data/lib/pay/engine.rb CHANGED
@@ -17,6 +17,10 @@ module Pay
17
17
  end
18
18
  end
19
19
 
20
+ initializer "pay.deprecator" do |app|
21
+ app.deprecators[:pay] = Pay.deprecator if app.respond_to?(:deprecators)
22
+ end
23
+
20
24
  initializer "pay.receipts" do
21
25
  if defined?(::Receipts::VERSION)
22
26
  raise "[Pay] receipts gem must be version ~> 2" unless Pay::Engine.version_matches?(required: "~> 2", current: ::Receipts::VERSION)
data/lib/pay/errors.rb CHANGED
@@ -3,6 +3,10 @@ module Pay
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
+ # Raised when a payment processor cannot perform an operation, such as pausing a Braintree subscription
7
+ class NotSupportedError < Error
8
+ end
9
+
6
10
  class PaymentError < Error
7
11
  attr_reader :payment
8
12
 
@@ -1,7 +1,10 @@
1
1
  module Pay
2
2
  module LemonSqueezy
3
3
  class Error < Pay::Error
4
- delegate :message, to: :cause
4
+ # Wrapped processor errors report the original message; errors raised with a string keep theirs
5
+ def message
6
+ cause&.message || super
7
+ end
5
8
  end
6
9
 
7
10
  module Webhooks
@@ -38,12 +41,6 @@ module Pay
38
41
  owner.to_sgid.to_s
39
42
  end
40
43
 
41
- def self.owner_from_passthrough(passthrough)
42
- GlobalID::Locator.locate_signed passthrough
43
- rescue JSON::ParserError
44
- nil
45
- end
46
-
47
44
  def self.configure_webhooks
48
45
  Pay::Webhooks.configure do |events|
49
46
  events.subscribe "lemon_squeezy.order_created", Pay::LemonSqueezy::Webhooks::Order.new
@@ -1,7 +1,10 @@
1
1
  module Pay
2
2
  module PaddleBilling
3
3
  class Error < Pay::Error
4
- delegate :message, to: :cause
4
+ # Wrapped processor errors report the original message; errors raised with a string keep theirs
5
+ def message
6
+ cause&.message || super
7
+ end
5
8
  end
6
9
 
7
10
  module Webhooks
@@ -1,7 +1,10 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
3
  class Error < Pay::Error
4
- delegate :message, to: :cause
4
+ # Wrapped processor errors report the original message; errors raised with a string keep theirs
5
+ def message
6
+ cause&.message || super
7
+ end
5
8
  end
6
9
 
7
10
  module Webhooks