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,27 +1,6 @@
1
1
  module Pay
2
2
  module PaddleBilling
3
- class Subscription
4
- attr_reader :pay_subscription
5
-
6
- delegate :active?,
7
- :canceled?,
8
- :on_grace_period?,
9
- :on_trial?,
10
- :ends_at,
11
- :name,
12
- :owner,
13
- :pause_starts_at,
14
- :pause_starts_at?,
15
- :processor_id,
16
- :processor_plan,
17
- :processor_subscription,
18
- :prorate,
19
- :prorate?,
20
- :quantity,
21
- :quantity?,
22
- :trial_ends_at,
23
- to: :pay_subscription
24
-
3
+ class Subscription < Pay::Subscription
25
4
  def self.sync_from_transaction(transaction_id)
26
5
  transaction = ::Paddle::Transaction.retrieve(id: transaction_id)
27
6
  sync(transaction.subscription_id) if transaction.subscription_id
@@ -85,12 +64,8 @@ module Pay
85
64
  end
86
65
  end
87
66
 
88
- def initialize(pay_subscription)
89
- @pay_subscription = pay_subscription
90
- end
91
-
92
- def subscription(**options)
93
- @paddle_billing_subscription ||= ::Paddle::Subscription.retrieve(id: processor_id, **options)
67
+ def api_record(**options)
68
+ @api_record ||= ::Paddle::Subscription.retrieve(id: processor_id, **options)
94
69
  end
95
70
 
96
71
  # Get a transaction to update payment method
@@ -107,7 +82,7 @@ module Pay
107
82
  id: processor_id,
108
83
  effective_from: options.fetch(:effective_from, (paused? ? "immediately" : "next_billing_period"))
109
84
  )
110
- pay_subscription.update(
85
+ update(
111
86
  status: response.status,
112
87
  ends_at: response.scheduled_change&.effective_at || Time.current
113
88
  )
@@ -128,6 +103,7 @@ module Pay
128
103
  }]
129
104
 
130
105
  ::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
106
+ update(quantity: quantity)
131
107
  rescue ::Paddle::Error => e
132
108
  raise Pay::PaddleBilling::Error, e
133
109
  end
@@ -139,12 +115,12 @@ module Pay
139
115
  end
140
116
 
141
117
  def paused?
142
- pay_subscription.status == "paused"
118
+ status == "paused"
143
119
  end
144
120
 
145
121
  def pause
146
122
  response = ::Paddle::Subscription.pause(id: processor_id)
147
- pay_subscription.update!(status: :paused, pause_starts_at: response.scheduled_change.effective_at)
123
+ update!(status: :paused, pause_starts_at: response.scheduled_change.effective_at)
148
124
  rescue ::Paddle::Error => e
149
125
  raise Pay::PaddleBilling::Error, e
150
126
  end
@@ -166,7 +142,7 @@ module Pay
166
142
  ::Paddle::Subscription.resume(id: processor_id, effective_from: "immediately")
167
143
  end
168
144
 
169
- pay_subscription.update(status: :active, pause_starts_at: nil)
145
+ update(ends_at: nil, status: :active, pause_starts_at: nil)
170
146
  rescue ::Paddle::Error => e
171
147
  raise Pay::PaddleBilling::Error, e
172
148
  end
@@ -178,7 +154,7 @@ module Pay
178
154
  }]
179
155
 
180
156
  ::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
181
- pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
157
+ update(processor_plan: plan, ends_at: nil, status: :active)
182
158
  end
183
159
 
184
160
  # Retries the latest invoice for a Past Due subscription
@@ -1,33 +1,28 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
- class Charge
4
- attr_reader :pay_charge
3
+ class Charge < Pay::Charge
4
+ store_accessor :data, :paddle_receipt_url
5
5
 
6
- delegate :processor_id, :customer, to: :pay_charge
7
-
8
- def initialize(pay_charge)
9
- @pay_charge = pay_charge
10
- end
11
-
12
- def charge
6
+ def api_record
13
7
  return unless customer.subscription
8
+
14
9
  payments = PaddleClassic.client.payments.list(subscription_id: customer.subscription.processor_id)
15
10
  charges = payments.data.select { |p| p[:id].to_s == processor_id }
16
11
  charges.try(:first)
