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.

Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -696
  3. data/app/controllers/pay/webhooks/braintree_controller.rb +10 -3
  4. data/app/controllers/pay/webhooks/paddle_controller.rb +7 -8
  5. data/app/controllers/pay/webhooks/stripe_controller.rb +6 -3
  6. data/app/jobs/pay/{email_sync_job.rb → customer_sync_job.rb} +3 -4
  7. data/app/models/pay/application_record.rb +1 -5
  8. data/app/models/pay/charge.rb +31 -18
  9. data/app/models/pay/customer.rb +87 -0
  10. data/app/models/pay/merchant.rb +19 -0
  11. data/app/models/pay/payment_method.rb +41 -0
  12. data/app/models/pay/subscription.rb +42 -30
  13. data/app/models/pay/webhook.rb +36 -0
  14. data/app/views/layouts/pay/application.html.erb +2 -3
  15. data/app/views/pay/payments/show.html.erb +109 -81
  16. data/app/views/pay/user_mailer/receipt.html.erb +2 -2
  17. data/app/views/pay/user_mailer/refund.html.erb +2 -2
  18. data/config/locales/en.yml +1 -1
  19. data/db/migrate/1_create_pay_tables.rb +72 -0
  20. data/lib/generators/active_record/billable_generator.rb +44 -0
  21. data/lib/generators/active_record/merchant_generator.rb +44 -0
  22. data/lib/generators/active_record/templates/billable_migration.rb +17 -0
  23. data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
  24. data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
  25. data/lib/generators/pay/merchant_generator.rb +17 -0
  26. data/lib/generators/pay/orm_helpers.rb +10 -6
  27. data/lib/pay/adapter.rb +9 -0
  28. data/lib/pay/attributes.rb +74 -0
  29. data/lib/pay/billable/sync_customer.rb +30 -0
  30. data/lib/pay/braintree/billable.rb +133 -110
  31. data/lib/pay/braintree/payment_method.rb +42 -0
  32. data/lib/pay/braintree/subscription.rb +9 -12
  33. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -1
  34. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +4 -4
  35. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
  36. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -1
  37. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +2 -2
  38. data/lib/pay/braintree/webhooks/subscription_went_active.rb +1 -1
  39. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +1 -1
  40. data/lib/pay/braintree.rb +3 -2
  41. data/lib/pay/engine.rb +6 -1
  42. data/lib/pay/env.rb +8 -0
  43. data/lib/pay/fake_processor/billable.rb +45 -21
  44. data/lib/pay/fake_processor/payment_method.rb +21 -0
  45. data/lib/pay/fake_processor/subscription.rb +11 -8
  46. data/lib/pay/fake_processor.rb +2 -1
  47. data/lib/pay/nano_id.rb +13 -0
  48. data/lib/pay/paddle/billable.rb +18 -48
  49. data/lib/pay/paddle/charge.rb +5 -5
  50. data/lib/pay/paddle/payment_method.rb +60 -0
  51. data/lib/pay/paddle/response.rb +0 -0
  52. data/lib/pay/paddle/subscription.rb +49 -8
  53. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  54. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  55. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  56. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +26 -28
  57. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  58. data/lib/pay/paddle.rb +7 -3
  59. data/lib/pay/payment.rb +1 -1
  60. data/lib/pay/receipts.rb +35 -7
  61. data/lib/pay/stripe/billable.rb +80 -102
  62. data/lib/pay/stripe/charge.rb +59 -3
  63. data/lib/pay/stripe/merchant.rb +10 -10
  64. data/lib/pay/stripe/payment_method.rb +61 -0
  65. data/lib/pay/stripe/subscription.rb +70 -8
  66. data/lib/pay/stripe/webhooks/account_updated.rb +2 -3
  67. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
  68. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
  69. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +15 -0
  70. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +15 -0
  71. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  72. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  73. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  74. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -14
  75. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  76. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  77. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  78. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -36
  79. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -9
  80. data/lib/pay/stripe/webhooks/subscription_renewing.rb +14 -6
  81. data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -29
  82. data/lib/pay/stripe.rb +12 -3
  83. data/lib/pay/version.rb +1 -1
  84. data/lib/pay/webhooks/delegator.rb +4 -0
  85. data/lib/pay/webhooks/process_job.rb +9 -0
  86. data/lib/pay/webhooks.rb +1 -0
  87. data/lib/pay.rb +7 -78
  88. data/lib/tasks/pay.rake +20 -0
  89. metadata +31 -39
  90. data/app/models/pay.rb +0 -5
  91. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  92. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  93. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  94. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -15
  95. data/db/migrate/20210309004259_add_data_to_pay_billable.rb +0 -19
  96. data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +0 -5
  97. data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +0 -7
  98. data/lib/generators/active_record/pay_generator.rb +0 -58
  99. data/lib/generators/active_record/templates/migration.rb +0 -9
  100. data/lib/pay/billable/sync_email.rb +0 -40
  101. 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 PayGenerator < Rails::Generators::NamedBase
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
@@ -9,5 +9,14 @@ module Pay
9
9
  ActiveRecord::Base.connection_config[:adapter]
