activemerchant 1.124.0 → 1.125.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +85 -0
  3. data/lib/active_merchant/billing/check.rb +5 -8
  4. data/lib/active_merchant/billing/credit_card.rb +10 -0
  5. data/lib/active_merchant/billing/gateway.rb +1 -1
  6. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  7. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
  8. data/lib/active_merchant/billing/gateways/cyber_source.rb +11 -3
  9. data/lib/active_merchant/billing/gateways/d_local.rb +4 -5
  10. data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
  11. data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
  12. data/lib/active_merchant/billing/gateways/elavon.rb +6 -3
  13. data/lib/active_merchant/billing/gateways/element.rb +20 -2
  14. data/lib/active_merchant/billing/gateways/global_collect.rb +106 -16
  15. data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
  16. data/lib/active_merchant/billing/gateways/kushki.rb +7 -0
  17. data/lib/active_merchant/billing/gateways/mercado_pago.rb +3 -1
  18. data/lib/active_merchant/billing/gateways/mundipagg.rb +8 -6
  19. data/lib/active_merchant/billing/gateways/nmi.rb +2 -1
  20. data/lib/active_merchant/billing/gateways/orbital.rb +17 -2
  21. data/lib/active_merchant/billing/gateways/pay_trace.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/payflow.rb +1 -1
  23. data/lib/active_merchant/billing/gateways/paymentez.rb +9 -2
  24. data/lib/active_merchant/billing/gateways/paysafe.rb +41 -5
  25. data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -1
  26. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  27. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  28. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  29. data/lib/active_merchant/billing/gateways/safe_charge.rb +1 -0
  30. data/lib/active_merchant/billing/gateways/stripe.rb +20 -10
  31. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +37 -3
  32. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +20 -6
  33. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  34. data/lib/active_merchant/billing/gateways/worldpay.rb +181 -64
  35. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  36. data/lib/active_merchant/version.rb +1 -1
  37. metadata +6 -2
@@ -23,9 +23,9 @@ module ActiveMerchant #:nodoc:
23
23
  'unchecked' => 'P'
24
24
  }
25
25
 
26
- DEFAULT_API_VERSION = '2015-04-07'
26
+ DEFAULT_API_VERSION = '2020-08-27'
27
27
 
28
- self.supported_countries = %w(AT AU BE BG BR CA CH CY CZ DE DK EE ES FI FR GB GR HK IE IT JP LT LU LV MT MX NL NO NZ PL PT RO SE SG SI SK US)
28
+ self.supported_countries = %w(AE AT AU BE BG BR CA CH CY CZ DE DK EE ES FI FR GB GR HK HU IE IN IT JP LT LU LV MT MX MY NL NO NZ PL PT RO SE SG SI SK US)
29
29
  self.default_currency = 'USD'
30
30
  self.money_format = :cents
31
31
  self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro unionpay]
@@ -223,9 +223,10 @@ module ActiveMerchant #:nodoc:
223
223
 
224
224
  post[:default_card] = r.params['id'] if options[:set_default] && r.success? && !r.params['id'].blank?
225
225
 
226
- r.process { update_customer(options[:customer], post) } if post.count > 0
226
+ r.process { update_customer(options[:customer], post.merge(expand: [:sources])) } if post.count > 0
227
227
  end
228
228
  else
229
+ post[:expand] = [:sources]
229
230
  commit(:post, 'customers', post.merge(params), options)
230
231
  end
231
232
  end
@@ -294,6 +295,17 @@ module ActiveMerchant #:nodoc:
294
295
  true
295
296
  end
296
297
 
298
+ # Helper method to prevent hitting the external_account limit from remote test runs
299
+ def delete_latest_test_external_account(account)
300
+ return unless test?
301
+
302
+ auth_header = { 'Authorization': "Bearer #{options[:login]}" }
303
+ url = "#{live_url}accounts/#{CGI.escape(account)}/external_accounts"
304
+ accounts_response = JSON.parse(ssl_get("#{url}?limit=100", auth_header))
305
+ to_delete = accounts_response['data'].reject { |ac| ac['default_for_currency'] }
306
+ ssl_request(:delete, "#{url}/#{to_delete.first['id']}", nil, auth_header)
307
+ end
308
+
297
309
  private
298
310
 
299
311
  class StripePaymentToken < PaymentToken
@@ -534,7 +546,6 @@ module ActiveMerchant #:nodoc:
534
546
  post[:metadata].merge!(options[:metadata]) if options[:metadata]