17
- rescue ::Paddle::Error => e
12
+ rescue ::Paddle::Classic::Error => e
18
13
  raise Pay::PaddleClassic::Error, e
19
14
  end
20
15
 
21
- def refund!(amount_to_refund)
16
+ def refund!(amount_to_refund = nil)
22
17
  return unless customer.subscription
18
+ amount_to_refund ||= amount
19
+
23
20
  payments = PaddleClassic.client.payments.list(subscription_id: customer.subscription.processor_id, is_paid: 1)
24
- if payments.total > 0
25
- PaddleClassic.client.payments.refund(order_id: payments.data.last[:id], amount: amount_to_refund)
26
- pay_charge.update(amount_refunded: amount_to_refund)
27
- else
28
- raise Error, "Payment not found"
29
- end
30
- rescue ::Paddle::Error => e
21
+ raise Error, "Payment not found" unless payments.total > 0
22
+
23
+ PaddleClassic.client.payments.refund(order_id: payments.data.last[:id], amount: amount_to_refund)
24
+ update(amount_refunded: amount_to_refund)
25
+ rescue ::Paddle::Classic::Error => e
31
26
  raise Pay::PaddleClassic::Error, e
32
27
  end
33
28
  end
@@ -1,29 +1,18 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
- class Billable
4
- attr_reader :pay_customer
3
+ class Customer < Pay::Customer
4
+ has_many :charges, dependent: :destroy, class_name: "Pay::PaddleClassic::Charge"
5
+ has_many :subscriptions, dependent: :destroy, class_name: "Pay::PaddleClassic::Subscription"
6
+ has_many :payment_methods, dependent: :destroy, class_name: "Pay::PaddleClassic::PaymentMethod"
7
+ has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::PaddleClassic::PaymentMethod"
5
8
 
6
- delegate :processor_id,
7
- :processor_id?,
8
- :email,
9
- :customer_name,
10
- :card_token,
11
- to: :pay_customer
12
-
13
- def initialize(pay_customer)
14
- @pay_customer = pay_customer
15
- end
16
-
17
- def customer
18
- # pass
9
+ def api_record
19
10
  end
20
11
 
21
- def update_customer!
22
- # pass
12
+ def update_api_record
23
13
  end
24
14
 
25
15
  def charge(amount, options = {})
26
- subscription = pay_customer.subscription
27
16
  return unless subscription.processor_id
28
17
  raise Pay::Error, "A charge_name is required to create a one-time charge" if options[:charge_name].nil?
29
18
 
@@ -38,7 +27,7 @@ module Pay
38
27
  # Lookup subscription payment method details
39
28
  attributes.merge! Pay::PaddleClassic::PaymentMethod.payment_method_details_for(subscription_id: subscription.processor_id)
40
29
 
41
- charge = pay_customer.charges.find_or_initialize_by(processor_id: response[:invoice_id])
30
+ charge = charges.find_or_initialize_by(processor_id: response[:invoice_id])
42
31
  charge.update(attributes)
43
32
  charge
44
33
  rescue ::Paddle::Error => e
@@ -52,18 +41,7 @@ module Pay
52
41
  # Paddle does not use payment method tokens. The method signature has it here
53
42
  # to have a uniform API with the other payment processors.
54
43
  def add_payment_method(token = nil, default: true)
55
- Pay::PaddleClassic::PaymentMethod.sync(pay_customer: pay_customer)
56
- end
57
-
58
- def trial_end_date(subscription)
59
- return unless subscription.state == "trialing"
60
- Time.zone.parse(subscription.next_payment[:date]).end_of_day
61
- end
62
-
63
- def processor_subscription(subscription_id, options = {})
64
- PaddleClassic.client.users.list(subscription_id: subscription_id).data.try(:first)
65
- rescue ::Paddle::Error => e
66
- raise Pay::PaddleClassic::Error, e
44
+ Pay::PaddleClassic::PaymentMethod.sync(pay_customer: self)
67
45
  end
68
46
  end
69
47
  end
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
- class PaymentMethod
4
- attr_reader :pay_payment_method
5
-
6
- delegate :customer, :processor_id, to: :pay_payment_method
7
-
3
+ class PaymentMethod < Pay::PaymentMethod
8
4
  # Paddle doesn't provide PaymentMethod IDs, so we have to lookup via the Customer
