pay 7.3.0 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) 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/jobs/pay/customer_sync_job.rb +1 -1
  5. data/app/models/concerns/pay/routing.rb +13 -0
  6. data/{lib → app/models}/pay/braintree/charge.rb +5 -12
  7. data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +31 -71
  8. data/{lib → app/models}/pay/braintree/payment_method.rb +1 -9
  9. data/{lib → app/models}/pay/braintree/subscription.rb +15 -53
  10. data/app/models/pay/charge.rb +8 -27
  11. data/app/models/pay/customer.rb +2 -15
  12. data/app/models/pay/fake_processor/charge.rb +13 -0
  13. data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +20 -37
  14. data/{lib → app/models}/pay/fake_processor/merchant.rb +2 -9
  15. data/app/models/pay/fake_processor/payment_method.rb +11 -0
  16. data/app/models/pay/fake_processor/subscription.rb +60 -0
  17. data/app/models/pay/lemon_squeezy/charge.rb +86 -0
  18. data/app/models/pay/lemon_squeezy/customer.rb +78 -0
  19. data/app/models/pay/lemon_squeezy/payment_method.rb +27 -0
  20. data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
  21. data/app/models/pay/merchant.rb +0 -11
  22. data/{lib → app/models}/pay/paddle_billing/charge.rb +2 -8
  23. data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +18 -35
  24. data/{lib → app/models}/pay/paddle_billing/payment_method.rb +2 -12
  25. data/{lib → app/models}/pay/paddle_billing/subscription.rb +9 -33
  26. data/{lib → app/models}/pay/paddle_classic/charge.rb +13 -18
  27. data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +9 -31
  28. data/{lib → app/models}/pay/paddle_classic/payment_method.rb +1 -11
  29. data/{lib → app/models}/pay/paddle_classic/subscription.rb +11 -36
  30. data/app/models/pay/payment_method.rb +0 -5
  31. data/{lib → app/models}/pay/stripe/charge.rb +6 -22
  32. data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +73 -108
  33. data/{lib → app/models}/pay/stripe/merchant.rb +2 -11
  34. data/{lib → app/models}/pay/stripe/payment_method.rb +2 -10
  35. data/{lib → app/models}/pay/stripe/subscription.rb +37 -71
  36. data/app/models/pay/subscription.rb +7 -37
  37. data/app/models/pay/webhook.rb +3 -1
  38. data/config/routes.rb +1 -0
  39. data/db/migrate/2_add_pay_sti_columns.rb +24 -0
  40. data/lib/pay/attributes.rb +11 -3
  41. data/lib/pay/braintree.rb +25 -6
  42. data/lib/pay/engine.rb +2 -0
  43. data/lib/pay/fake_processor.rb +2 -6
  44. data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
  45. data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
  46. data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
  47. data/lib/pay/lemon_squeezy.rb +56 -104
  48. data/lib/pay/paddle_billing.rb +15 -6
  49. data/lib/pay/paddle_classic.rb +11 -9
  50. data/lib/pay/receipts.rb +6 -6
  51. data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
  52. data/lib/pay/stripe.rb +16 -7
  53. data/lib/pay/version.rb +1 -1
  54. data/lib/pay.rb +12 -1
  55. metadata +34 -38
  56. data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
  57. data/lib/pay/braintree/authorization_error.rb +0 -9
  58. data/lib/pay/braintree/error.rb +0 -23
  59. data/lib/pay/fake_processor/charge.rb +0 -21
  60. data/lib/pay/fake_processor/error.rb +0 -6
  61. data/lib/pay/fake_processor/payment_method.rb +0 -21
  62. data/lib/pay/fake_processor/subscription.rb +0 -90
  63. data/lib/pay/lemon_squeezy/billable.rb +0 -90
  64. data/lib/pay/lemon_squeezy/charge.rb +0 -68
  65. data/lib/pay/lemon_squeezy/error.rb +0 -7
  66. data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
  67. data/lib/pay/lemon_squeezy/subscription.rb +0 -185
  68. data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
  69. data/lib/pay/paddle_billing/error.rb +0 -7
  70. data/lib/pay/paddle_classic/error.rb +0 -7
  71. 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