activemerchant 1.119.0 → 1.124.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +216 -1
  3. data/README.md +4 -2
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +3 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +32 -14
  8. data/lib/active_merchant/billing/gateways/adyen.rb +94 -25
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +3 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +52 -8
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  17. data/lib/active_merchant/billing/gateways/credorax.rb +15 -9
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +53 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +9 -2
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/elavon.rb +70 -28
  22. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  23. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  24. data/lib/active_merchant/billing/gateways/global_collect.rb +24 -10
  25. data/lib/active_merchant/billing/gateways/hps.rb +55 -1
  26. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  27. data/lib/active_merchant/billing/gateways/litle.rb +1 -1
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  29. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  30. data/lib/active_merchant/billing/gateways/mit.rb +260 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +290 -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 +26 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +27 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +99 -59
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  38. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  39. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  40. data/lib/active_merchant/billing/gateways/payeezy.rb +34 -6
  41. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  42. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  43. data/lib/active_merchant/billing/gateways/payment_express.rb +5 -5
  44. data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
  45. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  46. data/lib/active_merchant/billing/gateways/paysafe.rb +376 -0
  47. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  48. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  49. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  50. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +42 -24
  52. data/lib/active_merchant/billing/gateways/safe_charge.rb +25 -13
  53. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  54. data/lib/active_merchant/billing/gateways/stripe.rb +18 -8
  55. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +126 -48
  56. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  58. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  59. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +78 -18
  61. data/lib/active_merchant/billing/response.rb +4 -0
  62. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  63. data/lib/active_merchant/billing.rb +1 -0
  64. data/lib/active_merchant/version.rb +1 -1
  65. data/lib/certs/cacert.pem +1582 -2431
  66. metadata +11 -3
@@ -37,12 +37,15 @@ module ActiveMerchant #:nodoc:
37
37
  post = {}
38
38
  add_invoice(post, amount, options)
39
39
  add_customer_data(post, options)
40
+ add_metadata(post, options)
40
41
 
41
42
  commit(:capture, post, authorization)
42
43
  end
43
44
 
44
45
  def void(authorization, _options = {})
45
46
  post = {}
47
+ add_metadata(post, options)
48
+
46
49
  commit(:void, post, authorization)
47
50
  end
48
51
 
@@ -50,6 +53,7 @@ module ActiveMerchant #:nodoc:
50
53
  post = {}
51
54
  add_invoice(post, amount, options)
52
55
  add_customer_data(post, options)
56
+ add_metadata(post, options)
53
57
 
54
58
  commit(:refund, post, authorization)
55
59
  end
@@ -79,8 +83,10 @@ module ActiveMerchant #:nodoc:
79
83
  add_invoice(post, amount, options)
80
84
  add_payment_method(post, payment_method, options)
81
85
  add_customer_data(post, options)
86
+ add_stored_credential_options(post, options)
82
87
  add_transaction_data(post, options)
83
88
  add_3ds(post, options)
89
+ add_metadata(post, options)
84
90
  end
85
91
 
86
92
  def add_invoice(post, money, options)
@@ -96,6 +102,11 @@ module ActiveMerchant #:nodoc:
96
102
  post[:metadata][:udf5] = application_id || 'ActiveMerchant'
97
103
  end
98
104
 
105
+ def add_metadata(post, options)
106
+ post[:metadata] = {} unless post[:metadata]
107
+ post[:metadata].merge!(options[:metadata]) if options[:metadata]
108
+ end
109
+
99
110
  def add_payment_method(post, payment_method, options)
100
111
  post[:source] = {}
101
112
  if payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :network_token
@@ -138,6 +149,26 @@ module ActiveMerchant #:nodoc:
138
149
  post[:previous_payment_id] = options[:previous_charge_id] if options[:previous_charge_id]
139
150
  end
140
151
 
