activemerchant 1.117.0 → 1.123.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -25,8 +25,8 @@ module ActiveMerchant #:nodoc:
25
25
  self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor'
26
26
 
27
27
  # Schema files can be found here: https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/
28
- TEST_XSD_VERSION = '1.164'
29
- PRODUCTION_XSD_VERSION = '1.164'
28
+ TEST_XSD_VERSION = '1.181'
29
+ PRODUCTION_XSD_VERSION = '1.181'
30
30
  ECI_BRAND_MAPPING = {
31
31
  visa: 'vbv',
32
32
  master: 'spa',
@@ -153,6 +153,10 @@ module ActiveMerchant #:nodoc:
153
153
  commit(build_refund_request(money, identification, options), :refund, money, options)
154
154
  end
155
155
 
156
+ def adjust(money, authorization, options = {})
157
+ commit(build_adjust_request(money, authorization, options), :adjust, money, options)
158
+ end
159
+
156
160
  def verify(payment, options = {})
157
161
  MultiResponse.run(:use_first_response) do |r|
158
162
  r.process { authorize(100, payment, options) }
@@ -256,8 +260,9 @@ module ActiveMerchant #:nodoc:
256
260
 
257
261
  private
258
262
 
259
- # Create all address hash key value pairs so that we still function if we
260
- # were only provided with one or two of them or even none
263
+ # Create all required address hash key value pairs
264
+ # If a value of nil is received, that value will be passed on to the gateway and will not be replaced with a default value
265
+ # Billing address fields received without an override value or with an empty string value will be replaced with the default_address values
261
266
  def setup_address_hash(options)
262
267
  default_address = {
263
268
  address1: 'Unspecified',
@@ -268,12 +273,23 @@ module ActiveMerchant #:nodoc:
268
273
  }
269
274
 
270
275
  submitted_address = options[:billing_address] || options[:address] || default_address
271
- options[:billing_address] = default_address.merge(submitted_address.symbolize_keys) { |_k, default, submitted| submitted.blank? ? default : submitted }
276
+ options[:billing_address] = default_address.merge(submitted_address.symbolize_keys) { |_k, default, submitted| check_billing_field_value(default, submitted) }
272
277
  options[:shipping_address] = options[:shipping_address] || {}
273
278
  end
274
279
 
280
+ def check_billing_field_value(default, submitted)
281
+ if submitted.nil?
282
+ nil
283
+ elsif submitted.blank?
284
+ default
285
+ else
286
+ submitted
287
+ end
288
+ end
289
+
275
290
  def build_auth_request(money, creditcard_or_reference, options)
276
291
  xml = Builder::XmlMarkup.new indent: 2
292
+ add_customer_id(xml, options)
277
293
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
278
294
  add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
279
295
  add_decision_manager_fields(xml, options)
@@ -287,7 +303,17 @@ module ActiveMerchant #:nodoc:
287
303
  add_partner_solution_id(xml)
288
304
  add_stored_credential_options(xml, options)
289
305
  add_merchant_description(xml, options)
306
+ add_sales_slip_number(xml, options)
307
+ add_airline_data(xml, options)
308
+ xml.target!
309
+ end
310
+
311
+ def build_adjust_request(money, authorization, options)
312
+ _, request_id = authorization.split(';')
290
313
 
314
+ xml = Builder::XmlMarkup.new indent: 2
315
+ add_purchase_data(xml, money, true, options)
316
+ add_incremental_auth_service(xml, request_id, options)
291
317
  xml.target!
292
318
  end
293
319
 
@@ -321,10 +347,13 @@ module ActiveMerchant #:nodoc:
321
347
 
322
348
  def build_purchase_request(money, payment_method_or_reference, options)
323
349
  xml = Builder::XmlMarkup.new indent: 2
350
+ add_customer_id(xml, options)
324
351
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
325
352
  add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
326
353
  add_decision_manager_fields(xml, options)
327
354
  add_mdd_fields(xml, options)
355
+ add_sales_slip_number(xml, options)
356
+ add_airline_data(xml, options)
328
357
  if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check'
329
358
  add_check_service(xml)
330
359
  add_issuer_additional_data(xml, options)
@@ -457,8 +486,8 @@ module ActiveMerchant #:nodoc:
457
486
 
458
487
  unless network_tokenization?(payment_method)
459
488
  xml.tag! 'businessRules' do
460
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
461
- xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv)
489
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
490
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
462
491
  end
