activemerchant 1.126.0 → 1.129.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +241 -0
  3. data/lib/active_merchant/billing/check.rb +40 -8
  4. data/lib/active_merchant/billing/credit_card.rb +28 -1
  5. data/lib/active_merchant/billing/credit_card_methods.rb +79 -23
  6. data/lib/active_merchant/billing/gateways/adyen.rb +67 -8
  7. data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
  8. data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -4
  10. data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
  12. data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
  13. data/lib/active_merchant/billing/gateways/borgun.rb +56 -16
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +64 -17
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
  17. data/lib/active_merchant/billing/gateways/checkout_v2.rb +228 -57
  18. data/lib/active_merchant/billing/gateways/commerce_hub.rb +361 -0
  19. data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
  20. data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +100 -26
  22. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +456 -0
  23. data/lib/active_merchant/billing/gateways/d_local.rb +44 -5
  24. data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
  25. data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
  26. data/lib/active_merchant/billing/gateways/element.rb +21 -1
  27. data/lib/active_merchant/billing/gateways/global_collect.rb +73 -22
  28. data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
  29. data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
  30. data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
  31. data/lib/active_merchant/billing/gateways/litle.rb +25 -5
  32. data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
  33. data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
  34. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
  35. data/lib/active_merchant/billing/gateways/monei.rb +2 -0
  36. data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
  37. data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
  38. data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
  39. data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
  40. data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
  41. data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
  42. data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  45. data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
  46. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -0
  47. data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
  48. data/lib/active_merchant/billing/gateways/priority.rb +29 -6
  49. data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
  50. data/lib/active_merchant/billing/gateways/reach.rb +277 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +9 -5
  52. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
  53. data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
  54. data/lib/active_merchant/billing/gateways/shift4.rb +342 -0
  55. data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
  56. data/lib/active_merchant/billing/gateways/stripe.rb +21 -1
  57. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
  58. data/lib/active_merchant/billing/gateways/tns.rb +2 -5
  59. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
  60. data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
  61. data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
  62. data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
  63. data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
  64. data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
  65. data/lib/active_merchant/billing/gateways/worldpay.rb +117 -9
  66. data/lib/active_merchant/billing/response.rb +15 -1
  67. data/lib/active_merchant/connection.rb +0 -2
  68. data/lib/active_merchant/country.rb +1 -0
  69. data/lib/active_merchant/errors.rb +4 -1
  70. data/lib/active_merchant/version.rb +1 -1
  71. metadata +24 -3
@@ -6,18 +6,12 @@ module ActiveMerchant #:nodoc:
6
6
 
7
7
  self.supported_countries = %w(BR MX CO CL AR PE)
8
8
  self.default_currency = 'USD'
9
- self.supported_cardtypes = %i[visa master american_express discover diners_club]
9
+ self.supported_cardtypes = %i[visa master american_express discover diners_club elo hipercard]
10
10
 
11
11
  self.homepage_url = 'http://www.ebanx.com/'
12
12
  self.display_name = 'EBANX'
13
13
 
14
- CARD_BRAND = {
15
- visa: 'visa',
16
- master: 'master_card',
17
- american_express: 'amex',
18
- discover: 'discover',
19
- diners_club: 'diners'
20
- }
14
+ TAGS = ['Spreedly']
21
15
 
22
16
  URL_MAP = {
23
17
  purchase: 'direct',
@@ -25,7 +19,8 @@ module ActiveMerchant #:nodoc:
25
19
  capture: 'capture',
26
20
  refund: 'refund',
27
21
  void: 'cancel',
28
- store: 'token'
22
+ store: 'token',
23
+ inquire: 'query'
29
24
  }
30
25
 
31
26
  HTTP_METHOD = {
@@ -34,13 +29,14 @@ module ActiveMerchant #:nodoc:
34
29
  capture: :get,
35
30
  refund: :post,
36
31
  void: :get,
37
- store: :post
32
+ store: :post,
33
+ inquire: :get
38
34
  }
