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
data/lib/pay/payment.rb CHANGED
@@ -10,6 +10,8 @@ module Pay
10
10
  options = {stripe_account: stripe_account}.compact
11
11
  intent = id.start_with?("seti_") ? ::Stripe::SetupIntent.retrieve(id, options) : ::Stripe::PaymentIntent.retrieve(id, options)
12
12
  new(intent, stripe_account: stripe_account)
13
+ rescue ::Stripe::StripeError => e
14
+ raise Pay::Stripe::Error, e
13
15
  end
14
16
 
15
17
  def initialize(intent, stripe_account: nil)
@@ -29,22 +31,10 @@ module Pay
29
31
  status == "canceled"
30
32
  end
31
33
 
32
- def cancelled?
33
- canceled?
34
- end
35
-
36
34
  def succeeded?
37
35
  status == "succeeded"
38
36
  end
39
37
 
40
- def payment_intent?
41
- intent.is_a?(::Stripe::PaymentIntent)
42
- end
43
-
44
- def setup_intent?
45
- intent.is_a?(::Stripe::SetupIntent)
46
- end
47
-
48
38
  def amount_with_currency
49
39
  Pay::Currency.format(amount, currency: currency)
50
40
  end
data/lib/pay/receipts.rb CHANGED
@@ -3,7 +3,6 @@ module Pay
3
3
  def receipt_filename
4
4
  "receipt-#{created_at.strftime("%Y-%m-%d")}.pdf"
5
5
  end
6
- alias_method :filename, :receipt_filename
7
6
 
8
7
  def receipt
9
8
  receipt_pdf.render
@@ -1,10 +1,8 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  module Webhooks
4
- class SubscriptionCreated
5
- def call(event)
6
- Pay::Stripe::Subscription.sync(event.data.object.id, stripe_account: event.try(:account))
7
- end
4
+ # If a subscription is manually created on Stripe, we want to sync it the same way as an update
5
+ class SubscriptionCreated < SubscriptionUpdated
8
6
  end
9
7
  end
10
8
  end
@@ -1,11 +1,8 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  module Webhooks
4
- class SubscriptionDeleted
5
- def call(event)
6
- # Canceled subscriptions are still accessible via the API, so we can sync their details
7
- Pay::Stripe::Subscription.sync(event.data.object.id, stripe_account: event.try(:account))
8
- end
4
+ # Canceled subscriptions are still accessible via the API, so they sync the same way as an update
5
+ class SubscriptionDeleted < SubscriptionUpdated
9
6
  end
10
7
  end
11
8
  end
data/lib/pay/stripe.rb CHANGED
@@ -175,9 +175,9 @@ module Pay
175
175
  when "subscription"
176
176
  Pay::Stripe::Subscription.sync(checkout_session.subscription, stripe_account: stripe_account)
177
177
  end
178
- rescue ::Stripe::InvalidRequestError
178
+ rescue ::Stripe::InvalidRequestError => e
179
179
  if try > retries
180
- raise
180
+ raise Pay::Stripe::Error, e
181
181
  else
182
182
  try += 1
183
183
  sleep 0.15 * try
data/lib/pay/sync.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Pay
2
+ # Shared skeleton for the processor Charge.sync, Subscription.sync, and PaymentMethod.sync class methods
3
+ module Sync
4
+ # Runs the sync block, retrying it when a webhook and an API call race to
5
+ # create the same record. The block retrieves the processor's object itself
6
+ # when the caller didn't pass one in, so each retry gets a fresh read.
7
+ def sync_with_retries(retries: 1)
8
+ try = 0
9
+ begin
10
+ yield
11
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
12
+ try += 1
13
+ raise if try > retries
14
+ sleep 0.15 * try
15
+ retry
16
+ end
17
+ end
18
+
19
+ # The Pay::Customer for this processor, or nil when the ID is blank or the customer isn't in the database
20
+ def find_pay_customer(processor_id)
21
+ Pay::Customer.find_by(processor: pay_processor, processor_id: processor_id) if processor_id.present?
22
+ end
23
+
24
+ # "stripe" for Pay::Stripe::Charge, "paddle_billing" for Pay::PaddleBilling::Subscription, and so on
25
+ def pay_processor
26
+ name.deconstantize.demodulize.underscore
27
+ end
28
+ end
29
+ end
data/lib/pay/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pay
2
- VERSION = "11.8.0"
2
+ VERSION = "12.0.0"
3
3
  end