463
492
  end
464
493
  end
@@ -500,6 +529,12 @@ module ActiveMerchant #:nodoc:
500
529
  end
501
530
  end
502
531
 
532
+ def add_customer_id(xml, options)
533
+ return unless options[:customer_id]
534
+
535
+ xml.tag! 'customerID', options[:customer_id]
536
+ end
537
+
503
538
  def add_merchant_description(xml, options)
504
539
  return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
505
540
 
@@ -512,6 +547,18 @@ module ActiveMerchant #:nodoc:
512
547
  end
513
548
  end
514
549
 
550
+ def add_sales_slip_number(xml, options)
551
+ xml.tag! 'salesSlipNumber', options[:sales_slip_number] if options[:sales_slip_number]
552
+ end
553
+
554
+ def add_airline_data(xml, options)
555
+ return unless options[:airline_agent_code]
556
+
557
+ xml.tag! 'airlineData' do
558
+ xml.tag! 'agentCode', options[:airline_agent_code]
559
+ end
560
+ end
561
+
515
562
  def add_purchase_data(xml, money = 0, include_grand_total = false, options = {})
516
563
  xml.tag! 'purchaseTotals' do
517
564
  xml.tag! 'currency', options[:currency] || currency(money)
@@ -521,6 +568,7 @@ module ActiveMerchant #:nodoc:
521
568
 
522
569
  def add_address(xml, payment_method, address, options, shipTo = false)
523
570
  first_name, last_name = address_names(address[:name], payment_method)
571
+ bill_to_merchant_tax_id = options[:merchant_tax_id] unless shipTo
524
572
 
525
573
  xml.tag! shipTo ? 'shipTo' : 'billTo' do
526
574
  xml.tag! 'firstName', first_name if first_name
@@ -538,6 +586,7 @@ module ActiveMerchant #:nodoc:
538
586
  xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo
539
587
  xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank?
540
588
  xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank?
589
+ xml.tag! 'merchantTaxID', bill_to_merchant_tax_id unless bill_to_merchant_tax_id.blank?
541
590
  end
542
591
  end
543
592
 
@@ -556,7 +605,7 @@ module ActiveMerchant #:nodoc:
556
605
  xml.tag! 'accountNumber', creditcard.number
557
606
  xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
558
607
  xml.tag! 'expirationYear', format(creditcard.year, :four_digits)
559
- xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank?
608
+ xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv].to_s == 'true' || creditcard.verification_value.blank?
560
609
  xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym]
561
610
  end
562
611
  end
@@ -593,7 +642,7 @@ module ActiveMerchant #:nodoc:
593
642
  xml.tag! 'merchantDefinedData' do
594
643
  (1..100).each do |each|
595
644
  key = "mdd_field_#{each}".to_sym
596
- xml.tag!("field#{each}", options[key]) if options[key]
645
+ xml.tag!('mddField', options[key], 'id' => each) if options[key]
597
646
  end
598
647
  end
599
648
  end
@@ -629,6 +678,13 @@ module ActiveMerchant #:nodoc:
629
678
  end
630
679
  end
631
680
 
681
+ def add_incremental_auth_service(xml, authorization, options)
682
+ xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
683
+ xml.tag! 'authRequestID', authorization
684
+ end
685
+ xml.tag! 'subsequentAuthReason', options[:auth_reason]
686
+ end
687
+
632
688
  def add_normalized_threeds_2_data(xml, payment_method, options)
633
689
  threeds_2_options = options[:three_d_secure]
634
690
  cc_brand = card_brand(payment_method).to_sym
@@ -842,6 +898,8 @@ module ActiveMerchant #:nodoc:
842
898
 
843
899
  xml.tag! 'installment' do
844
900
  xml.tag! 'totalCount', options[:installment_total_count]
901
+ xml.tag!('planType', options[:installment_plan_type]) if options[:installment_plan_type]
902
+ xml.tag!('firstInstallmentDate', options[:first_installment_date]) if options[:first_installment_date]
845
903
  end