39
35
 
40
36
  VERIFY_AMOUNT_PER_COUNTRY = {
41
37
  'br' => 100,
42
38
  'ar' => 100,
43
- 'co' => 100,
39
+ 'co' => 50000,
44
40
  'pe' => 300,
45
41
  'mx' => 2000,
46
42
  'cl' => 80000
@@ -57,7 +53,7 @@ module ActiveMerchant #:nodoc:
57
53
  add_operation(post)
58
54
  add_invoice(post, money, options)
59
55
  add_customer_data(post, payment, options)
60
- add_card_or_token(post, payment)
56
+ add_card_or_token(post, payment, options)
61
57
  add_address(post, options)
62
58
  add_customer_responsible_person(post, payment, options)
63
59
  add_additional_data(post, options)
@@ -71,7 +67,7 @@ module ActiveMerchant #:nodoc:
71
67
  add_operation(post)
72
68
  add_invoice(post, money, options)
73
69
  add_customer_data(post, payment, options)
74
- add_card_or_token(post, payment)
70
+ add_card_or_token(post, payment, options)
75
71
  add_address(post, options)
76
72
  add_customer_responsible_person(post, payment, options)
77
73
  add_additional_data(post, options)
@@ -124,6 +120,14 @@ module ActiveMerchant #:nodoc:
124
120
  end
125
121
  end
126
122
 
123
+ def inquire(authorization, options = {})
124
+ post = {}
125
+ add_integration_key(post)
126
+ add_authorization(post, authorization)
127
+
128
+ commit(:inquire, post)
129
+ end
130
+
127
131
  def supports_scrubbing?
128
132
  true
129
133
  end
@@ -151,7 +155,7 @@ module ActiveMerchant #:nodoc:
151
155
 
152
156
  def add_customer_data(post, payment, options)
153
157
  post[:payment][:name] = customer_name(payment, options)
154
- post[:payment][:email] = options[:email] || 'unspecified@example.com'
158
+ post[:payment][:email] = options[:email]
155
159
  post[:payment][:document] = options[:document]
156
160
  post[:payment][:birth_date] = options[:birth_date] if options[:birth_date]
157
161
  end
@@ -186,14 +190,15 @@ module ActiveMerchant #:nodoc:
186
190
  post[:payment][:order_number] = options[:order_id][0..39] if options[:order_id]
187
191
  end
188
192
 
189
- def add_card_or_token(post, payment)
190
- payment, brand = payment.split('|') if payment.is_a?(String)
191
- post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym]
193
+ def add_card_or_token(post, payment, options)
194
+ payment = payment.split('|')[0] if payment.is_a?(String)
195
+ post[:payment][:payment_type_code] = 'creditcard'
192
196
  post[:payment][:creditcard] = payment_details(payment)
197
+ post[:payment][:creditcard][:soft_descriptor] = options[:soft_descriptor] if options[:soft_descriptor]
193
198
  end
194
199
 
195
200
  def add_payment_details(post, payment)
196
- post[:payment_type_code] = CARD_BRAND[payment.brand.to_sym]
201
+ post[:payment_type_code] = 'creditcard'
197
202
  post[:creditcard] = payment_details(payment)
198
203
  end
199
204
 
@@ -216,6 +221,7 @@ module ActiveMerchant #:nodoc:
216
221
  post[:metadata] = {} if post[:metadata].nil?
217
222
  post[:metadata][:merchant_payment_code] = options[:order_id] if options[:order_id]
218
223
  post[:processing_type] = options[:processing_type] if options[:processing_type]
224
+ post[:payment][:tags] = TAGS
219
225
  end
220
226
 
221
227
  def parse(body)
@@ -253,13 +259,15 @@ module ActiveMerchant #:nodoc:
253
259
  end
254
260
 
255
261
  def success_from(action, response)
