pay 2.6.11 → 3.0.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.

Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -693
  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 +32 -17
  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 +34 -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 +22 -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 +130 -105
  31. data/lib/pay/braintree/payment_method.rb +33 -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/merchant.rb +37 -0
  48. data/lib/pay/nano_id.rb +13 -0
  49. data/lib/pay/paddle/billable.rb +18 -48
  50. data/lib/pay/paddle/charge.rb +5 -5
  51. data/lib/pay/paddle/payment_method.rb +58 -0
  52. data/lib/pay/paddle/response.rb +0 -0
  53. data/lib/pay/paddle/subscription.rb +49 -8
  54. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  55. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  56. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  57. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +23 -23
  58. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  59. data/lib/pay/paddle.rb +7 -3
  60. data/lib/pay/payment.rb +1 -1
  61. data/lib/pay/receipts.rb +35 -7
  62. data/lib/pay/stripe/billable.rb +82 -93
  63. data/lib/pay/stripe/charge.rb +65 -4
  64. data/lib/pay/stripe/merchant.rb +66 -0
  65. data/lib/pay/stripe/payment_method.rb +61 -0
  66. data/lib/pay/stripe/subscription.rb +91 -24
  67. data/lib/pay/stripe/webhooks/account_updated.rb +16 -0
  68. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
  69. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
  70. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +15 -0
  71. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +15 -0
  72. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  73. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  74. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  75. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -14
  76. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  77. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  78. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  79. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -35
  80. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -9
  81. data/lib/pay/stripe/webhooks/subscription_renewing.rb +14 -6
  82. data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -28
  83. data/lib/pay/stripe.rb +17 -3
  84. data/lib/pay/version.rb +1 -1
  85. data/lib/pay/webhooks/delegator.rb +4 -0
  86. data/lib/pay/webhooks/process_job.rb +9 -0
  87. data/lib/pay/webhooks.rb +1 -0
  88. data/lib/pay.rb +8 -57
  89. metadata +34 -36
  90. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  91. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  92. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  93. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -22
  94. data/db/migrate/20210423235138_add_currency_to_pay_charges.rb +0 -5
  95. data/lib/generators/active_record/pay_generator.rb +0 -58
  96. data/lib/generators/active_record/templates/migration.rb +0 -9
  97. data/lib/pay/billable/sync_email.rb +0 -40
  98. data/lib/pay/billable.rb +0 -168
@@ -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
@@ -0,0 +1,22 @@
1
+ module Pay
2
+ module Adapter
3
+ extend ActiveSupport::Concern
4
+
5
+ def self.current_adapter
6
+ if ActiveRecord::Base.respond_to?(:connection_db_config)
7
+ ActiveRecord::Base.connection_db_config.adapter
8
+ else
9
+ ActiveRecord::Base.connection_config[:adapter]
10
+ end
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
21
+ end
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,35 +137,26 @@ 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
150
+
151
+ # Associate charge with subscription if we can
152
+ if 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
156
+ end
153
157
 
154
- charge = billable.charges.find_or_initialize_by(
155
- processor: :braintree,
156
- processor_id: transaction.id
157
- )
158
- charge.update(attrs)
158
+ charge = pay_customer.charges.find_or_initialize_by(processor_id: transaction.id)
159
+ charge.update!(attrs)
159
160
  charge
160
161
  end
161
162
 
@@ -165,74 +166,98 @@ module Pay
165
166
  Pay.braintree_gateway
166
167
  end
167
168
 
168
- def update_card_on_file(payment_method)
169
- case payment_method
170
- when ::Braintree::CreditCard
171
- billable.update!(
172
- card_type: payment_method.card_type,
173
- card_last4: payment_method.last_4,
174
- card_exp_month: payment_method.expiration_month,
175
- card_exp_year: payment_method.expiration_year
176
- )
177
-
178
- when ::Braintree::PayPalAccount
179
- billable.update!(
180
- card_type: "PayPal",
181
- card_last4: payment_method.email
182
- )
169
+ def save_payment_method(payment_method, default:)
170
+ attributes = case payment_method
171
+ when ::Braintree::CreditCard, ::Braintree::ApplePayCard, ::Braintree::GooglePayCard, ::Braintree::SamsungPayCard, ::Braintree::VisaCheckoutCard
172
+ {
173
+ payment_method_type: :card,
174
+ brand: payment_method.card_type,
175
+ last4: payment_method.last_4,
176
+ exp_month: payment_method.expiration_month,
177
+ exp_year: payment_method.expiration_year
178
+ }
179
+
180
+ when ::Braintree::PayPalAccount
181
+ {
182
+ payment_method_type: :paypal,
183
+ brand: "PayPal",
184
+ email: payment_method.email
185
+ }
186
+ when ::Braintree::VenmoAccount
187
+ {
188
+ payment_method_type: :venmo,
189
+ brand: "Venmo",
190
+ username: payment_method.username
191
+ }
192
+ when ::Braintree::UsBankAccount
193
+ {
194
+ payment_method_type: "us_bank_account",
195
+ bank: payment_method.bank_name,
196
+ last4: payment_method.last_4
197
+ }
198
+ else
199
+ {
200
+ payment_method_type: payment_method.class.name.demodulize.underscore
201
+ }
183
202
  end