846
904
  end
847
905
 
@@ -6,7 +6,7 @@ module ActiveMerchant #:nodoc:
6
6
 
7
7
  self.supported_countries = %w[AR BR CL CO MX PE UY TR]
8
8
  self.default_currency = 'USD'
9
- self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal]
9
+ self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal elo alia carnet]
10
10
 
11
11
  self.homepage_url = 'https://dlocal.com/'
12
12
  self.display_name = 'dLocal'
@@ -170,6 +170,12 @@ module ActiveMerchant #:nodoc:
170
170
  card_data[:security_code] = credit_card.verification_value if credit_card.verification_value?
171
171
  card_data[:card_holder_name] = credit_card.name if credit_card.name
172
172
 
173
+ # the device_unique_id has to be sent in via the card data (as device_unique_identifier) no other fraud detection fields require this
174
+ if options[:fraud_detection].present?
175
+ card_data[:fraud_detection] = {} if (options[:fraud_detection][:device_unique_id]).present?
176
+ card_data[:fraud_detection][:device_unique_identifier] = (options[:fraud_detection][:device_unique_id]) if (options[:fraud_detection][:device_unique_id]).present?
177
+ end
178
+
173
179
  # additional data used for Visa transactions
174
180
  card_data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
175
181
  card_data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
@@ -210,6 +216,14 @@ module ActiveMerchant #:nodoc:
210
216
  hsh[:channel] = options[:channel] if valid_fraud_detection_option?(options[:channel])
211
217
  hsh[:dispatch_method] = options[:dispatch_method] if valid_fraud_detection_option?(options[:dispatch_method])
212
218
  hsh[:csmdds] = options[:csmdds] if valid_fraud_detection_option?(options[:csmdds])
219
+ hsh[:device_unique_id] = options[:device_unique_id] if valid_fraud_detection_option?(options[:device_unique_id])
220
+ hsh[:bill_to] = options[:bill_to] if valid_fraud_detection_option?(options[:bill_to])
221
+ hsh[:purchase_totals] = options[:purchase_totals] if valid_fraud_detection_option?(options[:purchase_totals])
222
+ hsh[:customer_in_site] = options[:customer_in_site] if valid_fraud_detection_option?(options[:customer_in_site])
223
+ hsh[:retail_transaction_data] = options[:retail_transaction_data] if valid_fraud_detection_option?(options[:retail_transaction_data])
224
+ hsh[:ship_to] = options[:ship_to] if valid_fraud_detection_option?(options[:ship_to])
225
+ hsh[:tax_voucher_required] = options[:tax_voucher_required] if valid_fraud_detection_option?(options[:tax_voucher_required])
226
+ hsh[:copy_paste_card_data] = options[:copy_paste_card_data] if valid_fraud_detection_option?(options[:copy_paste_card_data])
213
227
  end
214
228
  end
215
229
 
@@ -268,7 +282,13 @@ module ActiveMerchant #:nodoc:
268
282
  if error = response.dig('status_details', 'error')
269
283
  message = "#{error.dig('reason', 'description')} | #{error['type']}"
270
284
  elsif response['error_type']
271
- message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ') if response['validation_errors']
285
+ if response['validation_errors'].is_a?(Array)
286
+ message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ')
287
+ elsif response['validation_errors'].is_a?(Hash)
288
+ errors = response['validation_errors'].map { |k, v| "#{k}: #{v}" }.join(', ')
289
+ message = "#{response['error_type']} - #{errors}"
290
+ end
291
+
272
292
  message ||= response['error_type']
273
293
  end
274
294
 
@@ -287,15 +307,21 @@ module ActiveMerchant #:nodoc:
287
307
  error_code = nil
288
308
  if error = response.dig('status_details', 'error')
289
309
  code = error.dig('reason', 'id')
290
- error_code = STANDARD_ERROR_CODE_MAPPING[code]
310
+ standard_error_code = STANDARD_ERROR_CODE_MAPPING[code]
311
+ error_code = "#{code}, #{standard_error_code}"
291
312
  error_code ||= error['type']
292
313
  elsif response['error_type']
293
314
  error_code = response['error_type'] if response['validation_errors']