data/lib/pay.rb CHANGED
@@ -12,6 +12,7 @@ module Pay
12
12
  autoload :NanoId, "pay/nano_id"
13
13
  autoload :Payment, "pay/payment"
14
14
  autoload :Receipts, "pay/receipts"
15
+ autoload :Sync, "pay/sync"
15
16
  autoload :Currency, "pay/currency"
16
17
 
17
18
  # Payment processors
@@ -75,15 +76,16 @@ module Pay
75
76
  @@emails.subscription_trial_will_end = true
76
77
  @@emails.subscription_trial_ended = true
77
78
 
79
+ mattr_writer :mailer
78
80
  @@mailer = "Pay::UserMailer"
79
81
 
80
- def self.mailer=(value)
81
- @@mailer = value
82
- @@mailer_ref = nil
82
+ # Resolved on every call rather than memoized, so code reloading in development returns the current class
83
+ def self.mailer
84
+ @@mailer.constantize
83
85
  end
84
86
 
85
- def self.mailer
86
- @@mailer_ref ||= @@mailer&.constantize
87
+ def self.deprecator
88
+ @deprecator ||= ActiveSupport::Deprecation.new("13.0", "Pay")
87
89
  end
88
90
 
89
91
  mattr_accessor :parent_mailer
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pay
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.8.0
4
+ version: 12.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Charnes
@@ -42,6 +42,7 @@ files:
42
42
  - app/assets/stylesheets/pay/application.css
43
43
  - app/controllers/pay/application_controller.rb
44
44
  - app/controllers/pay/payments_controller.rb
45
+ - app/controllers/pay/webhooks/base_controller.rb
45
46
  - app/controllers/pay/webhooks/braintree_controller.rb
46
47
  - app/controllers/pay/webhooks/lemon_squeezy_controller.rb
47
48
  - app/controllers/pay/webhooks/paddle_billing_controller.rb
@@ -115,9 +116,9 @@ files:
115
116
  - lib/pay/attributes.rb
116
117
  - lib/pay/billable/sync_customer.rb
117
118
  - lib/pay/braintree.rb
119
+ - lib/pay/braintree/webhooks/subscription.rb
118
120
  - lib/pay/braintree/webhooks/subscription_canceled.rb
119
121
  - lib/pay/braintree/webhooks/subscription_charged_successfully.rb
120
- - lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb
121
122
  - lib/pay/braintree/webhooks/subscription_expired.rb
122
123
  - lib/pay/braintree/webhooks/subscription_trial_ended.rb
123
124
  - lib/pay/braintree/webhooks/subscription_went_active.rb
@@ -165,6 +166,7 @@ files:
165
166
  - lib/pay/stripe/webhooks/subscription_renewing.rb
166
167
  - lib/pay/stripe/webhooks/subscription_trial_will_end.rb
167
168
  - lib/pay/stripe/webhooks/subscription_updated.rb
169
+ - lib/pay/sync.rb
168
170
  - lib/pay/version.rb
169
171
  - lib/pay/webhooks.rb
170
172
  - lib/pay/webhooks/delegator.rb
@@ -189,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
191
  - !ruby/object:Gem::Version
190
192
  version: '0'
191
193
  requirements: []
192
- rubygems_version: 4.0.18
194
+ rubygems_version: 4.0.21
193
195
  specification_version: 4
194
196
  summary: Payments engine for Ruby on Rails
195
197
  test_files: []
@@ -1,24 +0,0 @@
1
- # A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
2
-
3
- module Pay
4
- module Braintree
5
- module Webhooks
6
- class SubscriptionChargedUnsuccessfully
7
- def call(event)
8
- # subscription = event.subscription
9
- # return if subscription.nil?
10
-
11
- # pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
- # return unless pay_subscription.present?
13
-
14
- # pay_customer = pay_subscription.customer
15
- # pay_charge = Pay::Braintree::Billable.new(pay_customer).save_transaction(subscription.transactions.first)
16
-
17
- # if Pay.send_emails
18
- # Pay.mailer.with(pay_customer: pay_charge.customer, charge: pay_charge).receipt.deliver_later
19
- # end
20
- end
21
- end
22
- end
23
- end
24
- end