9
5
  def self.sync(pay_customer:, attributes: nil)
10
6
  return unless pay_customer.subscription
@@ -44,15 +40,9 @@ module Pay
44
40
  end
45
41
  end
46
42
 
47
- def initialize(pay_payment_method)
48
- @pay_payment_method = pay_payment_method
49
- end
50
-
51
- # Sets payment method as default
52
43
  def make_default!
53
44
  end
54
45
 
55
- # Remove payment method
56
46
  def detach
57
47
  end
58
48
  end
@@ -1,27 +1,6 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
- class Subscription
4
- attr_reader :pay_subscription
5
-
6
- delegate :active?,
7
- :canceled?,
8
- :on_grace_period?,
9
- :on_trial?,
10
- :ends_at,
11
- :name,
12
- :owner,
13
- :pause_starts_at,
14
- :pause_starts_at?,
15
- :processor_id,
16
- :processor_plan,
17
- :processor_subscription,
18
- :prorate,
19
- :prorate?,
20
- :quantity,
21
- :quantity?,
22
- :trial_ends_at,
23
- to: :pay_subscription
24
-
3
+ class Subscription < Pay::Subscription
25
4
  def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
26
5
  # Passthrough is not return from this API, so we can't use that
27
6
  object ||= PaddleClassic.client.users.list(subscription_id: subscription_id).data.try(:first)
@@ -68,11 +47,7 @@ module Pay
68
47
  end
69
48
  end
70
49
 
71
- def initialize(pay_subscription)
72
- @pay_subscription = pay_subscription
73
- end
74
-
75
- def subscription(**options)
50
+ def api_record(**options)
76
51
  PaddleClassic.client.users.list(subscription_id: processor_id).data.try(:first)
77
52
  rescue ::Paddle::Error => e
78
53
  raise Pay::PaddleClassic::Error, e
@@ -87,17 +62,17 @@ module Pay
87
62
  elsif paused?
88
63
  pause_starts_at
89
64
  else
90
- Time.parse(processor_subscription.next_payment.date)
65
+ Time.parse(api_record.next_payment.date)
91
66
  end
92
67
 
93
68
  PaddleClassic.client.users.cancel(subscription_id: processor_id)