294
- elsif error = response.dig('error')
315
+ elsif response.dig('error', 'validation_errors')
316
+ error = response.dig('error')
295
317
  validation_errors = error.dig('validation_errors', 0)
296
318
  code = validation_errors['code'] if validation_errors && validation_errors['code']
297
319
  param = validation_errors['param'] if validation_errors && validation_errors['param']
298
320
  error_code = "#{error['error_type']} | #{code} | #{param}" if error['error_type']
321
+ elsif error = response.dig('error')
322
+ code = error.dig('reason', 'id')
323
+ standard_error_code = STANDARD_ERROR_CODE_MAPPING[code]
324
+ error_code = "#{code}, #{standard_error_code}"
299
325
  end
300
326
 
301
327
  error_code || STANDARD_ERROR_CODE[:processing_error]
@@ -39,6 +39,7 @@ module ActiveMerchant #:nodoc:
39
39
 
40
40
  def purchase(money, payment_method, options = {})
41
41
  request = build_xml_request do |xml|
42
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
42
43
  xml.ssl_transaction_type self.actions[:purchase]
43
44
  xml.ssl_amount amount(money)
44
45
 
@@ -63,6 +64,7 @@ module ActiveMerchant #:nodoc:
63
64
 
64
65
  def authorize(money, creditcard, options = {})
65
66
  request = build_xml_request do |xml|
67
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
66
68
  xml.ssl_transaction_type self.actions[:authorize]
67
69
  xml.ssl_amount amount(money)
68
70
 
@@ -82,6 +84,8 @@ module ActiveMerchant #:nodoc:
82
84
 
83
85
  def capture(money, authorization, options = {})
84
86
  request = build_xml_request do |xml|
87
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
88
+
85
89
  if options[:credit_card]
86
90
  xml.ssl_transaction_type self.actions[:capture]
87
91
  xml.ssl_amount amount(money)
@@ -107,6 +111,7 @@ module ActiveMerchant #:nodoc:
107
111
 
108
112
  def refund(money, identification, options = {})
109
113
  request = build_xml_request do |xml|
114
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
110
115
  xml.ssl_transaction_type self.actions[:refund]
111
116
  xml.ssl_amount amount(money)
112
117
  add_txn_id(xml, identification)
@@ -117,6 +122,7 @@ module ActiveMerchant #:nodoc:
117
122
 
118
123
  def void(identification, options = {})
119
124
  request = build_xml_request do |xml|
125
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
120
126
  xml.ssl_transaction_type self.actions[:void]
121
127
 
122
128
  add_txn_id(xml, identification)
@@ -129,6 +135,7 @@ module ActiveMerchant #:nodoc:
129
135
  raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' if creditcard.is_a?(String)
130
136
 
131
137
  request = build_xml_request do |xml|
138
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
132
139
  xml.ssl_transaction_type self.actions[:credit]
133
140
  xml.ssl_amount amount(money)
134
141
  add_invoice(xml, options)
@@ -143,6 +150,7 @@ module ActiveMerchant #:nodoc:
143
150
 
144
151
  def verify(credit_card, options = {})
145
152
  request = build_xml_request do |xml|
153
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
146
154
  xml.ssl_transaction_type self.actions[:verify]
147
155
  add_creditcard(xml, credit_card)
148
156
  add_address(xml, options)
@@ -154,6 +162,7 @@ module ActiveMerchant #:nodoc:
154
162
 
155
163
  def store(creditcard, options = {})
156
164
  request = build_xml_request do |xml|
165
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
157
166
  xml.ssl_transaction_type self.actions[:store]
158
167
  xml.ssl_add_token 'Y'
159
168
  add_creditcard(xml, creditcard)
@@ -167,6 +176,7 @@ module ActiveMerchant #:nodoc:
167
176
 
168
177
  def update(token, creditcard, options = {})
169
178
  request = build_xml_request do |xml|
179
+ xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
170
180
  xml.ssl_transaction_type self.actions[:update]
171
181
  add_token(xml, token)
172
182
  add_creditcard(xml, creditcard)
@@ -191,8 +201,8 @@ module ActiveMerchant #:nodoc:
191
201
  private
