pay 7.2.1 → 8.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
  4. data/app/controllers/pay/webhooks/stripe_controller.rb +2 -1
  5. data/app/jobs/pay/customer_sync_job.rb +1 -1
  6. data/app/models/concerns/pay/routing.rb +13 -0
  7. data/{lib → app/models}/pay/braintree/charge.rb +5 -12
  8. data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +31 -71
  9. data/{lib → app/models}/pay/braintree/payment_method.rb +1 -9
  10. data/{lib → app/models}/pay/braintree/subscription.rb +14 -52
  11. data/app/models/pay/charge.rb +8 -27
  12. data/app/models/pay/customer.rb +2 -15
  13. data/app/models/pay/fake_processor/charge.rb +13 -0
  14. data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +22 -35
  15. data/{lib → app/models}/pay/fake_processor/merchant.rb +2 -9
  16. data/app/models/pay/fake_processor/payment_method.rb +11 -0
  17. data/app/models/pay/fake_processor/subscription.rb +60 -0
  18. data/app/models/pay/lemon_squeezy/charge.rb +86 -0
  19. data/app/models/pay/lemon_squeezy/customer.rb +78 -0
  20. data/app/models/pay/lemon_squeezy/payment_method.rb +27 -0
  21. data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
  22. data/app/models/pay/merchant.rb +0 -11
  23. data/{lib → app/models}/pay/paddle_billing/charge.rb +2 -8
  24. data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +18 -35
  25. data/{lib → app/models}/pay/paddle_billing/payment_method.rb +2 -12
  26. data/{lib → app/models}/pay/paddle_billing/subscription.rb +9 -33
  27. data/{lib → app/models}/pay/paddle_classic/charge.rb +13 -18
  28. data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +9 -31
  29. data/{lib → app/models}/pay/paddle_classic/payment_method.rb +1 -11
  30. data/{lib → app/models}/pay/paddle_classic/subscription.rb +11 -36
  31. data/app/models/pay/payment_method.rb +0 -5
  32. data/{lib → app/models}/pay/stripe/charge.rb +6 -22
  33. data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +73 -108
  34. data/{lib → app/models}/pay/stripe/merchant.rb +2 -11
  35. data/{lib → app/models}/pay/stripe/payment_method.rb +2 -10
  36. data/{lib → app/models}/pay/stripe/subscription.rb +37 -71
  37. data/app/models/pay/subscription.rb +7 -37
  38. data/app/models/pay/webhook.rb +2 -0
  39. data/config/routes.rb +1 -0
  40. data/db/migrate/2_add_pay_sti_columns.rb +24 -0
  41. data/lib/pay/attributes.rb +11 -3
  42. data/lib/pay/braintree.rb +25 -6
  43. data/lib/pay/engine.rb +2 -0
  44. data/lib/pay/fake_processor.rb +2 -6
  45. data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
  46. data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
  47. data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
  48. data/lib/pay/lemon_squeezy.rb +56 -104
  49. data/lib/pay/paddle_billing.rb +15 -6
  50. data/lib/pay/paddle_classic.rb +11 -9
  51. data/lib/pay/receipts.rb +6 -6
  52. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +1 -1
  53. data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
  54. data/lib/pay/stripe/webhooks/subscription_trial_will_end.rb +1 -1
  55. data/lib/pay/stripe.rb +21 -7
  56. data/lib/pay/version.rb +1 -1
  57. data/lib/pay.rb +12 -1
  58. metadata +34 -38
  59. data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
  60. data/lib/pay/braintree/authorization_error.rb +0 -9
  61. data/lib/pay/braintree/error.rb +0 -23
  62. data/lib/pay/fake_processor/charge.rb +0 -21
  63. data/lib/pay/fake_processor/error.rb +0 -6
  64. data/lib/pay/fake_processor/payment_method.rb +0 -21
  65. data/lib/pay/fake_processor/subscription.rb +0 -90
  66. data/lib/pay/lemon_squeezy/billable.rb +0 -90
  67. data/lib/pay/lemon_squeezy/charge.rb +0 -68
  68. data/lib/pay/lemon_squeezy/error.rb +0 -7
  69. data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
  70. data/lib/pay/lemon_squeezy/subscription.rb +0 -185
  71. data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
  72. data/lib/pay/paddle_billing/error.rb +0 -7
  73. data/lib/pay/paddle_classic/error.rb +0 -7
  74. data/lib/pay/stripe/error.rb +0 -7