535
547
  post[:metadata][:email] = options[:email] if options[:email]
536
548
  post[:metadata][:order_id] = options[:order_id] if options[:order_id]
537
- post.delete(:metadata) if post[:metadata].empty?
538
549
  end
539
550
 
540
551
  def add_emv_metadata(post, creditcard)
@@ -573,11 +584,11 @@ module ActiveMerchant #:nodoc:
573
584
  end
574
585
 
575
586
  def add_radar_data(post, options = {})
576
- return unless options[:radar_session_id]
587
+ radar_options = {}
588
+ radar_options[:session] = options[:radar_session_id] if options[:radar_session_id]
589
+ radar_options[:skip_rules] = ['all'] if options[:skip_radar_rules]
577
590
 
578
- post[:radar_options] = {
579
- session: options[:radar_session_id]
580
- }
591
+ post[:radar_options] = radar_options unless radar_options.empty?
581
592
  end
582
593
 
583
594
  def parse(body)
@@ -668,7 +679,6 @@ module ActiveMerchant #:nodoc:
668
679
  card = card_from_response(response)
669
680
  avs_code = AVS_CODE_TRANSLATOR["line1: #{card['address_line1_check']}, zip: #{card['address_zip_check']}"]
670
681
  cvc_code = CVC_CODE_TRANSLATOR[card['cvc_check']]
671
-
672
682
  Response.new(success,
673
683
  message_from(success, response),
674
684
  response,
@@ -764,7 +774,7 @@ module ActiveMerchant #:nodoc:
764
774
  country: 'US',
765
775
  currency: 'usd',
766
776
  routing_number: bank_account.routing_number,
767
- name: bank_account.name,
777
+ account_holder_name: bank_account.name,
768
778
  account_holder_type: account_holder_type
769
779
  }
770
780
  }
@@ -5,16 +5,21 @@ module ActiveMerchant #:nodoc:
5
5
  # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents].
6
6
  # For the legacy API, see the Stripe gateway
7
7
  class StripePaymentIntentsGateway < StripeGateway
8
- self.supported_countries = %w(AT AU BE BG BR CA CH CY CZ DE DK EE ES FI FR GB GR HK IE IT JP LT LU LV MT MX NL NO NZ PL PT RO SE SG SI SK US)
9
-
10
8
  ALLOWED_METHOD_STATES = %w[automatic manual].freeze
11
9
  ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze
12
10
  CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor_suffix statement_descriptor receipt_email save_payment_method]
13
11
  CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session]
14
12
  UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor_suffix statement_descriptor receipt_email setup_future_usage]
15
- DEFAULT_API_VERSION = '2019-05-16'
13
+ DEFAULT_API_VERSION = '2020-08-27'
14
+ NO_WALLET_SUPPORT = %w(apple_pay google_pay android_pay)
16
15
 
17
16
  def create_intent(money, payment_method, options = {})
17
+ card_source_pay = payment_method.source.to_s if defined?(payment_method.source)
18
+ card_brand_pay = card_brand(payment_method) unless payment_method.is_a?(String) || payment_method.nil?
19
+ if NO_WALLET_SUPPORT.include?(card_source_pay) || NO_WALLET_SUPPORT.include?(card_brand_pay)
20
+ store_apple_or_google_pay_token = 'Direct Apple Pay and Google Pay transactions are not supported. Those payment methods must be stored before use.'
21
+ return Response.new(false, store_apple_or_google_pay_token)
22
+ end
18
23
  post = {}
19
24
  add_amount(post, money, options, true)
20
25
  add_capture_method(post, options)
@@ -35,6 +40,7 @@ module ActiveMerchant #:nodoc:
35
40
  add_ntid(post, options)
36
41
  add_claim_without_transaction_id(post, options)
37
42
  add_error_on_requires_action(post, options)
43
+ add_fulfillment_date(post, options)
38
44
  request_three_d_secure(post, options)
39
45
 
40
46
  CREATE_INTENT_ATTRIBUTES.each do |attribute|
@@ -56,6 +62,7 @@ module ActiveMerchant #:nodoc:
56
62
  CONFIRM_INTENT_ATTRIBUTES.each do |attribute|
57
63
  add_whitelisted_attribute(post, options, attribute)
58
64
  end
65
+
59
66
  commit(:post, "payment_intents/#{intent_id}/confirm", post, options)
60
67
  end
61
68
 