262
+ payment_status = response.try(:[], 'payment').try(:[], 'status')
263
+
256
264
  if %i[purchase capture refund].include?(action)
257
- response.try(:[], 'payment').try(:[], 'status') == 'CO'
265
+ payment_status == 'CO'
258
266
  elsif action == :authorize
259
- response.try(:[], 'payment').try(:[], 'status') == 'PE'
267
+ payment_status == 'PE'
260
268
  elsif action == :void
261
- response.try(:[], 'payment').try(:[], 'status') == 'CA'
262
- elsif action == :store
269
+ payment_status == 'CA'
270
+ elsif %i[store inquire].include?(action)
263
271
  response.try(:[], 'status') == 'SUCCESS'
264
272
  else
265
273
  false
@@ -274,7 +282,11 @@ module ActiveMerchant #:nodoc:
274
282
 
275
283
  def authorization_from(action, parameters, response)
276
284
  if action == :store
277
- "#{response.try(:[], 'token')}|#{CARD_BRAND[parameters[:payment_type_code].to_sym]}"
285
+ if success_from(action, response)
286
+ "#{response.try(:[], 'token')}|#{response['payment_type_code']}"
287
+ else
288
+ response.try(:[], 'token')
289
+ end
278
290
  else
279
291
  response.try(:[], 'payment').try(:[], 'hash')
280
292
  end
@@ -294,7 +306,7 @@ module ActiveMerchant #:nodoc:
294
306
  end
295
307
 
296
308
  def requires_http_get(action)
297
- return true if %i[capture void].include?(action)
309
+ return true if %i[capture void inquire].include?(action)
298
310
 
299
311
  false
300
312
  end
@@ -17,6 +17,11 @@ module ActiveMerchant #:nodoc:
17
17
  SERVICE_TEST_URL = 'https://certservices.elementexpress.com/express.asmx'
18
18
  SERVICE_LIVE_URL = 'https://services.elementexpress.com/express.asmx'
19
19
 
20
+ NETWORK_TOKEN_TYPE = {
21
+ apple_pay: '2',
22
+ google_pay: '1'
23
+ }
24
+
20
25
  def initialize(options = {})
21
26
  requires!(options, :account_id, :account_token, :application_id, :acceptor_id, :application_name, :application_version)
22
27
  super
@@ -171,6 +176,8 @@ module ActiveMerchant #:nodoc:
171
176
  add_payment_account_id(xml, payment)
172
177
  elsif payment.is_a?(Check)
173
178
  add_echeck(xml, payment)
179
+ elsif payment.is_a?(NetworkTokenizationCreditCard)
180
+ add_network_tokenization_card(xml, payment)
174
181
  else
175
182
  add_credit_card(xml, payment)
176
183
  end
@@ -200,7 +207,7 @@ module ActiveMerchant #:nodoc:
200
207
  xml.TransactionID options[:trans_id] if options[:trans_id]
201
208
  xml.TransactionAmount amount(money.to_i) if money
202
209
  xml.MarketCode market_code(money, options) if options[:market_code] || money
203
- xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20)
210
+ xml.ReferenceNumber options[:order_id].present? ? options[:order_id][0, 50] : SecureRandom.hex(20)
204
211
  xml.TicketNumber options[:ticket_number] if options[:ticket_number]
205
212
  xml.MerchantSuppliedTransactionId options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id]
206
213
  xml.PaymentType options[:payment_type] if options[:payment_type]
@@ -246,8 +253,21 @@ module ActiveMerchant #:nodoc:
246
253
  end
247
254
  end
248
255
 
