pay 2.2.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pay might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +161 -11
- data/Rakefile +2 -4
- data/app/controllers/pay/payments_controller.rb +3 -0
- data/app/controllers/pay/webhooks/braintree_controller.rb +1 -1
- data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
- data/app/mailers/pay/user_mailer.rb +14 -35
- data/app/models/pay/application_record.rb +6 -1
- data/app/models/pay/charge.rb +7 -0
- data/app/models/pay/subscription.rb +27 -4
- data/app/views/pay/payments/show.html.erb +1 -1
- data/app/views/pay/user_mailer/payment_action_required.html.erb +1 -1
- data/app/views/pay/user_mailer/receipt.html.erb +6 -6
- data/app/views/pay/user_mailer/refund.html.erb +6 -6
- data/app/views/pay/user_mailer/subscription_renewing.html.erb +1 -1
- data/config/locales/en.yml +137 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20200603134434_add_data_to_pay_models.rb +17 -0
- data/lib/generators/active_record/pay_generator.rb +1 -1
- data/lib/generators/pay/orm_helpers.rb +1 -2
- data/lib/pay.rb +9 -41
- data/lib/pay/billable.rb +16 -11
- data/lib/pay/braintree/billable.rb +25 -19
- data/lib/pay/braintree/charge.rb +7 -3
- data/lib/pay/braintree/subscription.rb +15 -5
- data/lib/pay/engine.rb +7 -0
- data/lib/pay/errors.rb +73 -0
- data/lib/pay/paddle.rb +38 -0
- data/lib/pay/paddle/billable.rb +95 -0
- data/lib/pay/paddle/charge.rb +39 -0
- data/lib/pay/paddle/subscription.rb +70 -0
- data/lib/pay/paddle/webhooks.rb +1 -0
- data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
- data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
- data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
- data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +43 -0
- data/lib/pay/paddle/webhooks/subscription_updated.rb +37 -0
- data/lib/pay/receipts.rb +6 -6
- data/lib/pay/stripe.rb +1 -1
- data/lib/pay/stripe/billable.rb +12 -6
- data/lib/pay/stripe/charge.rb +6 -2
- data/lib/pay/stripe/subscription.rb +15 -5
- data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -2
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
- data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
- data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -3
- data/lib/pay/version.rb +1 -1
- metadata +35 -43
@@ -1,6 +1,12 @@
|
|
1
1
|
module Pay
|
2
2
|
module Braintree
|
3
3
|
module Billable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
scope :braintree, -> { where(processor: :braintree) }
|
8
|
+
end
|
9
|
+
|
4
10
|
# Handles Billable#customer
|
5
11
|
#
|
6
12
|
# Returns Braintree::Customer
|
@@ -14,7 +20,7 @@ module Pay
|
|
14
20
|
last_name: try(:last_name),
|
15
21
|
payment_method_nonce: card_token
|
16
22
|
)
|
17
|
-
raise
|
23
|
+
raise Pay::Braintree::Error, result unless result.success?
|
18
24
|
|
19
25
|
update(processor: "braintree", processor_id: result.customer.id)
|
20
26
|
|
@@ -24,10 +30,10 @@ module Pay
|
|
24
30
|
|
25
31
|
result.customer
|
26
32
|
end
|
27
|
-
rescue ::Braintree::AuthorizationError
|
33
|
+
rescue ::Braintree::AuthorizationError
|
28
34
|
raise BraintreeAuthorizationError
|
29
35
|
rescue ::Braintree::BraintreeError => e
|
30
|
-
raise
|
36
|
+
raise Pay::Braintree::Error, e
|
31
37
|
end
|
32
38
|
|
33
39
|
# Handles Billable#charge
|
@@ -35,19 +41,19 @@ module Pay
|
|
35
41
|
# Returns a Pay::Charge
|
36
42
|
def create_braintree_charge(amount, options = {})
|
37
43
|
args = {
|
38
|
-
amount: amount / 100.0,
|
44
|
+
amount: amount.to_i / 100.0,
|
39
45
|
customer_id: customer.id,
|
40
46
|
options: {submit_for_settlement: true}
|
41
47
|
}.merge(options)
|
42
48
|
|
43
49
|
result = gateway.transaction.sale(args)
|
44
|
-
raise
|
50
|
+
raise Pay::Braintree::Error, result unless result.success?
|
45
51
|
|
46
52
|
save_braintree_transaction(result.transaction)
|
47
|
-
rescue ::Braintree::AuthorizationError
|
48
|
-
raise
|
53
|
+
rescue ::Braintree::AuthorizationError
|
54
|
+
raise Pay::Braintree::AuthorizationError
|
49
55
|
rescue ::Braintree::BraintreeError => e
|
50
|
-
raise
|
56
|
+
raise Pay::Braintree::Error, e
|
51
57
|
end
|
52
58
|
|
53
59
|
# Handles Billable#subscribe
|
@@ -68,13 +74,13 @@ module Pay
|
|
68
74
|
)
|
69
75
|
|
70
76
|
result = gateway.subscription.create(subscription_options)
|
71
|
-
raise
|
77
|
+
raise Pay::Braintree::Error, result unless result.success?
|
72
78
|
|
73
79
|
create_subscription(result.subscription, "braintree", name, plan, status: :active)
|
74
|
-
rescue ::Braintree::AuthorizationError
|
75
|
-
raise
|
80
|
+
rescue ::Braintree::AuthorizationError
|
81
|
+
raise Pay::Braintree::AuthorizationError
|
76
82
|
rescue ::Braintree::BraintreeError => e
|
77
|
-
raise
|
83
|
+
raise Pay::Braintree::Error, e
|
78
84
|
end
|
79
85
|
|
80
86
|
# Handles Billable#update_card
|
@@ -89,15 +95,15 @@ module Pay
|
|
89
95
|
verify_card: true
|
90
96
|
}
|
91
97
|
)
|
92
|
-
raise
|
98
|
+
raise Pay::Braintree::Error, result unless result.success?
|
93
99
|
|
94
100
|
update_braintree_card_on_file result.payment_method
|
95
101
|
update_subscriptions_to_payment_method(result.payment_method.token)
|
96
102
|
true
|
97
|
-
rescue ::Braintree::AuthorizationError
|
98
|
-
raise
|
103
|
+
rescue ::Braintree::AuthorizationError
|
104
|
+
raise Pay::Braintree::AuthorizationError
|
99
105
|
rescue ::Braintree::BraintreeError => e
|
100
|
-
raise
|
106
|
+
raise Pay::Braintree::Error, e
|
101
107
|
end
|
102
108
|
|
103
109
|
def update_braintree_email!
|
@@ -111,11 +117,11 @@ module Pay
|
|
111
117
|
def braintree_trial_end_date(subscription)
|
112
118
|
return unless subscription.trial_period
|
113
119
|
# Braintree returns dates without time zones, so we'll assume they're UTC
|
114
|
-
|
120
|
+
subscription.first_billing_date.end_of_day
|
115
121
|
end
|
116
122
|
|
117
123
|
def update_subscriptions_to_payment_method(token)
|
118
|
-
subscriptions.each do |subscription|
|
124
|
+
subscriptions.braintree.each do |subscription|
|
119
125
|
if subscription.active?
|
120
126
|
gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
|
121
127
|
end
|
@@ -175,7 +181,7 @@ module Pay
|
|
175
181
|
|
176
182
|
def card_details_for_braintree_transaction(transaction)
|
177
183
|
case transaction.payment_instrument_type
|
178
|
-
when "credit_card", "samsung_pay_card", "masterpass_card", "
|
184
|
+
when "credit_card", "samsung_pay_card", "masterpass_card", "visa_checkout_card"
|
179
185
|
payment_method = transaction.send("#{transaction.payment_instrument_type}_details")
|
180
186
|
{
|
181
187
|
card_type: payment_method.card_type,
|
data/lib/pay/braintree/charge.rb
CHANGED
@@ -3,14 +3,18 @@ module Pay
|
|
3
3
|
module Charge
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
included do
|
7
|
+
scope :braintree, -> { where(processor: :braintree) }
|
8
|
+
end
|
9
|
+
|
6
10
|
def braintree?
|
7
11
|
processor == "braintree"
|
8
12
|
end
|
9
13
|
|
10
14
|
def braintree_charge
|
11
15
|
Pay.braintree_gateway.transaction.find(processor_id)
|
12
|
-
rescue ::Braintree::
|
13
|
-
raise Error, e
|
16
|
+
rescue ::Braintree::Braintree::Error => e
|
17
|
+
raise Pay::Braintree::Error, e
|
14
18
|
end
|
15
19
|
|
16
20
|
def braintree_refund!(amount_to_refund)
|
@@ -18,7 +22,7 @@ module Pay
|
|
18
22
|
|
19
23
|
update(amount_refunded: amount_to_refund)
|
20
24
|
rescue ::Braintree::BraintreeError => e
|
21
|
-
raise Error, e
|
25
|
+
raise Pay::Braintree::Error, e
|
22
26
|
end
|
23
27
|
end
|
24
28
|
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module Pay
|
2
2
|
module Braintree
|
3
3
|
module Subscription
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
scope :braintree, -> { where(processor: :braintree) }
|
8
|
+
end
|
9
|
+
|
4
10
|
def braintree?
|
5
11
|
processor == "braintree"
|
6
12
|
end
|
@@ -18,14 +24,18 @@ module Pay
|
|
18
24
|
update(status: :canceled, ends_at: subscription.billing_period_end_date.to_date)
|
19
25
|
end
|
20
26
|
rescue ::Braintree::BraintreeError => e
|
21
|
-
raise Error, e
|
27
|
+
raise Pay::Braintree::Error, e
|
22
28
|
end
|
23
29
|
|
24
30
|
def braintree_cancel_now!
|
25
31
|
gateway.subscription.cancel(processor_subscription.id)
|
26
32
|
update(status: :canceled, ends_at: Time.zone.now)
|
27
33
|
rescue ::Braintree::BraintreeError => e
|
28
|
-
raise Error, e
|
34
|
+
raise Pay::Braintree::Error, e
|
35
|
+
end
|
36
|
+
|
37
|
+
def braintree_on_grace_period?
|
38
|
+
canceled? && Time.zone.now < ends_at
|
29
39
|
end
|
30
40
|
|
31
41
|
def braintree_resume
|
@@ -51,7 +61,7 @@ module Pay
|
|
51
61
|
|
52
62
|
update(status: :active)
|
53
63
|
rescue ::Braintree::BraintreeError => e
|
54
|
-
raise Error, e
|
64
|
+
raise Pay::Braintree::Error, e
|
55
65
|
end
|
56
66
|
|
57
67
|
def braintree_swap(plan)
|
@@ -90,7 +100,7 @@ module Pay
|
|
90
100
|
raise Error, "Braintree failed to swap plans: #{result.message}"
|
91
101
|
end
|
92
102
|
rescue ::Braintree::BraintreeError => e
|
93
|
-
raise Error, e
|
103
|
+
raise Pay::Braintree::Error, e
|
94
104
|
end
|
95
105
|
|
96
106
|
private
|
@@ -168,7 +178,7 @@ module Pay
|
|
168
178
|
|
169
179
|
cancel_now!
|
170
180
|
|
171
|
-
owner.subscribe(options.merge(name: name, plan: plan.id))
|
181
|
+
owner.subscribe(**options.merge(name: name, plan: plan.id))
|
172
182
|
end
|
173
183
|
end
|
174
184
|
end
|
data/lib/pay/engine.rb
CHANGED
@@ -11,6 +11,11 @@ begin
|
|
11
11
|
require "stripe_event"
|
12
12
|
rescue LoadError
|
13
13
|
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require "paddle_pay"
|
17
|
+
rescue LoadError
|
18
|
+
end
|
14
19
|
# rubocop:enable Lint/HandleExceptions
|
15
20
|
|
16
21
|
module Pay
|
@@ -21,6 +26,7 @@ module Pay
|
|
21
26
|
# Include processor backends
|
22
27
|
require "pay/stripe" if defined? ::Stripe
|
23
28
|
require "pay/braintree" if defined? ::Braintree
|
29
|
+
require "pay/paddle" if defined? ::PaddlePay
|
24
30
|
|
25
31
|
if Pay.automount_routes
|
26
32
|
app.routes.append do
|
@@ -32,6 +38,7 @@ module Pay
|
|
32
38
|
config.to_prepare do
|
33
39
|
Pay::Stripe.setup if defined? ::Stripe
|
34
40
|
Pay::Braintree.setup if defined? ::Braintree
|
41
|
+
Pay::Paddle.setup if defined? ::PaddlePay
|
35
42
|
|
36
43
|
Pay.charge_model.include Pay::Receipts if defined? ::Receipts::Receipt
|
37
44
|
end
|
data/lib/pay/errors.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Pay
|
2
|
+
class Error < StandardError
|
3
|
+
attr_reader :result
|
4
|
+
|
5
|
+
def initialize(result = nil)
|
6
|
+
@result = result
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class PaymentError < StandardError
|
11
|
+
attr_reader :payment
|
12
|
+
|
13
|
+
def initialize(payment)
|
14
|
+
@payment = payment
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ActionRequired < PaymentError
|
19
|
+
def message
|
20
|
+
I18n.t("errors.action_required")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidPaymentMethod < PaymentError
|
25
|
+
def message
|
26
|
+
I18n.t("errors.invalid_payment")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Braintree
|
31
|
+
class Error < Error
|
32
|
+
def message
|
33
|
+
result.message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class AuthorizationError < Braintree::Error
|
38
|
+
def message
|
39
|
+
I18n.t("errors.braintree.authorization")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Stripe
|
45
|
+
class Error < Error
|
46
|
+
def message
|
47
|
+
I18n.t("errors.stripe.#{result.code}", default: result.message)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module Paddle
|
53
|
+
class Error < Error
|
54
|
+
def message
|
55
|
+
I18n.t("errors.paddle.#{result.code}", default: result.message)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class BraintreeError < Braintree::Error
|
61
|
+
def message
|
62
|
+
ActiveSupport::Deprecation.warn("Pay::BraintreeError is deprecated. Instead, use `Pay::Braintree::Error`.")
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class BraintreeAuthorizationError < BraintreeError
|
68
|
+
def message
|
69
|
+
ActiveSupport::Deprecation.warn("Pay::BraintreeAuthorizationError is deprecated. Instead, use `Pay::Braintree::AuthorizationError`.")
|
70
|
+
I18n.t("errors.braintree.authorization")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/pay/paddle.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "pay/env"
|
2
|
+
require "pay/paddle/billable"
|
3
|
+
require "pay/paddle/charge"
|
4
|
+
require "pay/paddle/subscription"
|
5
|
+
require "pay/paddle/webhooks"
|
6
|
+
|
7
|
+
module Pay
|
8
|
+
module Paddle
|
9
|
+
include Env
|
10
|
+
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def setup
|
14
|
+
::PaddlePay.config.vendor_id = vendor_id
|
15
|
+
::PaddlePay.config.vendor_auth_code = vendor_auth_code
|
16
|
+
|
17
|
+
Pay.charge_model.include Pay::Paddle::Charge
|
18
|
+
Pay.subscription_model.include Pay::Paddle::Subscription
|
19
|
+
Pay.billable_models.each { |model| model.include Pay::Paddle::Billable }
|
20
|
+
end
|
21
|
+
|
22
|
+
def vendor_id
|
23
|
+
find_value_by_name(:paddle, :vendor_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def vendor_auth_code
|
27
|
+
find_value_by_name(:paddle, :vendor_auth_code)
|
28
|
+
end
|
29
|
+
|
30
|
+
def public_key_base64
|
31
|
+
find_value_by_name(:paddle, :public_key_base64)
|
32
|
+
end
|
33
|
+
|
34
|
+
def passthrough(owner:, **options)
|
35
|
+
options.merge(owner_sgid: owner.to_sgid.to_s).to_json
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Pay
|
2
|
+
module Paddle
|
3
|
+
module Billable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
scope :paddle, -> { where(processor: :paddle) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def paddle_customer
|
11
|
+
# pass
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_paddle_charge(amount, options = {})
|
15
|
+
return unless subscription.processor_id
|
16
|
+
raise Pay::Error, "A charge_name is required to create a one-time charge" if options[:charge_name].nil?
|
17
|
+
response = PaddlePay::Subscription::Charge.create(subscription.processor_id, amount.to_f / 100, options[:charge_name], options)
|
18
|
+
charge = charges.find_or_initialize_by(
|
19
|
+
processor: :paddle,
|
20
|
+
processor_id: response[:invoice_id]
|
21
|
+
)
|
22
|
+
charge.update(
|
23
|
+
amount: Integer(response[:amount].to_f * 100),
|
24
|
+
card_type: subscription.processor_subscription.payment_information[:payment_method],
|
25
|
+
paddle_receipt_url: response[:receipt_url],
|
26
|
+
created_at: Time.zone.parse(response[:payment_date])
|
27
|
+
)
|
28
|
+
charge
|
29
|
+
rescue ::PaddlePay::PaddlePayError => e
|
30
|
+
raise Pay::Paddle::Error, e
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_paddle_subscription(name, plan, options = {})
|
34
|
+
# pass
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_paddle_card(token)
|
38
|
+
sync_payment_information_from_paddle
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_paddle_email!
|
42
|
+
# pass
|
43
|
+
end
|
44
|
+
|
45
|
+
def paddle_trial_end_date(subscription)
|
46
|
+
return unless subscription.state == "trialing"
|
47
|
+
Time.zone.parse(subscription.next_payment[:date]).end_of_day
|
48
|
+
end
|
49
|
+
|
50
|
+
def paddle_subscription(subscription_id, options = {})
|
51
|
+
hash = PaddlePay::Subscription::User.list({subscription_id: subscription_id}, options).try(:first)
|
52
|
+
OpenStruct.new(hash)
|
53
|
+
rescue ::PaddlePay::PaddlePayError => e
|
54
|
+
raise Pay::Paddle::Error, e
|
55
|
+
end
|
56
|
+
|
57
|
+
def paddle_invoice!(options = {})
|
58
|
+
# pass
|
59
|
+
end
|
60
|
+
|
61
|
+
def paddle_upcoming_invoice
|
62
|
+
# pass
|
63
|
+
end
|
64
|
+
|
65
|
+
def sync_payment_information_from_paddle
|
66
|
+
payment_information = paddle_payment_information(subscription.processor_id)
|
67
|
+
update!(payment_information) unless payment_information.empty?
|
68
|
+
rescue ::PaddlePay::PaddlePayError => e
|
69
|
+
raise Pay::Paddle::Error, e
|
70
|
+
end
|
71
|
+
|
72
|
+
def paddle_payment_information(subscription_id)
|
73
|
+
subscription_user = PaddlePay::Subscription::User.list({subscription_id: subscription_id}).try(:first)
|
74
|
+
payment_information = subscription_user ? subscription_user[:payment_information] : nil
|
75
|
+
return {} if payment_information.nil?
|
76
|
+
|
77
|
+
case payment_information[:payment_method]
|
78
|
+
when "card"
|
79
|
+
{
|
80
|
+
card_type: payment_information[:card_type],
|
81
|
+
card_last4: payment_information[:last_four_digits],
|
82
|
+
card_exp_month: payment_information[:expiry_date].split("/").first,
|
83
|
+
card_exp_year: payment_information[:expiry_date].split("/").last
|
84
|
+
}
|
85
|
+
when "paypal"
|
86
|
+
{
|
87
|
+
card_type: "PayPal"
|
88
|
+
}
|
89
|
+
else
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Pay
|
2
|
+
module Paddle
|
3
|
+
module Charge
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
scope :paddle, -> { where(processor: :paddle) }
|
8
|
+
|
9
|
+
store_accessor :data, :paddle_receipt_url
|
10
|
+
end
|
11
|
+
|
12
|
+
def paddle?
|
13
|
+
processor == "paddle"
|
14
|
+
end
|
15
|
+
|
16
|
+
def paddle_charge
|
17
|
+
return unless owner.subscription
|
18
|
+
payments = PaddlePay::Subscription::Payment.list({subscription_id: owner.subscription.processor_id})
|
19
|
+
charges = payments.select { |p| p[:id].to_s == processor_id }
|
20
|
+
charges.try(:first)
|
21
|
+
rescue ::PaddlePay::PaddlePayError => e
|
22
|
+
raise Pay::Paddle::Error, e
|
23
|
+
end
|
24
|
+
|
25
|
+
def paddle_refund!(amount_to_refund)
|
26
|
+
return unless owner.subscription
|
27
|
+
payments = PaddlePay::Subscription::Payment.list({subscription_id: owner.subscription.processor_id, is_paid: 1})
|
28
|
+
if payments.count > 0
|
29
|
+
PaddlePay::Subscription::Payment.refund(payments.last[:id], {amount: amount_to_refund})
|
30
|
+
update(amount_refunded: amount_to_refund)
|
31
|
+
else
|
32
|
+
raise Error, "Payment not found"
|
33
|
+
end
|
34
|
+
rescue ::PaddlePay::PaddlePayError => e
|
35
|
+
raise Pay::Paddle::Error, e
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|