152
+ def add_stored_credential_options(post, options = {})
153
+ return unless options[:stored_credential]
154
+
155
+ case options[:stored_credential][:initial_transaction]
156
+ when true
157
+ post[:merchant_initiated] = false
158
+ when false
159
+ post[:'source.stored'] = true
160
+ post[:previous_payment_id] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
161
+ post[:merchant_initiated] = true
162
+ end
163
+
164
+ case options[:stored_credential][:reason_type]
165
+ when 'recurring' || 'installment'
166
+ post[:payment_type] = 'Recurring'
167
+ when 'unscheduled'
168
+ return
169
+ end
170
+ end
171
+
141
172
  def add_3ds(post, options)
142
173
  if options[:three_d_secure] || options[:execute_threed]
143
174
  post[:'3ds'] = {}
@@ -20,8 +20,9 @@ module ActiveMerchant #:nodoc:
20
20
  self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway'
21
21
 
22
22
  self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB)
23
+
23
24
  self.default_currency = 'EUR'
24
- self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW PYG RWF VND VUV XAF XOF XPF)
25
+ self.currencies_without_fractions = %w(BIF CLP DJF GNF ISK JPY KMF KRW PYG RWF VND VUV XAF XOF XPF)
25
26
  self.currencies_with_three_decimal_places = %w(BHD IQD JOD KWD LYD OMR TND)
26
27
 
27
28
  self.money_format = :cents
@@ -334,6 +335,7 @@ module ActiveMerchant #:nodoc:
334
335
  post[:f23] = options[:f23] if options[:f23]
335
336
  post[:'3ds_purchasedate'] = Time.now.utc.strftime('%Y%m%d%I%M%S')
336
337
  options.dig(:stored_credential, :initiator) == 'merchant' ? post[:'3ds_channel'] = '03' : post[:'3ds_channel'] = '02'
338
+ post[:'3ds_reqchallengeind'] = options[:three_ds_reqchallengeind] if options[:three_ds_reqchallengeind]
337
339
  post[:'3ds_redirect_url'] = three_ds_2_options[:notification_url]
338
340
  post[:'3ds_challengewindowsize'] = options[:three_ds_challenge_window_size] || '03'
339
341
  post[:d5] = browser_info[:user_agent]
@@ -345,14 +347,7 @@ module ActiveMerchant #:nodoc:
345
347
  post[:d6] = browser_info[:language]
346
348
  post[:'3ds_browserjavaenabled'] = browser_info[:java]
347
349
  post[:'3ds_browseracceptheader'] = browser_info[:accept_header]
348
- if (shipping_address = options[:shipping_address])
349
- post[:'3ds_shipaddrstate'] = shipping_address[:state]
350
- post[:'3ds_shipaddrpostcode'] = shipping_address[:zip]
351
- post[:'3ds_shipaddrline2'] = shipping_address[:address2]
352
- post[:'3ds_shipaddrline1'] = shipping_address[:address1]
353
- post[:'3ds_shipaddrcountry'] = shipping_address[:country]
354
- post[:'3ds_shipaddrcity'] = shipping_address[:city]
355
- end
350
+ add_complete_shipping_address(post, options[:shipping_address]) if options[:shipping_address]
356
351
  elsif options[:three_d_secure]
357
352
  add_normalized_3d_secure_2_data(post, options)
358
353
  end
@@ -372,6 +367,17 @@ module ActiveMerchant #:nodoc:
372
367
  end
373
368
  end
374
369
 
370
+ def add_complete_shipping_address(post, shipping_address)
371
+ return if shipping_address.values.any?(&:blank?)
372
+
373
+ post[:'3ds_shipaddrstate'] = shipping_address[:state]
374
+ post[:'3ds_shipaddrpostcode'] = shipping_address[:zip]
375
+ post[:'3ds_shipaddrline2'] = shipping_address[:address2]
376
+ post[:'3ds_shipaddrline1'] = shipping_address[:address1]
377
+ post[:'3ds_shipaddrcountry'] = shipping_address[:country]
378
+ post[:'3ds_shipaddrcity'] = shipping_address[:city]
379
+ end
380
+
375
381
  def add_normalized_3d_secure_2_data(post, options)
376
382
  three_d_secure_options = options[:three_d_secure]
377
383
 