@@ -1,36 +1,20 @@
1
1
  module Pay
2
2
  module Stripe
3
- class Billable
4
- include Rails.application.routes.url_helpers
5
-
6
- attr_reader :pay_customer
7
-
8
- delegate :processor_id,
9
- :processor_id?,
10
- :email,
11
- :customer_name,
12
- :payment_method_token,
13
- :payment_method_token?,
14
- :stripe_account,
15
- to: :pay_customer
16
-
17
- def self.default_url_options
18
- Rails.application.config.action_mailer.default_url_options || {}
19
- end
3
+ class Customer < Pay::Customer
4
+ include Pay::Routing
20
5
 
21
- def initialize(pay_customer)
22
- @pay_customer = pay_customer
23
- end
6
+ has_many :charges, dependent: :destroy, class_name: "Pay::Stripe::Charge"
7
+ has_many :subscriptions, dependent: :destroy, class_name: "Pay::Stripe::Subscription"
8
+ has_many :payment_methods, dependent: :destroy, class_name: "Pay::Stripe::PaymentMethod"
9
+ has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::Stripe::PaymentMethod"
24
10
 
25
11
  # Returns a hash of attributes for the Stripe::Customer object
26
- def customer_attributes
27
- owner = pay_customer.owner
28
-
12
+ def api_record_attributes
29
13
  attributes = case owner.class.pay_stripe_customer_attributes
30
14
  when Symbol
31
- owner.send(owner.class.pay_stripe_customer_attributes, pay_customer)
15
+ owner.send(owner.class.pay_stripe_customer_attributes, self)
32
16
  when Proc
33
- owner.class.pay_stripe_customer_attributes.call(pay_customer)
17
+ owner.class.pay_stripe_customer_attributes.call(self)
34
18
  end
35
19
 
36
20
  # Guard against attributes being returned nil
@@ -39,55 +23,30 @@ module Pay
39
23
  {email: email, name: customer_name}.merge(attributes)
40
24
  end
41
25
 
42
- # Retrieves a Stripe::Customer object
43
- #
44
- # Finds an existing Stripe::Customer if processor_id exists
45
- # Creates a new Stripe::Customer using `customer_attributes` if empty processor_id
46
- #
47
- # Updates the default payment method automatically if a payment_method_token is set
48
- #
49
- # Returns a Stripe::Customer object
50
- def customer
51
- stripe_customer = if processor_id?
52
- ::Stripe::Customer.retrieve({id: processor_id, expand: ["tax", "invoice_credit_balance"]}, stripe_options)
53
- else
54
- sc = ::Stripe::Customer.create(customer_attributes.merge(expand: ["tax"]), stripe_options)
55
- pay_customer.update!(processor_id: sc.id, stripe_account: stripe_account)
56
- sc
57
- end
58
-
59
- if payment_method_token?
60
- add_payment_method(payment_method_token, default: true)
61
- pay_customer.payment_method_token = nil
26
+ def api_record(expand: ["tax", "invoice_credit_balance"])
27
+ with_lock do
28
+ if processor_id?
29
+ ::Stripe::Customer.retrieve({id: processor_id, expand: expand}, stripe_options)
30
+ else
31
+ ::Stripe::Customer.create(api_record_attributes.merge(expand: expand), stripe_options).tap do |customer|
32
+ update!(processor_id: customer.id, stripe_account: stripe_account)
33
+ end
34
+ end
62
35
  end
63
-
64
- stripe_customer
65
36
  rescue ::Stripe::StripeError => e
66
37
  raise Pay::Stripe::Error, e
67
38
  end
68
39
 
69
- # Syncs name and email to Stripe::Customer
70
- # You can also pass in other attributes that will be merged into the default attributes
71
- def update_customer!(**attributes)
72
- customer unless processor_id?
73
- ::Stripe::Customer.update(
74
- processor_id,
75
- customer_attributes.merge(attributes),
76
- stripe_options
77
- )
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)
78
43
  end
79
44
 
80
45
  # Charges an amount to the customer's default payment method
81
46
  def charge(amount, options = {})
82
- add_payment_method(payment_method_token, default: true) if payment_method_token?
83
-
84
- payment_method = pay_customer.default_payment_method
85
- args = {
86
- confirm: true,
87
- payment_method: payment_method&.processor_id
88
- }.merge(options)
89
-
47
+ args = {confirm: true, payment_method: default_payment_method&.processor_id}.merge(options)
90
48
  payment_intent = create_payment_intent(amount, args)