184
203
 
185
- # Clear the card token so we don't accidentally update twice
186
- billable.card_token = nil
204
+ pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.token).first_or_initialize
205
+
206
+ pay_customer.payment_methods.update_all(default: false) if default
207
+ pay_payment_method.update!(attributes.merge(default: default))
208
+
209
+ # Reload the Rails association
210
+ pay_customer.reload_default_payment_method if default
211
+
212
+ pay_payment_method
187
213
  end
188
214
 
189
215
  def card_details_for_braintree_transaction(transaction)
190
216
  case transaction.payment_instrument_type
191
- when "credit_card", "samsung_pay_card", "masterpass_card", "visa_checkout_card"
192
- payment_method = transaction.send("#{transaction.payment_instrument_type}_details")
193
- {
194
- card_type: payment_method.card_type,
195
- card_last4: payment_method.last_4,
196
- card_exp_month: payment_method.expiration_month,
197
- card_exp_year: payment_method.expiration_year
198
- }
217
+ when "android_pay_card", "apple_pay_card", "credit_card", "google_pay_card", "samsung_pay_card", "visa_checkout_card"
218
+ # Lookup the attribute with the payment method details by name
219
+ attribute_name = transaction.payment_instrument_type
199
220
 
200
- when "paypal_account"
201
- {
202
- card_type: "PayPal",
203
- card_last4: transaction.paypal_details.payer_email,
204
- card_exp_month: nil,
205
- card_exp_year: nil
206
- }
221
+ # The attribute name for Apple and Google Pay don't include _card for some reason
222
+ if ["apple_pay_card", "google_pay_card"].include?(transaction.payment_instrument_type)
223
+ attribute_name = attribute_name.split("_card").first
224
+
225
+ # Android Pay was renamed to Google Pay, but test nonces still use android_pay_card
226
+ elsif attribute_name == "android_pay_card"
227
+ attribute_name = "google_pay"
228
+ end
229
+
230
+ # Retrieve payment method details from transaction
231
+ payment_method = transaction.send("#{attribute_name}_details")
207
232
 
208
- when "android_pay_card"
209
- payment_method = transaction.android_pay_details
210
233
  {
211
- card_type: payment_method.source_card_type,
212
- card_last4: payment_method.source_card_last_4,
213
- card_exp_month: payment_method.expiration_month,
214
- card_exp_year: payment_method.expiration_year
234
+ payment_method_type: :card,
235
+ brand: payment_method.card_type,
236
+ last4: payment_method.last_4,
237
+ exp_month: payment_method.expiration_month,
238
+ exp_year: payment_method.expiration_year
215
239
  }
216
240
 
217
- when "venmo_account"
241
+ when "paypal_account"
218
242
  {
219
- card_type: "Venmo",
220
- card_last4: transaction.venmo_account_details.username,
221
- card_exp_month: nil,
222
- card_exp_year: nil
243
+ payment_method_type: :paypal,
244
+ brand: "PayPal",
245
+ last4: transaction.paypal_details.payer_email,
246
+ exp_month: nil,
247
+ exp_year: nil
223
248
  }
224
249
 
225
- when "apple_pay_card"
226
- payment_method = transaction.apple_pay_details
250
+ when "venmo_account"
227
251
  {
228
- card_type: payment_method.card_type,
229
- card_last4: payment_method.last_4,
230
- card_exp_month: payment_method.expiration_month,
231
- card_exp_year: payment_method.expiration_year
252
+ payment_method_type: :venmo,
253
+ brand: "Venmo",
254
+ last4: transaction.venmo_account_details.username,
255
+ exp_month: nil,
256
+ exp_year: nil
232
257
  }
233
258
 
234
259
  else
235
- {}
260
+ {payment_method_type: "unknown"}
236
261
  end
237
262
  end
238
263
  end