@@ -91,6 +98,7 @@ module ActiveMerchant #:nodoc:
91
98
  add_metadata(post, options)
92
99
  add_shipping_address(post, options)
93
100
  add_connected_account(post, options)
101
+ add_fulfillment_date(post, options)
94
102
 
95
103
  UPDATE_INTENT_ATTRIBUTES.each do |attribute|
96
104
  add_whitelisted_attribute(post, options, attribute)
@@ -106,6 +114,7 @@ module ActiveMerchant #:nodoc:
106
114
 
107
115
  add_metadata(post, options)
108
116
  add_return_url(post, options)
117
+ add_fulfillment_date(post, options)
109
118
  post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
110
119
  post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
111
120
  post[:description] = options[:description] if options[:description]
@@ -190,6 +199,7 @@ module ActiveMerchant #:nodoc:
190
199
  post[:description] = options[:description] if options[:description]
191
200
  post[:email] = options[:email] if options[:email]
192
201
  options = format_idempotency_key(options, 'customer')
202
+ post[:expand] = [:sources]
193
203
  customer = commit(:post, 'customers', post, options)
194
204
  customer_id = customer.params['id']
195
205
  end
@@ -215,6 +225,16 @@ module ActiveMerchant #:nodoc:
215
225
  create_setup_intent(payment_method, options.merge!(confirm: true))
216
226
  end
217
227
 
228
+ def setup_purchase(money, options = {})
229
+ requires!(options, :payment_method_types)
230
+ post = {}
231
+ add_currency(post, options, money)
232
+ add_amount(post, money, options)
233
+ add_payment_method_types(post, options)
234
+ add_metadata(post, options)
235
+ commit(:post, 'payment_intents', post, options)
236
+ end
237
+
218
238
  private
219
239
 
220
240
  def off_session_request?(options = {})
@@ -245,6 +265,16 @@ module ActiveMerchant #:nodoc:
245
265
  post[:customer] = customer if customer.start_with?('cus_')
246
266
  end
247
267
 
268
+ def add_fulfillment_date(post, options)
269
+ post[:fulfillment_date] = options[:fulfillment_date].to_i if options[:fulfillment_date]
270
+ end
271
+
272
+ def add_metadata(post, options = {})
273
+ super
274
+
275
+ post[:metadata][:event_type] = options[:event_type] if options[:event_type]
276
+ end
277
+
248
278
  def add_return_url(post, options)
249
279
  return unless options[:confirm]
250
280
 
@@ -432,6 +462,10 @@ module ActiveMerchant #:nodoc:
432
462
 
433
463
  super(response, options)
434
464
  end
465
+
466
+ def add_currency(post, options, money)
467
+ post[:currency] = options[:currency] || currency(money)
468
+ end
435
469
  end
436
470
  end
437
471
  end
@@ -16,7 +16,8 @@ module ActiveMerchant #:nodoc:
16
16
  refund: 'cc:refund',
17
17
  void: 'cc:void',
18
18
  void_release: 'cc:void:release',
19
- check_purchase: 'check:sale'
19
+ check_purchase: 'check:sale',
20
+ store: 'cc:save'
20
21
  }
21
22
 