49
+
91
50
  Pay::Payment.new(payment_intent).validate
92
51
 
93
52
  charge = payment_intent.latest_charge
@@ -96,24 +55,6 @@ module Pay
96
55
  raise Pay::Stripe::Error, e
97
56
  end
98
57
 
99
- # Creates and returns a Stripe::PaymentIntent
100
- def create_payment_intent(amount, options = {})
101
- args = {
102
- amount: amount,
103
- currency: "usd",
104
- customer: processor_id,
105
- expand: ["latest_charge.refunds"],
106
- return_url: root_url
107
- }.merge(options)
108
-
109
- ::Stripe::PaymentIntent.create(args, stripe_options)
110
- end
111
-
112
- # Used for creating Stripe Terminal charges
113
- def terminal_charge(amount, options = {})
114
- create_payment_intent(amount, options.merge(payment_method_types: ["card_present"], capture_method: "manual"))
115
- end
116
-
117
58
  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
118
59
  quantity = options.delete(:quantity)
119
60
  opts = {
@@ -122,7 +63,7 @@ module Pay
122
63
  }.merge(options)
123
64
 
124
65
  # Load the Stripe customer to verify it exists and update payment method if needed
125
- opts[:customer] = customer.id
66
+ opts[:customer] = processor_id || api_record.id
126
67
 
127
68
  # Create subscription on Stripe
128
69
  stripe_sub = ::Stripe::Subscription.create(opts.merge(Pay::Stripe::Subscription.expand_options), stripe_options)
@@ -141,7 +82,7 @@ module Pay
141
82
  end
142
83
 
143
84
  def add_payment_method(payment_method_id, default: false)
144
- customer unless processor_id?
85
+ api_record unless processor_id?
145
86
  payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: processor_id}, stripe_options)
146
87
 
147
88
  if default
@@ -159,50 +100,56 @@ module Pay
159
100
 
160
101
  # Save the Stripe::PaymentMethod to the database
161
102
  def save_payment_method(payment_method, default:)
162
- pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.id).first_or_initialize
103
+ pay_payment_method = payment_methods.where(processor_id: payment_method.id).first_or_initialize
163
104
 
164
105
  attributes = Pay::Stripe::PaymentMethod.extract_attributes(payment_method).merge(default: default)
165
106
 
166
107
  # Ignore the payment method if it's already in the database
167
- pay_customer.payment_methods.where.not(id: pay_payment_method.id).update_all(default: false) if default
108
+ payment_methods.where.not(id: pay_payment_method.id).update_all(default: false) if default
168
109
  pay_payment_method.update!(attributes)
169
110
 
170
111
  # Reload the Rails association
171
- pay_customer.reload_default_payment_method
112
+ reload_default_payment_method
172
113
 
173
114
  pay_payment_method
174
115
  end
175
116
 
176
- def processor_subscription(subscription_id, options = {})
177
- ::Stripe::Subscription.retrieve(options.merge(id: subscription_id), stripe_options)
178
- end
117
+ ### Stripe extras
179
118
 
180
- def invoice!(options = {})
181
- return unless processor_id?
182
- ::Stripe::Invoice.create(options.merge(customer: processor_id), stripe_options).pay
119
+ # Creates and returns a Stripe::PaymentIntent
120
+ def create_payment_intent(amount, options = {})
121
+ args = {
122
+ amount: amount,
123
+ currency: "usd",
124
+ customer: processor_id || api_record.id,
125
+ expand: ["latest_charge.refunds"],
126
+ return_url: root_url
127
+ }.merge(options)
128
+
129
+ ::Stripe::PaymentIntent.create(args, stripe_options)
183
130
  end
184
131
 
185
- def upcoming_invoice
186
- ::Stripe::Invoice.upcoming({customer: processor_id}, stripe_options)
132
+ # Used for creating Stripe Terminal charges
133
+ def terminal_charge(amount, options = {})
134
+ create_payment_intent(amount, options.merge(payment_method_types: ["card_present"], capture_method: "manual"))
187
135
  end
188
136
 
189
137
  def create_setup_intent(options = {})