10
10
  end
11
11
  end
12
+
13
+ def self.json_column_type
14
+ case current_adapter
15
+ when "postgresql"
16
+ :jsonb
17
+ else
18
+ :json
19
+ end
20
+ end
12
21
  end
13
22
  end
@@ -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 :billable
4
+ attr_reader :pay_customer
5
5
 
6
6
  delegate :processor_id,
7
7
  :processor_id?,
8
8
  :email,
9
9
  :customer_name,
10
- :card_token,
11
- to: :billable
10
+ :payment_method_token,
11
+ :payment_method_token?,
12
+ to: :pay_customer
12
13
 
13
- def initialize(billable)
14
- @billable = billable
14
+ def initialize(pay_customer)
15
+ @pay_customer = pay_customer
15
16
  end
16
17
 
17
- # Handles Billable#customer
18
+ # Retrieve the Braintree::Customer object
18
19
  #
19
- # Returns Braintree::Customer
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
- update_card card_token if card_token.present?
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
- billable.update(processor: "braintree", processor_id: result.customer.id)
35
-
36
- if card_token.present?
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
- billable.create_pay_subscription(result.subscription, "braintree", name, plan, status: :active)
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
- # Handles Billable#update_card
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: true,
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
- update_card_on_file result.payment_method
112
- update_subscriptions_to_payment_method(result.payment_method.token)
113
- true
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
- attrs[:subscription] = Pay::Subscription.find_by(processor: :braintree, processor_id: transaction.subscription_id)
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 = billable.charges.find_or_initialize_by(
160
- processor: :braintree,
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 update_card_on_file(payment_method)
176
- case payment_method
177
- when ::Braintree::CreditCard
178
- billable.update!(
179
- card_type: payment_method.card_type,
180
- card_last4: payment_method.last_4,
181
- card_exp_month: payment_method.expiration_month,
182
- card_exp_year: payment_method.expiration_year
183
- )
184
-
185
- when ::Braintree::PayPalAccount
186
- billable.update!(
187
- card_type: "PayPal",
188
- card_last4: payment_method.email
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
- # Clear the card token so we don't accidentally update twice
193
- billable.card_token = nil
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", "samsung_pay_card", "masterpass_card", "visa_checkout_card"
199
- payment_method = transaction.send("#{transaction.payment_instrument_type}_details")
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
- when "paypal_account"
208
- {
209
- card_type: "PayPal",
210
- card_last4: transaction.paypal_details.payer_email,
211
- card_exp_month: nil,
212
- card_exp_year: nil
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
- card_type: payment_method.source_card_type,
219
- card_last4: payment_method.source_card_last_4,
220
- card_exp_month: payment_method.expiration_month,
221
- card_exp_year: payment_method.expiration_year
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 "venmo_account"
246
+ when "paypal_account"
225
247
  {
226
- card_type: "Venmo",
227
- card_last4: transaction.venmo_account_details.username,
228
- card_exp_month: nil,
229
- card_exp_year: nil
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 "apple_pay_card"
233
- payment_method = transaction.apple_pay_details
255
+ when "venmo_account"
234
256
  {
235
- card_type: payment_method.card_type,
236
- card_last4: payment_method.last_4,
237
- card_exp_month: payment_method.expiration_month,
238
- card_exp_year: payment_method.expiration_year
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