256
+ def add_network_tokenization_card(xml, payment)
257
+ xml.card do
258
+ xml.CardNumber payment.number
259
+ xml.ExpirationMonth format(payment.month, :two_digits)
260
+ xml.ExpirationYear format(payment.year, :two_digits)
261
+ xml.CardholderName "#{payment.first_name} #{payment.last_name}"
262
+ xml.Cryptogram payment.payment_cryptogram
263
+ xml.ElectronicCommerceIndicator payment.eci if payment.eci.present?
264
+ xml.WalletType NETWORK_TOKEN_TYPE.fetch(payment.source, '0')
265
+ end
266
+ end
267
+
249
268
  def add_address(xml, options)
250
269
  if address = options[:billing_address] || options[:address]
270
+ address[:email] ||= options[:email]
251
271
  xml.address do
252
272
  xml.BillingAddress1 address[:address1] if address[:address1]
253
273
  xml.BillingAddress2 address[:address2] if address[:address2]
@@ -36,7 +36,7 @@ module ActiveMerchant #:nodoc:
36
36
  add_creator_info(post, options)
37
37
  add_fraud_fields(post, options)
38
38
  add_external_cardholder_authentication_data(post, options)
39
- commit(:authorize, post, options: options)
39
+ commit(:post, :authorize, post, options: options)
40
40
  end
41
41
 
42
42
  def capture(money, authorization, options = {})
@@ -44,7 +44,7 @@ module ActiveMerchant #:nodoc:
44
44
  add_order(post, money, options, capture: true)
45
45
  add_customer_data(post, options)
46
46
  add_creator_info(post, options)
47
- commit(:capture, post, authorization: authorization)
47
+ commit(:post, :capture, post, authorization: authorization)
48
48
  end
49
49
 
50
50
  def refund(money, authorization, options = {})
@@ -52,13 +52,13 @@ module ActiveMerchant #:nodoc:
52
52
  add_amount(post, money, options)
53
53
  add_refund_customer_data(post, options)
54
54
  add_creator_info(post, options)
55
- commit(:refund, post, authorization: authorization)
55
+ commit(:post, :refund, post, authorization: authorization)
56
56
  end
57
57
 
58
58
  def void(authorization, options = {})
59
59
  post = nestable_hash
60
60
  add_creator_info(post, options)
61
- commit(:void, post, authorization: authorization)
61
+ commit(:post, :void, post, authorization: authorization)
62
62
  end
63
63
 
64
64
  def verify(payment, options = {})
@@ -68,6 +68,10 @@ module ActiveMerchant #:nodoc:
68
68
  end
69
69
  end
70
70
 
71
+ def inquire(authorization, options = {})
72
+ commit(:get, :inquire, nil, authorization: authorization)
73
+ end
74
+
71
75
  def supports_scrubbing?
72
76
  true
73
77
  end
@@ -76,7 +80,10 @@ module ActiveMerchant #:nodoc:
76
80
  transcript.
77
81
  gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]').