190
- customer unless processor_id?
191
- ::Stripe::SetupIntent.create({
192
- customer: processor_id,
193
- usage: :off_session
194
- }.merge(options), stripe_options)
138
+ ::Stripe::SetupIntent.create({customer: processor_id || api_record.id, usage: :off_session}.merge(options), stripe_options)
195
139
  end
196
140
 
197
- def trial_end_date(stripe_sub)
198
- # Times in Stripe are returned in UTC
199
- stripe_sub.trial_end.present? ? Time.at(stripe_sub.trial_end) : nil
141
+ def invoice!(options = {})
142
+ ::Stripe::Invoice.create(options.merge(customer: processor_id || api_record.id), stripe_options).pay
143
+ end
144
+
145
+ def upcoming_invoice
146
+ ::Stripe::Invoice.upcoming({customer: processor_id || api_record.id}, stripe_options)
200
147
  end
201
148
 
202
149
  # Syncs a customer's subscriptions from Stripe to the database.
203
150
  # Note that by default canceled subscriptions are NOT returned by Stripe. In order to include them, use `sync_subscriptions(status: "all")`.
204
151
  def sync_subscriptions(**options)
205
- subscriptions = ::Stripe::Subscription.list(options.merge(customer: customer), stripe_options)
152
+ subscriptions = ::Stripe::Subscription.list(options.with_defaults(customer: processor_id), stripe_options)
206
153
  subscriptions.map do |subscription|
207
154
  Pay::Stripe::Subscription.sync(subscription.id)
208
155
  end
@@ -221,7 +168,7 @@ module Pay
221
168
  # checkout(line_items: "price_12345", allow_promotion_codes: true)
222
169
  #
223
170
  def checkout(**options)
224
- customer unless processor_id?
171
+ api_record unless processor_id?
225
172
  args = {
226
173
  customer: processor_id,
227
174
  mode: "payment"
@@ -261,7 +208,7 @@ module Pay
261
208
  # checkout_charge(amount: 15_00, name: "T-shirt", quantity: 2)
262
209
  #
263
210
  def checkout_charge(amount:, name:, quantity: 1, **options)
264
- customer unless processor_id?
211
+ api_record unless processor_id?
265
212
  currency = options.delete(:currency) || "usd"
266
213
  checkout(
267
214
  line_items: {
@@ -277,7 +224,7 @@ module Pay
277
224
  end
278
225
 
279
226
  def billing_portal(**options)
280
- customer unless processor_id?
227
+ api_record unless processor_id?
281
228
  args = {
282
229
  customer: processor_id,
283
230
  return_url: options.delete(:return_url) || root_url
@@ -285,10 +232,28 @@ module Pay
285
232
  ::Stripe::BillingPortal::Session.create(args.merge(options), stripe_options)
286
233
  end
287
234
 
235
+ def customer_session(**options)
236
+ api_record unless processor_id?
237
+ args = {customer: processor_id}
238
+ ::Stripe::CustomerSession.create(args.merge(options), stripe_options)
239
+ end
240
+
288
241
  def authorize(amount, options = {})
289
242
  charge(amount, options.merge(capture_method: :manual))
290
243
  end
291
244
 
245
+ # Creates a meter event to bill for usage
246
+ #
247
+ # create_meter_event(:api_request, value: 1)
248
+ # create_meter_event(:api_request, token: 7)
249
+ def create_meter_event(event_name, payload: {}, **options)
250
+ api_record unless processor_id?
251
+ ::Stripe::Billing::MeterEvent.create({
252
+ event_name: event_name,
253
+ payload: {stripe_customer_id: processor_id}.merge(payload)
254
+ }.merge(options))
255
+ end
256
+
292
257
  private
293
258
 
294
259
  # Options for Stripe requests
@@ -299,7 +264,7 @@ module Pay
299
264
  # Includes the `session_id` param for Stripe Checkout with existing params (and makes sure the curly braces aren't escaped)
300
265
  def merge_session_id_param(url)
301
266
  uri = URI.parse(url)
302
- uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s).to_h.merge("session_id" => "{CHECKOUT_SESSION_ID}").to_a)
267
+ uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s).to_h.merge("stripe_checkout_session_id" => "{CHECKOUT_SESSION_ID}").to_a)
303
268
  uri.to_s.gsub("%7BCHECKOUT_SESSION_ID%7D", "{CHECKOUT_SESSION_ID}")
304
269
  end
305
270
  end
@@ -1,15 +1,6 @@
1
1
  module Pay