192
202
 
193
203
  def add_invoice(xml, options)
194
- xml.ssl_invoice_number truncate((options[:order_id] || options[:invoice]), 25)
195
- xml.ssl_description truncate(options[:description], 255)
204
+ xml.ssl_invoice_number url_encode_truncate((options[:order_id] || options[:invoice]), 25)
205
+ xml.ssl_description url_encode_truncate(options[:description], 255)
196
206
  end
197
207
 
198
208
  def add_approval_code(xml, authorization)
@@ -209,8 +219,8 @@ module ActiveMerchant #:nodoc:
209
219
 
210
220
  add_verification_value(xml, creditcard) if creditcard.verification_value?
211
221
 
212
- xml.ssl_first_name truncate(creditcard.first_name, 20)
213
- xml.ssl_last_name truncate(creditcard.last_name, 30)
222
+ xml.ssl_first_name url_encode_truncate(creditcard.first_name, 20)
223
+ xml.ssl_last_name url_encode_truncate(creditcard.last_name, 30)
214
224
  end
215
225
 
216
226
  def add_currency(xml, money, options)
@@ -230,7 +240,7 @@ module ActiveMerchant #:nodoc:
230
240
  end
231
241
 
232
242
  def add_customer_email(xml, options)
233
- xml.ssl_email truncate(options[:email], 100) unless empty?(options[:email])
243
+ xml.ssl_email url_encode_truncate(options[:email], 100) unless empty?(options[:email])
234
244
  end
235
245
 
236
246
  def add_salestax(xml, options)
@@ -243,27 +253,27 @@ module ActiveMerchant #:nodoc:
243
253
  billing_address = options[:billing_address] || options[:address]
244
254
 
245
255
  if billing_address
246
- xml.ssl_avs_address truncate(billing_address[:address1], 30)
247
- xml.ssl_address2 truncate(billing_address[:address2], 30)
248
- xml.ssl_avs_zip truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
249
- xml.ssl_city truncate(billing_address[:city], 30)
250
- xml.ssl_state truncate(billing_address[:state], 10)
251
- xml.ssl_company truncate(billing_address[:company], 50)
252
- xml.ssl_phone truncate(billing_address[:phone], 20)
253
- xml.ssl_country truncate(billing_address[:country], 50)
256
+ xml.ssl_avs_address url_encode_truncate(billing_address[:address1], 30)
257
+ xml.ssl_address2 url_encode_truncate(billing_address[:address2], 30)
258
+ xml.ssl_avs_zip url_encode_truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
259
+ xml.ssl_city url_encode_truncate(billing_address[:city], 30)
260
+ xml.ssl_state url_encode_truncate(billing_address[:state], 10)
261
+ xml.ssl_company url_encode_truncate(billing_address[:company], 50)
262
+ xml.ssl_phone url_encode_truncate(billing_address[:phone], 20)
263
+ xml.ssl_country url_encode_truncate(billing_address[:country], 50)
254
264
  end
255
265
 
256
266
  if shipping_address = options[:shipping_address]
257
- xml.ssl_ship_to_address1 truncate(shipping_address[:address1], 30)
258
- xml.ssl_ship_to_address2 truncate(shipping_address[:address2], 30)
259
- xml.ssl_ship_to_city truncate(shipping_address[:city], 30)
260
- xml.ssl_ship_to_company truncate(shipping_address[:company], 50)
261
- xml.ssl_ship_to_country truncate(shipping_address[:country], 50)
262
- xml.ssl_ship_to_first_name truncate(shipping_address[:first_name], 20)
263
- xml.ssl_ship_to_last_name truncate(shipping_address[:last_name], 30)
264
- xml.ssl_ship_to_phone truncate(shipping_address[:phone], 10)
265
- xml.ssl_ship_to_state truncate(shipping_address[:state], 2)
266
- xml.ssl_ship_to_zip truncate(shipping_address[:zip], 10)
267
+ xml.ssl_ship_to_address1 url_encode_truncate(shipping_address[:address1], 30)
268
+ xml.ssl_ship_to_address2 url_encode_truncate(shipping_address[:address2], 30)
269
+ xml.ssl_ship_to_city url_encode_truncate(shipping_address[:city], 30)
270
+ xml.ssl_ship_to_company url_encode_truncate(shipping_address[:company], 50)
271
+ xml.ssl_ship_to_country url_encode_truncate(shipping_address[:country], 50)
272
+ xml.ssl_ship_to_first_name url_encode_truncate(shipping_address[:first_name], 20)
273
+ xml.ssl_ship_to_last_name url_encode_truncate(shipping_address[:last_name], 30)
274
+ xml.ssl_ship_to_phone url_encode_truncate(shipping_address[:phone], 10)
275
+ xml.ssl_ship_to_state url_encode_truncate(shipping_address[:state], 2)
276
+ xml.ssl_ship_to_zip url_encode_truncate(shipping_address[:zip], 10)
267
277
  end
