pay 2.3.1 → 2.5.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 +7 -5
- data/app/controllers/pay/webhooks/braintree_controller.rb +7 -53
- data/app/controllers/pay/webhooks/paddle_controller.rb +19 -18
- data/app/controllers/pay/webhooks/stripe_controller.rb +47 -0
- data/app/mailers/pay/user_mailer.rb +14 -35
- data/app/models/pay/subscription.rb +12 -18
- 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 -1
- data/db/migrate/20200603134434_add_data_to_pay_models.rb +6 -1
- data/lib/pay.rb +19 -50
- data/lib/pay/billable.rb +9 -9
- data/lib/pay/braintree.rb +1 -0
- data/lib/pay/braintree/billable.rb +11 -11
- data/lib/pay/braintree/charge.rb +3 -3
- data/lib/pay/braintree/subscription.rb +21 -13
- data/lib/pay/braintree/webhooks.rb +11 -0
- data/lib/pay/braintree/webhooks/subscription_canceled.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +24 -0
- data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +24 -0
- data/lib/pay/braintree/webhooks/subscription_expired.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_went_active.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +19 -0
- data/lib/pay/engine.rb +0 -1
- data/lib/pay/errors.rb +73 -0
- data/lib/pay/paddle/billable.rb +34 -5
- data/lib/pay/paddle/charge.rb +2 -2
- data/lib/pay/paddle/subscription.rb +22 -13
- data/lib/pay/paddle/webhooks.rb +8 -0
- data/lib/pay/paddle/webhooks/subscription_cancelled.rb +3 -3
- data/lib/pay/paddle/webhooks/subscription_created.rb +15 -15
- data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +5 -5
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +17 -38
- data/lib/pay/paddle/webhooks/subscription_updated.rb +20 -17
- data/lib/pay/receipts.rb +6 -6
- data/lib/pay/stripe.rb +3 -1
- data/lib/pay/stripe/billable.rb +4 -4
- data/lib/pay/stripe/charge.rb +2 -2
- data/lib/pay/stripe/subscription.rb +20 -12
- data/lib/pay/stripe/webhooks.rb +14 -15
- data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -2
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +1 -1
- data/lib/pay/stripe/webhooks/payment_action_required.rb +1 -1
- data/lib/pay/stripe/webhooks/subscription_created.rb +2 -1
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -3
- data/lib/pay/version.rb +1 -1
- data/lib/pay/webhooks/delegator.rb +61 -0
- metadata +21 -88
data/config/routes.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Pay::Engine.routes.draw do
|
4
4
|
resources :payments, only: [:show], module: :pay
|
5
|
-
post "webhooks/stripe", to: "
|
5
|
+
post "webhooks/stripe", to: "pay/webhooks/stripe#create"
|
6
6
|
post "webhooks/braintree", to: "pay/webhooks/braintree#create"
|
7
7
|
post "webhooks/paddle", to: "pay/webhooks/paddle#create"
|
8
8
|
end
|
@@ -5,7 +5,12 @@ class AddDataToPayModels < ActiveRecord::Migration[4.2]
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def data_column_type
|
8
|
-
|
8
|
+
default_hash = ActiveRecord::Base.configurations.default_hash
|
9
|
+
|
10
|
+
# Rails 6.1 uses a symbol key instead of a string
|
11
|
+
adapter = default_hash.dig(:adapter) || default_hash.dig("adapter")
|
12
|
+
|
13
|
+
case adapter
|
9
14
|
when "mysql2"
|
10
15
|
:json
|
11
16
|
when "postgresql"
|
data/lib/pay.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
+
require "pay/version"
|
1
2
|
require "pay/engine"
|
2
3
|
require "pay/billable"
|
3
4
|
require "pay/receipts"
|
4
5
|
require "pay/payment"
|
6
|
+
require "pay/errors"
|
5
7
|
|
6
8
|
module Pay
|
9
|
+
module Webhooks
|
10
|
+
autoload :Delegator, "pay/webhooks/delegator"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
delegate :configure, :instrument, to: :delegator
|
14
|
+
|
15
|
+
def delegator
|
16
|
+
@delegator ||= Delegator.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
7
21
|
# Define who owns the subscription
|
8
22
|
mattr_accessor :billable_class
|
9
23
|
mattr_accessor :billable_table
|
@@ -35,18 +49,14 @@ module Pay
|
|
35
49
|
mattr_accessor :send_emails
|
36
50
|
@@send_emails = true
|
37
51
|
|
38
|
-
mattr_accessor :email_receipt_subject
|
39
|
-
@@email_receipt_subject = "Payment receipt"
|
40
|
-
mattr_accessor :email_refund_subject
|
41
|
-
@@email_refund_subject = "Payment refunded"
|
42
|
-
mattr_accessor :email_renewing_subject
|
43
|
-
@@email_renewing_subject = "Your upcoming subscription renewal"
|
44
|
-
mattr_accessor :email_action_required_subject
|
45
|
-
@@email_action_required_subject = "Confirm your payment"
|
46
|
-
|
47
52
|
mattr_accessor :automount_routes
|
48
53
|
@@automount_routes = true
|
49
54
|
|
55
|
+
mattr_accessor :default_product_name
|
56
|
+
@@default_product_name = "default"
|
57
|
+
mattr_accessor :default_plan_name
|
58
|
+
@@default_plan_name = "default"
|
59
|
+
|
50
60
|
mattr_accessor :routes_path
|
51
61
|
@@routes_path = "/pay"
|
52
62
|
|
@@ -101,45 +111,4 @@ module Pay
|
|
101
111
|
business_address &&
|
102
112
|
support_email
|
103
113
|
end
|
104
|
-
|
105
|
-
class Error < StandardError
|
106
|
-
end
|
107
|
-
|
108
|
-
class BraintreeError < Error
|
109
|
-
attr_reader :result
|
110
|
-
|
111
|
-
def initialize(result = nil)
|
112
|
-
@result = result
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class BraintreeAuthorizationError < BraintreeError
|
117
|
-
def message
|
118
|
-
"Either the data you submitted is malformed and does not match the API or the API key you used may not be authorized to perform this action."
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
class InvalidPaymentMethod < Error
|
123
|
-
attr_reader :payment
|
124
|
-
|
125
|
-
def initialize(payment)
|
126
|
-
@payment = payment
|
127
|
-
end
|
128
|
-
|
129
|
-
def message
|
130
|
-
"This payment attempt failed because of an invalid payment method."
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
class ActionRequired < Error
|
135
|
-
attr_reader :payment
|
136
|
-
|
137
|
-
def initialize(payment)
|
138
|
-
@payment = payment
|
139
|
-
end
|
140
|
-
|
141
|
-
def message
|
142
|
-
"This payment attempt failed because additional action is required before it can be completed."
|
143
|
-
end
|
144
|
-
end
|
145
114
|
end
|
data/lib/pay/billable.rb
CHANGED
@@ -36,7 +36,7 @@ module Pay
|
|
36
36
|
|
37
37
|
def customer
|
38
38
|
check_for_processor
|
39
|
-
raise Pay::Error, "
|
39
|
+
raise Pay::Error, I18n.t("errors.email_required") if email.nil?
|
40
40
|
|
41
41
|
customer = send("#{processor}_customer")
|
42
42
|
update_card(card_token) if card_token.present?
|
@@ -52,7 +52,7 @@ module Pay
|
|
52
52
|
send("create_#{processor}_charge", amount_in_cents, options)
|
53
53
|
end
|
54
54
|
|
55
|
-
def subscribe(name:
|
55
|
+
def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
|
56
56
|
check_for_processor
|
57
57
|
send("create_#{processor}_subscription", name, plan, options)
|
58
58
|
end
|
@@ -63,7 +63,7 @@ module Pay
|
|
63
63
|
send("update_#{processor}_card", token)
|
64
64
|
end
|
65
65
|
|
66
|
-
def on_trial?(name:
|
66
|
+
def on_trial?(name: Pay.default_product_name, plan: nil)
|
67
67
|
return true if default_generic_trial?(name, plan)
|
68
68
|
|
69
69
|
sub = subscription(name: name)
|
@@ -81,7 +81,7 @@ module Pay
|
|
81
81
|
send("#{processor}_subscription", subscription_id, options)
|
82
82
|
end
|
83
83
|
|
84
|
-
def subscribed?(name:
|
84
|
+
def subscribed?(name: Pay.default_product_name, processor_plan: nil)
|
85
85
|
subscription = subscription(name: name)
|
86
86
|
|
87
87
|
return false if subscription.nil?
|
@@ -90,13 +90,13 @@ module Pay
|
|
90
90
|
subscription.active? && subscription.processor_plan == processor_plan
|
91
91
|
end
|
92
92
|
|
93
|
-
def on_trial_or_subscribed?(name:
|
93
|
+
def on_trial_or_subscribed?(name: Pay.default_product_name, processor_plan: nil)
|
94
94
|
on_trial?(name: name, plan: processor_plan) ||
|
95
95
|
subscribed?(name: name, processor_plan: processor_plan)
|
96
96
|
end
|
97
97
|
|
98
|
-
def subscription(name:
|
99
|
-
subscriptions.for_name(name).last
|
98
|
+
def subscription(name: Pay.default_product_name)
|
99
|
+
subscriptions.loaded? ? subscriptions.reverse.detect { |s| s.name == name } : subscriptions.for_name(name).last
|
100
100
|
end
|
101
101
|
|
102
102
|
def invoice!(options = {})
|
@@ -123,14 +123,14 @@ module Pay
|
|
123
123
|
processor == "paddle"
|
124
124
|
end
|
125
125
|
|
126
|
-
def has_incomplete_payment?(name:
|
126
|
+
def has_incomplete_payment?(name: Pay.default_product_name)
|
127
127
|
subscription(name: name)&.has_incomplete_payment?
|
128
128
|
end
|
129
129
|
|
130
130
|
private
|
131
131
|
|
132
132
|
def check_for_processor
|
133
|
-
raise StandardError, "
|
133
|
+
raise StandardError, I18n.t("errors.no_processor", class_name: self.class.name) unless processor
|
134
134
|
end
|
135
135
|
|
136
136
|
# Used for creating a Pay::Subscription in the database
|
data/lib/pay/braintree.rb
CHANGED
@@ -20,7 +20,7 @@ module Pay
|
|
20
20
|
last_name: try(:last_name),
|
21
21
|
payment_method_nonce: card_token
|
22
22
|
)
|
23
|
-
raise
|
23
|
+
raise Pay::Braintree::Error, result unless result.success?
|
24
24
|
|
25
25
|
update(processor: "braintree", processor_id: result.customer.id)
|
26
26
|
|
@@ -33,7 +33,7 @@ module Pay
|
|
33
33
|
rescue ::Braintree::AuthorizationError
|
34
34
|
raise BraintreeAuthorizationError
|
35
35
|
rescue ::Braintree::BraintreeError => e
|
36
|
-
raise
|
36
|
+
raise Pay::Braintree::Error, e
|
37
37
|
end
|
38
38
|
|
39
39
|
# Handles Billable#charge
|
@@ -47,13 +47,13 @@ module Pay
|
|
47
47
|
}.merge(options)
|
48
48
|
|
49
49
|
result = gateway.transaction.sale(args)
|
50
|
-
raise
|
50
|
+
raise Pay::Braintree::Error, result unless result.success?
|
51
51
|
|
52
52
|
save_braintree_transaction(result.transaction)
|
53
53
|
rescue ::Braintree::AuthorizationError
|
54
|
-
raise
|
54
|
+
raise Pay::Braintree::AuthorizationError
|
55
55
|
rescue ::Braintree::BraintreeError => e
|
56
|
-
raise
|
56
|
+
raise Pay::Braintree::Error, e
|
57
57
|
end
|
58
58
|
|
59
59
|
# Handles Billable#subscribe
|
@@ -74,13 +74,13 @@ module Pay
|
|
74
74
|
)
|
75
75
|
|
76
76
|
result = gateway.subscription.create(subscription_options)
|
77
|
-
raise
|
77
|
+
raise Pay::Braintree::Error, result unless result.success?
|
78
78
|
|
79
79
|
create_subscription(result.subscription, "braintree", name, plan, status: :active)
|
80
80
|
rescue ::Braintree::AuthorizationError
|
81
|
-
raise
|
81
|
+
raise Pay::Braintree::AuthorizationError
|
82
82
|
rescue ::Braintree::BraintreeError => e
|
83
|
-
raise
|
83
|
+
raise Pay::Braintree::Error, e
|
84
84
|
end
|
85
85
|
|
86
86
|
# Handles Billable#update_card
|
@@ -95,15 +95,15 @@ module Pay
|
|
95
95
|
verify_card: true
|
96
96
|
}
|
97
97
|
)
|
98
|
-
raise
|
98
|
+
raise Pay::Braintree::Error, result unless result.success?
|
99
99
|
|
100
100
|
update_braintree_card_on_file result.payment_method
|
101
101
|
update_subscriptions_to_payment_method(result.payment_method.token)
|
102
102
|
true
|
103
103
|
rescue ::Braintree::AuthorizationError
|
104
|
-
raise
|
104
|
+
raise Pay::Braintree::AuthorizationError
|
105
105
|
rescue ::Braintree::BraintreeError => e
|
106
|
-
raise
|
106
|
+
raise Pay::Braintree::Error, e
|
107
107
|
end
|
108
108
|
|
109
109
|
def update_braintree_email!
|
data/lib/pay/braintree/charge.rb
CHANGED
@@ -13,8 +13,8 @@ module Pay
|
|
13
13
|
|
14
14
|
def braintree_charge
|
15
15
|
Pay.braintree_gateway.transaction.find(processor_id)
|
16
|
-
rescue ::Braintree::
|
17
|
-
raise Error, e
|
16
|
+
rescue ::Braintree::Braintree::Error => e
|
17
|
+
raise Pay::Braintree::Error, e
|
18
18
|
end
|
19
19
|
|
20
20
|
def braintree_refund!(amount_to_refund)
|
@@ -22,7 +22,7 @@ module Pay
|
|
22
22
|
|
23
23
|
update(amount_refunded: amount_to_refund)
|
24
24
|
rescue ::Braintree::BraintreeError => e
|
25
|
-
raise Error, e
|
25
|
+
raise Pay::Braintree::Error, e
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -3,14 +3,6 @@ module Pay
|
|
3
3
|
module Subscription
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
included do
|
7
|
-
scope :braintree, -> { where(processor: :braintree) }
|
8
|
-
end
|
9
|
-
|
10
|
-
def braintree?
|
11
|
-
processor == "braintree"
|
12
|
-
end
|
13
|
-
|
14
6
|
def braintree_cancel
|
15
7
|
subscription = processor_subscription
|
16
8
|
|
@@ -24,17 +16,33 @@ module Pay
|
|
24
16
|
update(status: :canceled, ends_at: subscription.billing_period_end_date.to_date)
|
25
17
|
end
|
26
18
|
rescue ::Braintree::BraintreeError => e
|
27
|
-
raise Error, e
|
19
|
+
raise Pay::Braintree::Error, e
|
28
20
|
end
|
29
21
|
|
30
22
|
def braintree_cancel_now!
|
31
23
|
gateway.subscription.cancel(processor_subscription.id)
|
32
24
|
update(status: :canceled, ends_at: Time.zone.now)
|
33
25
|
rescue ::Braintree::BraintreeError => e
|
34
|
-
raise Error, e
|
26
|
+
raise Pay::Braintree::Error, e
|
27
|
+
end
|
28
|
+
|
29
|
+
def braintree_on_grace_period?
|
30
|
+
canceled? && Time.zone.now < ends_at
|
31
|
+
end
|
32
|
+
|
33
|
+
def braintree_paused?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def braintree_pause
|
38
|
+
raise NotImplementedError, "Braintree does not support pausing subscriptions"
|
35
39
|
end
|
36
40
|
|
37
41
|
def braintree_resume
|
42
|
+
unless on_grace_period?
|
43
|
+
raise StandardError, "You can only resume subscriptions within their grace period."
|
44
|
+
end
|
45
|
+
|
38
46
|
if canceled? && on_trial?
|
39
47
|
duration = trial_ends_at.to_date - Date.today
|
40
48
|
|
@@ -57,7 +65,7 @@ module Pay
|
|
57
65
|
|
58
66
|
update(status: :active)
|
59
67
|
rescue ::Braintree::BraintreeError => e
|
60
|
-
raise Error, e
|
68
|
+
raise Pay::Braintree::Error, e
|
61
69
|
end
|
62
70
|
|
63
71
|
def braintree_swap(plan)
|
@@ -96,7 +104,7 @@ module Pay
|
|
96
104
|
raise Error, "Braintree failed to swap plans: #{result.message}"
|
97
105
|
end
|
98
106
|
rescue ::Braintree::BraintreeError => e
|
99
|
-
raise Error, e
|
107
|
+
raise Pay::Braintree::Error, e
|
100
108
|
end
|
101
109
|
|
102
110
|
private
|
@@ -174,7 +182,7 @@ module Pay
|
|
174
182
|
|
175
183
|
cancel_now!
|
176
184
|
|
177
|
-
owner.subscribe(options.merge(name: name, plan: plan.id))
|
185
|
+
owner.subscribe(**options.merge(name: name, plan: plan.id))
|
178
186
|
end
|
179
187
|
end
|
180
188
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Dir[File.join(__dir__, "webhooks", "**", "*.rb")].sort.each { |file| require file }
|
2
|
+
|
3
|
+
Pay::Webhooks.configure do |events|
|
4
|
+
events.subscribe "braintree.subscription_canceled", Pay::Braintree::Webhooks::SubscriptionCanceled.new
|
5
|
+
events.subscribe "braintree.subscription_charged_successfully", Pay::Braintree::Webhooks::SubscriptionChargedSuccessfully.new
|
6
|
+
events.subscribe "braintree.subscription_charged_unsuccessfully", Pay::Braintree::Webhooks::SubscriptionChargedUnsuccessfully.new
|
7
|
+
events.subscribe "braintree.subscription_expired", Pay::Braintree::Webhooks::SubscriptionExpired.new
|
8
|
+
events.subscribe "braintree.subscription_trial_ended", Pay::Braintree::Webhooks::SubscriptionTrialEnded.new
|
9
|
+
events.subscribe "braintree.subscription_went_active", Pay::Braintree::Webhooks::SubscriptionWentActive.new
|
10
|
+
events.subscribe "braintree.subscription_went_past_due", Pay::Braintree::Webhooks::SubscriptionWentPastDue.new
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A subscription is canceled.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionCanceled
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
pay_subscription.update!(ends_at: Time.current, status: :canceled)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionChargedSuccessfully
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
billable = pay_subscription.owner
|
15
|
+
charge = billable.save_braintree_transaction(subscription.transactions.first)
|
16
|
+
|
17
|
+
if Pay.send_emails
|
18
|
+
Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionChargedUnsuccessfully
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
# billable = pay_subscription.owner
|
15
|
+
# charge = billable.save_braintree_transaction(subscription.transactions.first)
|
16
|
+
|
17
|
+
# if Pay.send_emails
|
18
|
+
# Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|