2
2
  module Stripe
3
- class Merchant
4
- attr_reader :pay_merchant
5
-
6
- delegate :processor_id,
7
- to: :pay_merchant
8
-
9
- def initialize(pay_merchant)
10
- @pay_merchant = pay_merchant
11
- end
12
-
3
+ class Merchant < Pay::Merchant
13
4
  def create_account(**options)
14
5
  defaults = {
15
6
  type: "express",
@@ -20,7 +11,7 @@ module Pay
20
11
  }
21
12
 
22
13
  stripe_account = ::Stripe::Account.create(defaults.merge(options))
23
- pay_merchant.update(processor_id: stripe_account.id)
14
+ update(processor_id: stripe_account.id)
24
15
  stripe_account
25
16
  rescue ::Stripe::StripeError => e
26
17
  raise Pay::Stripe::Error, e
@@ -1,14 +1,6 @@
1
1
  module Pay
2
2
  module Stripe
3
- class PaymentMethod
4
- attr_reader :pay_payment_method
5
-
6
- delegate :customer, :processor_id, to: :pay_payment_method
7
-
8
- def initialize(pay_payment_method)
9
- @pay_payment_method = pay_payment_method
10
- end
11
-
3
+ class PaymentMethod < Pay::PaymentMethod
12
4
  # Syncs a PaymentIntent's payment method to the database
13
5
  def self.sync_payment_intent(id, stripe_account: nil)
14
6
  payment_intent = ::Stripe::PaymentIntent.retrieve({id: id, expand: ["payment_method"]}, {stripe_account: stripe_account}.compact)
@@ -39,7 +31,7 @@ module Pay
39
31
  return
40
32
  end
41
33
 
42
- default_payment_method_id = pay_customer.customer.invoice_settings.default_payment_method
34
+ default_payment_method_id = pay_customer.api_record.invoice_settings.default_payment_method
43
35
  default = (id == default_payment_method_id)
44
36
 
45
37
  attributes = extract_attributes(object).merge(default: default, stripe_account: stripe_account)
@@ -1,33 +1,7 @@
1
1
  module Pay
2
2
  module Stripe
3
- class Subscription
4
- attr_accessor :stripe_subscription
5
- attr_reader :pay_subscription
6
-
7
- delegate :active?,
8
- :canceled?,
9
- :ends_at?,
10
- :ends_at,
11
- :name,
12
- :on_trial?,
13
- :past_due?,
14
- :pause_starts_at,
15
- :pause_starts_at?,
16
- :processor_id,
17
- :processor_plan,
18
- :processor_subscription,
19
- :prorate,
20
- :prorate?,
21
- :quantity,
22
- :quantity?,
23
- :stripe_account,
24
- :subscription_items,
25
- :trial_ends_at,
26
- :pause_behavior,
27
- :pause_resumes_at,
28
- :current_period_start,
29
- :current_period_end,
30
- to: :pay_subscription
3
+ class Subscription < Pay::Subscription
4
+ attr_writer :api_record
31
5
 
32
6
  def self.sync_from_checkout_session(session_id, stripe_account: nil)
33
7
  checkout_session = ::Stripe::Checkout::Session.retrieve({id: session_id}, {stripe_account: stripe_account}.compact)
@@ -125,7 +99,7 @@ module Pay
125
99
  end
126
100
 
127
101
  # Cache the Stripe subscription on the Pay::Subscription that we return
128
- pay_subscription.stripe_subscription = object
102
+ pay_subscription.api_record = object
129
103
 
130
104
  # Sync the latest charge if we already have it loaded (like during subscrbe), otherwise, let webhooks take care of creating it
131
105
  if (charge = object.try(:latest_invoice).try(:charge)) && charge.try(:status) == "succeeded"
@@ -157,30 +131,20 @@ module Pay
157
131
  }
158
132
  end
159
133
 
160
- def initialize(pay_subscription)
161
- @pay_subscription = pay_subscription
162
- end
163
-
164
- def subscription(**options)
165
- options[:id] = processor_id
166
- @stripe_subscription ||= ::Stripe::Subscription.retrieve(options.merge(expand_options), {stripe_account: stripe_account}.compact)
167
- end
168
-
169
- def reload!
170
- @stripe_subscription = nil
134
+ def api_record(**options)
135
+ @api_record ||= ::Stripe::Subscription.retrieve(options.with_defaults(id: processor_id).merge(expand_options), {stripe_account: stripe_account}.compact)
171
136
  end
