pay 6.8.0 → 7.0.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/app/controllers/pay/webhooks/paddle_billing_controller.rb +51 -0
- data/app/controllers/pay/webhooks/{paddle_controller.rb → paddle_classic_controller.rb} +6 -6
- data/app/mailers/pay/application_mailer.rb +5 -1
- data/app/models/pay/charge.rb +1 -2
- data/app/models/pay/customer.rb +1 -2
- data/app/models/pay/merchant.rb +1 -3
- data/app/models/pay/payment_method.rb +1 -2
- data/app/models/pay/subscription.rb +10 -11
- data/app/models/pay/webhook.rb +10 -5
- data/app/views/pay/payments/show.html.erb +10 -17
- data/config/locales/en.yml +1 -1
- data/config/routes.rb +2 -1
- data/db/migrate/1_create_pay_tables.rb +6 -1
- data/lib/pay/braintree/subscription.rb +12 -2
- data/lib/pay/engine.rb +3 -2
- data/lib/pay/env.rb +1 -7
- data/lib/pay/fake_processor/subscription.rb +11 -1
- data/lib/pay/lemon_squeezy/billable.rb +90 -0
- data/lib/pay/lemon_squeezy/charge.rb +68 -0
- data/lib/pay/{paddle → lemon_squeezy}/error.rb +1 -1
- data/lib/pay/lemon_squeezy/payment_method.rb +40 -0
- data/lib/pay/lemon_squeezy/subscription.rb +185 -0
- data/lib/pay/lemon_squeezy/webhooks/subscription.rb +11 -0
- data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +11 -0
- data/lib/pay/lemon_squeezy.rb +138 -0
- data/lib/pay/paddle_billing/billable.rb +90 -0
- data/lib/pay/paddle_billing/charge.rb +68 -0
- data/lib/pay/paddle_billing/error.rb +7 -0
- data/lib/pay/paddle_billing/payment_method.rb +40 -0
- data/lib/pay/paddle_billing/subscription.rb +185 -0
- data/lib/pay/paddle_billing/webhooks/subscription.rb +11 -0
- data/lib/pay/paddle_billing/webhooks/transaction_completed.rb +11 -0
- data/lib/pay/paddle_billing.rb +58 -0
- data/lib/pay/{paddle → paddle_classic}/billable.rb +9 -10
- data/lib/pay/paddle_classic/charge.rb +35 -0
- data/lib/pay/paddle_classic/error.rb +7 -0
- data/lib/pay/{paddle → paddle_classic}/payment_method.rb +4 -4
- data/lib/pay/{paddle → paddle_classic}/subscription.rb +39 -30
- data/lib/pay/{paddle → paddle_classic}/webhooks/signature_verifier.rb +4 -4
- data/lib/pay/{paddle → paddle_classic}/webhooks/subscription_cancelled.rb +5 -4
- data/lib/pay/{paddle → paddle_classic}/webhooks/subscription_created.rb +2 -2
- data/lib/pay/{paddle → paddle_classic}/webhooks/subscription_payment_refunded.rb +2 -2
- data/lib/pay/{paddle → paddle_classic}/webhooks/subscription_payment_succeeded.rb +7 -7
- data/lib/pay/{paddle → paddle_classic}/webhooks/subscription_updated.rb +2 -2
- data/lib/pay/paddle_classic.rb +82 -0
- data/lib/pay/receipts.rb +1 -1
- data/lib/pay/stripe/billable.rb +6 -2
- data/lib/pay/stripe/charge.rb +8 -4
- data/lib/pay/stripe/payment_method.rb +9 -1
- data/lib/pay/stripe/subscription.rb +54 -4
- data/lib/pay/stripe.rb +3 -4
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +3 -2
- data/lib/tasks/pay.rake +2 -2
- metadata +33 -17
- data/lib/pay/paddle/charge.rb +0 -35
- data/lib/pay/paddle/response.rb +0 -0
- data/lib/pay/paddle.rb +0 -80
@@ -0,0 +1,185 @@
|
|
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
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Pay
|
2
|
+
module PaddleBilling
|
3
|
+
autoload :Billable, "pay/paddle_billing/billable"
|
4
|
+
autoload :Charge, "pay/paddle_billing/charge"
|
5
|
+
autoload :Error, "pay/paddle_billing/error"
|
6
|
+
autoload :PaymentMethod, "pay/paddle_billing/payment_method"
|
7
|
+
autoload :Subscription, "pay/paddle_billing/subscription"
|
8
|
+
|
9
|
+
module Webhooks
|
10
|
+
autoload :Subscription, "pay/paddle_billing/webhooks/subscription"
|
11
|
+
autoload :TransactionCompleted, "pay/paddle_billing/webhooks/transaction_completed"
|
12
|
+
end
|
13
|
+
|
14
|
+
extend Env
|
15
|
+
|
16
|
+
def self.enabled?
|
17
|
+
return false unless Pay.enabled_processors.include?(:paddle_billing) && defined?(::Paddle)
|
18
|
+
|
19
|
+
Pay::Engine.version_matches?(required: "~> 2.1", current: ::Paddle::VERSION) || (raise "[Pay] paddle gem must be version ~> 2.1")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.setup
|
23
|
+
::Paddle.config.environment = environment
|
24
|
+
::Paddle.config.api_key = api_key
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.environment
|
28
|
+
find_value_by_name(:paddle_billing, :environment) || "production"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.client_token
|
32
|
+
find_value_by_name(:paddle_billing, :client_token)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.api_key
|
36
|
+
find_value_by_name(:paddle_billing, :api_key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.signing_secret
|
40
|
+
find_value_by_name(:paddle_billing, :signing_secret)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.configure_webhooks
|
44
|
+
Pay::Webhooks.configure do |events|
|
45
|
+
events.subscribe "paddle_billing.subscription.activated", Pay::PaddleBilling::Webhooks::Subscription.new
|
46
|
+
events.subscribe "paddle_billing.subscription.canceled", Pay::PaddleBilling::Webhooks::Subscription.new
|
47
|
+
events.subscribe "paddle_billing.subscription.created", Pay::PaddleBilling::Webhooks::Subscription.new
|
48
|
+
events.subscribe "paddle_billing.subscription.imported", Pay::PaddleBilling::Webhooks::Subscription.new
|
49
|
+
events.subscribe "paddle_billing.subscription.past_due", Pay::PaddleBilling::Webhooks::Subscription.new
|
50
|
+
events.subscribe "paddle_billing.subscription.paused", Pay::PaddleBilling::Webhooks::Subscription.new
|
51
|
+
events.subscribe "paddle_billing.subscription.resumed", Pay::PaddleBilling::Webhooks::Subscription.new
|
52
|
+
events.subscribe "paddle_billing.subscription.trialing", Pay::PaddleBilling::Webhooks::Subscription.new
|
53
|
+
events.subscribe "paddle_billing.subscription.updated", Pay::PaddleBilling::Webhooks::Subscription.new
|
54
|
+
events.subscribe "paddle_billing.transaction.completed", Pay::PaddleBilling::Webhooks::TransactionCompleted.new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
class Billable
|
4
4
|
attr_reader :pay_customer
|
5
5
|
|
@@ -27,7 +27,7 @@ module Pay
|
|
27
27
|
return unless subscription.processor_id
|
28
28
|
raise Pay::Error, "A charge_name is required to create a one-time charge" if options[:charge_name].nil?
|
29
29
|
|
30
|
-
response =
|
30
|
+
response = PaddleClassic.client.charges.create(subscription_id: subscription.processor_id, amount: amount.to_f / 100, charge_name: options[:charge_name])
|
31
31
|
|
32
32
|
attributes = {
|
33
33
|
amount: (response[:amount].to_f * 100).to_i,
|
@@ -36,13 +36,13 @@ module Pay
|
|
36
36
|
}
|
37
37
|
|
38
38
|
# Lookup subscription payment method details
|
39
|
-
attributes.merge! Pay::
|
39
|
+
attributes.merge! Pay::PaddleClassic::PaymentMethod.payment_method_details_for(subscription_id: subscription.processor_id)
|
40
40
|
|
41
41
|
charge = pay_customer.charges.find_or_initialize_by(processor_id: response[:invoice_id])
|
42
42
|
charge.update(attributes)
|
43
43
|
charge
|
44
|
-
rescue ::
|
45
|
-
raise Pay::
|
44
|
+
rescue ::Paddle::Error => e
|
45
|
+
raise Pay::PaddleClassic::Error, e
|
46
46
|
end
|
47
47
|
|
48
48
|
def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
|
@@ -52,7 +52,7 @@ module Pay
|
|
52
52
|
# Paddle does not use payment method tokens. The method signature has it here
|
53
53
|
# to have a uniform API with the other payment processors.
|
54
54
|
def add_payment_method(token = nil, default: true)
|
55
|
-
Pay::
|
55
|
+
Pay::PaddleClassic::PaymentMethod.sync(pay_customer: pay_customer)
|
56
56
|
end
|
57
57
|
|
58
58
|
def trial_end_date(subscription)
|
@@ -61,10 +61,9 @@ module Pay
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def processor_subscription(subscription_id, options = {})
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
raise Pay::Paddle::Error, e
|
64
|
+
PaddleClassic.client.users.list(subscription_id: subscription_id).data.try(:first)
|
65
|
+
rescue ::Paddle::Error => e
|
66
|
+
raise Pay::PaddleClassic::Error, e
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Pay
|
2
|
+
module PaddleClassic
|
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 charge
|
13
|
+
return unless customer.subscription
|
14
|
+
payments = PaddleClassic.client.payments.list(subscription_id: customer.subscription.processor_id)
|
15
|
+
charges = payments.data.select { |p| p[:id].to_s == processor_id }
|
16
|
+
charges.try(:first)
|
17
|
+
rescue ::Paddle::Error => e
|
18
|
+
raise Pay::PaddleClassic::Error, e
|
19
|
+
end
|
20
|
+
|
21
|
+
def refund!(amount_to_refund)
|
22
|
+
return unless customer.subscription
|
23
|
+
payments = PaddleClassic.client.payments.list(subscription_id: customer.subscription.processor_id, is_paid: 1)
|
24
|
+
if payments.total > 0
|
25
|
+
PaddleClassic.client.payments.refund(order_id: payments.data.last[:id], amount: amount_to_refund)
|
26
|
+
pay_charge.update(amount_refunded: amount_to_refund)
|
27
|
+
else
|
28
|
+
raise Error, "Payment not found"
|
29
|
+
end
|
30
|
+
rescue ::Paddle::Error => e
|
31
|
+
raise Pay::PaddleClassic::Error, e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
class PaymentMethod
|
4
4
|
attr_reader :pay_payment_method
|
5
5
|
|
@@ -17,12 +17,12 @@ module Pay
|
|
17
17
|
|
18
18
|
payment_method.update!(attributes)
|
19
19
|
payment_method
|
20
|
-
rescue ::
|
21
|
-
raise Pay::
|
20
|
+
rescue ::Paddle::Error => e
|
21
|
+
raise Pay::PaddleClassic::Error, e
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.payment_method_details_for(subscription_id:)
|
25
|
-
subscription_user =
|
25
|
+
subscription_user = PaddleClassic.client.users.list(subscription_id: subscription_id).data.try(:first)
|
26
26
|
payment_information = subscription_user ? subscription_user[:payment_information] : {}
|
27
27
|
|
28
28
|
case payment_information[:payment_method]&.downcase
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
class Subscription
|
4
4
|
attr_reader :pay_subscription
|
5
5
|
|
@@ -24,14 +24,14 @@ module Pay
|
|
24
24
|
|
25
25
|
def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
|
26
26
|
# Passthrough is not return from this API, so we can't use that
|
27
|
-
object ||=
|
27
|
+
object ||= PaddleClassic.client.users.list(subscription_id: subscription_id).data.try(:first)
|
28
28
|
|
29
|
-
pay_customer = Pay::Customer.find_by(processor: :
|
29
|
+
pay_customer = Pay::Customer.find_by(processor: :paddle_classic, processor_id: object.user_id)
|
30
30
|
|
31
31
|
# If passthrough exists (only on webhooks) we can use it to create the Pay::Customer
|
32
32
|
if pay_customer.nil? && object.passthrough
|
33
|
-
owner = Pay::
|
34
|
-
pay_customer = owner&.set_payment_processor(:
|
33
|
+
owner = Pay::PaddleClassic.owner_from_passthrough(object.passthrough)
|
34
|
+
pay_customer = owner&.set_payment_processor(:paddle_classic, processor_id: object.user_id)
|
35
35
|
end
|
36
36
|
|
37
37
|
return unless pay_customer
|
@@ -40,7 +40,7 @@ module Pay
|
|
40
40
|
paddle_cancel_url: object.cancel_url,
|
41
41
|
paddle_update_url: object.update_url,
|
42
42
|
processor_plan: object.plan_id || object.subscription_plan_id,
|
43
|
-
quantity: object.quantity,
|
43
|
+
quantity: object.quantity || 1,
|
44
44
|
status: object.state || object.status
|
45
45
|
}
|
46
46
|
|
@@ -70,38 +70,45 @@ module Pay
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def subscription(**options)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
raise Pay::Paddle::Error, e
|
73
|
+
PaddleClassic.client.users.list(subscription_id: processor_id).data.try(:first)
|
74
|
+
rescue ::Paddle::Error => e
|
75
|
+
raise Pay::PaddleClassic::Error, e
|
77
76
|
end
|
78
77
|
|
78
|
+
# Paddle subscriptions are canceled immediately, however we still want to give the user access to the end of the period they paid for
|
79
79
|
def cancel(**options)
|
80
|
+
return if canceled?
|
81
|
+
|
80
82
|
ends_at = if on_trial?
|
81
83
|
trial_ends_at
|
82
84
|
elsif paused?
|
83
85
|
pause_starts_at
|
84
86
|
else
|
85
|
-
processor_subscription.next_payment
|
87
|
+
Time.parse(processor_subscription.next_payment.date)
|
86
88
|
end
|
87
89
|
|
88
|
-
|
89
|
-
pay_subscription.update(
|
90
|
+
PaddleClassic.client.users.cancel(subscription_id: processor_id)
|
91
|
+
pay_subscription.update(
|
92
|
+
status: (ends_at.future? ? :active : :canceled),
|
93
|
+
ends_at: ends_at
|
94
|
+
)
|
90
95
|
|
91
96
|
# Remove payment methods since customer cannot be reused after cancelling
|
92
97
|
Pay::PaymentMethod.where(customer_id: pay_subscription.customer_id).destroy_all
|
93
|
-
rescue ::
|
94
|
-
raise Pay::
|
98
|
+
rescue ::Paddle::Error => e
|
99
|
+
raise Pay::PaddleClassic::Error, e
|
95
100
|
end
|
96
101
|
|
97
102
|
def cancel_now!(**options)
|
98
|
-
|
103
|
+
return if canceled?
|
104
|
+
|
105
|
+
PaddleClassic.client.users.cancel(subscription_id: processor_id)
|
99
106
|
pay_subscription.update(status: :canceled, ends_at: Time.current)
|
100
107
|
|
101
108
|
# Remove payment methods since customer cannot be reused after cancelling
|
102
109
|
Pay::PaymentMethod.where(customer_id: pay_subscription.customer_id).destroy_all
|
103
|
-
rescue ::
|
104
|
-
raise Pay::
|
110
|
+
rescue ::Paddle::Error => e
|
111
|
+
raise Pay::PaddleClassic::Error, e
|
105
112
|
end
|
106
113
|
|
107
114
|
def change_quantity(quantity, **options)
|
@@ -119,23 +126,25 @@ module Pay
|
|
119
126
|
end
|
120
127
|
|
121
128
|
def pause
|
122
|
-
|
123
|
-
response = PaddlePay::Subscription::User.update(processor_id, attributes)
|
129
|
+
response = PaddleClassic.client.users.pause(subscription_id: processor_id)
|
124
130
|
pay_subscription.update(status: :paused, pause_starts_at: Time.zone.parse(response.dig(:next_payment, :date)))
|
125
|
-
rescue ::
|
126
|
-
raise Pay::
|
131
|
+
rescue ::Paddle::Error => e
|
132
|
+
raise Pay::PaddleClassic::Error, e
|
133
|
+
end
|
134
|
+
|
135
|
+
def resumable?
|
136
|
+
paused?
|
127
137
|
end
|
128
138
|
|
129
139
|
def resume
|
130
|
-
unless
|
140
|
+
unless resumable?
|
131
141
|
raise StandardError, "You can only resume paused subscriptions."
|
132
142
|
end
|
133
143
|
|
134
|
-
|
135
|
-
PaddlePay::Subscription::User.update(processor_id, attributes)
|
144
|
+
PaddleClassic.client.users.unpause(subscription_id: processor_id)
|
136
145
|
pay_subscription.update(status: :active, pause_starts_at: nil)
|
137
|
-
rescue ::
|
138
|
-
raise Pay::
|
146
|
+
rescue ::Paddle::Error => e
|
147
|
+
raise Pay::PaddleClassic::Error, e
|
139
148
|
end
|
140
149
|
|
141
150
|
def swap(plan, **options)
|
@@ -143,11 +152,11 @@ module Pay
|
|
143
152
|
|
144
153
|
attributes = {plan_id: plan, prorate: prorate}
|
145
154
|
attributes[:quantity] = quantity if quantity?
|
146
|
-
|
155
|
+
PaddleClassic.client.users.update(subscription_id: processor_id, **attributes)
|
147
156
|
|
148
157
|
pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
|
149
|
-
rescue ::
|
150
|
-
raise Pay::
|
158
|
+
rescue ::Paddle::Error => e
|
159
|
+
raise Pay::PaddleClassic::Error, e
|
151
160
|
end
|
152
161
|
|
153
162
|
# Retries the latest invoice for a Past Due subscription
|
@@ -3,14 +3,14 @@ require "json"
|
|
3
3
|
require "openssl"
|
4
4
|
|
5
5
|
module Pay
|
6
|
-
module
|
6
|
+
module PaddleClassic
|
7
7
|
module Webhooks
|
8
8
|
class SignatureVerifier
|
9
9
|
def initialize(data)
|
10
10
|
@data = data
|
11
|
-
@public_key_file = Pay::
|
12
|
-
@public_key = Pay::
|
13
|
-
@public_key_base64 = Pay::
|
11
|
+
@public_key_file = Pay::PaddleClassic.public_key_file
|
12
|
+
@public_key = Pay::PaddleClassic.public_key
|
13
|
+
@public_key_base64 = Pay::PaddleClassic.public_key_base64
|
14
14
|
end
|
15
15
|
|
16
16
|
def verify
|
@@ -1,23 +1,24 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionCancelled
|
5
5
|
def call(event)
|
6
|
-
pay_subscription = Pay::Subscription.find_by_processor_and_id(:
|
6
|
+
pay_subscription = Pay::Subscription.find_by_processor_and_id(:paddle_classic, event.subscription_id)
|
7
7
|
|
8
8
|
# We couldn't find the subscription for some reason, maybe it's from another service
|
9
9
|
return if pay_subscription.nil?
|
10
10
|
|
11
11
|
# User canceled subscriptions have an ends_at
|
12
12
|
# Automatically cancelled subscriptions need this value set
|
13
|
+
# Paddle subscriptions are canceled immediately, however we still want to give the user access to the end of the period they paid for
|
13
14
|
ends_at = Time.zone.parse(event.cancellation_effective_date)
|
14
15
|
pay_subscription.update!(
|
15
|
-
status: :canceled,
|
16
|
+
status: (ends_at.future? ? :active : :canceled),
|
16
17
|
trial_ends_at: (ends_at if pay_subscription.trial_ends_at?),
|
17
18
|
ends_at: ends_at
|
18
19
|
)
|
19
20
|
|
20
|
-
# Paddle doesn't allow reusing customers, so we should remove their payment methods
|
21
|
+
# Paddle classic doesn't allow reusing customers, so we should remove their payment methods
|
21
22
|
Pay::PaymentMethod.where(customer_id: pay_subscription.customer_id).destroy_all
|
22
23
|
end
|
23
24
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionCreated
|
5
5
|
def call(event)
|
6
|
-
Pay::
|
6
|
+
Pay::PaddleClassic::Subscription.sync(event.subscription_id, object: event)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionPaymentRefunded
|
5
5
|
def call(event)
|
6
|
-
pay_charge = Pay::Charge.find_by_processor_and_id(:
|
6
|
+
pay_charge = Pay::Charge.find_by_processor_and_id(:paddle_classic, event.subscription_payment_id)
|
7
7
|
return unless pay_charge.present?
|
8
8
|
|
9
9
|
pay_charge.update!(amount_refunded: (event.gross_refund.to_f * 100).to_i)
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionPaymentSucceeded
|
5
5
|
def call(event)
|
6
|
-
pay_customer = Pay::Customer.find_by(processor: :
|
6
|
+
pay_customer = Pay::Customer.find_by(processor: :paddle_classic, processor_id: event.user_id)
|
7
7
|
|
8
8
|
if pay_customer.nil?
|
9
|
-
owner = Pay::
|
10
|
-
pay_customer = owner&.set_payment_processor :
|
9
|
+
owner = Pay::PaddleClassic.owner_from_passthrough(event.passthrough)
|
10
|
+
pay_customer = owner&.set_payment_processor :paddle_classic, processor_id: event.user_id
|
11
11
|
end
|
12
12
|
|
13
13
|
if pay_customer.nil?
|
@@ -22,7 +22,7 @@ module Pay
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def create_charge(pay_customer, event)
|
25
|
-
payment_method_details = Pay::
|
25
|
+
payment_method_details = Pay::PaddleClassic::PaymentMethod.payment_method_details_for(subscription_id: event.subscription_id)
|
26
26
|
|
27
27
|
attributes = {
|
28
28
|
amount: (event.sale_gross.to_f * 100).to_i,
|
@@ -30,14 +30,14 @@ module Pay
|
|
30
30
|
currency: event.currency,
|
31
31
|
paddle_receipt_url: event.receipt_url,
|
32
32
|
subscription: pay_customer.subscriptions.find_by(processor_id: event.subscription_id),
|
33
|
-
metadata: Pay::
|
33
|
+
metadata: Pay::PaddleClassic.parse_passthrough(event.passthrough).except("owner_sgid")
|
34
34
|
}.merge(payment_method_details)
|
35
35
|
|
36
36
|
pay_charge = pay_customer.charges.find_or_initialize_by(processor_id: event.subscription_payment_id)
|
37
37
|
pay_charge.update!(attributes)
|
38
38
|
|
39
39
|
# Update customer's payment method
|
40
|
-
Pay::
|
40
|
+
Pay::PaddleClassic::PaymentMethod.sync(pay_customer: pay_customer, attributes: payment_method_details)
|
41
41
|
|
42
42
|
pay_charge
|
43
43
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Pay
|
2
|
-
module
|
2
|
+
module PaddleClassic
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionUpdated
|
5
5
|
def call(event)
|
6
|
-
pay_subscription = Pay::Subscription.find_by_processor_and_id(:
|
6
|
+
pay_subscription = Pay::Subscription.find_by_processor_and_id(:paddle_classic, event["subscription_id"])
|
7
7
|
|
8
8
|
return if pay_subscription.nil?
|
9
9
|
|