activemerchant 1.117.0 → 1.123.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +5 -3
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +6 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +96 -22
  8. data/lib/active_merchant/billing/gateways/adyen.rb +38 -21
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +5 -3
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +58 -8
  14. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  15. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  16. data/lib/active_merchant/billing/gateways/credorax.rb +16 -9
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +67 -9
  18. data/lib/active_merchant/billing/gateways/d_local.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/decidir.rb +29 -3
  20. data/lib/active_merchant/billing/gateways/elavon.rb +110 -26
  21. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  22. data/lib/active_merchant/billing/gateways/eway_rapid.rb +13 -0
  23. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +17 -6
  24. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +25 -16
  26. data/lib/active_merchant/billing/gateways/hps.rb +65 -2
  27. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  28. data/lib/active_merchant/billing/gateways/litle.rb +9 -4
  29. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  30. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +277 -0
  32. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  33. data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
  34. data/lib/active_merchant/billing/gateways/netbanx.rb +37 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +202 -47
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +57 -11
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +10 -5
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +26 -1
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -0
  45. data/lib/active_merchant/billing/gateways/paypal.rb +10 -2
  46. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  47. data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
  48. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  49. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  50. data/lib/active_merchant/billing/gateways/pin.rb +11 -0
  51. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  52. data/lib/active_merchant/billing/gateways/redsys.rb +78 -30
  53. data/lib/active_merchant/billing/gateways/safe_charge.rb +19 -8
  54. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  55. data/lib/active_merchant/billing/gateways/stripe.rb +8 -8
  56. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +86 -25
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  58. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  59. data/lib/active_merchant/billing/gateways/worldpay.rb +68 -20
  60. data/lib/active_merchant/billing/response.rb +2 -1
  61. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  62. data/lib/active_merchant/billing.rb +1 -0
  63. data/lib/active_merchant/version.rb +1 -1
  64. data/lib/certs/cacert.pem +1582 -2431
  65. metadata +10 -3
@@ -17,7 +17,7 @@ module ActiveMerchant #:nodoc:
17
17
  }
18
18
 
19
19
  def initialize(options = {})
20
- requires!(options, :username, :password, :merchant, :pem, :pem_password)
20
+ requires!(options, :username, :password, :merchant, :pem)
21
21
  super
22
22
  end
23
23
 
@@ -30,6 +30,7 @@ module ActiveMerchant #:nodoc:
30
30
  add_stored_credential_data(post, payment_method, options)
31
31
  add_customer_data(post, options)
32
32
  add_soft_descriptors(post, options)
33
+ add_customer_reference(post, options)
33
34
 
34
35
  commit('capture', post)
35
36
  end
@@ -43,6 +44,7 @@ module ActiveMerchant #:nodoc:
43
44
  add_stored_credential_data(post, payment_method, options)
44
45
  add_customer_data(post, options)
45
46
  add_soft_descriptors(post, options)
47
+ add_customer_reference(post, options)
46
48
 
47
49
  commit('preauth', post)
48
50
  end
@@ -53,6 +55,7 @@ module ActiveMerchant #:nodoc:
53
55
  add_reference(post, authorization, options)
54
56
  add_customer_data(post, options)
55
57
  add_soft_descriptors(post, options)
58
+ add_customer_reference(post, options)
56
59
 
57
60
  commit('captureWithoutAuth', post)
58
61
  end
@@ -64,6 +67,7 @@ module ActiveMerchant #:nodoc:
64
67
  add_customer_data(post, options)
65
68
  add_soft_descriptors(post, options)
66
69
  post['order.ECI'] = options[:eci] || 'SSL'
70
+ add_customer_reference(post, options)
67
71
 
68
72
  commit('refund', post)
69
73
  end
@@ -76,6 +80,7 @@ module ActiveMerchant #:nodoc:
76
80
  add_payment_method(post, payment_method)
77
81
  add_customer_data(post, options)
78
82
  add_soft_descriptors(post, options)
83
+ add_customer_reference(post, options)
79
84
 
80
85
  commit('refund', post)
81
86
  end
@@ -85,6 +90,7 @@ module ActiveMerchant #:nodoc:
85
90
  add_reference(post, authorization, options)
86
91
  add_customer_data(post, options)
87
92
  add_soft_descriptors(post, options)
93
+ add_customer_reference(post, options)
88
94
 
89
95
  commit('reversal', post)
90
96
  end
@@ -92,7 +98,7 @@ module ActiveMerchant #:nodoc:
92
98
  def store(payment_method, options = {})
93
99
  post = {}
