pay 2.1.2 → 2.3.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 +168 -14
- data/Rakefile +15 -17
- data/app/controllers/pay/payments_controller.rb +3 -0
- data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
- data/app/mailers/pay/user_mailer.rb +2 -2
- data/app/models/pay/application_record.rb +6 -1
- data/app/models/pay/charge.rb +7 -0
- data/app/models/pay/subscription.rb +24 -3
- data/app/views/pay/payments/show.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20170205020145_create_pay_subscriptions.rb +2 -1
- data/db/migrate/20170727235816_create_pay_charges.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 +11 -2
- data/lib/pay/billable.rb +7 -2
- data/lib/pay/braintree.rb +1 -1
- data/lib/pay/braintree/billable.rb +18 -4
- data/lib/pay/braintree/charge.rb +4 -0
- data/lib/pay/braintree/subscription.rb +6 -0
- data/lib/pay/engine.rb +7 -0
- data/lib/pay/paddle.rb +38 -0
- data/lib/pay/paddle/billable.rb +66 -0
- data/lib/pay/paddle/charge.rb +39 -0
- data/lib/pay/paddle/subscription.rb +59 -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 +64 -0
- data/lib/pay/paddle/webhooks/subscription_updated.rb +34 -0
- data/lib/pay/stripe.rb +1 -0
- data/lib/pay/stripe/billable.rb +14 -5
- data/lib/pay/stripe/charge.rb +4 -0
- data/lib/pay/stripe/subscription.rb +7 -1
- 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/version.rb +1 -1
- metadata +63 -8
@@ -1,5 +1,10 @@
|
|
1
1
|
module Pay
|
2
|
-
class ApplicationRecord <
|
2
|
+
class ApplicationRecord < Pay.model_parent_class.constantize
|
3
3
|
self.abstract_class = true
|
4
|
+
|
5
|
+
def self.json_column?(name)
|
6
|
+
return unless connected? && table_exists?
|
7
|
+
[:json, :jsonb].include?(attribute_types[name].type)
|
8
|
+
end
|
4
9
|
end
|
5
10
|
end
|
data/app/models/pay/charge.rb
CHANGED
@@ -2,6 +2,9 @@ module Pay
|
|
2
2
|
class Charge < ApplicationRecord
|
3
3
|
self.table_name = Pay.chargeable_table
|
4
4
|
|
5
|
+
# Only serialize for non-json columns
|
6
|
+
serialize :data unless json_column?("data")
|
7
|
+
|
5
8
|
# Associations
|
6
9
|
belongs_to :owner, polymorphic: true
|
7
10
|
|
@@ -39,5 +42,9 @@ module Pay
|
|
39
42
|
def paypal?
|
40
43
|
braintree? && card_type == "PayPal"
|
41
44
|
end
|
45
|
+
|
46
|
+
def paddle?
|
47
|
+
processor == "paddle"
|
48
|
+
end
|
42
49
|
end
|
43
50
|
end
|
@@ -2,7 +2,10 @@ module Pay
|
|
2
2
|
class Subscription < ApplicationRecord
|
3
3
|
self.table_name = Pay.subscription_table
|
4
4
|
|
5
|
-
STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid]
|
5
|
+
STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
|
6
|
+
|
7
|
+
# Only serialize for non-json columns
|
8
|
+
serialize :data unless json_column?("data")
|
6
9
|
|
7
10
|
# Associations
|
8
11
|
belongs_to :owner, polymorphic: true
|
@@ -66,6 +69,15 @@ module Pay
|
|
66
69
|
past_due? || incomplete?
|
67
70
|
end
|
68
71
|
|
72
|
+
def paused?
|
73
|
+
status == "paused"
|
74
|
+
end
|
75
|
+
|
76
|
+
def pause
|
77
|
+
return unless paddle?
|
78
|
+
send("#{processor}_pause")
|
79
|
+
end
|
80
|
+
|
69
81
|
def cancel
|
70
82
|
send("#{processor}_cancel")
|
71
83
|
end
|
@@ -76,8 +88,17 @@ module Pay
|
|
76
88
|
|
77
89
|
def resume
|
78
90
|
unless on_grace_period?
|
79
|
-
|
80
|
-
|
91
|
+
unless paddle?
|
92
|
+
raise StandardError,
|
93
|
+
"You can only resume subscriptions within their grace period."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
unless paused?
|
98
|
+
if paddle?
|
99
|
+
raise StandardError,
|
100
|
+
"You can only resume paused subscriptions."
|
101
|
+
end
|
81
102
|
end
|
82
103
|
|
83
104
|
send("#{processor}_resume")
|
@@ -51,7 +51,7 @@
|
|
51
51
|
</div>
|
52
52
|
<% end %>
|
53
53
|
|
54
|
-
<%= link_to t("back"),
|
54
|
+
<%= link_to t("back"), @redirect_to, class: "inline-block w-full px-4 py-3 bg-gray-200 hover:bg-gray-300 text-center text-gray-700 rounded-lg" %>
|
55
55
|
</div>
|
56
56
|
|
57
57
|
<p class="text-center text-gray-500 text-sm">
|
data/config/routes.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class CreatePaySubscriptions < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :pay_subscriptions do |t|
|
4
|
-
|
4
|
+
# Some Billable objects use string as ID, add `type: :string` if needed
|
5
|
+
t.references :owner, polymorphic: true
|
5
6
|
t.string :name, null: false
|
6
7
|
t.string :processor, null: false
|
7
8
|
t.string :processor_id, null: false
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class CreatePayCharges < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :pay_charges do |t|
|
4
|
+
# Some Billable objects use string as ID, add `type: :string` if needed
|
4
5
|
t.references :owner, polymorphic: true
|
5
6
|
t.string :processor, null: false
|
6
7
|
t.string :processor_id, null: false
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class AddDataToPayModels < ActiveRecord::Migration[4.2]
|
2
|
+
def change
|
3
|
+
add_column :pay_subscriptions, :data, data_column_type
|
4
|
+
add_column :pay_charges, :data, data_column_type
|
5
|
+
end
|
6
|
+
|
7
|
+
def data_column_type
|
8
|
+
case ActiveRecord::Base.configurations.default_hash.dig("adapter")
|
9
|
+
when "mysql2"
|
10
|
+
:json
|
11
|
+
when "postgresql"
|
12
|
+
:jsonb
|
13
|
+
else
|
14
|
+
:text
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
indent_depth = class_path.size - 1
|
29
|
-
content = content.split("\n").map { |line| " " * indent_depth + line }
|
29
|
+
content = content.split("\n").map { |line| " " * indent_depth + line }.join("\n") << "\n"
|
30
30
|
|
31
31
|
inject_into_class(model_path, class_path.last, content) if model_exists?
|
32
32
|
end
|
data/lib/pay.rb
CHANGED
@@ -12,6 +12,9 @@ module Pay
|
|
12
12
|
@@billable_class = "User"
|
13
13
|
@@billable_table = @@billable_class.tableize
|
14
14
|
|
15
|
+
mattr_accessor :model_parent_class
|
16
|
+
@@model_parent_class = "ApplicationRecord"
|
17
|
+
|
15
18
|
mattr_accessor :chargeable_class
|
16
19
|
mattr_accessor :chargeable_table
|
17
20
|
@@chargeable_class = "Pay::Charge"
|
@@ -105,11 +108,17 @@ module Pay
|
|
105
108
|
class BraintreeError < Error
|
106
109
|
attr_reader :result
|
107
110
|
|
108
|
-
def initialize(result)
|
111
|
+
def initialize(result = nil)
|
109
112
|
@result = result
|
110
113
|
end
|
111
114
|
end
|
112
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
|
+
|
113
122
|
class InvalidPaymentMethod < Error
|
114
123
|
attr_reader :payment
|
115
124
|
|
@@ -118,7 +127,7 @@ module Pay
|
|
118
127
|
end
|
119
128
|
|
120
129
|
def message
|
121
|
-
"This payment attempt failed
|
130
|
+
"This payment attempt failed because of an invalid payment method."
|
122
131
|
end
|
123
132
|
end
|
124
133
|
|
data/lib/pay/billable.rb
CHANGED
@@ -19,9 +19,10 @@ module Pay
|
|
19
19
|
include Pay::Billable::SyncEmail
|
20
20
|
include Pay::Stripe::Billable if defined? ::Stripe
|
21
21
|
include Pay::Braintree::Billable if defined? ::Braintree
|
22
|
+
include Pay::Paddle::Billable if defined? ::PaddlePay
|
22
23
|
|
23
|
-
has_many :charges, class_name: Pay.chargeable_class, foreign_key: :owner_id, inverse_of: :owner
|
24
|
-
has_many :subscriptions, class_name: Pay.subscription_class, foreign_key: :owner_id, inverse_of: :owner
|
24
|
+
has_many :charges, class_name: Pay.chargeable_class, foreign_key: :owner_id, inverse_of: :owner, as: :owner
|
25
|
+
has_many :subscriptions, class_name: Pay.subscription_class, foreign_key: :owner_id, inverse_of: :owner, as: :owner
|
25
26
|
|
26
27
|
attribute :plan, :string
|
27
28
|
attribute :quantity, :integer
|
@@ -118,6 +119,10 @@ module Pay
|
|
118
119
|
braintree? && card_type == "PayPal"
|
119
120
|
end
|
120
121
|
|
122
|
+
def paddle?
|
123
|
+
processor == "paddle"
|
124
|
+
end
|
125
|
+
|
121
126
|
def has_incomplete_payment?(name: "default")
|
122
127
|
subscription(name: name)&.has_incomplete_payment?
|
123
128
|
end
|
data/lib/pay/braintree.rb
CHANGED
@@ -19,7 +19,7 @@ module Pay
|
|
19
19
|
|
20
20
|
Pay.charge_model.include Pay::Braintree::Charge
|
21
21
|
Pay.subscription_model.include Pay::Braintree::Subscription
|
22
|
-
Pay.billable_models.each { |model| model.include Pay::
|
22
|
+
Pay.billable_models.each { |model| model.include Pay::Braintree::Billable }
|
23
23
|
end
|
24
24
|
|
25
25
|
def public_key
|
@@ -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
|
@@ -24,6 +30,8 @@ module Pay
|
|
24
30
|
|
25
31
|
result.customer
|
26
32
|
end
|
33
|
+
rescue ::Braintree::AuthorizationError
|
34
|
+
raise BraintreeAuthorizationError
|
27
35
|
rescue ::Braintree::BraintreeError => e
|
28
36
|
raise BraintreeError, e.message
|
29
37
|
end
|
@@ -33,7 +41,7 @@ module Pay
|
|
33
41
|
# Returns a Pay::Charge
|
34
42
|
def create_braintree_charge(amount, options = {})
|
35
43
|
args = {
|
36
|
-
amount: amount / 100.0,
|
44
|
+
amount: amount.to_i / 100.0,
|
37
45
|
customer_id: customer.id,
|
38
46
|
options: {submit_for_settlement: true}
|
39
47
|
}.merge(options)
|
@@ -42,6 +50,8 @@ module Pay
|
|
42
50
|
raise BraintreeError.new(result), result.message unless result.success?
|
43
51
|
|
44
52
|
save_braintree_transaction(result.transaction)
|
53
|
+
rescue ::Braintree::AuthorizationError
|
54
|
+
raise BraintreeAuthorizationError
|
45
55
|
rescue ::Braintree::BraintreeError => e
|
46
56
|
raise BraintreeError, e.message
|
47
57
|
end
|
@@ -67,6 +77,8 @@ module Pay
|
|
67
77
|
raise BraintreeError.new(result), result.message unless result.success?
|
68
78
|
|
69
79
|
create_subscription(result.subscription, "braintree", name, plan, status: :active)
|
80
|
+
rescue ::Braintree::AuthorizationError
|
81
|
+
raise BraintreeAuthorizationError
|
70
82
|
rescue ::Braintree::BraintreeError => e
|
71
83
|
raise BraintreeError, e.message
|
72
84
|
end
|
@@ -88,6 +100,8 @@ module Pay
|
|
88
100
|
update_braintree_card_on_file result.payment_method
|
89
101
|
update_subscriptions_to_payment_method(result.payment_method.token)
|
90
102
|
true
|
103
|
+
rescue ::Braintree::AuthorizationError
|
104
|
+
raise BraintreeAuthorizationError
|
91
105
|
rescue ::Braintree::BraintreeError => e
|
92
106
|
raise BraintreeError, e.message
|
93
107
|
end
|
@@ -103,11 +117,11 @@ module Pay
|
|
103
117
|
def braintree_trial_end_date(subscription)
|
104
118
|
return unless subscription.trial_period
|
105
119
|
# Braintree returns dates without time zones, so we'll assume they're UTC
|
106
|
-
|
120
|
+
subscription.first_billing_date.end_of_day
|
107
121
|
end
|
108
122
|
|
109
123
|
def update_subscriptions_to_payment_method(token)
|
110
|
-
subscriptions.each do |subscription|
|
124
|
+
subscriptions.braintree.each do |subscription|
|
111
125
|
if subscription.active?
|
112
126
|
gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
|
113
127
|
end
|
@@ -167,7 +181,7 @@ module Pay
|
|
167
181
|
|
168
182
|
def card_details_for_braintree_transaction(transaction)
|
169
183
|
case transaction.payment_instrument_type
|
170
|
-
when "credit_card", "samsung_pay_card", "masterpass_card", "
|
184
|
+
when "credit_card", "samsung_pay_card", "masterpass_card", "visa_checkout_card"
|
171
185
|
payment_method = transaction.send("#{transaction.payment_instrument_type}_details")
|
172
186
|
{
|
173
187
|
card_type: payment_method.card_type,
|
data/lib/pay/braintree/charge.rb
CHANGED
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/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,66 @@
|
|
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: DateTime.parse(response[:payment_date])
|
27
|
+
)
|
28
|
+
charge
|
29
|
+
rescue ::PaddlePay::PaddlePayError => e
|
30
|
+
raise Error, e.message
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_paddle_subscription(name, plan, options = {})
|
34
|
+
# pass
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_paddle_card(token)
|
38
|
+
# pass
|
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
|
+
DateTime.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 Error, e.message
|
55
|
+
end
|
56
|
+
|
57
|
+
def paddle_invoice!(options = {})
|
58
|
+
# pass
|
59
|
+
end
|
60
|
+
|
61
|
+
def paddle_upcoming_invoice
|
62
|
+
# pass
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|