172
137
 
173
138
  # Returns a SetupIntent or PaymentIntent client secret for the subscription
174
139
  def client_secret
175
- stripe_sub = subscription
176
- stripe_sub&.pending_setup_intent&.client_secret || stripe_sub&.latest_invoice&.payment_intent&.client_secret
140
+ api_record&.pending_setup_intent&.client_secret || api_record&.latest_invoice&.payment_intent&.client_secret
177
141
  end
178
142
 
179
143
  # Sets the default_payment_method on a subscription
180
144
  # Pass an empty string to unset
181
145
  def update_payment_method(id)
182
- @stripe_subscription = ::Stripe::Subscription.update(processor_id, {default_payment_method: id}.merge(expand_options), stripe_options)
183
- pay_subscription.update(payment_method_id: @stripe_subscription.default_payment_method&.id)
146
+ @api_record = ::Stripe::Subscription.update(processor_id, {default_payment_method: id}.merge(expand_options), stripe_options)
147
+ update(payment_method_id: @api_record.default_payment_method&.id)
184
148
  rescue ::Stripe::StripeError => e
185
149
  raise Pay::Stripe::Error, e
186
150
  end
@@ -195,8 +159,8 @@ module Pay
195
159
  if past_due? && options.fetch(:past_due_cancel_now, true)
196
160
  cancel_now!
197
161
  else
198
- @stripe_subscription = ::Stripe::Subscription.update(processor_id, {cancel_at_period_end: true}.merge(expand_options), stripe_options)
199
- pay_subscription.update(ends_at: (on_trial? ? trial_ends_at : Time.at(@stripe_subscription.current_period_end)))
162
+ @api_record = ::Stripe::Subscription.update(processor_id, {cancel_at_period_end: true}.merge(expand_options), stripe_options)
163
+ update(ends_at: (on_trial? ? trial_ends_at : Time.at(@api_record.current_period_end)))
200
164
  end
201
165
  rescue ::Stripe::StripeError => e
202
166
  raise Pay::Stripe::Error, e
@@ -209,8 +173,8 @@ module Pay
209
173
  def cancel_now!(**options)
210
174
  return if canceled? && ends_at.past?
211
175
 
212
- @stripe_subscription = ::Stripe::Subscription.cancel(processor_id, options.merge(expand_options), stripe_options)
213
- pay_subscription.update(ends_at: Time.current, status: :canceled)
176
+ @api_record = ::Stripe::Subscription.cancel(processor_id, options.merge(expand_options), stripe_options)
177
+ update(ends_at: Time.current, status: :canceled)
214
178
  rescue ::Stripe::StripeError => e
215
179
  raise Pay::Stripe::Error, e
216
180
  end
@@ -223,11 +187,11 @@ module Pay
223
187
  subscription_item_id = options.delete(:subscription_item_id) || subscription_items&.first&.dig("id")
224
188
  if subscription_item_id
225
189
  ::Stripe::SubscriptionItem.update(subscription_item_id, options.merge(quantity: quantity), stripe_options)
226
- @stripe_subscription = nil
190
+ @api_record = nil
227
191
  else
228
- @stripe_subscription = ::Stripe::Subscription.update(processor_id, options.merge(quantity: quantity).merge(expand_options), stripe_options)
192
+ @api_record = ::Stripe::Subscription.update(processor_id, options.merge(quantity: quantity).merge(expand_options), stripe_options)
229
193
  end
230
- true
194
+ update(quantity: quantity)
231
195
  rescue ::Stripe::StripeError => e
232
196
  raise Pay::Stripe::Error, e
233
197
  end
@@ -265,12 +229,12 @@ module Pay
265
229
  # https://docs.stripe.com/billing/subscriptions/pause-payment
266
230
  def pause(**options)
267
231
  attributes = {pause_collection: options.reverse_merge(behavior: "void")}