94
100
  add_payment_method(post, payment_method)
95
- add_card_reference(post)
101
+ add_card_reference(post, options)
96
102
 
97
103
  commit('registerAccount', post)
98
104
  end
@@ -149,12 +155,16 @@ module ActiveMerchant #:nodoc:
149
155
  return unless payment_method.brand == 'visa'
150
156
 
151
157
  stored_credential = options[:stored_credential]
152
- if stored_credential[:initial_transaction]
153
- post['card.storedCredentialUsage'] = 'INITIAL_STORAGE'
154
- elsif stored_credential[:reason_type] == ('recurring' || 'installment')
158
+ if stored_credential[:reason_type] == 'unscheduled'
159
+ if stored_credential[:initiator] == 'merchant'
160
+ post['card.storedCredentialUsage'] = 'UNSCHEDULED_MIT'
161
+ elsif stored_credential[:initiator] == 'customer'
162
+ post['card.storedCredentialUsage'] = 'UNSCHEDULED_CIT'
163
+ end
164
+ elsif stored_credential[:reason_type] == 'recurring'
155
165
  post['card.storedCredentialUsage'] = 'RECURRING'
156
- elsif stored_credential[:reason_type] == 'unscheduled'
157
- post['card.storedCredentialUsage'] = 'UNSCHEDULED'
166
+ elsif stored_credential[:reason_type] == 'installment'
167
+ post['card.storedCredentialUsage'] = 'INSTALLMENT'
158
168
  end
159
169
  end
160
170
 
@@ -181,8 +191,12 @@ module ActiveMerchant #:nodoc:
181
191
  post['card.CVN'] = payment_method.verification_value
182
192
  end
183
193
 
184
- def add_card_reference(post)
185
- post['customer.customerReferenceNumber'] = options[:order_id]
194
+ def add_card_reference(post, options)
195
+ post['customer.customerReferenceNumber'] = options[:customer_reference_number] || options[:order_id]
196
+ end
197
+
198
+ def add_customer_reference(post, options)
199
+ post['customer.customerReferenceNumber'] = options[:customer_reference_number] if options[:customer_reference_number]
186
200
  end
187
201
 
188
202
  def add_reference(post, authorization, options)
@@ -126,6 +126,7 @@ module ActiveMerchant #:nodoc:
126
126
  184 => 'Authentication error',
127
127
  190 => 'Refusal with no specific reason',
128
128
  191 => 'Expiry date incorrect',
129
+ 195 => 'Requires SCA authentication',
129
130
 
130
131
  201 => 'Card expired',
131
132
  202 => 'Card blocked temporarily or under suspicion of fraud',
@@ -202,10 +203,12 @@ module ActiveMerchant #:nodoc:
202
203
  add_order(data, options[:order_id])
203
204
  add_payment(data, payment)
204
205
  add_external_mpi_fields(data, options)
205
- add_threeds(data, options) if options[:execute_threed]
206
+ add_three_ds_data(data, options) if options[:execute_threed]
207
+ add_stored_credential_options(data, options)
206
208
  data[:description] = options[:description]
207
209
  data[:store_in_vault] = options[:store]
208
210
  data[:sca_exemption] = options[:sca_exemption]
211
+ data[:sca_exemption_direct_payment_enabled] = options[:sca_exemption_direct_payment_enabled]
209
212
 
210
213
  commit data, options
211
214
  end
@@ -219,10 +222,12 @@ module ActiveMerchant #:nodoc:
219
222
  add_order(data, options[:order_id])
220
223
  add_payment(data, payment)
221
224
  add_external_mpi_fields(data, options)
222
- add_threeds(data, options) if options[:execute_threed]
225
+ add_three_ds_data(data, options) if options[:execute_threed]
226
+ add_stored_credential_options(data, options)
223
227
  data[:description] = options[:description]
224
228
  data[:store_in_vault] = options[:store]
225
229
  data[:sca_exemption] = options[:sca_exemption]
230
+ data[:sca_exemption_direct_payment_enabled] = options[:sca_exemption_direct_payment_enabled]
226
231
 
227
232
  commit data, options
228
233
  end
@@ -307,7 +312,7 @@ module ActiveMerchant #:nodoc:
307
312
  test? ? test_url : live_url
308
313
  end
309
314
 
310
- def threeds_url
315
+ def webservice_url
311
316
  test? ? 'https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2' : 'https://sis.redsys.es/sis/services/SerClsWSEntradaV2'
312
317
  end
313
318
 
@@ -331,57 +336,87 @@ module ActiveMerchant #:nodoc:
331
336
  return unless options[:three_d_secure]