78
82
  gsub(%r(("cardNumber\\+":\\+")\d+), '\1[FILTERED]').
79
- gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]')
83
+ gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]').
84
+ gsub(%r(("dpan\\+":\\+")\d+), '\1[FILTERED]').
85
+ gsub(%r(("pan\\+":\\+")\d+), '\1[FILTERED]').
86
+ gsub(%r(("cryptogram\\+":\\+"|("cavv\\+" : \\+"))[^\\]*), '\1[FILTERED]')
80
87
  end
81
88
 
82
89
  private
@@ -89,7 +96,9 @@ module ActiveMerchant #:nodoc:
89
96
  'jcb' => '125',
90
97
  'diners_club' => '132',
91
98
  'cabal' => '135',
92
- 'naranja' => '136'
99
+ 'naranja' => '136',
100
+ 'apple_pay': '302',
101
+ 'google_pay': '320'
93
102
  }
94
103
 
95
104
  def add_order(post, money, options, capture: false)
@@ -250,20 +259,58 @@ module ActiveMerchant #:nodoc:
250
259
  month = format(payment.month, :two_digits)
251
260
  expirydate = "#{month}#{year}"
252
261
  pre_authorization = options[:pre_authorization] ? 'PRE_AUTHORIZATION' : 'FINAL_AUTHORIZATION'
253
- post['cardPaymentMethodSpecificInput'] = {
254
- 'paymentProductId' => BRAND_MAP[payment.brand],
262
+ product_id = options[:payment_product_id] || BRAND_MAP[payment.brand]
263
+ specifics_inputs = {
264
+ 'paymentProductId' => product_id,
255
265
  'skipAuthentication' => 'true', # refers to 3DSecure
256
266
  'skipFraudService' => 'true',
257
267
  'authorizationMode' => pre_authorization
258
268
  }
259
- post['cardPaymentMethodSpecificInput']['requiresApproval'] = options[:requires_approval] unless options[:requires_approval].nil?
269
+ specifics_inputs['requiresApproval'] = options[:requires_approval] unless options[:requires_approval].nil?
270
+ if payment.is_a?(NetworkTokenizationCreditCard)
271
+ add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate)
272
+ elsif payment.is_a?(CreditCard)
273
+ options[:google_pay_pan_only] ? add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate) : add_credit_card(post, payment, specifics_inputs, expirydate)
274
+ end
275
+ end
260
276
 
261
- post['cardPaymentMethodSpecificInput']['card'] = {
262
- 'cvv' => payment.verification_value,
263
- 'cardNumber' => payment.number,
264
- 'expiryDate' => expirydate,
265
- 'cardholderName' => payment.name
266
- }
277
+ def add_credit_card(post, payment, specifics_inputs, expirydate)
278
+ post['cardPaymentMethodSpecificInput'] = specifics_inputs.merge({
279
+ 'card' => {
280
+ 'cvv' => payment.verification_value,
281
+ 'cardNumber' => payment.number,
282
+ 'expiryDate' => expirydate,
283
+ 'cardholderName' => payment.name
284
+ }
285
+ })
286
+ end
287
+
288
+ def add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate)
289
+ specifics_inputs['paymentProductId'] = options[:google_pay_pan_only] ? BRAND_MAP[:google_pay] : BRAND_MAP[payment.source]
290
+ post['mobilePaymentMethodSpecificInput'] = specifics_inputs
291
+ add_decrypted_payment_data(post, payment, options, expirydate)
292
+ end
293
+
294
+ def add_decrypted_payment_data(post, payment, options, expirydate)
295
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.payment_cryptogram
296
+ data = {
297
+ 'cardholderName' => payment.name,
298
+ 'cryptogram' => payment.payment_cryptogram,
299
+ 'eci' => payment.eci,
300
+ 'expiryDate' => expirydate,
301
+ 'dpan' => payment.number
302
+ }
303
+ data['paymentMethod'] = 'TOKENIZED_CARD' if payment.source == :google_pay
304
+ # else case when google payment is an ONLY_PAN, doesn't have cryptogram or eci.
305
+ elsif options[:google_pay_pan_only]
306
+ data = {
307
+ 'cardholderName' => payment.name,
308
+ 'expiryDate' => expirydate,
309
+ 'pan' => payment.number,
310
+ 'paymentMethod' => 'CARD'
311
+ }
312
+ end
313
+ post['mobilePaymentMethodSpecificInput']['decryptedPaymentData'] = data if data
267
314
  end
268
315
 
269
316
  def add_customer_data(post, options, payment = nil)
@@ -370,6 +417,8 @@ module ActiveMerchant #:nodoc:
370
417
  uri + "payments/#{authorization}/refund"
371
418
  when :void
372
419
  uri + "payments/#{authorization}/cancel"
420
+ when :inquire
421
+ uri + "payments/#{authorization}"
373
422
  end
374
423
  end
375
424
 
@@ -377,9 +426,9 @@ module ActiveMerchant #:nodoc:
377
426
  "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
378
427
  end
379
428
 
380
- def commit(action, post, authorization: nil, options: {})
429
+ def commit(method, action, post, authorization: nil, options: {})
381
430
  begin
382
- raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization, options))
431
+ raw_response = ssl_request(method, url(action, authorization), post&.to_json, headers(method, action, post, authorization, options))
383
432
  response = parse(raw_response)
384
433
  rescue ResponseError => e
385
434
  response = parse(e.response.body) if e.response.code.to_i >= 400
@@ -405,10 +454,10 @@ module ActiveMerchant #:nodoc:
405
454
  }