268
278
  end
269
279
 
@@ -283,12 +293,17 @@ module ActiveMerchant #:nodoc:
283
293
  xml.ssl_cardholder_ip options[:ip] if options.has_key?(:ip)
284
294
  end
285
295
 
296
+ # add_recurring_token is a field that can be sent in to obtain a token from Elavon for use with their tokenization program
286
297
  def add_auth_purchase_params(xml, options)
287
298
  xml.ssl_dynamic_dba options[:dba] if options.has_key?(:dba)
288
- xml.ssl_merchant_initiated_unscheduled options[:merchant_initiated_unscheduled] if options.has_key?(:merchant_initiated_unscheduled)
299
+ xml.ssl_merchant_initiated_unscheduled merchant_initiated_unscheduled(options) if merchant_initiated_unscheduled(options)
300
+ xml.ssl_add_token options[:add_recurring_token] if options.has_key?(:add_recurring_token)
301
+ xml.ssl_token options[:ssl_token] if options.has_key?(:ssl_token)
289
302
  xml.ssl_customer_code options[:customer] if options.has_key?(:customer)
290
303
  xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number)
304
+ xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
291
305
  add_custom_fields(xml, options) if options[:custom_fields]
306
+ add_stored_credential(xml, options) if options[:stored_credential]
292
307
  end
293
308
 
294
309
  def add_custom_fields(xml, options)
@@ -337,6 +352,32 @@ module ActiveMerchant #:nodoc:
337
352
  }
338
353
  end
339
354
 
355
+ def add_stored_credential(xml, options)
356
+ network_transaction_id = options.dig(:stored_credential, :network_transaction_id)
357
+ case
358
+ when network_transaction_id.nil?
359
+ return
360
+ when network_transaction_id.to_s.include?('|')
361
+ oar_data, ps2000_data = options[:stored_credential][:network_transaction_id].split('|')
362
+ xml.ssl_oar_data oar_data unless oar_data.nil? || oar_data.empty?
363
+ xml.ssl_ps2000_data ps2000_data unless ps2000_data.nil? || ps2000_data.empty?
364
+ when network_transaction_id.to_s.length > 22
365
+ xml.ssl_oar_data options.dig(:stored_credential, :network_transaction_id)
366
+ else
367
+ xml.ssl_ps2000_data options.dig(:stored_credential, :network_transaction_id)
368
+ end
369
+ end
370
+
371
+ def merchant_initiated_unscheduled(options)
372
+ return options[:merchant_initiated_unscheduled] if options[:merchant_initiated_unscheduled]
373
+ return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled' || options.dig(:stored_credential, :reason_type) == 'recurring'
374
+ end
375
+
376
+ def entry_mode(options)
377
+ return options[:entry_mode] if options[:entry_mode]
378
+ return 12 if options[:stored_credential]
379
+ end
380
+
340
381
  def build_xml_request
341
382
  builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
342
383
  xml.txn do
@@ -352,7 +393,9 @@ module ActiveMerchant #:nodoc:
352
393
 
353
394
  def commit(request)
354
395
  request = "xmldata=#{request}".delete('&')
396
+
355
397
  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
398
+ response = hash_html_decode(response)
356
399
 