@@ -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) }
@@ -285,6 +289,7 @@ module ActiveMerchant #:nodoc:
285
289
 
286
290
  def build_auth_request(money, creditcard_or_reference, options)
287
291
  xml = Builder::XmlMarkup.new indent: 2
292
+ add_customer_id(xml, options)
288
293
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
289
294
  add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
290
295
  add_decision_manager_fields(xml, options)
@@ -298,7 +303,17 @@ module ActiveMerchant #:nodoc:
298
303
  add_partner_solution_id(xml)
299
304
  add_stored_credential_options(xml, options)
300
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(';')
301
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)
302
317
  xml.target!
303
318
  end
304
319
 
@@ -332,10 +347,13 @@ module ActiveMerchant #:nodoc:
332
347
 
333
348
  def build_purchase_request(money, payment_method_or_reference, options)
334
349
  xml = Builder::XmlMarkup.new indent: 2
350
+ add_customer_id(xml, options)
335
351
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
336
352
  add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
337
353
  add_decision_manager_fields(xml, options)
338
354
  add_mdd_fields(xml, options)
355
+ add_sales_slip_number(xml, options)
356
+ add_airline_data(xml, options)
339
357
  if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check'
340
358
  add_check_service(xml)
341
359
  add_issuer_additional_data(xml, options)
@@ -468,8 +486,8 @@ module ActiveMerchant #:nodoc:
468
486
 
469
487
  unless network_tokenization?(payment_method)
470
488
  xml.tag! 'businessRules' do
471
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
472
- 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'
473
491
  end
474
492
  end
475
493
  end
@@ -511,6 +529,12 @@ module ActiveMerchant #:nodoc:
511
529
  end
512
530
  end
513
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
+
514
538
  def add_merchant_description(xml, options)
515
539
  return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
516
540
 
@@ -523,6 +547,18 @@ module ActiveMerchant #:nodoc:
523
547
  end
524
548
  end
525
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
+
526
562
  def add_purchase_data(xml, money = 0, include_grand_total = false, options = {})
527
563
  xml.tag! 'purchaseTotals' do
528
564
  xml.tag! 'currency', options[:currency] || currency(money)
@@ -532,6 +568,7 @@ module ActiveMerchant #:nodoc:
532
568
 
533
569
  def add_address(xml, payment_method, address, options, shipTo = false)
534
570
  first_name, last_name = address_names(address[:name], payment_method)
571
+ bill_to_merchant_tax_id = options[:merchant_tax_id] unless shipTo
535
572
 
536
573
  xml.tag! shipTo ? 'shipTo' : 'billTo' do
537
574
  xml.tag! 'firstName', first_name if first_name
@@ -549,6 +586,7 @@ module ActiveMerchant #:nodoc:
549
586
  xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo
550
587
  xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank?
551
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?
552
590
  end
553
591
  end
554
592
 
@@ -567,7 +605,7 @@ module ActiveMerchant #:nodoc:
567
605
  xml.tag! 'accountNumber', creditcard.number
568
606
  xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
569
607
  xml.tag! 'expirationYear', format(creditcard.year, :four_digits)
570
- 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?
571
609
  xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym]
572
610
  end
573
611
  end
@@ -604,7 +642,7 @@ module ActiveMerchant #:nodoc:
604
642
  xml.tag! 'merchantDefinedData' do
605
643
  (1..100).each do |each|
606
644
  key = "mdd_field_#{each}".to_sym
607
- xml.tag!("field#{each}", options[key]) if options[key]
645
+ xml.tag!('mddField', options[key], 'id' => each) if options[key]
608
646
  end
609
647
  end
610
648
  end
@@ -640,6 +678,13 @@ module ActiveMerchant #:nodoc:
640
678
  end
641
679
  end
642
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
+
643
688
  def add_normalized_threeds_2_data(xml, payment_method, options)
644
689
  threeds_2_options = options[:three_d_secure]
645
690
  cc_brand = card_brand(payment_method).to_sym
@@ -853,6 +898,8 @@ module ActiveMerchant #:nodoc:
853
898
 
