pay 7.1.0 → 7.2.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 +5 -1
- data/app/models/pay/charge.rb +1 -1
- data/app/models/pay/customer.rb +1 -1
- data/app/models/pay/subscription.rb +3 -3
- data/lib/pay/braintree/billable.rb +1 -1
- data/lib/pay/fake_processor/billable.rb +3 -2
- data/lib/pay/paddle_billing/charge.rb +2 -2
- data/lib/pay/paddle_billing/payment_method.rb +8 -1
- data/lib/pay/paddle_billing/subscription.rb +7 -3
- data/lib/pay/paddle_classic/subscription.rb +4 -1
- data/lib/pay/stripe/charge.rb +1 -1
- data/lib/pay/stripe/payment_method.rb +1 -1
- data/lib/pay/stripe/subscription.rb +3 -3
- data/lib/pay/stripe.rb +1 -1
- data/lib/pay/version.rb +1 -1
- metadata +3 -11
- 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/subscription.rb +0 -11
- data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
- data/lib/pay/lemon_squeezy.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b50cc63cfa61beffbc725d9f6c623c469a6357e0808ff06d3e814c4aa4e19955
|
4
|
+
data.tar.gz: ae87dbfcd24862baa2f156cb2b1b94fe836a0d113853b562cbe59835e59569c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 459524ff5b772a08c1ee1d994088b71f0e9a854f4b1afa76ecdb60fa8229f43ea72ed327a6dbf55d497478d033f99f1d19c4ed978c69931e27ad9ddc99fd57af
|
7
|
+
data.tar.gz: 200898e080a94d76520e0a85088d43691f64a27da3d94ac4fa74a9f14db4f6a5c1bdc16992422b3f4c4ab9b63bb35344e099799984cca0beb4cff8a6dc6e8512
|
data/README.md
CHANGED
@@ -44,7 +44,7 @@ Want to add a new payment provider? Contributions are welcome.
|
|
44
44
|
* **Payment Processors**
|
45
45
|
* [Stripe](docs/stripe/1_overview.md)
|
46
46
|
* [Braintree](docs/braintree/1_overview.md)
|
47
|
-
* [Paddle](docs/
|
47
|
+
* [Paddle](docs/paddle_billing/1_overview.md)
|
48
48
|
* [Fake Processor](docs/fake_processor/1_overview.md)
|
49
49
|
* **Marketplaces**
|
50
50
|
* [Stripe Connect](docs/marketplaces/stripe_connect.md)
|
@@ -55,6 +55,10 @@ Want to add a new payment provider? Contributions are welcome.
|
|
55
55
|
|
56
56
|
If you have an issue you'd like to submit, please do so using the issue tracker in GitHub. In order for us to help you in the best way possible, please be as detailed as you can.
|
57
57
|
|
58
|
+
For those using devcontainers, if you want to test the application with different databases:
|
59
|
+
1. Uncomment the `DATABASE_URL` corresponding to the database type you wish to use in the `.devcontainer/devcontainer.json` file.
|
60
|
+
2. Rebuild the devcontainer, which will configure the application to use the selected database for your development environment.
|
61
|
+
|
58
62
|
If you'd like to open a PR please make sure the following things pass:
|
59
63
|
|
60
64
|
```ruby
|
data/app/models/pay/charge.rb
CHANGED
@@ -45,7 +45,7 @@ module Pay
|
|
45
45
|
|
46
46
|
# Helpers for payment processors
|
47
47
|
%w[braintree stripe paddle_billing paddle_classic fake_processor].each do |processor_name|
|
48
|
-
define_method "#{processor_name}?" do
|
48
|
+
define_method :"#{processor_name}?" do
|
49
49
|
customer.processor == processor_name
|
50
50
|
end
|
51
51
|
|
data/app/models/pay/customer.rb
CHANGED
@@ -28,7 +28,7 @@ module Pay
|
|
28
28
|
%w[stripe braintree paddle_billing paddle_classic fake_processor].each do |processor_name|
|
29
29
|
scope processor_name, -> { where(processor: processor_name) }
|
30
30
|
|
31
|
-
define_method "#{processor_name}?" do
|
31
|
+
define_method :"#{processor_name}?" do
|
32
32
|
processor == processor_name
|
33
33
|
end
|
34
34
|
end
|
@@ -9,11 +9,11 @@ module Pay
|
|
9
9
|
|
10
10
|
# Scopes
|
11
11
|
scope :for_name, ->(name) { where(name: name) }
|
12
|
-
scope :on_trial, -> { where("trial_ends_at > ?", Time.current) }
|
12
|
+
scope :on_trial, -> { where(status: ["trialing", "active"]).where("trial_ends_at > ?", Time.current) }
|
13
13
|
scope :canceled, -> { where.not(ends_at: nil) }
|
14
14
|
scope :cancelled, -> { canceled }
|
15
15
|
scope :on_grace_period, -> { where("#{table_name}.ends_at IS NOT NULL AND #{table_name}.ends_at > ?", Time.current) }
|
16
|
-
scope :active, -> { where(status:
|
16
|
+
scope :active, -> { where(status: "active").pause_not_started.where("#{table_name}.ends_at IS NULL OR #{table_name}.ends_at > ?", Time.current).or(on_trial) }
|
17
17
|
scope :paused, -> { where(status: "paused").or(where("pause_starts_at <= ?", Time.current)) }
|
18
18
|
scope :pause_not_started, -> { where("pause_starts_at IS NULL OR pause_starts_at > ?", Time.current) }
|
19
19
|
scope :active_or_paused, -> { active.or(paused) }
|
@@ -44,7 +44,7 @@ module Pay
|
|
44
44
|
|
45
45
|
# Helper methods for payment processors
|
46
46
|
%w[braintree stripe paddle_billing paddle_classic fake_processor].each do |processor_name|
|
47
|
-
define_method "#{processor_name}?" do
|
47
|
+
define_method :"#{processor_name}?" do
|
48
48
|
customer.processor == processor_name
|
49
49
|
end
|
50
50
|
|
@@ -265,7 +265,7 @@ module Pay
|
|
265
265
|
end
|
266
266
|
|
267
267
|
# Retrieve payment method details from transaction
|
268
|
-
payment_method = transaction.send("#{attribute_name}_details")
|
268
|
+
payment_method = transaction.send(:"#{attribute_name}_details")
|
269
269
|
|
270
270
|
{
|
271
271
|
payment_method_type: :card,
|
@@ -28,7 +28,8 @@ module Pay
|
|
28
28
|
# Make to generate a processor_id
|
29
29
|
customer
|
30
30
|
|
31
|
-
|
31
|
+
valid_attributes = options.slice(*Pay::Charge.attribute_names.map(&:to_sym))
|
32
|
+
attributes = {
|
32
33
|
processor_id: NanoId.generate,
|
33
34
|
amount: amount,
|
34
35
|
data: {
|
@@ -38,7 +39,7 @@ module Pay
|
|
38
39
|
exp_month: Date.today.month,
|
39
40
|
exp_year: Date.today.year
|
40
41
|
}
|
41
|
-
)
|
42
|
+
}.deep_merge(valid_attributes)
|
42
43
|
pay_customer.charges.create!(attributes)
|
43
44
|
end
|
44
45
|
|
@@ -37,8 +37,8 @@ module Pay
|
|
37
37
|
subscription: pay_customer.subscriptions.find_by(processor_id: object.subscription_id)
|
38
38
|
}
|
39
39
|
|
40
|
-
if object.
|
41
|
-
case
|
40
|
+
if (details = Array.wrap(object.payments).first&.method_details)
|
41
|
+
case details.type.downcase
|
42
42
|
when "card"
|
43
43
|
attrs[:payment_method_type] = "card"
|
44
44
|
attrs[:brand] = details.card.type
|
@@ -5,6 +5,13 @@ module Pay
|
|
5
5
|
|
6
6
|
delegate :customer, :processor_id, to: :pay_payment_method
|
7
7
|
|
8
|
+
def self.sync_from_transaction(pay_customer:, transaction:)
|
9
|
+
transaction = ::Paddle::Transaction.retrieve(id: transaction)
|
10
|
+
return unless transaction.status == "completed"
|
11
|
+
return if transaction.payments.empty?
|
12
|
+
sync(pay_customer: pay_customer, attributes: transaction.payments.first)
|
13
|
+
end
|
14
|
+
|
8
15
|
def self.sync(pay_customer:, attributes:)
|
9
16
|
details = attributes.method_details
|
10
17
|
attrs = {
|
@@ -19,7 +26,7 @@ module Pay
|
|
19
26
|
attrs[:exp_year] = details.card.expiry_year
|
20
27
|
end
|
21
28
|
|
22
|
-
payment_method = pay_customer.payment_methods.find_or_initialize_by(processor_id: attributes.
|
29
|
+
payment_method = pay_customer.payment_methods.find_or_initialize_by(processor_id: attributes.payment_method_id)
|
23
30
|
payment_method.update!(attrs)
|
24
31
|
payment_method
|
25
32
|
end
|
@@ -56,9 +56,13 @@ module Pay
|
|
56
56
|
# Remove payment methods since customer cannot be reused after cancelling
|
57
57
|
Pay::PaymentMethod.where(customer_id: object.customer_id).destroy_all
|
58
58
|
when "trialing"
|
59
|
-
attributes[:trial_ends_at] = Time.parse(object.next_billed_at)
|
59
|
+
attributes[:trial_ends_at] = Time.parse(object.next_billed_at) if object.next_billed_at
|
60
60
|
when "paused"
|
61
|
-
attributes[:pause_starts_at] = Time.parse(object.paused_at)
|
61
|
+
attributes[:pause_starts_at] = Time.parse(object.paused_at) if object.paused_at
|
62
|
+
when "active", "past_due"
|
63
|
+
attributes[:trial_ends_at] = nil
|
64
|
+
attributes[:pause_starts_at] = nil
|
65
|
+
attributes[:ends_at] = nil
|
62
66
|
end
|
63
67
|
|
64
68
|
case object.scheduled_change&.action
|
@@ -105,7 +109,7 @@ module Pay
|
|
105
109
|
)
|
106
110
|
pay_subscription.update(
|
107
111
|
status: response.status,
|
108
|
-
ends_at: response.scheduled_change.
|
112
|
+
ends_at: response.scheduled_change&.effective_at || Time.current
|
109
113
|
)
|
110
114
|
rescue ::Paddle::Error => e
|
111
115
|
raise Pay::PaddleBilling::Error, e
|
@@ -44,12 +44,15 @@ module Pay
|
|
44
44
|
status: object.state || object.status
|
45
45
|
}
|
46
46
|
|
47
|
-
# If paused or delete while on trial, set ends_at to match
|
48
47
|
case attributes[:status]
|
49
48
|
when "trialing"
|
50
49
|
attributes[:trial_ends_at] = Time.zone.parse(object.next_bill_date)
|
51
50
|
attributes[:ends_at] = nil
|
51
|
+
when "active", "past_due"
|
52
|
+
attributes[:trial_ends_at] = nil
|
53
|
+
attributes[:ends_at] = nil
|
52
54
|
when "paused", "deleted"
|
55
|
+
# If paused or delete while on trial, set ends_at to match
|
53
56
|
attributes[:trial_ends_at] = nil
|
54
57
|
attributes[:ends_at] = Time.zone.parse(object.next_bill_date)
|
55
58
|
end
|
data/lib/pay/stripe/charge.rb
CHANGED
@@ -29,7 +29,7 @@ module Pay
|
|
29
29
|
refunds = []
|
30
30
|
object.refunds.auto_paging_each { |refund| refunds << refund }
|
31
31
|
|
32
|
-
payment_method = object.payment_method_details.
|
32
|
+
payment_method = object.payment_method_details.try(object.payment_method_details.type)
|
33
33
|
attrs = {
|
34
34
|
amount: object.amount,
|
35
35
|
amount_captured: object.amount_captured,
|
@@ -60,7 +60,7 @@ module Pay
|
|
60
60
|
|
61
61
|
# Extracts payment method details from a Stripe::PaymentMethod object
|
62
62
|
def self.extract_attributes(payment_method)
|
63
|
-
details = payment_method.
|
63
|
+
details = payment_method.try(payment_method.type)
|
64
64
|
|
65
65
|
{
|
66
66
|
payment_method_type: payment_method.type,
|
@@ -207,7 +207,7 @@ module Pay
|
|
207
207
|
# cancel_now!(prorate: true)
|
208
208
|
# cancel_now!(invoice_now: true)
|
209
209
|
def cancel_now!(**options)
|
210
|
-
return if canceled?
|
210
|
+
return if canceled? && ends_at.past?
|
211
211
|
|
212
212
|
@stripe_subscription = ::Stripe::Subscription.cancel(processor_id, options.merge(expand_options), stripe_options)
|
213
213
|
pay_subscription.update(ends_at: Time.current, status: :canceled)
|
@@ -342,13 +342,13 @@ module Pay
|
|
342
342
|
# create_usage_record(quantity: 4, action: :increment)
|
343
343
|
# create_usage_record(subscription_item_id: "si_1234", quantity: 100, action: :set)
|
344
344
|
def create_usage_record(**options)
|
345
|
-
subscription_item_id = options.
|
345
|
+
subscription_item_id = options.delete(:subscription_item_id) || metered_subscription_item&.dig("id")
|
346
346
|
::Stripe::SubscriptionItem.create_usage_record(subscription_item_id, options, stripe_options)
|
347
347
|
end
|
348
348
|
|
349
349
|
# Returns usage record summaries for a subscription item
|
350
350
|
def usage_record_summaries(**options)
|
351
|
-
subscription_item_id = options.
|
351
|
+
subscription_item_id = options.delete(:subscription_item_id) || metered_subscription_item&.dig("id")
|
352
352
|
::Stripe::SubscriptionItem.list_usage_record_summaries(subscription_item_id, options, stripe_options)
|
353
353
|
end
|
354
354
|
|
data/lib/pay/stripe.rb
CHANGED
data/lib/pay/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.
|
4
|
+
version: 7.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Charnes
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-05-16 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -104,14 +104,6 @@ files:
|
|
104
104
|
- lib/pay/fake_processor/merchant.rb
|
105
105
|
- lib/pay/fake_processor/payment_method.rb
|
106
106
|
- lib/pay/fake_processor/subscription.rb
|
107
|
-
- lib/pay/lemon_squeezy.rb
|
108
|
-
- lib/pay/lemon_squeezy/billable.rb
|
109
|
-
- lib/pay/lemon_squeezy/charge.rb
|
110
|
-
- lib/pay/lemon_squeezy/error.rb
|
111
|
-
- lib/pay/lemon_squeezy/payment_method.rb
|
112
|
-
- lib/pay/lemon_squeezy/subscription.rb
|
113
|
-
- lib/pay/lemon_squeezy/webhooks/subscription.rb
|
114
|
-
- lib/pay/lemon_squeezy/webhooks/transaction_completed.rb
|
115
107
|
- lib/pay/nano_id.rb
|
116
108
|
- lib/pay/paddle_billing.rb
|
117
109
|
- lib/pay/paddle_billing/billable.rb
|
@@ -184,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
176
|
- !ruby/object:Gem::Version
|
185
177
|
version: '0'
|
186
178
|
requirements: []
|
187
|
-
rubygems_version: 3.
|
179
|
+
rubygems_version: 3.5.6
|
188
180
|
signing_key:
|
189
181
|
specification_version: 4
|
190
182
|
summary: Payments engine for Ruby on Rails
|
@@ -1,90 +0,0 @@
|
|
1
|
-
module Pay
|
2
|
-
module PaddleBilling
|
3
|
-
class Billable
|
4
|
-
attr_reader :pay_customer
|
5
|
-
|
6
|
-
delegate :processor_id,
|
7
|
-
:processor_id?,
|
8
|
-
:email,
|
9
|
-
:customer_name,
|
10
|
-
:card_token,
|
11
|
-
to: :pay_customer
|
12
|
-
|
13
|
-
def initialize(pay_customer)
|
14
|
-
@pay_customer = pay_customer
|
15
|
-
end
|
16
|
-
|
17
|
-
def customer_attributes
|
18
|
-
{email: email, name: customer_name}
|
19
|
-
end
|
20
|
-
|
21
|
-
# Retrieves a Paddle::Customer object
|
22
|
-
#
|
23
|
-
# Finds an existing Paddle::Customer if processor_id exists
|
24
|
-
# Creates a new Paddle::Customer using `email` and `customer_name` if empty processor_id
|
25
|
-
#
|
26
|
-
# Returns a Paddle::Customer object
|
27
|
-
def customer
|
28
|
-
if processor_id?
|
29
|
-
::Paddle::Customer.retrieve(id: processor_id)
|
30
|
-
else
|
31
|
-
sc = ::Paddle::Customer.create(email: email, name: customer_name)
|
32
|
-
pay_customer.update!(processor_id: sc.id)
|
33
|
-
sc
|
34
|
-
end
|
35
|
-
rescue ::Paddle::Error => e
|
36
|
-
raise Pay::PaddleBilling::Error, e
|
37
|
-
end
|
38
|
-
|
39
|
-
# Syncs name and email to Paddle::Customer
|
40
|
-
# You can also pass in other attributes that will be merged into the default attributes
|
41
|
-
def update_customer!(**attributes)
|
42
|
-
customer unless processor_id?
|
43
|
-
attrs = customer_attributes.merge(attributes)
|
44
|
-
::Paddle::Customer.update(id: processor_id, **attrs)
|
45
|
-
end
|
46
|
-
|
47
|
-
def charge(amount, options = {})
|
48
|
-
return Pay::Error unless options
|
49
|
-
|
50
|
-
items = options[:items]
|
51
|
-
opts = options.except(:items).merge(customer_id: processor_id)
|
52
|
-
transaction = ::Paddle::Transaction.create(items: items, **opts)
|
53
|
-
|
54
|
-
attrs = {
|
55
|
-
amount: transaction.details.totals.grand_total,
|
56
|
-
created_at: transaction.created_at,
|
57
|
-
currency: transaction.currency_code,
|
58
|
-
metadata: transaction.details.line_items&.first&.id
|
59
|
-
}
|
60
|
-
|
61
|
-
charge = pay_customer.charges.find_or_initialize_by(processor_id: transaction.id)
|
62
|
-
charge.update(attrs)
|
63
|
-
charge
|
64
|
-
rescue ::Paddle::Error => e
|
65
|
-
raise Pay::PaddleBilling::Error, e
|
66
|
-
end
|
67
|
-
|
68
|
-
def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
|
69
|
-
# pass
|
70
|
-
end
|
71
|
-
|
72
|
-
# Paddle does not use payment method tokens. The method signature has it here
|
73
|
-
# to have a uniform API with the other payment processors.
|
74
|
-
def add_payment_method(token = nil, default: true)
|
75
|
-
Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer)
|
76
|
-
end
|
77
|
-
|
78
|
-
def trial_end_date(subscription)
|
79
|
-
return unless subscription.state == "trialing"
|
80
|
-
Time.zone.parse(subscription.next_payment[:date]).end_of_day
|
81
|
-
end
|
82
|
-
|
83
|
-
def processor_subscription(subscription_id, options = {})
|
84
|
-
::Paddle::Subscription.retrieve(id: subscription_id, **options)
|
85
|
-
rescue ::Paddle::Error => e
|
86
|
-
raise Pay::PaddleBilling::Error, e
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
module Pay
|
2
|
-
module PaddleBilling
|
3
|
-
class Charge
|
4
|
-
attr_reader :pay_charge
|
5
|
-
|
6
|
-
delegate :processor_id, :customer, to: :pay_charge
|
7
|
-
|
8
|
-
def initialize(pay_charge)
|
9
|
-
@pay_charge = pay_charge
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.sync(charge_id, object: nil, try: 0, retries: 1)
|
13
|
-
# Skip loading the latest charge details from the API if we already have it
|
14
|
-
object ||= ::Paddle::Transaction.retrieve(id: charge_id)
|
15
|
-
|
16
|
-
# Ignore transactions that aren't completed
|
17
|
-
return unless object.status == "completed"
|
18
|
-
|
19
|
-
# Ignore charges without a Customer
|
20
|
-
return if object.customer_id.blank?
|
21
|
-
|
22
|
-
pay_customer = Pay::Customer.find_by(processor: :paddle_billing, processor_id: object.customer_id)
|
23
|
-
return unless pay_customer
|
24
|
-
|
25
|
-
# Ignore transactions that are payment method changes
|
26
|
-
# But update the customer's payment method
|
27
|
-
if object.origin == "subscription_payment_method_change"
|
28
|
-
Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer, attributes: object.payments.first)
|
29
|
-
return
|
30
|
-
end
|
31
|
-
|
32
|
-
attrs = {
|
33
|
-
amount: object.details.totals.grand_total,
|
34
|
-
created_at: object.created_at,
|
35
|
-
currency: object.currency_code,
|
36
|
-
metadata: object.details.line_items&.first&.id,
|
37
|
-
subscription: pay_customer.subscriptions.find_by(processor_id: object.subscription_id)
|
38
|
-
}
|
39
|
-
|
40
|
-
if object.payment
|
41
|
-
case object.payment.method_details.type.downcase
|
42
|
-
when "card"
|
43
|
-
attrs[:payment_method_type] = "card"
|
44
|
-
attrs[:brand] = details.card.type
|
45
|
-
attrs[:exp_month] = details.card.expiry_month
|
46
|
-
attrs[:exp_year] = details.card.expiry_year
|
47
|
-
attrs[:last4] = details.card.last4
|
48
|
-
when "paypal"
|
49
|
-
attrs[:payment_method_type] = "paypal"
|
50
|
-
end
|
51
|
-
|
52
|
-
# Update customer's payment method
|
53
|
-
Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer, attributes: object.payments.first)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Update or create the charge
|
57
|
-
if (pay_charge = pay_customer.charges.find_by(processor_id: object.id))
|
58
|
-
pay_charge.with_lock do
|
59
|
-
pay_charge.update!(attrs)
|
60
|
-
end
|
61
|
-
pay_charge
|
62
|
-
else
|
63
|
-
pay_customer.charges.create!(attrs.merge(processor_id: object.id))
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
module Pay
|
2
|
-
module PaddleBilling
|
3
|
-
class PaymentMethod
|
4
|
-
attr_reader :pay_payment_method
|
5
|
-
|
6
|
-
delegate :customer, :processor_id, to: :pay_payment_method
|
7
|
-
|
8
|
-
def self.sync(pay_customer:, attributes:)
|
9
|
-
details = attributes.method_details
|
10
|
-
attrs = {
|
11
|
-
type: details.type.downcase
|
12
|
-
}
|
13
|
-
|
14
|
-
case details.type.downcase
|
15
|
-
when "card"
|
16
|
-
attrs[:brand] = details.card.type
|
17
|
-
attrs[:last4] = details.card.last4
|
18
|
-
attrs[:exp_month] = details.card.expiry_month
|
19
|
-
attrs[:exp_year] = details.card.expiry_year
|
20
|
-
end
|
21
|
-
|
22
|
-
payment_method = pay_customer.payment_methods.find_or_initialize_by(processor_id: attributes.stored_payment_method_id)
|
23
|
-
payment_method.update!(attrs)
|
24
|
-
payment_method
|
25
|
-
end
|
26
|
-
|
27
|
-
def initialize(pay_payment_method)
|
28
|
-
@pay_payment_method = pay_payment_method
|
29
|
-
end
|
30
|
-
|
31
|
-
# Sets payment method as default
|
32
|
-
def make_default!
|
33
|
-
end
|
34
|
-
|
35
|
-
# Remove payment method
|
36
|
-
def detach
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,185 +0,0 @@
|
|
1
|
-
module Pay
|
2
|
-
module PaddleBilling
|
3
|
-
class Subscription
|
4
|
-
attr_reader :pay_subscription
|
5
|
-
|
6
|
-
delegate :active?,
|
7
|
-
:canceled?,
|
8
|
-
:on_grace_period?,
|
9
|
-
:on_trial?,
|
10
|
-
:ends_at,
|
11
|
-
:name,
|
12
|
-
:owner,
|
13
|
-
:pause_starts_at,
|
14
|
-
:pause_starts_at?,
|
15
|
-
:processor_id,
|
16
|
-
:processor_plan,
|
17
|
-
:processor_subscription,
|
18
|
-
:prorate,
|
19
|
-
:prorate?,
|
20
|
-
:quantity,
|
21
|
-
:quantity?,
|
22
|
-
:trial_ends_at,
|
23
|
-
to: :pay_subscription
|
24
|
-
|
25
|
-
def self.sync_from_transaction(transaction_id)
|
26
|
-
transaction = ::Paddle::Transaction.retrieve(id: transaction_id)
|
27
|
-
sync(transaction.subscription_id) if transaction.subscription_id
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
|
31
|
-
# Passthrough is not return from this API, so we can't use that
|
32
|
-
object ||= ::Paddle::Subscription.retrieve(id: subscription_id)
|
33
|
-
|
34
|
-
pay_customer = Pay::Customer.find_by(processor: :paddle_billing, processor_id: object.customer_id)
|
35
|
-
return unless pay_customer
|
36
|
-
|
37
|
-
attributes = {
|
38
|
-
current_period_end: object.current_billing_period&.ends_at,
|
39
|
-
current_period_start: object.current_billing_period&.starts_at,
|
40
|
-
ends_at: (object.canceled_at ? Time.parse(object.canceled_at) : nil),
|
41
|
-
metadata: object.custom_data,
|
42
|
-
paddle_cancel_url: object.management_urls&.cancel,
|
43
|
-
paddle_update_url: object.management_urls&.update_payment_method,
|
44
|
-
pause_starts_at: (object.paused_at ? Time.parse(object.paused_at) : nil),
|
45
|
-
status: object.status
|
46
|
-
}
|
47
|
-
|
48
|
-
if object.items&.first
|
49
|
-
item = object.items.first
|
50
|
-
attributes[:processor_plan] = item.price.id
|
51
|
-
attributes[:quantity] = item.quantity
|
52
|
-
end
|
53
|
-
|
54
|
-
case attributes[:status]
|
55
|
-
when "canceled"
|
56
|
-
# Remove payment methods since customer cannot be reused after cancelling
|
57
|
-
Pay::PaymentMethod.where(customer_id: object.customer_id).destroy_all
|
58
|
-
when "trialing"
|
59
|
-
attributes[:trial_ends_at] = Time.parse(object.next_billed_at)
|
60
|
-
when "paused"
|
61
|
-
attributes[:pause_starts_at] = Time.parse(object.paused_at)
|
62
|
-
end
|
63
|
-
|
64
|
-
case object.scheduled_change&.action
|
65
|
-
when "cancel"
|
66
|
-
attributes[:ends_at] = Time.parse(object.scheduled_change.effective_at)
|
67
|
-
when "pause"
|
68
|
-
attributes[:pause_starts_at] = Time.parse(object.scheduled_change.effective_at)
|
69
|
-
when "resume"
|
70
|
-
attributes[:pause_resumes_at] = Time.parse(object.scheduled_change.effective_at)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Update or create the subscription
|
74
|
-
if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: subscription_id))
|
75
|
-
pay_subscription.with_lock do
|
76
|
-
pay_subscription.update!(attributes)
|
77
|
-
end
|
78
|
-
pay_subscription
|
79
|
-
else
|
80
|
-
pay_customer.subscriptions.create!(attributes.merge(name: name, processor_id: subscription_id))
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def initialize(pay_subscription)
|
85
|
-
@pay_subscription = pay_subscription
|
86
|
-
end
|
87
|
-
|
88
|
-
def subscription(**options)
|
89
|
-
@paddle_billing_subscription ||= ::Paddle::Subscription.retrieve(id: processor_id, **options)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Get a transaction to update payment method
|
93
|
-
def payment_method_transaction
|
94
|
-
::Paddle::Subscription.get_transaction(id: processor_id)
|
95
|
-
end
|
96
|
-
|
97
|
-
# If a subscription is paused, cancel immediately
|
98
|
-
# Otherwise, cancel at period end
|
99
|
-
def cancel(**options)
|
100
|
-
return if canceled?
|
101
|
-
|
102
|
-
response = ::Paddle::Subscription.cancel(
|
103
|
-
id: processor_id,
|
104
|
-
effective_from: options.fetch(:effective_from, (paused? ? "immediately" : "next_billing_period"))
|
105
|
-
)
|
106
|
-
pay_subscription.update(
|
107
|
-
status: response.status,
|
108
|
-
ends_at: response.scheduled_change.effective_at
|
109
|
-
)
|
110
|
-
rescue ::Paddle::Error => e
|
111
|
-
raise Pay::PaddleBilling::Error, e
|
112
|
-
end
|
113
|
-
|
114
|
-
def cancel_now!(**options)
|
115
|
-
cancel(options.merge(effective_from: "immediately"))
|
116
|
-
rescue ::Paddle::Error => e
|
117
|
-
raise Pay::PaddleBilling::Error, e
|
118
|
-
end
|
119
|
-
|
120
|
-
def change_quantity(quantity, **options)
|
121
|
-
items = [{
|
122
|
-
price_id: processor_plan,
|
123
|
-
quantity: quantity
|
124
|
-
}]
|
125
|
-
|
126
|
-
::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
|
127
|
-
rescue ::Paddle::Error => e
|
128
|
-
raise Pay::PaddleBilling::Error, e
|
129
|
-
end
|
130
|
-
|
131
|
-
# A subscription could be set to cancel or pause in the future
|
132
|
-
# It is considered on grace period until the cancel or pause time begins
|
133
|
-
def on_grace_period?
|
134
|
-
(canceled? && Time.current < ends_at) || (paused? && pause_starts_at? && Time.current < pause_starts_at)
|
135
|
-
end
|
136
|
-
|
137
|
-
def paused?
|
138
|
-
pay_subscription.status == "paused"
|
139
|
-
end
|
140
|
-
|
141
|
-
def pause
|
142
|
-
response = ::Paddle::Subscription.pause(id: processor_id)
|
143
|
-
pay_subscription.update!(status: :paused, pause_starts_at: response.scheduled_change.effective_at)
|
144
|
-
rescue ::Paddle::Error => e
|
145
|
-
raise Pay::PaddleBilling::Error, e
|
146
|
-
end
|
147
|
-
|
148
|
-
def resumable?
|
149
|
-
paused?
|
150
|
-
end
|
151
|
-
|
152
|
-
def resume
|
153
|
-
unless resumable?
|
154
|
-
raise StandardError, "You can only resume paused subscriptions."
|
155
|
-
end
|
156
|
-
|
157
|
-
# Paddle Billing API only allows "resuming" subscriptions when they are paused
|
158
|
-
# So cancel the scheduled change if it is in the future
|
159
|
-
if paused? && pause_starts_at? && Time.current < pause_starts_at
|
160
|
-
::Paddle::Subscription.update(id: processor_id, scheduled_change: nil)
|
161
|
-
else
|
162
|
-
::Paddle::Subscription.resume(id: processor_id, effective_from: "immediately")
|
163
|
-
end
|
164
|
-
|
165
|
-
pay_subscription.update(status: :active, pause_starts_at: nil)
|
166
|
-
rescue ::Paddle::Error => e
|
167
|
-
raise Pay::PaddleBilling::Error, e
|
168
|
-
end
|
169
|
-
|
170
|
-
def swap(plan, **options)
|
171
|
-
items = [{
|
172
|
-
price_id: plan,
|
173
|
-
quantity: quantity || 1
|
174
|
-
}]
|
175
|
-
|
176
|
-
::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
|
177
|
-
pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
|
178
|
-
end
|
179
|
-
|
180
|
-
# Retries the latest invoice for a Past Due subscription
|
181
|
-
def retry_failed_payment
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
data/lib/pay/lemon_squeezy.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
module Pay
|
2
|
-
module LemonSqueezy
|
3
|
-
autoload :Billable, "pay/stripe/billable"
|
4
|
-
autoload :Charge, "pay/stripe/charge"
|
5
|
-
autoload :Error, "pay/stripe/error"
|
6
|
-
autoload :Merchant, "pay/stripe/merchant"
|
7
|
-
autoload :PaymentMethod, "pay/stripe/payment_method"
|
8
|
-
autoload :Subscription, "pay/stripe/subscription"
|
9
|
-
|
10
|
-
module Webhooks
|
11
|
-
autoload :AccountUpdated, "pay/stripe/webhooks/account_updated"
|
12
|
-
autoload :ChargeRefunded, "pay/stripe/webhooks/charge_refunded"
|
13
|
-
autoload :ChargeSucceeded, "pay/stripe/webhooks/charge_succeeded"
|
14
|
-
autoload :CheckoutSessionCompleted, "pay/stripe/webhooks/checkout_session_completed"
|
15
|
-
autoload :CheckoutSessionAsyncPaymentSucceeded, "pay/stripe/webhooks/checkout_session_async_payment_succeeded"
|
16
|
-
autoload :CustomerDeleted, "pay/stripe/webhooks/customer_deleted"
|
17
|
-
autoload :CustomerUpdated, "pay/stripe/webhooks/customer_updated"
|
18
|
-
autoload :PaymentActionRequired, "pay/stripe/webhooks/payment_action_required"
|
19
|
-
autoload :PaymentFailed, "pay/stripe/webhooks/payment_failed"
|
20
|
-
autoload :PaymentIntentSucceeded, "pay/stripe/webhooks/payment_intent_succeeded"
|
21
|
-
autoload :PaymentMethodAttached, "pay/stripe/webhooks/payment_method_attached"
|
22
|
-
autoload :PaymentMethodDetached, "pay/stripe/webhooks/payment_method_detached"
|
23
|
-
autoload :PaymentMethodUpdated, "pay/stripe/webhooks/payment_method_updated"
|
24
|
-
autoload :SubscriptionCreated, "pay/stripe/webhooks/subscription_created"
|
25
|
-
autoload :SubscriptionDeleted, "pay/stripe/webhooks/subscription_deleted"
|
26
|
-
autoload :SubscriptionRenewing, "pay/stripe/webhooks/subscription_renewing"
|
27
|
-
autoload :SubscriptionUpdated, "pay/stripe/webhooks/subscription_updated"
|
28
|
-
autoload :SubscriptionTrialWillEnd, "pay/stripe/webhooks/subscription_trial_will_end"
|
29
|
-
end
|
30
|
-
|
31
|
-
extend Env
|
32
|
-
|
33
|
-
REQUIRED_VERSION = "~> 1"
|
34
|
-
|
35
|
-
def self.enabled?
|
36
|
-
return false unless Pay.enabled_processors.include?(:lemonsqueezy) && defined?(::Lemonzsqueezy)
|
37
|
-
|
38
|
-
Pay::Engine.version_matches?(required: REQUIRED_VERSION, current: ::Lemonsqueezy::VERSION) || (raise "[Pay] lemonsqueezy gem must be version #{REQUIRED_VERSION}")
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.setup
|
42
|
-
::Stripe.api_key = private_key
|
43
|
-
|
44
|
-
# Used by Stripe to identify Pay for support
|
45
|
-
::Stripe.set_app_info("PayRails", partner_id: "pp_partner_IqhY0UExnJYLxg", version: Pay::VERSION, url: "https://github.com/pay-rails/pay")
|
46
|
-
|
47
|
-
# Automatically retry requests that fail
|
48
|
-
# This automatically includes idempotency keys in the request to guarantee that retires are safe
|
49
|
-
# https://github.com/stripe/stripe-ruby#configuring-automatic-retries
|
50
|
-
::Stripe.max_network_retries = 2
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.public_key
|
54
|
-
find_value_by_name(:stripe, :public_key)
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.private_key
|
58
|
-
find_value_by_name(:stripe, :private_key)
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.signing_secret
|
62
|
-
find_value_by_name(:stripe, :signing_secret)
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.configure_webhooks
|
66
|
-
Pay::Webhooks.configure do |events|
|
67
|
-
# Listen to the charge event to make sure we get non-subscription
|
68
|
-
# purchases as well. Invoice is only for subscriptions and manual creation
|
69
|
-
# so it does not include individual charges.
|
70
|
-
events.subscribe "stripe.charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
|
71
|
-
events.subscribe "stripe.charge.refunded", Pay::Stripe::Webhooks::ChargeRefunded.new
|
72
|
-
|
73
|
-
events.subscribe "stripe.payment_intent.succeeded", Pay::Stripe::Webhooks::PaymentIntentSucceeded.new
|
74
|
-
|
75
|
-
# Warn user of upcoming charges for their subscription. This is handy for
|
76
|
-
# notifying annual users their subscription will renew shortly.
|
77
|
-
# This probably should be ignored for monthly subscriptions.
|
78
|
-
events.subscribe "stripe.invoice.upcoming", Pay::Stripe::Webhooks::SubscriptionRenewing.new
|
79
|
-
|
80
|
-
# Payment action is required to process an invoice
|
81
|
-
events.subscribe "stripe.invoice.payment_action_required", Pay::Stripe::Webhooks::PaymentActionRequired.new
|
82
|
-
|
83
|
-
# If an invoice payment fails, we want to notify the user via email to update their payment details
|
84
|
-
events.subscribe "stripe.invoice.payment_failed", Pay::Stripe::Webhooks::PaymentFailed.new
|
85
|
-
|
86
|
-
# If a subscription is manually created on Stripe, we want to sync
|
87
|
-
events.subscribe "stripe.customer.subscription.created", Pay::Stripe::Webhooks::SubscriptionCreated.new
|
88
|
-
|
89
|
-
# If the plan, quantity, or trial ending date is updated on Stripe, we want to sync
|
90
|
-
events.subscribe "stripe.customer.subscription.updated", Pay::Stripe::Webhooks::SubscriptionUpdated.new
|
91
|
-
|
92
|
-
# When a customers subscription is canceled, we want to update our records
|
93
|
-
events.subscribe "stripe.customer.subscription.deleted", Pay::Stripe::Webhooks::SubscriptionDeleted.new
|
94
|
-
|
95
|
-
# When a customers subscription trial period is 3 days from ending or ended immediately this event is fired
|
96
|
-
events.subscribe "stripe.customer.subscription.trial_will_end", Pay::Stripe::Webhooks::SubscriptionTrialWillEnd.new
|
97
|
-
|
98
|
-
# Monitor changes for customer's default card changing and invoice credit updates
|
99
|
-
events.subscribe "stripe.customer.updated", Pay::Stripe::Webhooks::CustomerUpdated.new
|
100
|
-
|
101
|
-
# If a customer was deleted in Stripe, their subscriptions should be cancelled
|
102
|
-
events.subscribe "stripe.customer.deleted", Pay::Stripe::Webhooks::CustomerDeleted.new
|
103
|
-
|
104
|
-
# If a customer's payment source was deleted in Stripe, we should update as well
|
105
|
-
events.subscribe "stripe.payment_method.attached", Pay::Stripe::Webhooks::PaymentMethodAttached.new
|
106
|
-
events.subscribe "stripe.payment_method.updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
107
|
-
events.subscribe "stripe.payment_method.card_automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
108
|
-
events.subscribe "stripe.payment_method.detached", Pay::Stripe::Webhooks::PaymentMethodDetached.new
|
109
|
-
|
110
|
-
# If an account is updated in stripe, we should update it as well
|
111
|
-
events.subscribe "stripe.account.updated", Pay::Stripe::Webhooks::AccountUpdated.new
|
112
|
-
|
113
|
-
# Handle subscriptions in Stripe Checkout Sessions
|
114
|
-
events.subscribe "stripe.checkout.session.completed", Pay::Stripe::Webhooks::CheckoutSessionCompleted.new
|
115
|
-
events.subscribe "stripe.checkout.session.async_payment_succeeded", Pay::Stripe::Webhooks::CheckoutSessionAsyncPaymentSucceeded.new
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.to_client_reference_id(record)
|
120
|
-
raise ArgumentError, "#{record.class.name} does not include Pay. Allowed models: #{model_names.to_a.join(", ")}" unless model_names.include?(record.class.name)
|
121
|
-
[record.class.name, record.id].join("_")
|
122
|
-
end
|
123
|
-
|
124
|
-
def self.find_by_client_reference_id(client_reference_id)
|
125
|
-
# If there is a client reference ID, make sure we have a Pay::Customer record
|
126
|
-
# client_reference_id should be in the format of "User/1"
|
127
|
-
model_name, id = client_reference_id.split("_", 2)
|
128
|
-
|
129
|
-
# Only allow model names that use Pay
|
130
|
-
return unless model_names.include?(model_name)
|
131
|
-
|
132
|
-
model_name.constantize.find(id)
|
133
|
-
rescue ActiveRecord::RecordNotFound
|
134
|
-
Rails.logger.error "[Pay] Unable to locate record with: #{client_reference_id}"
|
135
|
-
nil
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|