268
- @stripe_subscription = ::Stripe::Subscription.update(processor_id, attributes.merge(expand_options), stripe_options)
269
- behavior = @stripe_subscription.pause_collection&.behavior
270
- pay_subscription.update(
232
+ @api_record = ::Stripe::Subscription.update(processor_id, attributes.merge(expand_options), stripe_options)
233
+ behavior = @api_record.pause_collection&.behavior
234
+ update(
271
235
  pause_behavior: behavior,
272
- pause_resumes_at: (@stripe_subscription.pause_collection&.resumes_at ? Time.at(@stripe_subscription.pause_collection&.resumes_at) : nil),
273
- pause_starts_at: ((behavior == "void") ? Time.at(@stripe_subscription.current_period_end) : nil)
236
+ pause_resumes_at: (@api_record.pause_collection&.resumes_at ? Time.at(@api_record.pause_collection&.resumes_at) : nil),
237
+ pause_starts_at: ((behavior == "void") ? Time.at(@api_record.current_period_end) : nil)
274
238
  )
275
239
  end
276
240
 
@@ -278,8 +242,8 @@ module Pay
278
242
  #
279
243
  # https://docs.stripe.com/billing/subscriptions/pause-payment#unpausing
280
244
  def unpause
281
- @stripe_subscription = ::Stripe::Subscription.update(processor_id, {pause_collection: ""}.merge(expand_options), stripe_options)
282
- pay_subscription.update(
245
+ @api_record = ::Stripe::Subscription.update(processor_id, {pause_collection: ""}.merge(expand_options), stripe_options)
246
+ update(
283
247
  pause_behavior: nil,
284
248
  pause_resumes_at: nil,
285
249
  pause_starts_at: nil
@@ -298,16 +262,14 @@ module Pay
298
262
  if paused?
299
263
  unpause
300
264
  else
301
- @stripe_subscription = ::Stripe::Subscription.update(
302
- processor_id,
303
- {
304
- plan: processor_plan,
305
- trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
306
- cancel_at_period_end: false
307
- }.merge(expand_options),
308
- stripe_options
309
- )
265
+ @api_record = ::Stripe::Subscription.update(processor_id, {
266
+ plan: processor_plan,
267
+ trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
268
+ cancel_at_period_end: false
269
+ }.merge(expand_options),
270
+ stripe_options)
310
271
  end
272
+ update(ends_at: nil, status: :active)
311
273
  rescue ::Stripe::StripeError => e
312
274
  raise Pay::Stripe::Error, e
313
275
  end
@@ -317,7 +279,7 @@ module Pay
317
279
 
318
280
  proration_behavior = options.delete(:proration_behavior) || (prorate ? "always_invoice" : "none")
319
281
 
320
- @stripe_subscription = ::Stripe::Subscription.update(
282
+ @api_record = ::Stripe::Subscription.update(
321
283
  processor_id,
322
284
  {
323
285
  cancel_at_period_end: false,
@@ -330,11 +292,11 @@ module Pay
330
292
  )
331
293
 
332
294
  # Validate that swap was successful and handle SCA if needed
333
- if (payment_intent = @stripe_subscription.latest_invoice.payment_intent)
295
+ if (payment_intent = @api_record.latest_invoice.payment_intent)
334
296
  Pay::Payment.new(payment_intent).validate
335
297
  end
336
298
 
337
- pay_subscription.sync!(object: @stripe_subscription)
299
+ sync!(object: @api_record)
338
300
  rescue ::Stripe::StripeError => e
339
301
  raise Pay::Stripe::Error, e
340
302
  end
@@ -374,7 +336,7 @@ module Pay
374
336
  payment_intent = ::Stripe::PaymentIntent.retrieve({id: payment_intent_id}, stripe_options)
375
337
 
376
338
  payment_intent = if payment_intent.status == "requires_payment_method"
377
- ::Stripe::PaymentIntent.confirm(payment_intent_id, {payment_method: pay_subscription.customer.default_payment_method.processor_id}, stripe_options)
339
+ ::Stripe::PaymentIntent.confirm(payment_intent_id, {payment_method: customer.default_payment_method.processor_id}, stripe_options)
378
340
  else
379
341
  ::Stripe::PaymentIntent.confirm(payment_intent_id, stripe_options)
380
342
  end
@@ -390,6 +352,10 @@ module Pay
390
352
  end
391
353
  end
392
354
 
355
+ def latest_payment
356
+ api_record(expand: ["latest_invoice.payment_intent"]).latest_invoice.payment_intent
357
+ end
358
+
393
359
  private
394
360
 
395
361
  # Options for Stripe requests
@@ -9,7 +9,7 @@ module Pay
9
9
 
10
10
  # Scopes
11
11
  scope :for_name, ->(name) { where(name: name) }
12
- scope :on_trial, -> { where(status: ["trialing", "active"]).where("trial_ends_at > ?", Time.current) }
12
+ scope :on_trial, -> { where(status: ["on_trial", "trialing", "active"]).where("trial_ends_at > ?", Time.current) }
13
13
  scope :canceled, -> { where.not(ends_at: nil) }
14
14
  scope :cancelled, -> { canceled }
15
15
  scope :on_grace_period, -> { where("#{table_name}.ends_at IS NOT NULL AND #{table_name}.ends_at > ?", Time.current) }
@@ -40,10 +40,8 @@ module Pay
40
40
  validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
41
41
  validates :status, presence: true
42
42
 
43
- delegate_missing_to :payment_processor
44
-
45
43
  # Helper methods for payment processors
46
- %w[braintree stripe paddle_billing paddle_classic fake_processor].each do |processor_name|
44
+ %w[braintree stripe paddle_billing paddle_classic lemon_squeezy fake_processor].each do |processor_name|
47
45
  define_method :"#{processor_name}?" do
48
46
  customer.processor == processor_name
49
47
  end
@@ -55,16 +53,8 @@ module Pay
55
53
  joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
56
54
  end
57
55
 
58
- def self.pay_processor_for(name)
59
- "Pay::#{name.to_s.classify}::Subscription".constantize
60
- end
61
-
62
- def payment_processor
63
- @payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
64
- end
65
-
66
56
  def sync!(**options)
67
- self.class.pay_processor_for(customer.processor).sync(processor_id, **options)
57
+ self.class.sync(processor_id, **options)
68
58
  reload
69
59
  end
70
60
 
@@ -105,6 +95,10 @@ module Pay
105
95
  ends_at? && ends_at <= Time.current
106
96
  end
107
97
 
98
+ def on_grace_period?
99
+ ends_at? && ends_at > Time.current
100
+ end
101
+
108
102
  # If you cancel during a trial, you should still retain access until the end of the trial
109
103
  # Otherwise a subscription is active unless it has ended or is currently paused
110
104
  # Check the subscription status so we don't accidentally consider "incomplete", "unpaid", or other statuses as active
@@ -129,35 +123,11 @@ module Pay
129
123
  past_due? || incomplete?
130
124
  end
131
125
 
132
- def change_quantity(quantity, **options)
133
- payment_processor.change_quantity(quantity, **options)
134
- update(quantity: quantity)
135
- end
136
-
137
- def resume
138
- payment_processor.resume
139
- update(ends_at: nil, status: :active)
140
- self
141
- end
142
-
143
- def swap(plan, **options)
144
- raise ArgumentError, "plan must be a string. Got `#{plan.inspect}` instead." unless plan.is_a?(String)
145
- payment_processor.swap(plan, **options)
146
- end
147
-
148
126
  def swap_and_invoice(plan)
149
127
  swap(plan)
150
128
  customer.invoice!(subscription: processor_id)
151
129
  end
152
130
 
153
- def processor_subscription(**options)
154
- payment_processor.subscription(**options)
155
- end
156
-
157
- def latest_payment
158
- processor_subscription(expand: ["latest_invoice.payment_intent"]).latest_invoice.payment_intent
159
- end
160
-
161
131
  private
162
132
 
163
133
  def cancel_if_active
@@ -21,6 +21,8 @@ module Pay
21
21
  to_recursive_ostruct(event["data"])
22
22
  when "paddle_classic"
23
23
  to_recursive_ostruct(event)
24
+ when "lemon_squeezy"
25
+ Pay::LemonSqueezy.construct_from_webhook_event(event)
24
26
  when "stripe"
25
27
  ::Stripe::Event.construct_from(event)
26
28
  else
data/config/routes.rb CHANGED
@@ -6,4 +6,5 @@ Pay::Engine.routes.draw do
6
6
  post "webhooks/braintree", to: "pay/webhooks/braintree#create" if Pay::Braintree.enabled?
7
7
  post "webhooks/paddle_billing", to: "pay/webhooks/paddle_billing#create" if Pay::PaddleBilling.enabled?
8
8
  post "webhooks/paddle_classic", to: "pay/webhooks/paddle_classic#create" if Pay::PaddleClassic.enabled?
9
+ post "webhooks/lemon_squeezy", to: "pay/webhooks/lemon_squeezy#create" if Pay::LemonSqueezy.enabled?
9
10
  end