22
23
  STANDARD_ERROR_CODE_MAPPING = {
@@ -43,14 +44,14 @@ module ActiveMerchant #:nodoc:
43
44
  super
44
45
  end
45
46
 
46
- def authorize(money, credit_card, options = {})
47
+ def authorize(money, payment, options = {})
47
48
  post = {}
48
49
 
49
50
  add_amount(post, money)
50
51
  add_invoice(post, options)
51
- add_payment(post, credit_card)
52
- unless credit_card.track_data.present?
53
- add_address(post, credit_card, options)
52
+ add_payment(post, payment)
53
+ unless payment.is_a?(CreditCard) && payment.track_data.present?
54
+ add_address(post, payment, options)
54
55
  add_customer_data(post, options)
55
56
  end
56
57
  add_split_payments(post, options)
@@ -97,6 +98,12 @@ module ActiveMerchant #:nodoc:
97
98
  commit(:refund, post)
98
99
  end
99
100
 
101
+ def store(payment, options = {})
102
+ post = {}
103
+ add_payment(post, payment, options)
104
+ commit(:store, post)
105
+ end
106
+
100
107
  def verify(creditcard, options = {})
101
108
  MultiResponse.run(:use_first_response) do |r|
102
109
  r.process { authorize(1, creditcard, options) }
@@ -213,6 +220,8 @@ module ActiveMerchant #:nodoc:
213
220
  elsif payment.respond_to?(:track_data) && payment.track_data.present?
214
221
  post[:magstripe] = payment.track_data
215
222
  post[:cardpresent] = true
223
+ elsif payment.is_a?(String)
224
+ post[:card] = payment
216
225
  else
217
226
  post[:card] = payment.number
218
227
  post[:cvv2] = payment.verification_value if payment.verification_value?
@@ -299,6 +308,7 @@ module ActiveMerchant #:nodoc:
299
308
  status: fields['UMstatus'],
300
309
  auth_code: fields['UMauthCode'],
301
310
  ref_num: fields['UMrefNum'],
311
+ card_ref: fields['UMcardRef'],
302
312
  batch: fields['UMbatch'],
303
313
  avs_result: fields['UMavsResult'],
304
314
  avs_result_code: fields['UMavsResultCode'],
@@ -321,7 +331,7 @@ module ActiveMerchant #:nodoc:
321
331
  error_code = (STANDARD_ERROR_CODE_MAPPING[response[:error_code]] || STANDARD_ERROR_CODE[:processing_error]) unless approved
322
332
  Response.new(approved, message_from(response), response,
323
333
  test: test?,
324
- authorization: response[:ref_num],
334
+ authorization: authorization_from(action, response),
325
335
  cvv_result: response[:cvv2_result_code],
326
336
  avs_result: { code: response[:avs_result_code] },
327
337
  error_code: error_code)
@@ -337,6 +347,10 @@ module ActiveMerchant #:nodoc:
337
347
  end
338
348
  end
339
349
 
350
+ def authorization_from(action, response)
351
+ return (action == :store ? response[:card_ref] : response[:ref_num])
352
+ end
353
+
340
354
  def post_data(action, parameters = {})
341
355
  parameters[:command] = TRANSACTIONS[action]
342
356
  parameters[:key] = @options[:login]
@@ -0,0 +1,193 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class WompiGateway < Gateway
4
+ self.test_url = 'https://sync.sandbox.wompi.co/v1'
5
+ self.live_url = 'https://sync.production.wompi.co/v1'
6
+
7
+ self.supported_countries = ['CO']
8
+ self.default_currency = 'COP'
9
+ self.supported_cardtypes = %i[visa master american_express]
10
+
11
+ self.homepage_url = 'https://wompi.co/'
12
+ self.display_name = 'Wompi'
13
+
14
+ self.money_format = :cents
15
+
16
+ def initialize(options = {})
17
+ ## Sandbox keys have prefix pub_test_ and prv_test_
18
+ ## Production keys have prefix pub_prod_ and prv_prod_
19
+ begin
20
+ requires!(options, :prod_private_key, :prod_public_key)
21
+ rescue ArgumentError
22
+ begin
23
+ requires!(options, :test_private_key, :test_public_key)
24
+ rescue ArgumentError
25
+ raise ArgumentError, 'Gateway requires both test_private_key and test_public_key, or both prod_private_key and prod_public_key'
26
+ end
27
+ end
28
+ super
29
+ end
30
+
31
+ def purchase(money, payment, options = {})
32
+ post = {
33
+ reference: options[:reference] || generate_reference,
34
+ public_key: public_key
35
+ }
36
+ add_invoice(post, money, options)
37
+ add_card(post, payment, options)
38
+
39
+ commit('sale', post, '/transactions_sync')
40
+ end
41
+
42
+ def authorize(money, payment, options = {})
43
+ post = {
44
+ public_key: public_key,
45
+ type: 'CARD',
46
+ financial_operation: 'PREAUTHORIZATION'
47
+ }
48
+ add_auth_params(post, money, payment, options)
49
+
50
+ commit('authorize', post, '/payment_sources_sync')
51
+ end
52
+
53
+ def capture(money, authorization, options = {})
54
+ post = {
55
+ reference: options[:reference] || generate_reference,
56
+ public_key: public_key,
57
+ payment_source_id: authorization.to_i
58
+ }
59
+ add_invoice(post, money, options)
60
+ commit('capture', post, '/transactions_sync')
61
+ end
62
+
63
+ def refund(money, authorization, options = {})
64
+ post = { amount_in_cents: amount(money).to_i, transaction_id: authorization.to_s }
65
+ commit('refund', post, '/refunds_sync')
66
+ end
67
+
68
+ def void(authorization, options = {})
69
+ commit('void', {}, "/transactions/#{authorization}/void_sync")
70
+ end
71
+
72
+ def supports_scrubbing?
73
+ true
74
+ end
75
+
76
+ def scrub(transcript)
77
+ transcript.gsub(/(Bearer )\w+/, '\1[REDACTED]').
78
+ gsub(/(\\\"number\\\":\\\")\d+/, '\1[REDACTED]').
79
+ gsub(/(\\\"cvc\\\":\\\")\d+/, '\1[REDACTED]').
80
+ gsub(/(\\\"phone_number\\\":\\\")\+?\d+/, '\1[REDACTED]').
81
+ gsub(/(\\\"email\\\":\\\")\S+\\\",/, '\1[REDACTED]\",').
82
+ gsub(/(\\\"legal_id\\\":\\\")\d+/, '\1[REDACTED]')
83
+ end
84
+
85
+ private
86
+
87
+ def headers
88
+ {
89
+ 'Authorization' => "Bearer #{private_key}",
90
+ 'Content-Type' => 'application/json'
91
+ }
92
+ end
93
+
94
+ def generate_reference
95
+ SecureRandom.alphanumeric(12)
96
+ end
97
+
98
+ def private_key
99
+ test? ? options[:test_private_key] : options[:prod_private_key]
100
+ end
101
+
102
+ def public_key
103
+ test? ? options[:test_public_key] : options[:prod_public_key]
104
+ end
105
+
106
+ def add_invoice(post, money, options)
107
+ post[:amount_in_cents] = amount(money).to_i
108
+ post[:currency] = (options[:currency] || currency(money))
109
+ end
110
+
111
+ def add_card(post, card, options)
112
+ payment_method = {
113
+ type: 'CARD'
114
+ }
115
+ add_basic_card_info(payment_method, card, options)
116
+ post[:payment_method] = payment_method
117
+ end
118
+
119
+ def add_auth_params(post, money, card, options)
120
+ data = {
121
+ amount_in_cents: amount(money).to_i,
122
+ currency: (options[:currency] || currency(money))
123
+ }
124
+ add_basic_card_info(data, card, options)
125
+ post[:data] = data
126
+ end
127
+
128
+ def add_basic_card_info(post, card, options)
129
+ installments = options[:installments] ? options[:installments].to_i : 1
130
+ cvc = card.verification_value || nil
131
+
132
+ post[:number] = card.number
133
+ post[:exp_month] = card.month.to_s.rjust(2, '0')
134
+ post[:exp_year] = card.year.to_s[2..3]
135
+ post[:installments] = installments
136
+ post[:card_holder] = card.name
137
+ post[:cvc] = cvc if cvc && !cvc.empty?
138
+ end
139
+
140
+ def parse(body)
141
+ JSON.parse(body)
142
+ end
143
+
144
+ def commit(action, parameters, endpoint)
145
+ url = (test? ? test_url : live_url) + endpoint
146
+ response = parse(ssl_post(url, post_data(action, parameters), headers))
147
+ Response.new(
148
+ success_from(response),
149
+ message_from(response),
150
+ response,
151
+ authorization: authorization_from(response),
152
+ avs_result: nil,
153
+ cvv_result: nil,
154
+ test: test?,
155
+ error_code: error_code_from(response)
156
+ )
157
+ end
158
+
159
+ def handle_response(response)
160
+ case response.code.to_i
161
+ when 200...300, 401, 404, 422
162
+ response.body
163
+ else
164
+ raise ResponseError.new(response)
165
+ end
166
+ end
167
+
168
+ def success_from(response)
169
+ success_statuses.include? response.dig('data', 'status')
170
+ end
171
+
172
+ def success_statuses
173
+ %w(APPROVED AVAILABLE)
174
+ end
175
+
176
+ def message_from(response)
177
+ response.dig('data', 'status_message') || response.dig('error', 'reason') || response.dig('error', 'messages').to_json
178
+ end
179
+
180
+ def authorization_from(response)
181
+ response.dig('data', 'transaction_id') || response.dig('data', 'id') || response.dig('data', 'transaction', 'id')
182
+ end
183
+
184
+ def post_data(action, parameters = {})
185
+ parameters.to_json
186
+ end
187
+
188
+ def error_code_from(response)
189
+ response.dig('error', 'type') unless success_from(response)
190
+ end
191
+ end
192
+ end
193
+ end