357
400
  Response.new(
358
401
  response[:result] == '0',
@@ -362,14 +405,19 @@ module ActiveMerchant #:nodoc:
362
405
  authorization: authorization_from(response),
363
406
  error_code: response[:errorCode],
364
407
  avs_result: { code: response[:avs_response] },
365
- cvv_result: response[:cvv2_response]
408
+ cvv_result: response[:cvv2_response],
409
+ network_transaction_id: build_network_transaction_id(response)
366
410
  )
367
411
  end
368
412
 
413
+ def build_network_transaction_id(response)
414
+ "#{response[:oar_data]}|#{response[:ps2000_data]}"
415
+ end
416
+
369
417
  def headers
370
418
  {
371
419
  'Accept' => 'application/xml',
372
- 'Content-type' => 'application/x-www-form-urlencoded'
420
+ 'Content-type' => 'application/x-www-form-urlencoded;charset=utf8'
373
421
  }
374
422
  end
375
423
 
@@ -383,6 +431,42 @@ module ActiveMerchant #:nodoc:
383
431
  def authorization_from(response)
384
432
  [response[:approval_code], response[:txn_id]].join(';')
385
433
  end
434
+
435
+ def url_encode_truncate(value, size)
436
+ return nil unless value
437
+
438
+ encoded = url_encode(value)
439
+
440
+ while encoded.length > size
441
+ value.chop!
442
+ encoded = url_encode(value)
443
+ end
444
+ encoded
445
+ end
446
+
447
+ def url_encode(value)
448
+ if value.is_a?(String)
449
+ encoded = CGI.escape(value)
450
+ encoded = encoded.tr('+', ' ') # don't encode spaces
451
+ encoded = encoded.gsub('%26', '%26amp;') # account for Elavon's weird '&' handling
452
+ encoded
453
+ else
454
+ value.to_s
455
+ end
456
+ end
457
+
458
+ def hash_html_decode(hash)
459
+ hash.each do |k, v|
460
+ if v.is_a?(String)
461
+ # decode all string params
462
+ v = v.gsub('&', '&') # account for Elavon's weird '&' handling
463
+ hash[k] = CGI.unescape_html(v)
464
+ elsif v.is_a?(Hash)
465
+ hash_html_decode(v)
466
+ end
467
+ end
468
+ hash
469
+ end
386
470
  end
387
471
  end
388
472
  end
@@ -192,6 +192,8 @@ module ActiveMerchant #:nodoc:
192
192
  xml.PaymentType options[:payment_type] if options[:payment_type]
193
193
  xml.SubmissionType options[:submission_type] if options[:submission_type]
194
194
  xml.DuplicateCheckDisableFlag options[:duplicate_check_disable_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_check_disable_flag].nil?
195
+ xml.DuplicateOverrideFlag options[:duplicate_override_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_override_flag].nil?
196
+ xml.MerchantDescriptor options[:merchant_descriptor] if options[:merchant_descriptor]
195
197
  end
196
198
  end
197
199
 
@@ -53,6 +53,7 @@ module ActiveMerchant #:nodoc:
53
53
  add_invoice(params, amount, options)
54
54
  add_customer_data(params, options, payment_method)
55
55
  add_credit_card(params, payment_method, options)
56
+ add_3ds_authenticated_data(params, options) if options[:three_d_secure]
56
57
  params['Method'] = payment_method.respond_to?(:number) ? 'ProcessPayment' : 'TokenPayment'
57
58
  commit(url_for('Transaction'), params)
58
59
  end
@@ -197,6 +198,18 @@ module ActiveMerchant #:nodoc:
197
198
  params
198
199
  end
199
200
 
201
+ def add_3ds_authenticated_data(params, options)
202
+ three_d_secure_options = options[:three_d_secure]
203
+ params['PaymentInstrument'] ||= {} if params['PaymentInstrument'].nil?
204
+ threed_secure_auth = params['PaymentInstrument']['ThreeDSecureAuth'] = {}
205
+ threed_secure_auth['Cryptogram'] = three_d_secure_options[:cavv]
206
+ threed_secure_auth['ECI'] = three_d_secure_options[:eci]
207
+ threed_secure_auth['XID'] = three_d_secure_options[:xid]
208
+ threed_secure_auth['AuthStatus'] = three_d_secure_options[:authentication_response_status]
209
+ threed_secure_auth['dsTransactionId'] = three_d_secure_options[:ds_transaction_id]
210
+ threed_secure_auth['Version'] = three_d_secure_options[:version]
211
+ end
212
+
200
213
  def add_invoice(params, money, options, key = 'Payment')
