pay 7.3.0 → 8.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
- data/app/jobs/pay/customer_sync_job.rb +1 -1
- data/app/models/concerns/pay/routing.rb +13 -0
- data/{lib → app/models}/pay/braintree/charge.rb +5 -12
- data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +31 -71
- data/{lib → app/models}/pay/braintree/payment_method.rb +1 -9
- data/{lib → app/models}/pay/braintree/subscription.rb +15 -53
- data/app/models/pay/charge.rb +8 -27
- data/app/models/pay/customer.rb +2 -15
- data/app/models/pay/fake_processor/charge.rb +13 -0
- data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +20 -37
- data/{lib → app/models}/pay/fake_processor/merchant.rb +2 -9
- data/app/models/pay/fake_processor/payment_method.rb +11 -0
- data/app/models/pay/fake_processor/subscription.rb +60 -0
- data/app/models/pay/lemon_squeezy/charge.rb +86 -0
- data/app/models/pay/lemon_squeezy/customer.rb +78 -0
- data/app/models/pay/lemon_squeezy/payment_method.rb +27 -0
- data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
- data/app/models/pay/merchant.rb +0 -11
- data/{lib → app/models}/pay/paddle_billing/charge.rb +2 -8
- data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +18 -35
- data/{lib → app/models}/pay/paddle_billing/payment_method.rb +2 -12
- data/{lib → app/models}/pay/paddle_billing/subscription.rb +9 -33
- data/{lib → app/models}/pay/paddle_classic/charge.rb +13 -18
- data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +9 -31
- data/{lib → app/models}/pay/paddle_classic/payment_method.rb +1 -11
- data/{lib → app/models}/pay/paddle_classic/subscription.rb +11 -36
- data/app/models/pay/payment_method.rb +0 -5
- data/{lib → app/models}/pay/stripe/charge.rb +6 -22
- data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +73 -108
- data/{lib → app/models}/pay/stripe/merchant.rb +2 -11
- data/{lib → app/models}/pay/stripe/payment_method.rb +2 -10
- data/{lib → app/models}/pay/stripe/subscription.rb +37 -71
- data/app/models/pay/subscription.rb +7 -37
- data/app/models/pay/webhook.rb +3 -1
- data/config/routes.rb +1 -0
- data/db/migrate/2_add_pay_sti_columns.rb +24 -0
- data/lib/pay/attributes.rb +11 -3
- data/lib/pay/braintree.rb +25 -6
- data/lib/pay/engine.rb +2 -0
- data/lib/pay/fake_processor.rb +2 -6
- data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
- data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
- data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
- data/lib/pay/lemon_squeezy.rb +56 -104
- data/lib/pay/paddle_billing.rb +15 -6
- data/lib/pay/paddle_classic.rb +11 -9
- data/lib/pay/receipts.rb +6 -6
- data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
- data/lib/pay/stripe.rb +16 -7
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +12 -1
- metadata +34 -38
- data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
- data/lib/pay/braintree/authorization_error.rb +0 -9
- data/lib/pay/braintree/error.rb +0 -23
- data/lib/pay/fake_processor/charge.rb +0 -21
- data/lib/pay/fake_processor/error.rb +0 -6
- data/lib/pay/fake_processor/payment_method.rb +0 -21
- data/lib/pay/fake_processor/subscription.rb +0 -90
- data/lib/pay/lemon_squeezy/billable.rb +0 -90
- data/lib/pay/lemon_squeezy/charge.rb +0 -68
- data/lib/pay/lemon_squeezy/error.rb +0 -7
- data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
- data/lib/pay/lemon_squeezy/subscription.rb +0 -185
- data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
- data/lib/pay/paddle_billing/error.rb +0 -7
- data/lib/pay/paddle_classic/error.rb +0 -7
- data/lib/pay/stripe/error.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fe510d1e6a11611a8cd4678e965e1051f6e69ad6c404f55c3fa96312dfff51a
|
4
|
+
data.tar.gz: 54b73170c35476916fd17dc4ca11a7bc5d117888a04385e0947ef26dfd069e8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b55b7a23acd69d6f1a795805ba707f3cdbbceca636359ea509419a7a547764e048ce3c36c82f58b0fd34a97e2dac0561b76266ae42b7d62f39d6d9ce38f25944
|
7
|
+
data.tar.gz: ed7f92af40c8bfe066668a8c20212df7c316eb237db7091a5d44ca60e728c037e12d5e846ec6e4b87b3087cf22733568f101ceda2132ff945367b9245a46975a
|
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
|
@@ -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).
|
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
|
@@ -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
|
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
|
-
|
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
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
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,
|
13
|
+
owner.send(owner.class.pay_braintree_customer_attributes, self)
|
25
14
|
when Proc
|
26
|
-
owner.class.pay_braintree_customer_attributes.call(
|
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
|
-
|
40
|
-
def customer
|
25
|
+
def api_record
|
41
26
|
if processor_id?
|
42
|
-
|
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(
|
29
|
+
result = gateway.customer.create(api_record_attributes)
|
52
30
|
raise Pay::Braintree::Error, result unless result.success?
|
53
|
-
|
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
|
71
|
-
|
72
|
-
gateway.customer.update(processor_id,
|
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:
|
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 =
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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 =
|
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 =
|
201
|
+
pay_payment_method = payment_methods.where(processor_id: payment_method.token).first_or_initialize
|
242
202
|
|
243
|
-
|
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
|
-
|
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
|
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(
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
168
|
+
update(status: :active)
|
202
169
|
end
|
203
170
|
end
|
204
171
|
|
@@ -224,30 +191,25 @@ module Pay
|
|
224
191
|
|
225
192
|
def discount_for_switching_to_monthly(current_plan, plan)
|
226
193
|
cycles = (money_remaining_on_yearly_plan(current_plan) / plan.price).floor
|
227
|
-
|
194
|
+
ActiveSupport::InheritableOptions.new(
|
228
195
|
amount: plan.price,
|
229
196
|
number_of_billing_cycles: cycles
|
230
197
|
)
|
231
198
|
end
|
232
199
|
|
233
200
|
def money_remaining_on_yearly_plan(current_plan)
|
234
|
-
end_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
|
-
|
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
|
-
|
248
|
-
amount: amount,
|
249
|
-
number_of_billing_cycles: 1
|
250
|
-
)
|
212
|
+
ActiveSupport::InheritableOptions.new(amount: amount, number_of_billing_cycles: 1)
|
251
213
|
end
|
252
214
|
|
253
215
|
def swap_across_frequencies(plan)
|
data/app/models/pay/charge.rb
CHANGED
@@ -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
|
-
|
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"
|
data/app/models/pay/customer.rb
CHANGED
@@ -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
|
-
|
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)
|