854
899
  xml.tag! 'installment' do
855
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]
856
903
  end
857
904
  end
858
905
 
@@ -4,9 +4,9 @@ module ActiveMerchant #:nodoc:
4
4
  self.test_url = 'https://sandbox.dlocal.com'
5
5
  self.live_url = 'https://api.dlocal.com'
6
6
 
7
- self.supported_countries = %w[AR BR CL CO MX PE UY TR]
7
+ self.supported_countries = %w[AR BD BO BR CL CM CN CO CR DO EC EG GH IN ID KE MY MX MA NG PA PY PE PH SN ZA TR UY VN]
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'
@@ -78,6 +78,7 @@ module ActiveMerchant #:nodoc:
78
78
  add_country(post, card, options)
79
79
  add_payer(post, card, options)
80
80
  add_card(post, card, action, options)
81
+ add_additional_data(post, options)
81
82
  post[:order_id] = options[:order_id] || generate_unique_id
82
83
  post[:description] = options[:description] if options[:description]
83
84
  end
@@ -87,6 +88,10 @@ module ActiveMerchant #:nodoc:
87
88
  post[:currency] = (options[:currency] || currency(money))
88
89
  end
89
90
 
91
+ def add_additional_data(post, options)
92
+ post[:additional_risk_data] = options[:additional_data]
93
+ end
94
+
90
95
  def add_country(post, card, options)
91
96
  return unless address = options[:billing_address] || options[:address]
92
97
 
@@ -109,6 +114,8 @@ module ActiveMerchant #:nodoc:
109
114
  post[:payer][:document] = options[:document] if options[:document]
110
115
  post[:payer][:document2] = options[:document2] if options[:document2]
111
116
  post[:payer][:user_reference] = options[:user_reference] if options[:user_reference]
117
+ post[:payer][:event_uuid] = options[:device_id] if options[:device_id]
118
+ post[:payer][:onboarding_ip_address] = options[:ip] if options[:ip]
112
119
  post[:payer][:address] = add_address(post, card, options)
113
120
  end
114
121
 
@@ -282,7 +282,13 @@ module ActiveMerchant #:nodoc:
282
282
  if error = response.dig('status_details', 'error')
283
283
  message = "#{error.dig('reason', 'description')} | #{error['type']}"
284
284
  elsif response['error_type']
285
- 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
+
286
292
  message ||= response['error_type']
287
293
  end
288
294
 
@@ -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,9 +293,12 @@ 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
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)
291
304
  xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
@@ -357,7 +370,7 @@ module ActiveMerchant #:nodoc:
357
370
 
358
371
  def merchant_initiated_unscheduled(options)
359
372
  return options[:merchant_initiated_unscheduled] if options[:merchant_initiated_unscheduled]
360
- return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == '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'
361
374
  end
362
375
 
363
376
  def entry_mode(options)
@@ -382,6 +395,7 @@ module ActiveMerchant #:nodoc:
382
395
  request = "xmldata=#{request}".delete('&')
383
396
 
384
397
  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
398
+ response = hash_html_decode(response)
385
399
 
386
400
  Response.new(
387
401
  response[:result] == '0',
@@ -403,7 +417,7 @@ module ActiveMerchant #:nodoc:
403
417
  def headers
404
418
  {
405
419
  'Accept' => 'application/xml',
406
- 'Content-type' => 'application/x-www-form-urlencoded'
420
+ 'Content-type' => 'application/x-www-form-urlencoded;charset=utf8'
407
421
  }
408
422
  end
409
423
 
@@ -418,12 +432,40 @@ module ActiveMerchant #:nodoc:
418
432
  [response[:approval_code], response[:txn_id]].join(';')
419
433
  end
420
434
 
421
- def truncate(value, size)
435
+ def url_encode_truncate(value, size)
422
436
  return nil unless value
423
437
 
424
- difference = value.force_encoding('iso-8859-1').length - value.length
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
425
457
 
426
- return value.to_s[0, (size - difference)]
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
427
469
  end
428
470
  end
429
471
  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
 
@@ -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]