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
@@ -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]