pay 7.2.1 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 690bb7ad1078274199d3a9c3c32b5ace7fb405bf0c37f0bcabeffeb8af734902
4
- data.tar.gz: 1e0df19aea4d907f3878450fcbb320421a3abb7da1fe0187ba7bde1878267d60
3
+ metadata.gz: 785afa1f8e3c9c0306a572d25e18a26c91ef44dd02549f1ab332ccc528586f69
4
+ data.tar.gz: 004d09453ad0180666f3f4b14da51f76e66e0956adc4119b1171b44555a9f1f7
5
5
  SHA512:
6
- metadata.gz: a0767157a5d6ecc7a710e71047080b4e5d6edbdf4a27fb7f2fcfbf585558b68b43af5f483d87815668dcc4d85128f3fe6c098a5fc2899195529142a1ba3682fa
7
- data.tar.gz: 400de12e3525fc3395ba4bf4dd8b2df158de62cb18db60a298d51acec1fe1a16fa0c32f0f3750f9f8a01f0337de9f71d61b33628e0c39848508dc818c85f5356
6
+ metadata.gz: 78ec12fabf38197f4317db716ba2ef1c389c3f96f94a9bad2603821ee4d7fa19268c315f51106d4793052824ed61311110f7be1e7e568e3a2dcb5b32e1d3a18f
7
+ data.tar.gz: f486f57a001aabad0b149e37f675287aafbd1829f94d1a1d30a92e54cde1558d5bdcbbedc209ed213a6f483d1ee400230de7d38a55ff00638f0c9f19fd5d2ed9
data/README.md CHANGED
@@ -23,6 +23,7 @@ Our supported payment processors are:
23
23
  - Stripe ([SCA Compatible](https://stripe.com/docs/strong-customer-authentication) using API version `2022-11-15`)
24
24
  - Paddle (SCA Compatible & supports PayPal)
25
25
  - Braintree (supports PayPal)
26
+ - Lemon Squeezy (supports PayPal)
26
27
  - [Fake Processor](docs/fake_processor/1_overview.md) (used for generic trials without cards, free subscriptions, testing, etc)
27
28
 
28
29
  Want to add a new payment provider? Contributions are welcome.
@@ -45,6 +46,7 @@ Want to add a new payment provider? Contributions are welcome.
45
46
  * [Stripe](docs/stripe/1_overview.md)
46
47
  * [Braintree](docs/braintree/1_overview.md)
47
48
  * [Paddle](docs/paddle_billing/1_overview.md)
49
+ * [Lemon Squeezy](docs/lemon_squeezy/1_overview.md)
48
50
  * [Fake Processor](docs/fake_processor/1_overview.md)
49
51
  * **Marketplaces**
50
52
  * [Stripe Connect](docs/marketplaces/stripe_connect.md)
@@ -0,0 +1,45 @@
1
+ module Pay
2
+ module Webhooks
3
+ class LemonSqueezyController < Pay::ApplicationController
4
+ if Rails.application.config.action_controller.default_protect_from_forgery
5
+ skip_before_action :verify_authenticity_token
6
+ end
7
+
8
+ def create
9
+ if valid_signature?(request.headers["X-Signature"])
10
+ queue_event(verify_params.as_json)
11
+ head :ok
12
+ else
13
+ head :bad_request
14
+ end
15
+ rescue Pay::LemonSqueezy::Error
16
+ head :bad_request
17
+ end
18
+
19
+ private
20
+
21
+ def queue_event(event)
22
+ return unless Pay::Webhooks.delegator.listening?("lemon_squeezy.#{params[:meta][:event_name]}")
23
+
24
+ record = Pay::Webhook.create!(processor: :lemon_squeezy, event_type: params[:meta][:event_name], event: event)
25
+ Pay::Webhooks::ProcessJob.perform_later(record)
26
+ end
27
+
28
+ # Pass Lemon Squeezy signature from request.headers["X-Signature"]
29
+ def valid_signature?(signature)
30
+ return false if signature.blank?
31
+
32
+ key = Pay::LemonSqueezy.signing_secret
33
+ data = request.raw_post
34
+ digest = OpenSSL::Digest.new("sha256")
35
+
36
+ hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
37
+ ActiveSupport::SecurityUtils.secure_compare(hmac, signature)
38
+ end
39
+
40
+ def verify_params
41
+ params.except(:action, :controller).permit!
42
+ end
43
+ end
44
+ end
45
+ end
@@ -6,7 +6,8 @@ module Pay
6
6
  end
7
7
 
8
8
  def create
9
- queue_event(verified_event)
9
+ event = verified_event
10
+ queue_event(event) if event.livemode || Pay::Stripe.webhook_receive_test_events
10
11
  head :ok
11
12
  rescue ::Stripe::SignatureVerificationError => e
12
13
  log_error(e)
@@ -1,7 +1,7 @@
1
1
  module Pay
2
2
  class CustomerSyncJob < ApplicationJob
3
3
  def perform(pay_customer_id)
4
- Pay::Customer.find(pay_customer_id).update_customer!
4
+ Pay::Customer.find(pay_customer_id).update_api_record
5
5
  rescue ActiveRecord::RecordNotFound
6
6
  Rails.logger.info "Couldn't find a Pay::Customer with ID = #{pay_customer_id}"
7
7
  end
@@ -0,0 +1,13 @@
1
+ module Pay
2
+ module Routing
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Rails.application.routes.url_helpers
7
+ end
8
+
9
+ def default_url_options
10
+ Rails.application.config.action_mailer.default_url_options || {}
11
+ end
12
+ end
13
+ end
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Braintree
3
- class Charge
4
- attr_reader :pay_charge
5
-
6
- delegate :processor_id, to: :pay_charge
7
-
3
+ class Charge < Pay::Charge
8
4
  def self.sync(charge_id, object: nil, try: 0, retries: 1)
9
5
  object ||= Pay.braintree_gateway.transaction.find(charge_id)
10
6
 
@@ -22,19 +18,16 @@ module Pay
22
18
  end
23
19
  end
24
20
 
25
- def initialize(pay_charge)
26
- @pay_charge = pay_charge
27
- end
28
-
29
- def charge
21
+ def api_record
30
22
  Pay.braintree_gateway.transaction.find(processor_id)
31
23
  rescue ::Braintree::Braintree::Error => e
32
24
  raise Pay::Braintree::Error, e
33
25
  end
34
26
 
35
- def refund!(amount_to_refund)
27
+ def refund!(amount_to_refund = nil)
28
+ amount_to_refund ||= amount
36
29
  Pay.braintree_gateway.transaction.refund(processor_id, amount_to_refund / 100.0)
37
- pay_charge.update(amount_refunded: amount_to_refund)
30
+ update(amount_refunded: amount_to_refund)
38
31
  rescue ::Braintree::BraintreeError => e
39
32
  raise Pay::Braintree::Error, e
40
33
  end
@@ -1,62 +1,34 @@
1
1
  module Pay
2
2
  module Braintree
3
- class Billable
4
- attr_reader :pay_customer
5
-
6
- delegate :processor_id,
7
- :processor_id?,
8
- :email,
9
- :customer_name,
10
- :payment_method_token,
11
- :payment_method_token?,
12
- to: :pay_customer
13
-
14
- def initialize(pay_customer)
15
- @pay_customer = pay_customer
16
- end
3
+ class Customer < Pay::Customer
4
+ has_many :charges, dependent: :destroy, class_name: "Pay::Braintree::Charge"
5
+ has_many :subscriptions, dependent: :destroy, class_name: "Pay::Braintree::Subscription"
6
+ has_many :payment_methods, dependent: :destroy, class_name: "Pay::Braintree::PaymentMethod"
7
+ has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::Braintree::PaymentMethod"
17
8
 
18
9
  # Returns a hash of attributes for the Stripe::Customer object
19
- def customer_attributes
20
- owner = pay_customer.owner
21
-
10
+ def api_record_attributes
22
11
  attributes = case owner.class.pay_braintree_customer_attributes
23
12
  when Symbol
24
- owner.send(owner.class.pay_braintree_customer_attributes, pay_customer)
13
+ owner.send(owner.class.pay_braintree_customer_attributes, self)
25
14
  when Proc
26
- owner.class.pay_braintree_customer_attributes.call(pay_customer)
15
+ owner.class.pay_braintree_customer_attributes.call(self)
27
16
  end
28
17
 
29
- # Guard against attributes being returned nil
30
- attributes ||= {}
31
-
32
18
  first_name, last_name = customer_name.split(" ", 2)
33
- {email: email, first_name: first_name, last_name: last_name}.merge(attributes)
19
+ {email: email, first_name: first_name, last_name: last_name}.merge(attributes || {})
34
20
  end
35
21
 
36
22
  # Retrieve the Braintree::Customer object
37
23
  #
38
24
  # - If no processor_id is present, creates a Customer.
39
- # - When 'payment_method_token' is present, it will also set the default payment method
40
- def customer
25
+ def api_record
41
26
  if processor_id?
42
- customer = gateway.customer.find(processor_id)
43
-
44
- if payment_method_token?
45
- add_payment_method(payment_method_token, default: true)
46
- pay_customer.payment_method_token = nil
47
- end
48
-
49
- customer
27
+ gateway.customer.find(processor_id)
50
28
  else
51
- result = gateway.customer.create(customer_attributes.merge(payment_method_nonce: payment_method_token))
29
+ result = gateway.customer.create(api_record_attributes)
52
30
  raise Pay::Braintree::Error, result unless result.success?
53
- pay_customer.update!(processor_id: result.customer.id)
54
-
55
- if payment_method_token?
56
- save_payment_method(result.customer.payment_methods.last, default: true)
57
- pay_customer.payment_method_token = nil
58
- end
59
-
31
+ update!(processor_id: result.customer.id)
60
32
  result.customer
61
33
  end
62
34
  rescue ::Braintree::AuthorizationError => e
@@ -67,15 +39,15 @@ module Pay
67
39
 
68
40
  # Syncs name and email to Braintree::Customer
69
41
  # You can also pass in other attributes that will be merged into the default attributes
70
- def update_customer!(**attributes)
71
- customer unless processor_id?
72
- gateway.customer.update(processor_id, customer_attributes.merge(attributes))
42
+ def update_api_record(**attributes)
43
+ api_record unless processor_id?
44
+ gateway.customer.update(processor_id, api_record_attributes.merge(attributes))
73
45
  end
74
46
 
75
47
  def charge(amount, options = {})
76
48
  args = {
77
49
  amount: amount.to_i / 100.0,
78
- customer_id: customer.id,
50
+ customer_id: processor_id || api_record.id,
79
51
  options: {submit_for_settlement: true},
80
52
  custom_fields: options.delete(:metadata)
81
53
  }.merge(options)
@@ -91,7 +63,7 @@ module Pay
91
63
  end
92
64
 
93
65
  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
94
- token = customer.payment_methods.find(&:default?).try(:token)
66
+ token = api_record.payment_methods.find(&:default?).try(:token)
95
67
  raise Pay::Error, "Customer has no default payment method" if token.nil?
96
68
 
97
69
  # Standardize the trial period options
@@ -100,20 +72,20 @@ module Pay
100
72
  end
101
73
 
102
74
  metadata = options.delete(:metadata)
103
- subscription_options = options.merge(
104
- payment_method_token: token,
105
- plan_id: plan
106
- )
75
+ subscription_options = options.merge(payment_method_token: token, plan_id: plan)
107
76
 
108
77
  result = gateway.subscription.create(subscription_options)
109
78
  raise Pay::Braintree::Error, result unless result.success?
110
79
 
111
- subscription = pay_customer.subscriptions.create!(
80
+ # Braintree returns dates without time zones, so we'll assume they're UTC
81
+ trial_end_date = result.subscription.trial_period.present? ? result.subscription.first_billing_date.end_of_day : nil
82
+
83
+ subscription = subscriptions.create!(
112
84
  name: name,
113
85
  processor_id: result.subscription.id,
114
86
  processor_plan: plan,
115
87
  status: :active,
116
- trial_ends_at: trial_end_date(result.subscription),
88
+ trial_ends_at: trial_end_date,
117
89
  ends_at: nil,
118
90
  metadata: metadata
119
91
  )
@@ -130,10 +102,8 @@ module Pay
130
102
  end
131
103
 
132
104
  def add_payment_method(token, default: false)
133
- customer unless processor_id?
134
-
135
105
  result = gateway.payment_method.create(
136
- customer_id: processor_id,
106
+ customer_id: processor_id || api_record.id,
137
107
  payment_method_nonce: token,
138
108
  options: {
139
109
  make_default: default,
@@ -145,7 +115,7 @@ module Pay
145
115
  pay_payment_method = save_payment_method(result.payment_method, default: default)
146
116
 
147
117
  # Update existing subscriptions to the new payment method
148
- pay_customer.subscriptions.each do |subscription|
118
+ subscriptions.each do |subscription|
149
119
  if subscription.active?
150
120
  gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
151
121
  end
@@ -158,16 +128,6 @@ module Pay
158
128
  raise Pay::Braintree::Error, e
159
129
  end
160
130
 
161
- def trial_end_date(subscription)
162
- return unless subscription.trial_period
163
- # Braintree returns dates without time zones, so we'll assume they're UTC
164
- subscription.first_billing_date.end_of_day
165
- end
166
-
167
- def processor_subscription(subscription_id, options = {})
168
- gateway.subscription.find(subscription_id)
169
- end
170
-
171
131
  def save_transaction(transaction)
172
132
  attrs = card_details_for_braintree_transaction(transaction)
173
133
  attrs[:amount] = transaction.amount.to_f * 100
@@ -178,7 +138,7 @@ module Pay
178
138
 
179
139
  # Associate charge with subscription if we can
180
140
  if transaction.subscription_id
181
- pay_subscription = pay_customer.subscriptions.find_by(processor_id: transaction.subscription_id)
141
+ pay_subscription = subscriptions.find_by(processor_id: transaction.subscription_id)
182
142
  pay_subscription ||= Pay::Braintree::Subscription.sync(transaction.subscription_id)
183
143
 
184
144
  if pay_subscription
@@ -187,7 +147,7 @@ module Pay
187
147
  end
188
148
  end
189
149
 
190
- charge = pay_customer.charges.find_or_initialize_by(processor_id: transaction.id)
150
+ charge = charges.find_or_initialize_by(processor_id: transaction.id)
191
151
  charge.update!(attrs)
192
152
  charge
193
153
  end
@@ -238,13 +198,13 @@ module Pay
238
198
  }
239
199
  end
240
200
 
241
- pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.token).first_or_initialize
201
+ pay_payment_method = payment_methods.where(processor_id: payment_method.token).first_or_initialize
242
202
 
243
- pay_customer.payment_methods.update_all(default: false) if default
203
+ payment_methods.update_all(default: false) if default
244
204
  pay_payment_method.update!(attributes.merge(default: default))
245
205
 
246
206
  # Reload the Rails association
247
- pay_customer.reload_default_payment_method if default
207
+ reload_default_payment_method if default
248
208
 
249
209
  pay_payment_method
250
210
  end
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Braintree
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
  def self.sync(id, object: nil, try: 0, retries: 1)
9
5
  object ||= Pay.braintree_gateway.payment_method.find(id)
10
6
 
@@ -14,10 +10,6 @@ module Pay
14
10
  pay_customer.save_payment_method(object, default: object.default?)
15
11
  end
16
12
 
17
- def initialize(pay_payment_method)
18
- @pay_payment_method = pay_payment_method
19
- end
20
-
21
13
  # Sets payment method as default on Stripe
22
14
  def make_default!
23
15
  result = gateway.customer.update(customer.processor_id, default_payment_method_token: processor_id)
@@ -1,25 +1,6 @@
1
1
  module Pay
2
2
  module Braintree
3
- class Subscription
4
- attr_reader :pay_subscription
5
-
6
- delegate :active?,
7
- :canceled?,
8
- :ends_at?,
9
- :customer,
10
- :ends_at,
11
- :name,
12
- :on_trial?,
13
- :processor_id,
14
- :processor_plan,
15
- :processor_subscription,
16
- :prorate,
17
- :prorate?,
18
- :quantity,
19
- :quantity?,
20
- :trial_ends_at,
21
- to: :pay_subscription
22
-
3
+ class Subscription < Pay::Subscription
23
4
  def self.sync(subscription_id, object: nil, name: nil, try: 0, retries: 1)
24
5
  object ||= Pay.braintree_gateway.subscription.find(subscription_id)
25
6
 
@@ -69,11 +50,7 @@ module Pay
69
50
  end
70
51
  end
71
52
 
72
- def initialize(pay_subscription)
73
- @pay_subscription = pay_subscription
74
- end
75
-
76
- def subscription(**options)
53
+ def api_record(**options)
77
54
  gateway.subscription.find(processor_id)
78
55
  end
79
56
 
@@ -84,11 +61,9 @@ module Pay
84
61
  result = if on_trial?
85
62
  gateway.subscription.cancel(processor_id)
86
63
  else
87
- gateway.subscription.update(subscription.id, {
88
- number_of_billing_cycles: subscription.current_billing_cycle
89
- })
64
+ gateway.subscription.update(processor_id, {number_of_billing_cycles: api_record.current_billing_cycle})
90
65
  end
91
- pay_subscription.sync!(object: result.subscription)
66
+ sync!(object: result.subscription)
92
67
  rescue ::Braintree::BraintreeError => e
93
68
  raise Pay::Braintree::Error, e
94
69
  end
@@ -97,7 +72,7 @@ module Pay
97
72
  return if canceled?
98
73
 
99
74
  result = gateway.subscription.cancel(processor_id)
100
- pay_subscription.sync!(object: result.subscription)
75
+ sync!(object: result.subscription)
101
76
  rescue ::Braintree::BraintreeError => e
102
77
  raise Pay::Braintree::Error, e
103
78
  end
@@ -106,10 +81,6 @@ module Pay
106
81
  raise NotImplementedError, "Braintree does not support setting quantity on subscriptions"
107
82
  end
108
83
 
109
- def on_grace_period?
110
- ends_at? && ends_at > Time.current
111
- end
112
-
113
84
  def paused?
114
85
  false
115
86
  end
@@ -138,15 +109,13 @@ module Pay
138
109
  trial_duration_unit: :day
139
110
  )
140
111
  else
141
- subscription = processor_subscription
142
-
143
- gateway.subscription.update(subscription.id, {
112
+ gateway.subscription.update(processor_id, {
144
113
  never_expires: true,
145
114
  number_of_billing_cycles: nil
146
115
  })
147
116
  end
148
117
 
149
- pay_subscription.update(status: :active)
118
+ update(ends_at: nil, status: :active)
150
119
  rescue ::Braintree::BraintreeError => e
151
120
  raise Pay::Braintree::Error, e
152
121
  end
@@ -171,9 +140,7 @@ module Pay
171
140
  return
172
141
  end
173
142
 
174
- subscription = processor_subscription
175
-
176
- result = gateway.subscription.update(subscription.id, {
143
+ result = gateway.subscription.update(processor_id, {
177
144
  plan_id: braintree_plan.id,
178
145
  price: braintree_plan.price,
179
146
  never_expires: true,
@@ -184,7 +151,7 @@ module Pay
184
151
  })
185
152
  raise Error, "Braintree failed to swap plans: #{result.message}" unless result.success?
186
153
 
187
- pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
154
+ update(processor_plan: plan, ends_at: nil, status: :active)
188
155
  rescue ::Braintree::BraintreeError => e
189
156
  raise Pay::Braintree::Error, e
190
157
  end
@@ -198,7 +165,7 @@ module Pay
198
165
  )
199
166
 
200
167
  if result.success?
201
- pay_subscription.update(status: :active)
168
+ update(status: :active)
202
169
  end
203
170
  end
204
171
 
@@ -231,23 +198,18 @@ module Pay
231
198
  end
232
199
 
233
200
  def money_remaining_on_yearly_plan(current_plan)
234
- end_date = processor_subscription.billing_period_end_date.to_date
201
+ end_date = api_record.billing_period_end_date.to_date
235
202
  (current_plan.price / 365) * (end_date - Date.today)
236
203
  end
237
204
 
238
- def discount_for_switching_to_yearly
239
- amount = 0
240
-
241
- processor_subscription.discounts.each do |discount|
205
+ def discount_for_switching_to_yearly(amount: 0)
206
+ api_record.discounts.each do |discount|
242
207
  if discount.id == "plan-credit"
243
208
  amount += discount.amount * discount.number_of_billing_cycles
244
209
  end
245
210
  end
246
211
 
247
- OpenStruct.new(
248
- amount: amount,
249
- number_of_billing_cycles: 1
250
- )
212
+ OpenStruct.new(amount: amount, number_of_billing_cycles: 1)
251
213
  end
252
214
 
253
215
  def swap_across_frequencies(plan)
@@ -1,7 +1,5 @@
1
1
  module Pay
2
2
  class Charge < Pay::ApplicationRecord
3
- self.inheritance_column = nil
4
-
5
3
  # Associations
6
4
  belongs_to :customer
7
5
  belongs_to :subscription, optional: true
@@ -15,10 +13,6 @@ module Pay
15
13
  validates :amount, presence: true
16
14
  validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
17
15
 
18
- # Store the payment method kind (card, paypal, etc)
19
- store_accessor :data, :paddle_receipt_url
20
- store_accessor :data, :stripe_receipt_url
21
-
22
16
  # Payment method attributes
23
17
  store_accessor :data, :payment_method_type # card, paypal, sepa, etc
24
18
  store_accessor :data, :brand # Visa, Mastercard, Discover, PayPal
@@ -44,7 +38,7 @@ module Pay
44
38
  store_accessor :data, :refunds # array of refunds
45
39
 
46
40
  # Helpers for payment processors
47
- %w[braintree stripe paddle_billing paddle_classic fake_processor].each do |processor_name|
41
+ %w[braintree stripe paddle_billing paddle_classic lemon_squeezy fake_processor].each do |processor_name|
48
42
  define_method :"#{processor_name}?" do
49
43
  customer.processor == processor_name
50
44
  end
@@ -52,33 +46,14 @@ module Pay
52
46
  scope processor_name, -> { joins(:customer).where(pay_customers: {processor: processor_name}) }
53
47
  end
54
48
 
55
- delegate :capture, :credit_note!, :credit_notes, to: :payment_processor
56
-
57
49
  def self.find_by_processor_and_id(processor, processor_id)
58
50
  joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
59
51
  end
60
52
 
61
- def self.pay_processor_for(name)
62
- "Pay::#{name.to_s.classify}::Charge".constantize
63
- end
64
-
65
- def payment_processor
66
- @payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
67
- end
68
-
69
- def processor_charge
70
- payment_processor.charge
71
- end
72
-
73
53
  def captured?
74
54
  amount_captured > 0
75
55
  end
76
56
 
77
- def refund!(refund_amount = nil)
78
- refund_amount ||= amount
79
- payment_processor.refund!(refund_amount)
80
- end
81
-
82
57
  def refunded?
83
58
  amount_refunded.to_i > 0
84
59
  end
@@ -104,7 +79,13 @@ module Pay
104
79
  when "card"
105
80
  "#{brand.titleize} (**** **** **** #{last4})"
106
81
  when "paypal"
107
- "#{brand} (#{email})"
82
+ # Sometimes brand and email are missing (Stripe)
83
+ brand ||= "PayPal"
84
+ if email.present?
85
+ brand + " (#{email})"
86
+ else
87
+ brand
88
+ end
108
89
 
109
90
  # Braintree
110
91
  when "venmo"
@@ -13,8 +13,6 @@ module Pay
13
13
  validates :processor, presence: true
14
14
  validates :processor_id, allow_blank: true, uniqueness: {scope: :processor, case_sensitive: true}
15
15
 
16
- attribute :payment_method_token, :string
17
-
18
16
  # Account(s) for marketplace payments
19
17
  store_accessor :data, :braintree_account
20
18
 
@@ -23,9 +21,8 @@ module Pay
23
21
  store_accessor :data, :currency
24
22
 
25
23
  delegate :email, to: :owner
26
- delegate_missing_to :pay_processor
27
24
 
28
- %w[stripe braintree paddle_billing paddle_classic fake_processor].each do |processor_name|
25
+ %w[stripe braintree paddle_billing paddle_classic lemon_squeezy fake_processor].each do |processor_name|
29
26
  scope processor_name, -> { where(processor: processor_name) }
30
27
 
31
28
  define_method :"#{processor_name}?" do
@@ -33,15 +30,6 @@ module Pay
33
30
  end
34
31
  end
35
32
 
36
- def self.pay_processor_for(name)
37
- "Pay::#{name.to_s.classify}::Billable".constantize
38
- end
39
-
40
- def pay_processor
41
- return if processor.blank?
42
- @pay_processor ||= self.class.pay_processor_for(processor).new(self)
43
- end
44
-
45
33
  def update_payment_method(payment_method_id)
46
34
  add_payment_method(payment_method_id, default: true)
47
35
  end
@@ -51,8 +39,7 @@ module Pay
51
39
  end
52
40
 
53
41
  def subscribed?(name: Pay.default_product_name, processor_plan: nil)
54
- query = {name: name, processor_plan: processor_plan}.compact
55
- subscriptions.active.where(query).exists?
42
+ subscriptions.active.where({name: name, processor_plan: processor_plan}.compact).exists?
56
43
  end
57
44
 
58
45
  def on_trial?(name: Pay.default_product_name, plan: nil)
@@ -0,0 +1,13 @@
1
+ module Pay
2
+ module FakeProcessor
3
+ class Charge < Pay::Charge
4
+ def api_record
5
+ self
6
+ end
7
+
8
+ def refund!(amount_to_refund = nil)
9
+ update(amount_refunded: amount_to_refund || amount)
10
+ end
11
+ end
12
+ end
13
+ end