pay 2.7.0 → 3.0.1
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 +35 -696
- data/app/controllers/pay/webhooks/braintree_controller.rb +10 -3
- data/app/controllers/pay/webhooks/paddle_controller.rb +7 -8
- data/app/controllers/pay/webhooks/stripe_controller.rb +6 -3
- data/app/jobs/pay/{email_sync_job.rb → customer_sync_job.rb} +3 -4
- data/app/models/pay/application_record.rb +1 -5
- data/app/models/pay/charge.rb +31 -18
- data/app/models/pay/customer.rb +87 -0
- data/app/models/pay/merchant.rb +19 -0
- data/app/models/pay/payment_method.rb +41 -0
- data/app/models/pay/subscription.rb +42 -30
- data/app/models/pay/webhook.rb +36 -0
- data/app/views/layouts/pay/application.html.erb +2 -3
- data/app/views/pay/payments/show.html.erb +109 -81
- data/app/views/pay/user_mailer/receipt.html.erb +2 -2
- data/app/views/pay/user_mailer/refund.html.erb +2 -2
- data/config/locales/en.yml +1 -1
- data/db/migrate/1_create_pay_tables.rb +72 -0
- data/lib/generators/active_record/billable_generator.rb +44 -0
- data/lib/generators/active_record/merchant_generator.rb +44 -0
- data/lib/generators/active_record/templates/billable_migration.rb +17 -0
- data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
- data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
- data/lib/generators/pay/merchant_generator.rb +17 -0
- data/lib/generators/pay/orm_helpers.rb +10 -6
- data/lib/pay/adapter.rb +9 -0
- data/lib/pay/attributes.rb +74 -0
- data/lib/pay/billable/sync_customer.rb +30 -0
- data/lib/pay/braintree/billable.rb +133 -110
- data/lib/pay/braintree/payment_method.rb +42 -0
- data/lib/pay/braintree/subscription.rb +9 -12
- data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -1
- data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +4 -4
- data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
- data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -1
- data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +2 -2
- data/lib/pay/braintree/webhooks/subscription_went_active.rb +1 -1
- data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +1 -1
- data/lib/pay/braintree.rb +3 -2
- data/lib/pay/engine.rb +6 -1
- data/lib/pay/env.rb +8 -0
- data/lib/pay/fake_processor/billable.rb +45 -21
- data/lib/pay/fake_processor/payment_method.rb +21 -0
- data/lib/pay/fake_processor/subscription.rb +11 -8
- data/lib/pay/fake_processor.rb +2 -1
- data/lib/pay/nano_id.rb +13 -0
- data/lib/pay/paddle/billable.rb +18 -48
- data/lib/pay/paddle/charge.rb +5 -5
- data/lib/pay/paddle/payment_method.rb +60 -0
- data/lib/pay/paddle/response.rb +0 -0
- data/lib/pay/paddle/subscription.rb +49 -8
- data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
- data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
- data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +26 -28
- data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
- data/lib/pay/paddle.rb +7 -3
- data/lib/pay/payment.rb +1 -1
- data/lib/pay/receipts.rb +35 -7
- data/lib/pay/stripe/billable.rb +80 -102
- data/lib/pay/stripe/charge.rb +59 -3
- data/lib/pay/stripe/merchant.rb +10 -10
- data/lib/pay/stripe/payment_method.rb +61 -0
- data/lib/pay/stripe/subscription.rb +70 -8
- data/lib/pay/stripe/webhooks/account_updated.rb +2 -3
- data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
- data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +15 -0
- data/lib/pay/stripe/webhooks/checkout_session_completed.rb +15 -0
- data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
- data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
- data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
- data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -14
- data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
- data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
- data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
- data/lib/pay/stripe/webhooks/subscription_created.rb +1 -36
- data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -9
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +14 -6
- data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -29
- data/lib/pay/stripe.rb +12 -3
- data/lib/pay/version.rb +1 -1
- data/lib/pay/webhooks/delegator.rb +4 -0
- data/lib/pay/webhooks/process_job.rb +9 -0
- data/lib/pay/webhooks.rb +1 -0
- data/lib/pay.rb +7 -78
- data/lib/tasks/pay.rake +20 -0
- metadata +31 -39
- data/app/models/pay.rb +0 -5
- data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
- data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
- data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
- data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -15
- data/db/migrate/20210309004259_add_data_to_pay_billable.rb +0 -19
- data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +0 -5
- data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +0 -7
- data/lib/generators/active_record/pay_generator.rb +0 -58
- data/lib/generators/active_record/templates/migration.rb +0 -9
- data/lib/pay/billable/sync_email.rb +0 -40
- data/lib/pay/billable.rb +0 -172
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddPayBillableTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
change_table :<%= table_name %>, bulk: true do |t|
|
6
|
+
t.string :processor
|
7
|
+
t.string :processor_id
|
8
|
+
t.public_send(Pay::Adapter.json_column_type, :pay_data)
|
9
|
+
t.datetime :trial_ends_at
|
10
|
+
t.string :card_type
|
11
|
+
t.string :card_last4
|
12
|
+
t.string :card_exp_month
|
13
|
+
t.string :card_exp_year
|
14
|
+
t.text :extra_billing_info
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddPayMerchantTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
add_column table_name, :merchant_processor, :string
|
6
|
+
|
7
|
+
# We may already have the pay_data column if a Pay::Billable object is also a Pay::Merchant
|
8
|
+
unless ActiveRecord::Base.connection.column_exists?(table_name, :pay_data)
|
9
|
+
add_column :<%= table_name %>, :pay_data, Pay::Adapter.json_column_type
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -4,13 +4,12 @@ require "rails/generators/named_base"
|
|
4
4
|
|
5
5
|
module Pay
|
6
6
|
module Generators
|
7
|
-
class
|
7
|
+
class BillableGenerator < Rails::Generators::NamedBase
|
8
8
|
include Rails::Generators::ResourceHelpers
|
9
9
|
|
10
|
-
namespace "pay"
|
11
10
|
source_root File.expand_path("../templates", __FILE__)
|
12
11
|
|
13
|
-
desc "Generates a migration to add Billable fields to a model."
|
12
|
+
desc "Generates a migration to add Pay::Billable fields to a model."
|
14
13
|
|
15
14
|
hook_for :orm
|
16
15
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Pay
|
6
|
+
module Generators
|
7
|
+
class MerchantGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
source_root File.expand_path("../templates", __FILE__)
|
11
|
+
|
12
|
+
desc "Generates a migration to add Pay::Merchant fields to a model."
|
13
|
+
|
14
|
+
hook_for :orm
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,12 +3,6 @@
|
|
3
3
|
module Pay
|
4
4
|
module Generators
|
5
5
|
module OrmHelpers
|
6
|
-
def model_contents
|
7
|
-
<<-CONTENT
|
8
|
-
include Pay::Billable
|
9
|
-
CONTENT
|
10
|
-
end
|
11
|
-
|
12
6
|
private
|
13
7
|
|
14
8
|
def model_exists?
|
@@ -30,6 +24,16 @@ module Pay
|
|
30
24
|
def model_path
|
31
25
|
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
32
26
|
end
|
27
|
+
|
28
|
+
def migration_version
|
29
|
+
if rails5_and_up?
|
30
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def rails5_and_up?
|
35
|
+
Rails::VERSION::MAJOR >= 5
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
data/lib/pay/adapter.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Pay
|
2
|
+
# Adds Pay methods to ActiveRecord models
|
3
|
+
|
4
|
+
module Attributes
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module CustomerExtension
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
has_many :pay_customers, class_name: "Pay::Customer", as: :owner, inverse_of: :owner
|
12
|
+
has_many :charges, through: :pay_customers, class_name: "Pay::Charge"
|
13
|
+
has_many :subscriptions, through: :pay_customers, class_name: "Pay::Subscription"
|
14
|
+
has_one :payment_processor, -> { where(default: true, deleted_at: nil) }, class_name: "Pay::Customer", as: :owner, inverse_of: :owner
|
15
|
+
|
16
|
+
after_commit :cancel_active_pay_subscriptions!, on: [:destroy]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Changes a user's payment processor
|
20
|
+
#
|
21
|
+
# This has several effects:
|
22
|
+
# - Finds or creates a Pay::Customer for the process and marks it as default
|
23
|
+
# - Removes the default flag from all other Pay::Customers
|
24
|
+
# - Removes the default flag from all Pay::PaymentMethods
|
25
|
+
def set_payment_processor(processor_name, allow_fake: false, **attributes)
|
26
|
+
raise Pay::Error, "Processor `#{processor_name}` is not allowed" if processor_name.to_s == "fake_processor" && !allow_fake
|
27
|
+
|
28
|
+
ActiveRecord::Base.transaction do
|
29
|
+
pay_customers.update_all(default: false)
|
30
|
+
pay_customer = pay_customers.active.where(processor: processor_name).first_or_initialize
|
31
|
+
pay_customer.update!(attributes.merge(default: true))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return new payment processor
|
35
|
+
reload_payment_processor
|
36
|
+
end
|
37
|
+
|
38
|
+
def cancel_active_pay_subscriptions!
|
39
|
+
subscriptions.active.each(&:cancel_now!)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module MerchantExtension
|
44
|
+
extend ActiveSupport::Concern
|
45
|
+
|
46
|
+
included do
|
47
|
+
has_many :pay_merchants, class_name: "Pay::Merchant", as: :owner, inverse_of: :owner
|
48
|
+
has_one :merchant_processor, -> { where(default: true) }, class_name: "Pay::Merchant", as: :owner, inverse_of: :owner
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_merchant_processor(processor_name, **attributes)
|
52
|
+
ActiveRecord::Base.transaction do
|
53
|
+
pay_merchants.update_all(default: false)
|
54
|
+
pay_merchant = pay_merchants.where(processor: processor_name).first_or_initialize
|
55
|
+
pay_merchant.update!(attributes.merge(default: true))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return new payment processor
|
59
|
+
reload_merchant_processor
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class_methods do
|
64
|
+
def pay_customer
|
65
|
+
include Billable::SyncCustomer
|
66
|
+
include CustomerExtension
|
67
|
+
end
|
68
|
+
|
69
|
+
def pay_merchant
|
70
|
+
include MerchantExtension
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Pay
|
2
|
+
module Billable
|
3
|
+
module SyncCustomer
|
4
|
+
# Syncs customer details to the payment processor.
|
5
|
+
# This way they're kept in sync and email notifications are
|
6
|
+
# always sent to the correct email address after an update.
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
after_update :enqeue_sync_email_job, if: :pay_should_sync_customer?
|
12
|
+
end
|
13
|
+
|
14
|
+
def pay_should_sync_customer?
|
15
|
+
try(:saved_change_to_email?)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def enqeue_sync_email_job
|
21
|
+
if saved_change_to_email?
|
22
|
+
# Queue job to update each payment processor for this customer
|
23
|
+
pay_customers.pluck(:id).each do |pay_customer_id|
|
24
|
+
CustomerSyncJob.perform_later(id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,40 +1,42 @@
|
|
1
1
|
module Pay
|
2
2
|
module Braintree
|
3
3
|
class Billable
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :pay_customer
|
5
5
|
|
6
6
|
delegate :processor_id,
|
7
7
|
:processor_id?,
|
8
8
|
:email,
|
9
9
|
:customer_name,
|
10
|
-
:
|
11
|
-
|
10
|
+
:payment_method_token,
|
11
|
+
:payment_method_token?,
|
12
|
+
to: :pay_customer
|
12
13
|
|
13
|
-
def initialize(
|
14
|
-
@
|
14
|
+
def initialize(pay_customer)
|
15
|
+
@pay_customer = pay_customer
|
15
16
|
end
|
16
17
|
|
17
|
-
#
|
18
|
+
# Retrieve the Braintree::Customer object
|
18
19
|
#
|
19
|
-
#
|
20
|
+
# - If no processor_id is present, creates a Customer.
|
21
|
+
# - When 'payment_method_token' is present, it will also set the default payment method
|
20
22
|
def customer
|
21
23
|
if processor_id?
|
22
24
|
customer = gateway.customer.find(processor_id)
|
23
|
-
|
25
|
+
|
26
|
+
if payment_method_token?
|
27
|
+
add_payment_method(payment_method_token, default: true)
|
28
|
+
pay_customer.payment_method_token = nil
|
29
|
+
end
|
30
|
+
|
24
31
|
customer
|
25
32
|
else
|
26
|
-
result = gateway.customer.create(
|
27
|
-
email: email,
|
28
|
-
first_name: try(:first_name),
|
29
|
-
last_name: try(:last_name),
|
30
|
-
payment_method_nonce: card_token
|
31
|
-
)
|
33
|
+
result = gateway.customer.create(email: email, first_name: try(:first_name), last_name: try(:last_name), payment_method_nonce: payment_method_token)
|
32
34
|
raise Pay::Braintree::Error, result unless result.success?
|
35
|
+
pay_customer.update!(processor_id: result.customer.id)
|
33
36
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
update_card_on_file result.customer.payment_methods.last
|
37
|
+
if payment_method_token?
|
38
|
+
save_payment_method(result.customer.payment_methods.last, default: true)
|
39
|
+
pay_customer.payment_method_token = nil
|
38
40
|
end
|
39
41
|
|
40
42
|
result.customer
|
@@ -45,14 +47,12 @@ module Pay
|
|
45
47
|
raise Pay::Braintree::Error, e
|
46
48
|
end
|
47
49
|
|
48
|
-
# Handles Billable#charge
|
49
|
-
#
|
50
|
-
# Returns a Pay::Charge
|
51
50
|
def charge(amount, options = {})
|
52
51
|
args = {
|
53
52
|
amount: amount.to_i / 100.0,
|
54
53
|
customer_id: customer.id,
|
55
|
-
options: {submit_for_settlement: true}
|
54
|
+
options: {submit_for_settlement: true},
|
55
|
+
custom_fields: options.delete(:metadata)
|
56
56
|
}.merge(options)
|
57
57
|
|
58
58
|
result = gateway.transaction.sale(args)
|
@@ -65,9 +65,6 @@ module Pay
|
|
65
65
|
raise Pay::Braintree::Error, e
|
66
66
|
end
|
67
67
|
|
68
|
-
# Handles Billable#subscribe
|
69
|
-
#
|
70
|
-
# Returns Pay::Subscription
|
71
68
|
def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
|
72
69
|
token = customer.payment_methods.find(&:default?).try(:token)
|
73
70
|
raise Pay::Error, "Customer has no default payment method" if token.nil?
|
@@ -77,6 +74,7 @@ module Pay
|
|
77
74
|
options.merge!(trial_period: true, trial_duration: trial_period_days, trial_duration_unit: :day)
|
78
75
|
end
|
79
76
|
|
77
|
+
metadata = options.delete(:metadata)
|
80
78
|
subscription_options = options.merge(
|
81
79
|
payment_method_token: token,
|
82
80
|
plan_id: plan
|
@@ -85,32 +83,44 @@ module Pay
|
|
85
83
|
result = gateway.subscription.create(subscription_options)
|
86
84
|
raise Pay::Braintree::Error, result unless result.success?
|
87
85
|
|
88
|
-
|
86
|
+
pay_customer.subscriptions.create!(
|
87
|
+
name: name,
|
88
|
+
processor_id: result.subscription.id,
|
89
|
+
processor_plan: plan,
|
90
|
+
status: :active,
|
91
|
+
trial_ends_at: trial_end_date(result.subscription),
|
92
|
+
ends_at: nil,
|
93
|
+
metadata: metadata
|
94
|
+
)
|
89
95
|
rescue ::Braintree::AuthorizationError => e
|
90
96
|
raise Pay::Braintree::AuthorizationError, e
|
91
97
|
rescue ::Braintree::BraintreeError => e
|
92
98
|
raise Pay::Braintree::Error, e
|
93
99
|
end
|
94
100
|
|
95
|
-
|
96
|
-
#
|
97
|
-
# Returns true if successful
|
98
|
-
def update_card(token)
|
101
|
+
def add_payment_method(token, default: false)
|
99
102
|
customer unless processor_id?
|
100
103
|
|
101
104
|
result = gateway.payment_method.create(
|
102
105
|
customer_id: processor_id,
|
103
106
|
payment_method_nonce: token,
|
104
107
|
options: {
|
105
|
-
make_default:
|
108
|
+
make_default: default,
|
106
109
|
verify_card: true
|
107
110
|
}
|
108
111
|
)
|
109
112
|
raise Pay::Braintree::Error, result unless result.success?
|
110
113
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
pay_payment_method = save_payment_method(result.payment_method, default: default)
|
115
|
+
|
116
|
+
# Update existing subscriptions to the new payment method
|
117
|
+
pay_customer.subscriptions.each do |subscription|
|
118
|
+
if subscription.active?
|
119
|
+
gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
pay_payment_method
|
114
124
|
rescue ::Braintree::AuthorizationError => e
|
115
125
|
raise Pay::Braintree::AuthorizationError, e
|
116
126
|
rescue ::Braintree::BraintreeError => e
|
@@ -127,119 +137,132 @@ module Pay
|
|
127
137
|
subscription.first_billing_date.end_of_day
|
128
138
|
end
|
129
139
|
|
130
|
-
def update_subscriptions_to_payment_method(token)
|
131
|
-
billable.subscriptions.braintree.each do |subscription|
|
132
|
-
if subscription.active?
|
133
|
-
gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
140
|
def processor_subscription(subscription_id, options = {})
|
139
141
|
gateway.subscription.find(subscription_id)
|
140
142
|
end
|
141
143
|
|
142
|
-
def braintree_invoice!(options = {})
|
143
|
-
# pass
|
144
|
-
end
|
145
|
-
|
146
|
-
def braintree_upcoming_invoice
|
147
|
-
# pass
|
148
|
-
end
|
149
|
-
|
150
144
|
def save_transaction(transaction)
|
151
145
|
attrs = card_details_for_braintree_transaction(transaction)
|
152
146
|
attrs[:amount] = transaction.amount.to_f * 100
|
147
|
+
attrs[:metadata] = transaction.custom_fields
|
148
|
+
attrs[:currency] = transaction.currency_iso_code
|
149
|
+
attrs[:application_fee_amount] = transaction.service_fee_amount
|
153
150
|
|
154
151
|
# Associate charge with subscription if we can
|
155
152
|
if transaction.subscription_id
|
156
|
-
|
153
|
+
pay_subscription = pay_customer.subscriptions.find_by(processor_id: transaction.subscription_id)
|
154
|
+
attrs[:subscription] = pay_subscription
|
155
|
+
attrs[:metadata] = pay_subscription.metadata
|
157
156
|
end
|
158
157
|
|
159
|
-
charge =
|
160
|
-
|
161
|
-
processor_id: transaction.id,
|
162
|
-
currency: transaction.currency_iso_code,
|
163
|
-
application_fee_amount: transaction.service_fee_amount
|
164
|
-
)
|
165
|
-
charge.update(attrs)
|
158
|
+
charge = pay_customer.charges.find_or_initialize_by(processor_id: transaction.id)
|
159
|
+
charge.update!(attrs)
|
166
160
|
charge
|
167
161
|
end
|
168
162
|
|
169
|
-
private
|
170
|
-
|
171
163
|
def gateway
|
172
164
|
Pay.braintree_gateway
|
173
165
|
end
|
174
166
|
|
175
|
-
def
|
176
|
-
case payment_method
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
167
|
+
def save_payment_method(payment_method, default:)
|
168
|
+
attributes = case payment_method
|
169
|
+
when ::Braintree::CreditCard, ::Braintree::ApplePayCard, ::Braintree::GooglePayCard, ::Braintree::SamsungPayCard, ::Braintree::VisaCheckoutCard
|
170
|
+
{
|
171
|
+
payment_method_type: :card,
|
172
|
+
brand: payment_method.card_type,
|
173
|
+
last4: payment_method.last_4,
|
174
|
+
exp_month: payment_method.expiration_month,
|
175
|
+
exp_year: payment_method.expiration_year
|
176
|
+
}
|
177
|
+
|
178
|
+
when ::Braintree::PayPalAccount
|
179
|
+
{
|
180
|
+
payment_method_type: :paypal,
|
181
|
+
brand: "PayPal",
|
182
|
+
email: payment_method.email
|
183
|
+
}
|
184
|
+
when ::Braintree::VenmoAccount
|
185
|
+
{
|
186
|
+
payment_method_type: :venmo,
|
187
|
+
brand: "Venmo",
|
188
|
+
username: payment_method.username
|
189
|
+
}
|
190
|
+
when ::Braintree::UsBankAccount
|
191
|
+
{
|
192
|
+
payment_method_type: "us_bank_account",
|
193
|
+
bank: payment_method.bank_name,
|
194
|
+
last4: payment_method.last_4
|
195
|
+
}
|
196
|
+
else
|
197
|
+
{
|
198
|
+
payment_method_type: payment_method.class.name.demodulize.underscore,
|
199
|
+
brand: payment_method.try(:card_type),
|
200
|
+
last4: payment_method.try(:last_4),
|
201
|
+
exp_month: payment_method.try(:expiration_month),
|
202
|
+
exp_year: payment_method.try(:expiration_year),
|
203
|
+
bank: payment_method.try(:bank_name),
|
204
|
+
username: payment_method.try(:username),
|
205
|
+
email: payment_method.try(:email)
|
206
|
+
}
|
190
207
|
end
|
191
208
|
|
192
|
-
|
193
|
-
|
209
|
+
pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.token).first_or_initialize
|
210
|
+
|
211
|
+
pay_customer.payment_methods.update_all(default: false) if default
|
212
|
+
pay_payment_method.update!(attributes.merge(default: default))
|
213
|
+
|
214
|
+
# Reload the Rails association
|
215
|
+
pay_customer.reload_default_payment_method if default
|
216
|
+
|
217
|
+
pay_payment_method
|
194
218
|
end
|
195
219
|
|
196
220
|
def card_details_for_braintree_transaction(transaction)
|
197
221
|
case transaction.payment_instrument_type
|
198
|
-
when "credit_card", "
|
199
|
-
|
200
|
-
|
201
|
-
card_type: payment_method.card_type,
|
202
|
-
card_last4: payment_method.last_4,
|
203
|
-
card_exp_month: payment_method.expiration_month,
|
204
|
-
card_exp_year: payment_method.expiration_year
|
205
|
-
}
|
222
|
+
when "android_pay_card", "apple_pay_card", "credit_card", "google_pay_card", "samsung_pay_card", "visa_checkout_card"
|
223
|
+
# Lookup the attribute with the payment method details by name
|
224
|
+
attribute_name = transaction.payment_instrument_type
|
206
225
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
226
|
+
# The attribute name for Apple and Google Pay don't include _card for some reason
|
227
|
+
if ["apple_pay_card", "google_pay_card"].include?(transaction.payment_instrument_type)
|
228
|
+
attribute_name = attribute_name.split("_card").first
|
229
|
+
|
230
|
+
# Android Pay was renamed to Google Pay, but test nonces still use android_pay_card
|
231
|
+
elsif attribute_name == "android_pay_card"
|
232
|
+
attribute_name = "google_pay"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Retrieve payment method details from transaction
|
236
|
+
payment_method = transaction.send("#{attribute_name}_details")
|
214
237
|
|
215
|
-
when "android_pay_card"
|
216
|
-
payment_method = transaction.android_pay_details
|
217
238
|
{
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
239
|
+
payment_method_type: :card,
|
240
|
+
brand: payment_method.card_type,
|
241
|
+
last4: payment_method.last_4,
|
242
|
+
exp_month: payment_method.expiration_month,
|
243
|
+
exp_year: payment_method.expiration_year
|
222
244
|
}
|
223
245
|
|
224
|
-
when "
|
246
|
+
when "paypal_account"
|
225
247
|
{
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
248
|
+
payment_method_type: :paypal,
|
249
|
+
brand: "PayPal",
|
250
|
+
last4: transaction.paypal_details.payer_email,
|
251
|
+
exp_month: nil,
|
252
|
+
exp_year: nil
|
230
253
|
}
|
231
254
|
|
232
|
-
when "
|
233
|
-
payment_method = transaction.apple_pay_details
|
255
|
+
when "venmo_account"
|
234
256
|
{
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
257
|
+
payment_method_type: :venmo,
|
258
|
+
brand: "Venmo",
|
259
|
+
last4: transaction.venmo_account_details.username,
|
260
|
+
exp_month: nil,
|
261
|
+
exp_year: nil
|
239
262
|
}
|
240
263
|
|
241
264
|
else
|
242
|
-
{}
|
265
|
+
{payment_method_type: "unknown"}
|
243
266
|
end
|
244
267
|
end
|
245
268
|
end
|