pay 7.3.0 → 8.1.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.
- 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)
|