pay 7.3.0 → 11.2.2
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 +8 -4
- data/app/controllers/pay/payments_controller.rb +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 +7 -12
- data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +33 -71
- data/{lib → app/models}/pay/braintree/payment_method.rb +4 -10
- data/{lib → app/models}/pay/braintree/subscription.rb +23 -61
- data/app/models/pay/charge.rb +16 -45
- data/app/models/pay/customer.rb +5 -16
- data/app/models/pay/fake_processor/charge.rb +19 -0
- data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +28 -38
- data/{lib → app/models}/pay/fake_processor/merchant.rb +4 -9
- data/app/models/pay/fake_processor/payment_method.rb +13 -0
- data/app/models/pay/fake_processor/subscription.rb +70 -0
- data/app/models/pay/lemon_squeezy/charge.rb +96 -0
- data/app/models/pay/lemon_squeezy/customer.rb +80 -0
- data/app/models/pay/lemon_squeezy/payment_method.rb +29 -0
- data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
- data/app/models/pay/merchant.rb +2 -11
- data/{lib → app/models}/pay/paddle_billing/charge.rb +15 -13
- data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +20 -35
- data/{lib → app/models}/pay/paddle_billing/payment_method.rb +13 -13
- data/{lib → app/models}/pay/paddle_billing/subscription.rb +40 -43
- data/{lib → app/models}/pay/paddle_classic/charge.rb +15 -18
- data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +11 -31
- data/{lib → app/models}/pay/paddle_classic/payment_method.rb +3 -11
- data/{lib → app/models}/pay/paddle_classic/subscription.rb +17 -37
- data/app/models/pay/payment_method.rb +4 -5
- data/app/models/pay/stripe/charge.rb +155 -0
- data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +78 -111
- data/{lib → app/models}/pay/stripe/merchant.rb +5 -20
- data/{lib → app/models}/pay/stripe/payment_method.rb +11 -17
- data/{lib → app/models}/pay/stripe/subscription.rb +83 -112
- data/app/models/pay/subscription.rb +13 -47
- data/app/models/pay/webhook.rb +5 -1
- data/app/views/pay/user_mailer/payment_action_required.text.erb +9 -0
- data/app/views/pay/user_mailer/payment_failed.text.erb +9 -0
- data/app/views/pay/user_mailer/receipt.text.erb +20 -0
- data/app/views/pay/user_mailer/refund.text.erb +21 -0
- data/app/views/pay/user_mailer/subscription_renewing.text.erb +8 -0
- data/app/views/pay/user_mailer/subscription_trial_ended.text.erb +8 -0
- data/app/views/pay/user_mailer/subscription_trial_will_end.text.erb +8 -0
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20250415151129_add_object_to_pay_models.rb +7 -0
- data/db/migrate/2_add_pay_sti_columns.rb +24 -0
- data/lib/pay/attributes.rb +16 -8
- 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 +58 -104
- data/lib/pay/nano_id.rb +1 -1
- data/lib/pay/paddle_billing.rb +15 -6
- data/lib/pay/paddle_classic/webhooks/signature_verifier.rb +1 -1
- data/lib/pay/paddle_classic.rb +11 -9
- data/lib/pay/receipts.rb +45 -44
- data/lib/pay/stripe/webhooks/charge_updated.rb +11 -0
- data/lib/pay/stripe/webhooks/customer_updated.rb +13 -9
- data/lib/pay/stripe/webhooks/payment_action_required.rb +10 -6
- data/lib/pay/stripe/webhooks/payment_failed.rb +6 -4
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +9 -4
- data/lib/pay/stripe.rb +28 -9
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +19 -1
- data/lib/tasks/pay.rake +2 -2
- metadata +45 -43
- 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/charge.rb +0 -176
- 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: 0e194484cea3ae3dee4d9fdded7e5732f182a5e7699128f2f86bfa020284806f
|
4
|
+
data.tar.gz: 14b67bbf65d49fb2d7d73fdfbc8519baf5ddcade178ba88bd5b5b30534413d09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2c00bd4c90a603a91b62202ba36c5cb3cfdd99fcacdd347e274c2bf6c6cb4d813115373e23c9a3e575559d3ec0d37561c8f9a08a60e028415dace786dfcbb16
|
7
|
+
data.tar.gz: d62df086e5566fa6c25304c8cdbb54ef9ef401ba0a3023541085c57ab32214287c953d78f1c86be4aeaeb34cc26c29635bc00e4e4b757f7f3579d65fa2ceb02b
|
data/README.md
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
# 💳 Pay - Payments engine for Ruby on Rails
|
4
4
|
|
5
|
-
[](https://github.com/pay-rails/pay/actions) [](https://badge.fury.io/rb/pay)
|
6
|
-
|
7
|
-
<img src="docs/images/stripe_partner_badge.svg" height="26px">
|
5
|
+
[](https://github.com/pay-rails/pay/actions) [](https://badge.fury.io/rb/pay) <img src="docs/images/stripe_partner_badge.svg" height="26px">
|
8
6
|
|
9
7
|
Pay is a payments engine for Ruby on Rails 6.0 and higher.
|
10
8
|
|
11
|
-
|
9
|
+
> [!TIP]
|
10
|
+
> Check out [Jumpstart](https://jumpstartrails.com) for Rails Starter Kit with Pay already integrated!
|
11
|
+
|
12
|
+
**Upgrading?** Check the [UPGRADE](UPGRADE.md) guide for required changes and/or migration when upgrading from a previous version of Pay.
|
12
13
|
|
13
14
|
## 🧑💻 Tutorial
|
14
15
|
|
@@ -23,6 +24,7 @@ Our supported payment processors are:
|
|
23
24
|
- Stripe ([SCA Compatible](https://stripe.com/docs/strong-customer-authentication) using API version `2022-11-15`)
|
24
25
|
- Paddle (SCA Compatible & supports PayPal)
|
25
26
|
- Braintree (supports PayPal)
|
27
|
+
- Lemon Squeezy (supports PayPal)
|
26
28
|
- [Fake Processor](docs/fake_processor/1_overview.md) (used for generic trials without cards, free subscriptions, testing, etc)
|
27
29
|
|
28
30
|
Want to add a new payment provider? Contributions are welcome.
|
@@ -45,7 +47,9 @@ Want to add a new payment provider? Contributions are welcome.
|
|
45
47
|
* [Stripe](docs/stripe/1_overview.md)
|
46
48
|
* [Braintree](docs/braintree/1_overview.md)
|
47
49
|
* [Paddle](docs/paddle_billing/1_overview.md)
|
50
|
+
* [Lemon Squeezy](docs/lemon_squeezy/1_overview.md)
|
48
51
|
* [Fake Processor](docs/fake_processor/1_overview.md)
|
52
|
+
* [Asaas (Community)](https://github.com/PedroAugustoRamalhoDuarte/pay-asaas)
|
49
53
|
* **Marketplaces**
|
50
54
|
* [Stripe Connect](docs/marketplaces/stripe_connect.md)
|
51
55
|
* **Contributing**
|
@@ -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,22 +18,21 @@ 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
|
41
34
|
end
|
42
35
|
end
|
43
36
|
end
|
37
|
+
|
38
|
+
ActiveSupport.run_load_hooks :pay_braintree_charge, Pay::Braintree::Charge
|
@@ -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 = Pay::Braintree::Charge.find_or_initialize_by(customer: self, 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
|
@@ -302,3 +262,5 @@ module Pay
|
|
302
262
|
end
|
303
263
|
end
|
304
264
|
end
|
265
|
+
|
266
|
+
ActiveSupport.run_load_hooks :pay_braintree_customer, Pay::Braintree::Customer
|
@@ -1,23 +1,15 @@
|
|
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
|
|
11
|
-
pay_customer = Pay::Customer.find_by(
|
7
|
+
pay_customer = Pay::Braintree::Customer.find_by(processor_id: object.customer_id)
|
12
8
|
return unless pay_customer
|
13
9
|
|
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)
|
@@ -40,3 +32,5 @@ module Pay
|
|
40
32
|
end
|
41
33
|
end
|
42
34
|
end
|
35
|
+
|
36
|
+
ActiveSupport.run_load_hooks :pay_braintree_payment_method, Pay::Braintree::PaymentMethod
|
@@ -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
|
|
@@ -50,15 +31,12 @@ module Pay
|
|
50
31
|
attributes[:ends_at] = object.paid_through_date.end_of_day
|
51
32
|
end
|
52
33
|
|
53
|
-
pay_subscription =
|
54
|
-
if pay_subscription
|
34
|
+
if (pay_subscription = find_by(customer: pay_customer, processor_id: object.id))
|
55
35
|
pay_subscription.with_lock { pay_subscription.update!(attributes) }
|
56
36
|
else
|
57
37
|
name ||= Pay.default_product_name
|
58
|
-
|
38
|
+
create!(attributes.merge(customer: pay_customer, name: name, processor_id: object.id))
|
59
39
|
end
|
60
|
-
|
61
|
-
pay_subscription
|
62
40
|
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
|
63
41
|
try += 1
|
64
42
|
if try <= retries
|
@@ -69,11 +47,7 @@ module Pay
|
|
69
47
|
end
|
70
48
|
end
|
71
49
|
|
72
|
-
def
|
73
|
-
@pay_subscription = pay_subscription
|
74
|
-
end
|
75
|
-
|
76
|
-
def subscription(**options)
|
50
|
+
def api_record(**options)
|
77
51
|
gateway.subscription.find(processor_id)
|
78
52
|
end
|
79
53
|
|
@@ -84,11 +58,9 @@ module Pay
|
|
84
58
|
result = if on_trial?
|
85
59
|
gateway.subscription.cancel(processor_id)
|
86
60
|
else
|
87
|
-
gateway.subscription.update(
|
88
|
-
number_of_billing_cycles: subscription.current_billing_cycle
|
89
|
-
})
|
61
|
+
gateway.subscription.update(processor_id, {number_of_billing_cycles: api_record.current_billing_cycle})
|
90
62
|
end
|
91
|
-
|
63
|
+
sync!(object: result.subscription)
|
92
64
|
rescue ::Braintree::BraintreeError => e
|
93
65
|
raise Pay::Braintree::Error, e
|
94
66
|
end
|
@@ -97,7 +69,7 @@ module Pay
|
|
97
69
|
return if canceled?
|
98
70
|
|
99
71
|
result = gateway.subscription.cancel(processor_id)
|
100
|
-
|
72
|
+
sync!(object: result.subscription)
|
101
73
|
rescue ::Braintree::BraintreeError => e
|
102
74
|
raise Pay::Braintree::Error, e
|
103
75
|
end
|
@@ -106,10 +78,6 @@ module Pay
|
|
106
78
|
raise NotImplementedError, "Braintree does not support setting quantity on subscriptions"
|
107
79
|
end
|
108
80
|
|
109
|
-
def on_grace_period?
|
110
|
-
ends_at? && ends_at > Time.current
|
111
|
-
end
|
112
|
-
|
113
81
|
def paused?
|
114
82
|
false
|
115
83
|
end
|
@@ -124,7 +92,7 @@ module Pay
|
|
124
92
|
|
125
93
|
def resume
|
126
94
|
unless resumable?
|
127
|
-
raise
|
95
|
+
raise Error, "You can only resume subscriptions within their grace period."
|
128
96
|
end
|
129
97
|
|
130
98
|
if canceled? && on_trial?
|
@@ -138,15 +106,13 @@ module Pay
|
|
138
106
|
trial_duration_unit: :day
|
139
107
|
)
|
140
108
|
else
|
141
|
-
subscription
|
142
|
-
|
143
|
-
gateway.subscription.update(subscription.id, {
|
109
|
+
gateway.subscription.update(processor_id, {
|
144
110
|
never_expires: true,
|
145
111
|
number_of_billing_cycles: nil
|
146
112
|
})
|
147
113
|
end
|
148
114
|
|
149
|
-
|
115
|
+
update(ends_at: nil, status: :active)
|
150
116
|
rescue ::Braintree::BraintreeError => e
|
151
117
|
raise Pay::Braintree::Error, e
|
152
118
|
end
|
@@ -165,26 +131,25 @@ module Pay
|
|
165
131
|
end
|
166
132
|
|
167
133
|
braintree_plan = find_braintree_plan(plan)
|
134
|
+
prorate = options.fetch(:prorate) { true }
|
168
135
|
|
169
|
-
if would_change_billing_frequency?(braintree_plan) && prorate
|
136
|
+
if would_change_billing_frequency?(braintree_plan) && prorate
|
170
137
|
swap_across_frequencies(braintree_plan)
|
171
138
|
return
|
172
139
|
end
|
173
140
|
|
174
|
-
|
175
|
-
|
176
|
-
result = gateway.subscription.update(subscription.id, {
|
141
|
+
result = gateway.subscription.update(processor_id, {
|
177
142
|
plan_id: braintree_plan.id,
|
178
143
|
price: braintree_plan.price,
|
179
144
|
never_expires: true,
|
180
145
|
number_of_billing_cycles: nil,
|
181
146
|
options: {
|
182
|
-
prorate_charges: prorate
|
147
|
+
prorate_charges: prorate
|
183
148
|
}
|
184
149
|
})
|
185
150
|
raise Error, "Braintree failed to swap plans: #{result.message}" unless result.success?
|
186
151
|
|
187
|
-
|
152
|
+
update(processor_plan: plan, ends_at: nil, status: :active)
|
188
153
|
rescue ::Braintree::BraintreeError => e
|
189
154
|
raise Pay::Braintree::Error, e
|
190
155
|
end
|
@@ -198,7 +163,7 @@ module Pay
|
|
198
163
|
)
|
199
164
|
|
200
165
|
if result.success?
|
201
|
-
|
166
|
+
update(status: :active)
|
202
167
|
end
|
203
168
|
end
|
204
169
|
|
@@ -224,30 +189,25 @@ module Pay
|
|
224
189
|
|
225
190
|
def discount_for_switching_to_monthly(current_plan, plan)
|
226
191
|
cycles = (money_remaining_on_yearly_plan(current_plan) / plan.price).floor
|
227
|
-
|
192
|
+
ActiveSupport::InheritableOptions.new(
|
228
193
|
amount: plan.price,
|
229
194
|
number_of_billing_cycles: cycles
|
230
195
|
)
|
231
196
|
end
|
232
197
|
|
233
198
|
def money_remaining_on_yearly_plan(current_plan)
|
234
|
-
end_date =
|
199
|
+
end_date = api_record.billing_period_end_date.to_date
|
235
200
|
(current_plan.price / 365) * (end_date - Date.today)
|
236
201
|
end
|
237
202
|
|
238
|
-
def discount_for_switching_to_yearly
|
239
|
-
|
240
|
-
|
241
|
-
processor_subscription.discounts.each do |discount|
|
203
|
+
def discount_for_switching_to_yearly(amount: 0)
|
204
|
+
api_record.discounts.each do |discount|
|
242
205
|
if discount.id == "plan-credit"
|
243
206
|
amount += discount.amount * discount.number_of_billing_cycles
|
244
207
|
end
|
245
208
|
end
|
246
209
|
|
247
|
-
|
248
|
-
amount: amount,
|
249
|
-
number_of_billing_cycles: 1
|
250
|
-
)
|
210
|
+
ActiveSupport::InheritableOptions.new(amount: amount, number_of_billing_cycles: 1)
|
251
211
|
end
|
252
212
|
|
253
213
|
def swap_across_frequencies(plan)
|
@@ -283,3 +243,5 @@ module Pay
|
|
283
243
|
end
|
284
244
|
end
|
285
245
|
end
|
246
|
+
|
247
|
+
ActiveSupport.run_load_hooks :pay_braintree_subscription, Pay::Braintree::Subscription
|