406
455
  end
407
456
 
408
- def headers(action, post, authorization = nil, options = {})
457
+ def headers(method, action, post, authorization = nil, options = {})
409
458
  headers = {
410
459
  'Content-Type' => content_type,
411
- 'Authorization' => auth_digest(action, post, authorization, options),
460
+ 'Authorization' => auth_digest(method, action, post, authorization, options),
412
461
  'Date' => date
413
462
  }
414
463
 
@@ -416,9 +465,9 @@ module ActiveMerchant #:nodoc:
416
465
  headers
417
466
  end
418
467
 
419
- def auth_digest(action, post, authorization = nil, options = {})
468
+ def auth_digest(method, action, post, authorization = nil, options = {})
420
469
  data = <<~REQUEST
421
- POST
470
+ #{method.to_s.upcase}
422
471
  #{content_type}
423
472
  #{date}
424
473
  #{idempotency_key_for_signature(options)}
@@ -431,7 +480,7 @@ module ActiveMerchant #:nodoc:
431
480
  end
432
481
 
433
482
  def date
434
- @date ||= Time.now.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header
483
+ @date ||= Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header
435
484
  end
436
485
 
437
486
  def content_type
@@ -441,6 +490,8 @@ module ActiveMerchant #:nodoc:
441
490
  def success_from(action, response)
442
491
  return false if response['errorId'] || response['error_message']
443
492
 
493
+ return %w(CAPTURED CAPTURE_REQUESTED).include?(response.dig('payment', 'status')) if response.dig('payment', 'paymentOutput', 'paymentMethod') == 'mobile'
494
+
444
495
  case action
445
496
  when :authorize
446
497
  response.dig('payment', 'statusOutput', 'isAuthorized')
@@ -27,32 +27,32 @@ module ActiveMerchant #:nodoc:
27
27
  def purchase(money, payment, options = {})
28
28
  xml = build_purchase_and_authorize_request(money, payment, options)
29
29
 
30
- commit('sale', xml)
30
+ commit('sale', xml, options)
31
31
  end
32
32
 
33
33
  def authorize(money, payment, options = {})
34
34
  xml = build_purchase_and_authorize_request(money, payment, options)
35
35
 
36
- commit('preAuth', xml)
36
+ commit('preAuth', xml, options)
37
37
  end
38
38
 
39
39
  def capture(money, authorization, options = {})
40
40
  xml = build_capture_and_refund_request(money, authorization, options)
41
41
 
42
- commit('postAuth', xml)
42
+ commit('postAuth', xml, options)
43
43
  end
44
44
 
45
45
  def refund(money, authorization, options = {})
46
46
  xml = build_capture_and_refund_request(money, authorization, options)
47
47
 
48
- commit('return', xml)
48
+ commit('return', xml, options)
49
49
  end
50
50
 
51
51
  def void(authorization, options = {})
52
52
  xml = Builder::XmlMarkup.new(indent: 2)
53
53
  add_transaction_details(xml, options.merge!({ order_id: authorization }))
54
54
 
55
- commit('void', xml)
55
+ commit('void', xml, options)
56
56
  end
57
57
 
58
58
  def store(credit_card, options = {})
@@ -60,7 +60,7 @@ module ActiveMerchant #:nodoc:
60
60
  xml = Builder::XmlMarkup.new(indent: 2)
61
61
  add_storage_item(xml, credit_card, options)
62
62
 