94
- pay_subscription.update(
69
+ update(
95
70
  status: (ends_at.future? ? :active : :canceled),
96
71
  ends_at: ends_at
97
72
  )
98
73
 
99
74
  # Remove payment methods since customer cannot be reused after cancelling
100
- Pay::PaymentMethod.where(customer_id: pay_subscription.customer_id).destroy_all
75
+ Pay::PaymentMethod.where(customer_id: customer_id).destroy_all
101
76
  rescue ::Paddle::Error => e
102
77
  raise Pay::PaddleClassic::Error, e
103
78
  end
@@ -106,10 +81,10 @@ module Pay
106
81
  return if canceled?
107
82
 
108
83
  PaddleClassic.client.users.cancel(subscription_id: processor_id)
109
- pay_subscription.update(status: :canceled, ends_at: Time.current)
84
+ update(status: :canceled, ends_at: Time.current)
110
85
 
111
86
  # Remove payment methods since customer cannot be reused after cancelling
112
- Pay::PaymentMethod.where(customer_id: pay_subscription.customer_id).destroy_all
87
+ Pay::PaymentMethod.where(customer_id: customer_id).destroy_all
113
88
  rescue ::Paddle::Error => e
114
89
  raise Pay::PaddleClassic::Error, e
115
90
  end
@@ -125,12 +100,12 @@ module Pay
125
100
  end
126
101
 
127
102
  def paused?
128
- pay_subscription.status == "paused"
103
+ status == "paused"
129
104
  end
130
105
 
131
106
  def pause
132
107
  response = PaddleClassic.client.users.pause(subscription_id: processor_id)
133
- pay_subscription.update(status: :paused, pause_starts_at: Time.zone.parse(response.dig(:next_payment, :date)))
108
+ update(status: :paused, pause_starts_at: Time.zone.parse(response.dig(:next_payment, :date)))
134
109
  rescue ::Paddle::Error => e
135
110
  raise Pay::PaddleClassic::Error, e
136
111
  end
@@ -145,7 +120,7 @@ module Pay
145
120
  end
146
121
 
147
122
  PaddleClassic.client.users.unpause(subscription_id: processor_id)
148
- pay_subscription.update(status: :active, pause_starts_at: nil)
123
+ update(ends_at: nil, status: :active, pause_starts_at: nil)
149
124
  rescue ::Paddle::Error => e
150
125
  raise Pay::PaddleClassic::Error, e
151
126
  end
@@ -157,7 +132,7 @@ module Pay
157
132
  attributes[:quantity] = quantity if quantity?
158
133
  PaddleClassic.client.users.update(subscription_id: processor_id, **attributes)
159
134
 
160
- pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
135
+ update(processor_plan: plan, ends_at: nil, status: :active)
161
136
  rescue ::Paddle::Error => e
162
137
  raise Pay::PaddleClassic::Error, e
163
138
  end
@@ -1,7 +1,5 @@
1
1
  module Pay
2
2
  class PaymentMethod < Pay::ApplicationRecord
3
- self.inheritance_column = nil
4
-
5
3
  belongs_to :customer
6
4
 
7
5
  store_accessor :data, :brand # Visa, Mastercard, Discover, PayPal
@@ -12,9 +10,6 @@ module Pay
12
10
  store_accessor :data, :username
13
11
  store_accessor :data, :bank
14
12
 
15
- # Aliases to share PaymentMethodAttributes
16
- alias_attribute :payment_method_type, :type
17
-
18
13
  validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
19
14
 
20
15
  def self.find_by_processor_and_id(processor, processor_id)
@@ -1,16 +1,7 @@
1
1
  module Pay
2
2
  module Stripe
3
- class Charge
4
- attr_reader :pay_charge
5
-
6
- delegate :amount,
7
- :amount_captured,
8
- :invoice_id,
9
- :line_items,
10
- :payment_intent_id,
11
- :processor_id,
12
- :stripe_account,
13
- to: :pay_charge
3
+ class Charge < Pay::Charge
4
+ store_accessor :data, :stripe_receipt_url
14
5
 
15
6
  def self.sync(charge_id, object: nil, stripe_account: nil, try: 0, retries: 1)
16
7
  # Skip loading the latest charge details from the API if we already have it
@@ -108,11 +99,7 @@ module Pay
108
99
  end
109
100
  end
110
101
 
111
- def initialize(pay_charge)
112
- @pay_charge = pay_charge
113
- end
114
-
115
- def charge
102
+ def api_record
116
103
  ::Stripe::Charge.retrieve({id: processor_id, expand: ["customer", "invoice.subscription"]}, stripe_options)
117
104
  rescue ::Stripe::StripeError => e
118
105
  raise Pay::Stripe::Error, e
@@ -128,6 +115,8 @@ module Pay
128
115
  # refund!(5_00)
129
116
  # refund!(5_00, refund_application_fee: true)
130
117
  def refund!(amount_to_refund, **options)
118
+ amount_to_refund ||= amount
119
+
131
120
  if invoice_id.present?
132
121
  description = options.delete(:description) || I18n.t("pay.refund")
133
122
  lines = [{type: :custom_line_item, description: description, quantity: 1, unit_amount: amount_to_refund}]
@@ -135,7 +124,7 @@ module Pay
135
124
  else
136
125
  ::Stripe::Refund.create(options.merge(charge: processor_id, amount: amount_to_refund), stripe_options)
137
126
  end
138
- pay_charge.update!(amount_refunded: pay_charge.amount_refunded + amount_to_refund)
127
+ update!(amount_refunded: amount_refunded + amount_to_refund)
139
128
  rescue ::Stripe::StripeError => e
140
129
  raise Pay::Stripe::Error, e
141
130
  end
@@ -148,11 +137,6 @@ module Pay
148
137
  raise Pay::Stripe::Error, e
149
138
  end
150
139
 
151
- def credit_notes(**options)
152
- raise Pay::Stripe::Error, "no Stripe invoice_id on Pay::Charge" if invoice_id.blank?
153
- ::Stripe::CreditNote.list({invoice: invoice_id}.merge(options), stripe_options)
154
- end
155
-
156
140
  # https://stripe.com/docs/payments/capture-later
157
141
  #
158
142
  # capture