332
337
 
333
338
  if options[:three_d_secure][:version] == THREE_DS_V2
334
- data[:threeDSServerTransID] = options[:three_d_secure][:xid] if options[:three_d_secure][:xid]
339
+ data[:threeDSServerTransID] = options[:three_d_secure][:three_ds_server_trans_id] if options[:three_d_secure][:three_ds_server_trans_id]
335
340
  data[:dsTransID] = options[:three_d_secure][:ds_transaction_id] if options[:three_d_secure][:ds_transaction_id]
336
341
  data[:authenticacionValue] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv]
337
342
  data[:protocolVersion] = options[:three_d_secure][:version] if options[:three_d_secure][:version]
338
-
339
343
  data[:authenticacionMethod] = options[:authentication_method] if options[:authentication_method]
340
344
  data[:authenticacionType] = options[:authentication_type] if options[:authentication_type]
341
345
  data[:authenticacionFlow] = options[:authentication_flow] if options[:authentication_flow]
346
+ data[:eci_v2] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
342
347
  elsif options[:three_d_secure][:version] == THREE_DS_V1
343
348
  data[:txid] = options[:three_d_secure][:xid] if options[:three_d_secure][:xid]
344
349
  data[:cavv] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv]
345
- data[:eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
350
+ data[:eci_v1] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
351
+ end
352
+ end
353
+
354
+ def add_stored_credential_options(data, options)
355
+ return unless options[:stored_credential]
356
+
357
+ case options[:stored_credential][:initial_transaction]
358
+ when true
359
+ data[:DS_MERCHANT_COF_INI] = 'S'
360
+ when false
361
+ data[:DS_MERCHANT_COF_INI] = 'N'
362
+ data[:DS_MERCHANT_COF_TXNID] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
346
363
  end
364
+
365
+ case options[:stored_credential][:reason_type]
366
+ when 'recurring'
367
+ data[:DS_MERCHANT_COF_TYPE] = 'R'
368
+ when 'installment'
369
+ data[:DS_MERCHANT_COF_TYPE] = 'I'
370
+ when 'unscheduled'
371
+ return
372
+ end
373
+ end
374
+
375
+ def add_three_ds_data(data, options)
376
+ data[:three_ds_data] = { threeDSInfo: 'CardData' } if options[:execute_threed] == true
347
377
  end
348
378
 
349
- def add_threeds(data, options)
350
- data[:threeds] = { threeDSInfo: 'CardData' } if options[:execute_threed] == true
379
+ def determine_peticion_type(data)
380
+ three_ds_info = data.dig(:three_ds_data, :threeDSInfo)
381
+ return 'iniciaPeticion' if three_ds_info == 'CardData'
382
+ return 'trataPeticion' if three_ds_info == 'AuthenticationData' ||
383
+ three_ds_info == 'ChallengeResponse' ||
384
+ data[:sca_exemption] == 'MIT'
351
385
  end
352
386
 
353
- def determine_3ds_action(threeds_hash)
354
- return 'iniciaPeticion' if threeds_hash[:threeDSInfo] == 'CardData'
355
- return 'trataPeticion' if threeds_hash[:threeDSInfo] == 'AuthenticationData' ||
356
- threeds_hash[:threeDSInfo] == 'ChallengeResponse'
387
+ def use_webservice_endpoint?(data, options)
388
+ options[:use_webservice_endpoint].to_s == 'true' || data[:three_ds_data] || data[:sca_exemption] == 'MIT'
357
389
  end
358
390
 
359
391
  def commit(data, options = {})
360
- if data[:threeds]
361
- action = determine_3ds_action(data[:threeds])
392
+ xmlreq = xml_request_from(data, options)
393
+
394
+ if use_webservice_endpoint?(data, options)
395
+ peticion_type = determine_peticion_type(data)
396
+
362
397
  request = <<-REQUEST
363
398
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:impl="http://webservice.sis.sermepa.es" xmlns:intf="http://webservice.sis.sermepa.es" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
364
399
  <soapenv:Header/>
365
400
  <soapenv:Body>
366
- <intf:#{action} xmlns:intf="http://webservice.sis.sermepa.es">
401
+ <intf:#{peticion_type} xmlns:intf="http://webservice.sis.sermepa.es">
367
402
  <intf:datoEntrada>
368
- <![CDATA[#{xml_request_from(data, options)}]]>
403
+ <![CDATA[#{xmlreq}]]>
369
404
  </intf:datoEntrada>
370
- </intf:#{action}>
405
+ </intf:#{peticion_type}>
371
406
  </soapenv:Body>
372
407
  </soapenv:Envelope>
373
408
  REQUEST
374
- parse(ssl_post(threeds_url, request, headers(action)), action)
409
+ parse(ssl_post(webservice_url, request, headers(peticion_type)), peticion_type)
375
410
  else
376
- parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data, options))}", headers), action)
411
+ parse(ssl_post(url, "entrada=#{CGI.escape(xmlreq)}", headers), peticion_type)
377
412
  end
378
413
  end
379
414
 
380
- def headers(action = nil)
381
- if action
415
+ def headers(peticion_type = nil)
416
+ if peticion_type
382
417
  {
383
418
  'Content-Type' => 'text/xml',
384
- 'SOAPAction' => action
419
+ 'SOAPAction' => peticion_type
385
420
  }
386
421
  else
387
422
  {
@@ -445,7 +480,7 @@ module ActiveMerchant #:nodoc:
445
480
 
446
481
  def build_merchant_data(xml, data, options = {})
447
482
  # See https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2/wsdl/SerClsWSEntradaV2.wsdl
448
- # (which results from calling #threeds_url + '?WSDL', https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2?WSDL)
483
+ # (which results from calling #webservice_url + '?WSDL', https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2?WSDL)
449
484
  xml.DATOSENTRADA do
450
485
  # Basic elements
451
486
  xml.DS_Version 0.1
@@ -453,7 +488,7 @@ module ActiveMerchant #:nodoc:
453
488
  xml.DS_MERCHANT_AMOUNT data[:amount]
454
489
  xml.DS_MERCHANT_ORDER data[:order_id]
455
490
  xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
456
- if data[:description] && data[:threeds]
491
+ if data[:description] && use_webservice_endpoint?(data, options)
457
492
  xml.DS_MERCHANT_PRODUCTDESCRIPTION CGI.escape(data[:description])
458
493
  else
459
494
  xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
@@ -461,11 +496,18 @@ module ActiveMerchant #:nodoc:
461
496
  xml.DS_MERCHANT_TERMINAL options[:terminal] || @options[:terminal]
462
497
  xml.DS_MERCHANT_MERCHANTCODE @options[:login]
463
498
  xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication?
464
- xml.DS_MERCHANT_EXCEP_SCA data[:sca_exemption] if data[:sca_exemption]
499
+
500
+ peticion_type = determine_peticion_type(data) if data[:three_ds_data]
501
+ if peticion_type == 'iniciaPeticion' && data[:sca_exemption]
502
+ xml.DS_MERCHANT_EXCEP_SCA 'Y'
503
+ else
504
+ xml.DS_MERCHANT_EXCEP_SCA data[:sca_exemption] if data[:sca_exemption]
505
+ xml.DS_MERCHANT_DIRECTPAYMENT data[:sca_exemption_direct_payment_enabled] || 'true' if data[:sca_exemption] == 'MIT'
506
+ end
465
507
 
466
508
  # Only when card is present
467
509
  if data[:card]
468
- if data[:card][:name] && data[:threeds]
510
+ if data[:card][:name] && use_webservice_endpoint?(data, options)
469
511
  xml.DS_MERCHANT_TITULAR CGI.escape(data[:card][:name])
470
512
  else
471
513
  xml.DS_MERCHANT_TITULAR data[:card][:name]
@@ -486,7 +528,13 @@ module ActiveMerchant #:nodoc:
486
528
  # Requires account configuration to be able to use
487
529
  xml.DS_MERCHANT_DIRECTPAYMENT 'moto' if options.dig(:moto) && options.dig(:metadata, :manual_entry)
488
530
 
489
- xml.DS_MERCHANT_EMV3DS data[:threeds].to_json if data[:threeds]
531
+ xml.DS_MERCHANT_EMV3DS data[:three_ds_data].to_json if data[:three_ds_data]
532
+
533
+ if options[:stored_credential]
534
+ xml.DS_MERCHANT_COF_INI data[:DS_MERCHANT_COF_INI]
535
+ xml.DS_MERCHANT_COF_TYPE data[:DS_MERCHANT_COF_TYPE]
536
+ xml.DS_MERCHANT_COF_TXNID data[:DS_MERCHANT_COF_TXNID] if data[:DS_MERCHANT_COF_TXNID]
537
+ end
490
538
  end
491
539
  end
492
540
 
@@ -496,13 +544,13 @@ module ActiveMerchant #:nodoc:
496
544
  ds_merchant_mpi_external = {}
497
545
  ds_merchant_mpi_external[:TXID] = data[:txid] if data[:txid]
498
546
  ds_merchant_mpi_external[:CAVV] = data[:cavv] if data[:cavv]
499
- ds_merchant_mpi_external[:ECI] = data[:eci] if data[:eci]
547
+ ds_merchant_mpi_external[:ECI] = data[:eci_v1] if data[:eci_v1]
500
548
 
501
549
  ds_merchant_mpi_external[:threeDSServerTransID] = data[:threeDSServerTransID] if data[:threeDSServerTransID]
502
550
  ds_merchant_mpi_external[:dsTransID] = data[:dsTransID] if data[:dsTransID]
503
551
  ds_merchant_mpi_external[:authenticacionValue] = data[:authenticacionValue] if data[:authenticacionValue]
504
552
  ds_merchant_mpi_external[:protocolVersion] = data[:protocolVersion] if data[:protocolVersion]
505
-
553
+ ds_merchant_mpi_external[:Eci] = data[:eci_v2] if data[:eci_v2]
506
554
  ds_merchant_mpi_external[:authenticacionMethod] = data[:authenticacionMethod] if data[:authenticacionMethod]
507
555
  ds_merchant_mpi_external[:authenticacionType] = data[:authenticacionType] if data[:authenticacionType]
508
556
  ds_merchant_mpi_external[:authenticacionFlow] = data[:authenticacionFlow] if data[:authenticacionFlow]
@@ -593,7 +641,7 @@ module ActiveMerchant #:nodoc:
593
641
  def response_text(code)
594
642
  code = code.to_i
595
643
  code = 0 if code < 100
596
- RESPONSE_TEXTS[code] || 'Unkown code, please check in manual'
644
+ RESPONSE_TEXTS[code] || 'Unknown code, please check in manual'
597
645
  end
598
646
 
599
647
  def response_text_3ds(xml, params)
@@ -143,15 +143,24 @@ module ActiveMerchant #:nodoc:
143
143
  post[:sg_Descriptor] = options[:merchant_descriptor] if options[:merchant_descriptor]
144
144
  post[:sg_MerchantPhoneNumber] = options[:merchant_phone_number] if options[:merchant_phone_number]
145
145
  post[:sg_MerchantName] = options[:merchant_name] if options[:merchant_name]
146
+ post[:sg_ProductID] = options[:product_id] if options[:product_id]
146
147
  end
147
148
 
148
149
  def add_payment(post, payment, options = {})
149
- post[:sg_NameOnCard] = payment.name
150
- post[:sg_CardNumber] = payment.number
151
150
  post[:sg_ExpMonth] = format(payment.month, :two_digits)
152
151
  post[:sg_ExpYear] = format(payment.year, :two_digits)
153
- post[:sg_CVV2] = payment.verification_value
154
- post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
152
+ post[:sg_CardNumber] = payment.number
153
+
154
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
155
+ post[:sg_CAVV] = payment.payment_cryptogram
156
+ post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
157
+ post[:sg_IsExternalMPI] = 1
158
+ post[:sg_ExternalTokenProvider] = 5
159
+ else
160
+ post[:sg_CVV2] = payment.verification_value
161
+ post[:sg_NameOnCard] = payment.name
162
+ post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
163
+ end
155
164
  end
156
165
 
157
166
  def add_customer_details(post, payment, options)
@@ -170,12 +179,14 @@ module ActiveMerchant #:nodoc:
170
179
  end
171
180
 
172
181
  def add_external_mpi_data(post, options)
173
- post[:sg_eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
174
- post[:sg_cavv] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv]
182
+ post[:sg_ECI] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
183
+ post[:sg_CAVV] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv]
175
184
  post[:sg_dsTransID] = options[:three_d_secure][:ds_transaction_id] if options[:three_d_secure][:ds_transaction_id]
176
- post[:sg_threeDSProtocolVersion] = options[:three_d_secure][:version] || (options[:three_d_secure][:ds_transaction_id] ? '2' : '1')
177
- post[:sg_xid] = options[:three_d_secure][:xid]
185
+ post[:sg_threeDSProtocolVersion] = options[:three_d_secure][:ds_transaction_id] ? '2' : '1'
186
+ post[:sg_Xid] = options[:three_d_secure][:xid]
178
187
  post[:sg_IsExternalMPI] = 1
188
+ post[:sg_EnablePartialApproval] = options[:is_partial_approval]
189
+ post[:sg_challengePreference] = options[:three_d_secure][:challenge_preference] if options[:three_d_secure][:challenge_preference]
179
190
  end
180
191
 
181
192
  def parse(xml)
@@ -254,11 +254,20 @@ module ActiveMerchant #:nodoc:
254
254
  end
255
255
 
256
256
  def childnode_to_response(response, node, childnode)
257
- name = "#{node.name.downcase}_#{childnode.name.downcase}"
258
- if name == 'payment_method_data' && !childnode.elements.empty?
259
- response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first
257
+ node_name = node.name.downcase
258
+ childnode_name = childnode.name.downcase
259
+ composed_name = "#{node_name}_#{childnode_name}"
260
+
261
+ childnodes_present = !childnode.elements.empty?
262
+
263
+ if childnodes_present && composed_name == 'payment_method_data'
264
+ response[composed_name.to_sym] = Hash.from_xml(childnode.to_s).values.first
265
+ elsif childnodes_present && node_name == 'gateway_specific_response_fields'
266
+ response[node_name.to_sym] = {
267
+ childnode_name => Hash.from_xml(childnode.to_s).values.first
268
+ }
260
269
  else
261
- response[name.to_sym] = childnode.text
270
+ response[composed_name.to_sym] = childnode.text
262
271
  end
263
272
  end
264
273
 
@@ -279,14 +279,14 @@ module ActiveMerchant #:nodoc:
279
279
  transcript.
280
280
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
281
281
  gsub(%r((&?three_d_secure\[cryptogram\]=)[\w=]*(&?)), '\1[FILTERED]\2').
282
- gsub(%r((card\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]\2').
283
- gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]').
284
- gsub(%r((card\[emv_approval_data\]=)[^&]+(&?)), '\1[FILTERED]\2').
285
- gsub(%r((card\[emv_auth_data\]=)[^&]+(&?)), '\1[FILTERED]\2').
286
- gsub(%r((card\[encrypted_pin\]=)[^&]+(&?)), '\1[FILTERED]\2').
287
- gsub(%r((card\[encrypted_pin_key_id\]=)[\w=]+(&?)), '\1[FILTERED]\2').
288
- gsub(%r((card\[number\]=)\d+), '\1[FILTERED]').
289
- gsub(%r((card\[swipe_data\]=)[^&]+(&?)), '\1[FILTERED]\2')
282
+ gsub(%r(((\[card\]|card)\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]\3').
283
+ gsub(%r(((\[card\]|card)\[cvc\]=)\d+), '\1[FILTERED]').
284
+ gsub(%r(((\[card\]|card)\[emv_approval_data\]=)[^&]+(&?)), '\1[FILTERED]\3').
285
+ gsub(%r(((\[card\]|card)\[emv_auth_data\]=)[^&]+(&?)), '\1[FILTERED]\3').
286
+ gsub(%r(((\[card\]|card)\[encrypted_pin\]=)[^&]+(&?)), '\1[FILTERED]\3').
287
+ gsub(%r(((\[card\]|card)\[encrypted_pin_key_id\]=)[\w=]+(&?)), '\1[FILTERED]\3').
288
+ gsub(%r(((\[card\]|card)\[number\]=)\d+), '\1[FILTERED]').
289
+ gsub(%r(((\[card\]|card)\[swipe_data\]=)[^&]+(&?)), '\1[FILTERED]\3')
290
290
  end
291
291
 
292
292
  def supports_network_tokenization?
@@ -30,6 +30,9 @@ module ActiveMerchant #:nodoc:
30
30
  add_shipping_address(post, options)
31
31
  setup_future_usage(post, options)
32
32
  add_exemption(post, options)
33
+ add_stored_credentials(post, options)
34
+ add_ntid(post, options)
35
+ add_error_on_requires_action(post, options)
33
36
  request_three_d_secure(post, options)
34
37
 
35
38
  CREATE_INTENT_ATTRIBUTES.each do |attribute|
@@ -55,16 +58,22 @@ module ActiveMerchant #:nodoc:
55
58
  end
56
59
 
57
60
  def create_payment_method(payment_method, options = {})
58
- post = {}
59
- post[:type] = 'card'
60
- post[:card] = {}
61
- post[:card][:number] = payment_method.number
62
- post[:card][:exp_month] = payment_method.month
63
- post[:card][:exp_year] = payment_method.year
64
- post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
65
- add_billing_address(post, options)
61
+ post_data = create_payment_method_data(payment_method, options)
62
+
66
63
  options = format_idempotency_key(options, 'pm')
67
- commit(:post, 'payment_methods', post, options)
64
+ commit(:post, 'payment_methods', post_data, options)
65
+ end
66
+
67
+ def create_payment_method_data(payment_method, options = {})
68
+ post_data = {}
69
+ post_data[:type] = 'card'
70
+ post_data[:card] = {}
71
+ post_data[:card][:number] = payment_method.number
72
+ post_data[:card][:exp_month] = payment_method.month
73
+ post_data[:card][:exp_year] = payment_method.year
74
+ post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
75
+ add_billing_address(post_data, options)
76
+ post_data
68
77
  end
69
78
 
70
79
  def update_intent(money, intent_id, payment_method, options = {})
@@ -101,6 +110,17 @@ module ActiveMerchant #:nodoc:
101
110
  commit(:post, 'setup_intents', post, options)
102
111
  end
103
112
 
113
+ def retrieve_setup_intent(setup_intent_id)
114
+ # Retrieving a setup_intent passing 'expand[]=latest_attempt' allows the caller to
115
+ # check for a network_transaction_id and ds_transaction_id
116
+ # eg (latest_attempt -> payment_method_details -> card -> network_transaction_id)
117
+ #
118
+ # Being able to retrieve these fields enables payment flows that rely on MIT exemptions, e.g: off_session
119
+ commit(:post, "setup_intents/#{setup_intent_id}", {
120
+ 'expand[]': 'latest_attempt'
121
+ }, {})
122
+ end
123
+
104
124
  def authorize(money, payment_method, options = {})
105
125
  create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual'))
106
126
  end
@@ -164,7 +184,6 @@ module ActiveMerchant #:nodoc:
164
184
  if options[:customer]
165
185
  customer_id = options[:customer]
166
186
  else
167
- post[:validate] = options[:validate] unless options[:validate].nil?
168
187
  post[:description] = options[:description] if options[:description]
169
188
  post[:email] = options[:email] if options[:email]
170
189
  options = format_idempotency_key(options, 'customer')
@@ -172,7 +191,9 @@ module ActiveMerchant #:nodoc:
172
191
  customer_id = customer.params['id']
173
192
  end
174
193
  options = format_idempotency_key(options, 'attach')
175
- commit(:post, "payment_methods/#{params[:payment_method]}/attach", { customer: customer_id }, options)
194
+ attach_parameters = { customer: customer_id }
195
+ attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
196
+ commit(:post, "payment_methods/#{params[:payment_method]}/attach", attach_parameters, options)
176
197
  else
177
198
  super(payment_method, options)
178
199
  end
@@ -193,6 +214,10 @@ module ActiveMerchant #:nodoc:
193
214
 
194
215
  private
195
216
 
217
+ def off_session_request?(options = {})
218
+ (options[:off_session] || options[:setup_future_usage]) && options[:confirm] == true
219
+ end
220
+
196
221
  def add_connected_account(post, options = {})
197
222
  super(post, options)
198
223
  post[:application_fee_amount] = options[:application_fee] if options[:application_fee]
@@ -200,25 +225,21 @@ module ActiveMerchant #:nodoc:
200
225
 
201
226
  def add_whitelisted_attribute(post, options, attribute)
202
227
  post[attribute] = options[attribute] if options[attribute]
203
- post
204
228
  end
205
229
 
206
230
  def add_capture_method(post, options)
207
231
  capture_method = options[:capture_method].to_s
208
232
  post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method)
209
- post
210
233
  end
211
234
 
212
235
  def add_confirmation_method(post, options)
213
236
  confirmation_method = options[:confirmation_method].to_s
214
237
  post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method)
215
- post
216
238
  end
217
239
 
218
240
  def add_customer(post, options)
219
241
  customer = options[:customer].to_s
220
242
  post[:customer] = customer if customer.start_with?('cus_')
221
- post
222
243
  end
223
244
 
224
245
  def add_return_url(post, options)
@@ -226,22 +247,27 @@ module ActiveMerchant #:nodoc:
226
247
 
227
248
  post[:confirm] = options[:confirm]
228
249
  post[:return_url] = options[:return_url] if options[:return_url]
229
- post
230
250
  end
231
251
 
232
252
  def add_payment_method_token(post, payment_method, options)
233
253
  return if payment_method.nil?
234
254
 
235
255
  if payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
236
- p = create_payment_method(payment_method, options)
237
- return p unless p.success?
256
+ if off_session_request?(options)
257
+ post[:payment_method_data] = create_payment_method_data(payment_method, options)
258
+ return
259
+ else
260
+ p = create_payment_method(payment_method, options)
261
+ return p unless p.success?
238
262
 
239
- payment_method = p.params['id']
263
+ payment_method = p.params['id']
264
+ end
240
265
  end
241
266
 
242
- if payment_method.is_a?(StripePaymentToken)
267
+ case payment_method
268
+ when StripePaymentToken
243
269
  post[:payment_method] = payment_method.payment_data['id']
244
- elsif payment_method.is_a?(String)
270
+ when String
245
271
  if payment_method.include?('|')
246
272
  customer_id, payment_method_id = payment_method.split('|')
247
273
  token = payment_method_id
@@ -251,6 +277,8 @@ module ActiveMerchant #:nodoc:
251
277
  end
252
278
  post[:payment_method] = token
253
279
  end
280
+
281
+ post
254
282
  end
255
283
 
256
284
  def add_payment_method_types(post, options)
@@ -258,7 +286,6 @@ module ActiveMerchant #:nodoc:
258
286
  return if payment_method_types.nil?
259
287
 
260
288
  post[:payment_method_types] = Array(payment_method_types)
261
- post
262
289
  end
263
290
 
264
291
  def add_exemption(post, options = {})
@@ -269,6 +296,42 @@ module ActiveMerchant #:nodoc:
269
296
  post[:payment_method_options][:card][:moto] = true if options[:moto]
270
297
  end
271
298
 
299
+ # Stripe Payment Intents does not pass any parameters for cardholder/merchant initiated
300
+ # it also does not support installments for any country other than Mexico (reason for this is unknown)
301
+ # The only thing that Stripe PI requires for stored credentials to work currently is the network_transaction_id
302
+ # network_transaction_id is created when the card is authenticated using the field `setup_for_future_usage` with the value `off_session` see def setup_future_usage below
303
+
304
+ def add_stored_credentials(post, options = {})
305
+ return unless options[:stored_credential] && !options[:stored_credential].values.all?(&:nil?)
306
+
307
+ stored_credential = options[:stored_credential]
308
+ post[:payment_method_options] ||= {}
309
+ post[:payment_method_options][:card] ||= {}
310
+ post[:payment_method_options][:card][:mit_exemption] = {}
311
+
312
+ # Stripe PI accepts network_transaction_id and ds_transaction_id via mit field under card.
313
+ # The network_transaction_id can be sent in nested under stored credentials OR as its own field (add_ntid handles when it is sent in on its own)
314
+ # If it is sent is as its own field AND under stored credentials, the value sent under its own field is what will send.
315
+ post[:payment_method_options][:card][:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id]
316
+ post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
317
+ end
318
+
319
+ def add_ntid(post, options = {})
320
+ return unless options[:network_transaction_id]
321
+
322
+ post[:payment_method_options] ||= {}
323
+ post[:payment_method_options][:card] ||= {}
324
+ post[:payment_method_options][:card][:mit_exemption] = {}
325
+
326
+ post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = options[:network_transaction_id] if options[:network_transaction_id]
327
+ end
328
+
329
+ def add_error_on_requires_action(post, options = {})
330
+ return unless options[:confirm]
331
+
332
+ post[:error_on_requires_action] = true if options[:error_on_requires_action]
333
+ end
334
+
272
335
  def request_three_d_secure(post, options = {})
273
336
  return unless options[:request_three_d_secure] && %w(any automatic).include?(options[:request_three_d_secure])
274
337
 
@@ -292,7 +355,7 @@ module ActiveMerchant #:nodoc:
292
355
 
293
356
  def setup_future_usage(post, options = {})
294
357
  post[:setup_future_usage] = options[:setup_future_usage] if %w(on_session off_session).include?(options[:setup_future_usage])
295
- post[:off_session] = options[:off_session] if options[:off_session] && options[:confirm] == true
358
+ post[:off_session] = options[:off_session] if off_session_request?(options)
296
359
  post
297
360
  end
298
361
 
@@ -310,7 +373,6 @@ module ActiveMerchant #:nodoc:
310
373
  post[:billing_details][:email] = billing[:email] if billing[:email]
311
374
  post[:billing_details][:name] = billing[:name] if billing[:name]
312
375
  post[:billing_details][:phone] = billing[:phone] if billing[:phone]
313
- post
314
376
  end
315
377
 
316
378
  def add_shipping_address(post, options = {})
@@ -329,7 +391,6 @@ module ActiveMerchant #:nodoc:
329
391
  post[:shipping][:carrier] = shipping[:carrier] if shipping[:carrier]
330
392
  post[:shipping][:phone] = shipping[:phone] if shipping[:phone]
331
393
  post[:shipping][:tracking_number] = shipping[:tracking_number] if shipping[:tracking_number]
332
- post
333
394
  end
334
395
 
335
396
  def format_idempotency_key(options, suffix)