63
- commit('vault', xml)
63
+ commit('vault', xml, options)
64
64
  end
65
65
 
66
66
  def unstore(hosted_data_id)
@@ -343,7 +343,12 @@ module ActiveMerchant #:nodoc:
343
343
  }
344
344
  end
345
345
 
346
- def commit(action, request)
346
+ def override_store_id(options)
347
+ @credentials[:store_id] = options[:store_id] if options[:store_id].present?
348
+ end
349
+
350
+ def commit(action, request, options = {})
351
+ override_store_id(options)
347
352
  url = (test? ? test_url : live_url)
348
353
  soap_request = build_soap_request(action, request)
349
354
  response = parse(ssl_post(url, soap_request, build_header))
@@ -391,7 +396,7 @@ module ActiveMerchant #:nodoc:
391
396
  end
392
397
 
393
398
  def message_from(response)
394
- response[:TransactionResult]
399
+ [response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ')
395
400
  end
396
401
 
397
402
  def authorization_from(action, response)
@@ -3,7 +3,10 @@ require 'nokogiri'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class IveriGateway < Gateway
6
+ class_attribute :iveri_url
7
+
6
8
  self.live_url = self.test_url = 'https://portal.nedsecure.co.za/iVeriWebService/Service.asmx'
9
+ self.iveri_url = 'https://portal.host.iveri.com/iVeriWebService/Service.asmx'
7
10
 
8
11
  self.supported_countries = %w[US ZA GB]
9
12
  self.default_currency = 'ZAR'
@@ -91,7 +94,7 @@ module ActiveMerchant #:nodoc:
91
94
  xml[:soap].Envelope 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
92
95
  xml[:soap].Body do
93
96
  xml.Execute 'xmlns' => 'http://iveri.com/' do
94
- xml.validateRequest 'true'
97
+ xml.validateRequest('false')
95
98
  xml.protocol 'V_XML'
96
99
  xml.protocolVersion '2.0'
97
100
  xml.request vxml
@@ -118,8 +121,9 @@ module ActiveMerchant #:nodoc:
118
121
  def add_auth_purchase_params(post, money, payment_method, options)
119
122
  add_card_holder_authentication(post, options)
120
123
  add_amount(post, money, options)
121
- add_electronic_commerce_indicator(post, options)
124
+ add_electronic_commerce_indicator(post, options) unless options[:three_d_secure]
122
125
  add_payment_method(post, payment_method, options)
126
+ add_three_ds(post, options)
123
127
  end
124
128
 
125
129
  def add_amount(post, money, options)
@@ -156,7 +160,7 @@ module ActiveMerchant #:nodoc:
156
160
  def commit(post)
157
161
  raw_response =
158
162
  begin
159
- ssl_post(live_url, build_xml_envelope(post), headers(post))
163
+ ssl_post(url, build_xml_envelope(post), headers(post))
160
164
  rescue ActiveMerchant::ResponseError => e
161
165
  e.response.body
162
166
  end
@@ -178,6 +182,10 @@ module ActiveMerchant #:nodoc:
178
182
  test? ? 'Test' : 'Live'
179
183
  end
180
184
 
185
+ def url
186
+ @options[:url_override].to_s == 'iveri' ? iveri_url : live_url
187
+ end
188
+
181
189
  def headers(post)
182
190
  {
183
191
  'Content-Type' => 'text/xml; charset=utf-8',
@@ -249,6 +257,34 @@ module ActiveMerchant #:nodoc:
249
257
  tr('-', '_').
250
258
  downcase
251
259
  end
260
+
261
+ def add_three_ds(post, options)
262
+ return unless three_d_secure = options[:three_d_secure]
263
+
264
+ post.ElectronicCommerceIndicator(formatted_three_ds_eci(three_d_secure[:eci])) if three_d_secure[:eci]
265
+ post.CardHolderAuthenticationID(three_d_secure[:xid]) if three_d_secure[:xid]
266
+ post.CardHolderAuthenticationData(three_d_secure[:cavv]) if three_d_secure[:cavv]
267
+ post.ThreeDSecure_ProtocolVersion(three_d_secure[:version]) if three_d_secure[:version]
268
+ post.ThreeDSecure_DSTransID(three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id]
269
+ post.ThreeDSecure_VEResEnrolled(formatted_enrollment(three_d_secure[:enrolled])) if three_d_secure[:enrolled]
270
+ end
271
+
272
+ def formatted_enrollment(val)
273
+ case val
274
+ when 'Y', 'N', 'U' then val
275
+ when true, 'true' then 'Y'
276
+ when false, 'false' then 'N'
277
+ end
278
+ end
279
+
280
+ def formatted_three_ds_eci(val)
281
+ case val
282
+ when '05', '02' then 'ThreeDSecure'
283
+ when '06', '01' then 'ThreeDSecureAttempted'
284
+ when '07' then 'SecureChannel'
285
+ else val
286
+ end
287
+ end
252
288
  end
253
289
  end
254
290
  end
@@ -83,6 +83,8 @@ module ActiveMerchant #:nodoc:
83
83
  add_payment_method(post, payment_method, options)
84
84
  add_full_response(post, options)
85
85
  add_metadata(post, options)
86
+ add_months(post, options)
87
+ add_deferred(post, options)
86
88
 
87
89
  commit(action, post)
88
90
  end
@@ -96,6 +98,8 @@ module ActiveMerchant #:nodoc:
96
98
  add_contact_details(post, options[:contact_details]) if options[:contact_details]
97
99
  add_full_response(post, options)
98
100
  add_metadata(post, options)
101
+ add_months(post, options)
102
+ add_deferred(post, options)
99
103
 
100
104
  commit(action, post)
101
105
  end
@@ -108,6 +112,8 @@ module ActiveMerchant #:nodoc:
108
112
  add_invoice(action, post, amount, options)
109
113
  add_full_response(post, options)
110
114
  add_metadata(post, options)
115
+ add_months(post, options)
116
+ add_deferred(post, options)
111
117
 
112
118
  commit(action, post)
113
119
  end
@@ -140,7 +146,7 @@ module ActiveMerchant #:nodoc:
140
146
  sum[:iva] = amount[:iva].to_f if amount[:iva]
141
147
  sum[:subtotalIva0] = amount[:subtotal_iva_0].to_f if amount[:subtotal_iva_0]
142
148
  sum[:ice] = amount[:ice].to_f if amount[:ice]
143
- if (extra_taxes = amount[:extra_taxes]) && sum[:currency] == 'COP'
149
+ if (extra_taxes = amount[:extra_taxes])
144
150
  sum[:extraTaxes] ||= Hash.new
145
151
  sum[:extraTaxes][:propina] = extra_taxes[:propina].to_f if extra_taxes[:propina]
146
152
  sum[:extraTaxes][:tasaAeroportuaria] = extra_taxes[:tasa_aeroportuaria].to_f if extra_taxes[:tasa_aeroportuaria]
@@ -184,6 +190,20 @@ module ActiveMerchant #:nodoc:
184
190
  post[:metadata] = options[:metadata] if options[:metadata]
185
191
  end
186
192
 
193
+ def add_months(post, options)
194
+ post[:months] = options[:months] if options[:months]
195
+ end
196
+
197
+ def add_deferred(post, options)
198
+ return unless options[:deferred_grace_months] && options[:deferred_credit_type] && options[:deferred_months]
199
+
200
+ post[:deferred] = {
201
+ graceMonths: options[:deferred_grace_months],
202
+ creditType: options[:deferred_credit_type],
203
+ months: options[:deferred_months]
204
+ }
205
+ end
206
+
187
207
  ENDPOINT = {
188
208
  'tokenize' => 'tokens',
189
209
  'charge' => 'charges',