201
214
  currency_code = options[:currency] || currency(money)
202
215
  params[key] = {
@@ -212,8 +212,8 @@ module ActiveMerchant #:nodoc:
212
212
  xml.tag! 'Expiry_Date', expdate(credit_card)
213
213
  xml.tag! 'CardHoldersName', credit_card.name
214
214
  xml.tag! 'CardType', card_type(credit_card.brand)
215
- xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id]
216
215
 
216
+ add_wallet_provider_id(xml, credit_card, options)
217
217
  add_credit_card_eci(xml, credit_card, options)
218
218
  add_credit_card_verification_strings(xml, credit_card, options)
219
219
  end
@@ -221,10 +221,9 @@ module ActiveMerchant #:nodoc:
221
221
 
222
222
  def add_credit_card_eci(xml, credit_card, options)
223
223
  eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover'
224
- # Discover requires any Apple Pay transaction, regardless of in-app
225
- # or web, and regardless of the ECI contained in the PKPaymentToken,
226
- # to have an ECI value explicitly of 04.
227
- '04'
224
+ # Payeezy requires an ECI of 5 for apple pay transactions
225
+ # See: https://support.payeezy.com/hc/en-us/articles/203730589-Ecommerce-Flag-Values
226
+ '05'
228
227
  else
229
228
  (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
230
229
  end
@@ -276,10 +275,22 @@ module ActiveMerchant #:nodoc:
276
275
  xml.tag! 'Expiry_Date', expdate(credit_card)
277
276
  xml.tag! 'CardHoldersName', credit_card.name
278
277
  xml.tag! 'CardType', card_type(credit_card.brand)
279
- xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id]
278
+
279
+ add_wallet_provider_id(xml, credit_card, options)
280
280
  add_card_authentication_data(xml, options)
281
281
  end
282
282
 
283
+ def add_wallet_provider_id(xml, credit_card, options)
284
+ provider_id = if options[:wallet_provider_id]
285
+ options[:wallet_provider_id]
286
+ elsif credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay
287
+ # See: https://support.payeezy.com/hc/en-us/articles/206601408-First-Data-Payeezy-Gateway-Web-Service-API-Reference-Guide#3.9
288
+ 4
289
+ end
290
+
291
+ xml.tag! 'WalletProviderID', provider_id if provider_id
292
+ end
293
+
283
294
  def add_customer_data(xml, options)
284
295
  xml.tag! 'Customer_Ref', options[:customer] if options[:customer]
285
296
  xml.tag! 'Client_IP', options[:ip] if options[:ip]
@@ -28,6 +28,7 @@ module ActiveMerchant #:nodoc:
28
28
  add_payment_method(post, payment_method, options)
29
29
  add_billing_address(post, payment_method, options)
30
30
  add_shipping_address(post, options)
31
+ add_xdata(post, options)
31
32
  post[:action] = 'sale'
32
33
 
33
34
  commit(:post, post)
@@ -41,6 +42,7 @@ module ActiveMerchant #:nodoc:
41
42
  add_payment_method(post, payment_method, options)
42
43
  add_billing_address(post, payment_method, options)
43
44
  add_shipping_address(post, options)
45
+ add_xdata(post, options)
44
46
  post[:action] = 'authorize'
45
47
 
46
48
  commit(:post, post)
@@ -122,6 +124,16 @@ module ActiveMerchant #:nodoc:
122
124
  post[:service_fee_amount] = options[:service_fee_amount] if options[:service_fee_amount]
123
125
  end
124
126
 
127
+ def add_xdata(post, options)
128
+ post[:xdata] = {}
129
+ if xdata = options[:xdata]
130
+ (1..9).each do |n|
131
+ field = "xdata_#{n}".to_sym
132
+ post[:xdata][field] = xdata[field] if xdata[field]
133
+ end
134
+ end
135
+ end
136
+
125
137
  def add_billing_address(post, payment, options)
126
138
  post[:billing_address] = {}
127
139
  if address = options[:billing_address] || options[:address]