activemerchant 1.121.0 → 1.125.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 +217 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -19
  5. data/lib/active_merchant/billing/credit_card.rb +13 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +24 -12
  8. data/lib/active_merchant/billing/gateway.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/adyen.rb +75 -27
  10. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  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 +6 -3
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
  17. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +41 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +12 -6
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
  22. data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
  23. data/lib/active_merchant/billing/gateways/elavon.rb +65 -30
  24. data/lib/active_merchant/billing/gateways/element.rb +22 -2
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +130 -26
  26. data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
  27. data/lib/active_merchant/billing/gateways/kushki.rb +30 -0
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +6 -3
  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 +22 -11
  34. data/lib/active_merchant/billing/gateways/nmi.rb +29 -10
  35. data/lib/active_merchant/billing/gateways/orbital.rb +46 -8
  36. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  37. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
  44. data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
  45. data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
  46. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  47. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  48. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  49. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  50. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  51. data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
  52. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  53. data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
  54. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
  55. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  56. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
  58. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  59. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
  61. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  62. data/lib/active_merchant/billing/response.rb +4 -0
  63. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  64. data/lib/active_merchant/billing.rb +1 -0
  65. data/lib/active_merchant/version.rb +1 -1
  66. metadata +13 -3
@@ -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)
@@ -300,7 +305,15 @@ module ActiveMerchant #:nodoc:
300
305
  add_merchant_description(xml, options)
301
306
  add_sales_slip_number(xml, options)
302
307
  add_airline_data(xml, options)
308
+ xml.target!
309
+ end
303
310
 
311
+ def build_adjust_request(money, authorization, options)
312
+ _, request_id = authorization.split(';')
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)
304
317
  xml.target!
305
318
  end
306
319
 
@@ -334,6 +347,7 @@ module ActiveMerchant #:nodoc:
334
347
 
335
348
  def build_purchase_request(money, payment_method_or_reference, options)
336
349
  xml = Builder::XmlMarkup.new indent: 2
350
+ add_customer_id(xml, options)
337
351
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
338
352
  add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
339
353
  add_decision_manager_fields(xml, options)
@@ -472,8 +486,8 @@ module ActiveMerchant #:nodoc:
472
486
 
473
487
  unless network_tokenization?(payment_method)
474
488
  xml.tag! 'businessRules' do
475
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
476
- 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'
477
491
  end
478
492
  end
479
493
  end
@@ -486,6 +500,8 @@ module ActiveMerchant #:nodoc:
486
500
  end
487
501
 
488
502
  def add_line_item_data(xml, options)
503
+ return unless options[:line_items]
504
+
489
505
  options[:line_items].each_with_index do |value, index|
490
506
  xml.tag! 'item', { 'id' => index } do
491
507
  xml.tag! 'unitPrice', localized_amount(value[:declared_value].to_i, options[:currency] || default_currency)
@@ -493,6 +509,8 @@ module ActiveMerchant #:nodoc:
493
509
  xml.tag! 'productCode', value[:code] || 'shipping_only'
494
510
  xml.tag! 'productName', value[:description]
495
511
  xml.tag! 'productSKU', value[:sku]
512
+ xml.tag! 'taxAmount', value[:tax_amount] if value[:tax_amount]
513
+ xml.tag! 'nationalTax', value[:national_tax] if value[:national_tax]
496
514
  end
497
515
  end
498
516
  end
@@ -508,13 +526,21 @@ module ActiveMerchant #:nodoc:
508
526
  end
509
527
 
510
528
  def add_merchant_descriptor(xml, options)
511
- return unless options[:merchant_descriptor]
529
+ return unless options[:merchant_descriptor] || options[:user_po] || options[:taxable]
512
530
 
513
531
  xml.tag! 'invoiceHeader' do
514
- xml.tag! 'merchantDescriptor', options[:merchant_descriptor]
532
+ xml.tag! 'merchantDescriptor', options[:merchant_descriptor] if options[:merchant_descriptor]
533
+ xml.tag! 'userPO', options[:user_po] if options[:user_po]
534
+ xml.tag! 'taxable', options[:taxable] if options[:taxable]
515
535
  end
516
536
  end
517
537
 
538
+ def add_customer_id(xml, options)
539
+ return unless options[:customer_id]
540
+
541
+ xml.tag! 'customerID', options[:customer_id]
542
+ end
543
+
518
544
  def add_merchant_description(xml, options)
519
545
  return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
520
546
 
@@ -585,7 +611,7 @@ module ActiveMerchant #:nodoc:
585
611
  xml.tag! 'accountNumber', creditcard.number
586
612
  xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
587
613
  xml.tag! 'expirationYear', format(creditcard.year, :four_digits)
588
- xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank?
614
+ xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv].to_s == 'true' || creditcard.verification_value.blank?
589
615
  xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym]
590
616
  end
591
617
  end
@@ -608,11 +634,12 @@ module ActiveMerchant #:nodoc:
608
634
  end
609
635
 
610
636
  def add_other_tax(xml, options)
611
- return unless options[:local_tax_amount] || options[:national_tax_amount]
637
+ return unless options[:local_tax_amount] || options[:national_tax_amount] || options[:national_tax_indicator]
612
638
 
613
639
  xml.tag! 'otherTax' do
614
640
  xml.tag! 'localTaxAmount', options[:local_tax_amount] if options[:local_tax_amount]
615
641
  xml.tag! 'nationalTaxAmount', options[:national_tax_amount] if options[:national_tax_amount]
642
+ xml.tag! 'nationalTaxIndicator', options[:national_tax_indicator] if options[:national_tax_indicator]
616
643
  end
617
644
  end
618
645
 
@@ -658,6 +685,13 @@ module ActiveMerchant #:nodoc:
658
685
  end
659
686
  end
660
687
 
688
+ def add_incremental_auth_service(xml, authorization, options)
689
+ xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
690
+ xml.tag! 'authRequestID', authorization
691
+ end
692
+ xml.tag! 'subsequentAuthReason', options[:auth_reason]
693
+ end
694
+
661
695
  def add_normalized_threeds_2_data(xml, payment_method, options)
662
696
  threeds_2_options = options[:three_d_secure]
663
697
  cc_brand = card_brand(payment_method).to_sym
@@ -860,6 +894,7 @@ module ActiveMerchant #:nodoc:
860
894
  else
861
895
  add_address(xml, payment_method_or_reference, options[:billing_address], options)
862
896
  add_address(xml, payment_method_or_reference, options[:shipping_address], options, true)
897
+ add_line_item_data(xml, options)
863
898
  add_purchase_data(xml, money, true, options)
864
899
  add_installments(xml, options)
865
900
  add_creditcard(xml, payment_method_or_reference)
@@ -4,7 +4,7 @@ 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
9
  self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal elo alia carnet]
10
10
 
@@ -26,6 +26,7 @@ module ActiveMerchant #:nodoc:
26
26
  def authorize(money, payment, options = {})
27
27
  post = {}
28
28
  add_auth_purchase_params(post, money, payment, 'authorize', options)
29
+ post[:card][:verify] = true if options[:verify].to_s == 'true'
29
30
 
30
31
  commit('authorize', post, options)
31
32
  end
@@ -52,10 +53,7 @@ module ActiveMerchant #:nodoc:
52
53
  end
53
54
 
54
55
  def verify(credit_card, options = {})
55
- MultiResponse.run(:use_first_response) do |r|
56
- r.process { authorize(100, credit_card, options) }
57
- r.process(:ignore_result) { void(r.authorization, options) }
58
- end
56
+ authorize(0, credit_card, options.merge(verify: 'true'))
59
57
  end
60
58
 
61
59
  def supports_scrubbing?
@@ -78,6 +76,7 @@ module ActiveMerchant #:nodoc:
78
76
  add_country(post, card, options)
79
77
  add_payer(post, card, options)
80
78
  add_card(post, card, action, options)
79
+ add_additional_data(post, options)
81
80
  post[:order_id] = options[:order_id] || generate_unique_id
82
81
  post[:description] = options[:description] if options[:description]
83
82
  end
@@ -87,6 +86,10 @@ module ActiveMerchant #:nodoc:
87
86
  post[:currency] = (options[:currency] || currency(money))
88
87
  end
89
88
 
89
+ def add_additional_data(post, options)
90
+ post[:additional_risk_data] = options[:additional_data]
91
+ end
92
+
90
93
  def add_country(post, card, options)
91
94
  return unless address = options[:billing_address] || options[:address]
92
95
 
@@ -109,6 +112,8 @@ module ActiveMerchant #:nodoc:
109
112
  post[:payer][:document] = options[:document] if options[:document]
110
113
  post[:payer][:document2] = options[:document2] if options[:document2]
111
114
  post[:payer][:user_reference] = options[:user_reference] if options[:user_reference]
115
+ post[:payer][:event_uuid] = options[:device_id] if options[:device_id]
116
+ post[:payer][:onboarding_ip_address] = options[:ip] if options[:ip]
112
117
  post[:payer][:address] = add_address(post, card, options)
113
118
  end
114
119
 
@@ -184,7 +189,7 @@ module ActiveMerchant #:nodoc:
184
189
  def success_from(action, response)
185
190
  return false unless response['status_code']
186
191
 
187
- %w[100 200 400 600].include? response['status_code'].to_s
192
+ %w[100 200 400 600 700].include? response['status_code'].to_s
188
193
  end
189
194
 
190
195
  def message_from(action, response)
@@ -228,6 +233,7 @@ module ActiveMerchant #:nodoc:
228
233
  'X-Date' => timestamp,
229
234
  'X-Login' => @options[:login],
230
235
  'X-Trans-Key' => @options[:trans_key],
236
+ 'X-Version' => '2.1',
231
237
  'Authorization' => signature(post, timestamp)
232
238
  }
233
239
  headers.merge('X-Idempotency-Key' => options[:idempotency_key]) if options[:idempotency_key]
@@ -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
 
@@ -0,0 +1,173 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class DecidirPlusGateway < Gateway
4
+ self.test_url = 'https://developers.decidir.com/api/v2'
5
+ self.live_url = 'https://live.decidir.com/api/v2'
6
+
7
+ self.supported_countries = ['AR']
8
+ self.default_currency = 'ARS'
9
+ self.supported_cardtypes = %i[visa master american_express discover]
10
+
11
+ self.homepage_url = 'http://decidir.com.ar/home'
12
+ self.display_name = 'Decidir Plus'
13
+
14
+ def initialize(options = {})
15
+ requires!(options, :public_key, :private_key)
16
+ super
17
+ end
18
+
19
+ def purchase(money, payment, options = {})
20
+ post = {}
21
+
22
+ add_payment(post, payment, options)
23
+ add_purchase_data(post, money, payment, options)
24
+
25
+ commit(:post, 'payments', post)
26
+ end
27
+
28
+ def refund(money, authorization, options = {})
29
+ post = {}
30
+ post[:amount] = money
31
+
32
+ commit(:post, "payments/#{add_reference(authorization)}/refunds")
33
+ end
34
+
35
+ def store(payment, options = {})
36
+ post = {}
37
+ add_payment(post, payment, options)
38
+
39
+ commit(:post, 'tokens', post)
40
+ end
41
+
42
+ def supports_scrubbing?
43
+ true
44
+ end
45
+
46
+ def scrub(transcript)
47
+ transcript.
48
+ gsub(%r((Apikey: )\w+), '\1[FILTERED]').
49
+ gsub(%r(("card_number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
50
+ gsub(%r(("security_code\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
51
+ end
52
+
53
+ private
54
+
55
+ def add_reference(authorization)
56
+ return unless authorization
57
+
58
+ authorization.split('|')[0]
59
+ end
60
+
61
+ def add_payment(post, payment, options = {})
62
+ if payment.is_a?(String)
63
+ token, bin = payment.split('|')
64
+ post[:token] = token
65
+ post[:bin] = bin
66
+ else
67
+ post[:card_number] = payment.number
68
+ post[:card_expiration_month] = payment.month.to_s.rjust(2, '0')
69
+ post[:card_expiration_year] = payment.year.to_s[-2..-1]
70
+ post[:security_code] = payment.verification_value.to_s
71
+ post[:card_holder_name] = payment.name
72
+ post[:card_holder_identification] = {}
73
+ post[:card_holder_identification][:type] = options[:dni]
74
+ post[:card_holder_identification][:number] = options[:card_holder_identification_number]
75
+ end
76
+ end
77
+
78
+ def add_purchase_data(post, money, payment, options = {})
79
+ post[:site_transaction_id] = options[:site_transaction_id] || SecureRandom.hex
80
+ post[:payment_method_id] = 1
81
+ post[:amount] = money
82
+ post[:currency] = options[:currency] || self.default_currency
83
+ post[:installments] = options[:installments] || 1
84
+ post[:payment_type] = options[:payment_type] || 'single'
85
+ add_sub_payments(post, options)
86
+ end
87
+
88
+ def add_sub_payments(post, options)
89
+ # sub_payments field is required for purchase transactions, even if empty
90
+ post[:sub_payments] = []
91
+
92
+ return unless sub_payments = options[:sub_payments]
93
+
94
+ sub_payments.each do |sub_payment|
95
+ sub_payment_hash = {
96
+ site_id: sub_payment[:site_id],
97
+ installments: sub_payment[:installments],
98
+ amount: sub_payment[:amount]
99
+ }
100
+ post[:sub_payments] << sub_payment_hash
101
+ end
102
+ end
103
+
104
+ def parse(body)
105
+ JSON.parse(body)
106
+ end
107
+
108
+ def commit(method, endpoint, parameters = {}, options = {})
109
+ begin
110
+ raw_response = ssl_request(method, url(endpoint), post_data(parameters), headers(endpoint))
111
+ response = parse(raw_response)
112
+ rescue ResponseError => e
113
+ raw_response = e.response.body
114
+ response = parse(raw_response)
115
+ end
116
+
117
+ Response.new(
118
+ success_from(response),
119
+ message_from(response),
120
+ response,
121
+ authorization: authorization_from(response),
122
+ avs_result: AVSResult.new(code: response['some_avs_response_key']),
123
+ cvv_result: CVVResult.new(response['some_cvv_response_key']),
124
+ test: test?,
125
+ error_code: error_code_from(response)
126
+ )
127
+ end
128
+
129
+ def headers(endpoint)
130
+ {
131
+ 'Content-Type' => 'application/json',
132
+ 'apikey' => endpoint == 'tokens' ? @options[:public_key] : @options[:private_key]
133
+ }
134
+ end
135
+
136
+ def url(action, options = {})
137
+ base_url = (test? ? test_url : live_url)
138
+
139
+ return "#{base_url}/#{action}"
140
+ end
141
+
142
+ def success_from(response)
143
+ response.dig('status') == 'approved' || response.dig('status') == 'active'
144
+ end
145
+
146
+ def message_from(response)
147
+ response.dig('status') || error_message(response) || response.dig('message')
148
+ end
149
+
150
+ def authorization_from(response)
151
+ return nil unless response.dig('id') || response.dig('bin')
152
+
153
+ "#{response.dig('id')}|#{response.dig('bin')}"
154
+ end
155
+
156
+ def post_data(parameters = {})
157
+ parameters.to_json
158
+ end
159
+
160
+ def error_code_from(response)
161
+ response.dig('error_type') unless success_from(response)
162
+ end
163
+
164
+ def error_message(response)
165
+ return error_code_from(response) unless validation_errors = response.dig('validation_errors')
166
+
167
+ validation_errors = validation_errors[0]
168
+
169
+ "#{validation_errors.dig('code')}: #{validation_errors.dig('param')}"
170
+ end
171
+ end
172
+ end
173
+ end
@@ -214,6 +214,7 @@ module ActiveMerchant #:nodoc:
214
214
  post[:metadata] = options[:metadata] if options[:metadata]
215
215
  post[:metadata] = {} if post[:metadata].nil?
216
216
  post[:metadata][:merchant_payment_code] = options[:order_id] if options[:order_id]
217
+ post[:processing_type] = options[:processing_type] if options[:processing_type]
217
218
  end
218
219
 
219
220
  def parse(body)
@@ -222,7 +223,8 @@ module ActiveMerchant #:nodoc:
222
223
 
223
224
  def commit(action, parameters)
224
225
  url = url_for((test? ? test_url : live_url), action, parameters)
225
- response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), { 'x-ebanx-client-user-agent': "ActiveMerchant/#{ActiveMerchant::VERSION}" }))
226
+
227
+ response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), headers(parameters)))
226
228
 
227
229
  success = success_from(action, response)
228
230
 
@@ -236,6 +238,19 @@ module ActiveMerchant #:nodoc:
236
238
  )
237
239
  end
238
240
 
241
+ def headers(params)
242
+ processing_type = params[:processing_type]
243
+ commit_headers = { 'x-ebanx-client-user-agent': "ActiveMerchant/#{ActiveMerchant::VERSION}" }
244
+
245
+ add_processing_type_to_commit_headers(commit_headers, processing_type) if processing_type == 'local'
246
+
247
+ commit_headers
248
+ end
249
+
250
+ def add_processing_type_to_commit_headers(commit_headers, processing_type)
251
+ commit_headers['x-ebanx-api-processing-type'] = processing_type
252
+ end
253
+
239
254
  def success_from(action, response)
240
255
  if %i[purchase capture refund].include?(action)
241
256
  response.try(:[], 'payment').try(:[], 'status') == 'CO'
@@ -201,8 +201,8 @@ module ActiveMerchant #:nodoc:
201
201
  private
202
202
 
203
203
  def add_invoice(xml, options)
204
- xml.ssl_invoice_number truncate((options[:order_id] || options[:invoice]), 25)
205
- 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)
206
206
  end
207
207
 
208
208
  def add_approval_code(xml, authorization)
@@ -219,8 +219,8 @@ module ActiveMerchant #:nodoc:
219
219
 
220
220
  add_verification_value(xml, creditcard) if creditcard.verification_value?
221
221
 
222
- xml.ssl_first_name truncate(creditcard.first_name, 20)
223
- 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)
224
224
  end
225
225
 
226
226
  def add_currency(xml, money, options)
@@ -240,7 +240,7 @@ module ActiveMerchant #:nodoc:
240
240
  end
241
241
 
242
242
  def add_customer_email(xml, options)
243
- 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])
244
244
  end
245
245
 
246
246
  def add_salestax(xml, options)
@@ -253,27 +253,27 @@ module ActiveMerchant #:nodoc:
253
253
  billing_address = options[:billing_address] || options[:address]
254
254
 
255
255
  if billing_address
256
- xml.ssl_avs_address truncate(billing_address[:address1], 30)
257
- xml.ssl_address2 truncate(billing_address[:address2], 30)
258
- xml.ssl_avs_zip truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
259
- xml.ssl_city truncate(billing_address[:city], 30)
260
- xml.ssl_state truncate(billing_address[:state], 10)
261
- xml.ssl_company truncate(billing_address[:company], 50)
262
- xml.ssl_phone truncate(billing_address[:phone], 20)
263
- 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)
264
264
  end
265
265
 
266
266
  if shipping_address = options[:shipping_address]
267
- xml.ssl_ship_to_address1 truncate(shipping_address[:address1], 30)
268
- xml.ssl_ship_to_address2 truncate(shipping_address[:address2], 30)
269
- xml.ssl_ship_to_city truncate(shipping_address[:city], 30)
270
- xml.ssl_ship_to_company truncate(shipping_address[:company], 50)
271
- xml.ssl_ship_to_country truncate(shipping_address[:country], 50)
272
- xml.ssl_ship_to_first_name truncate(shipping_address[:first_name], 20)
273
- xml.ssl_ship_to_last_name truncate(shipping_address[:last_name], 30)
274
- xml.ssl_ship_to_phone truncate(shipping_address[:phone], 10)
275
- xml.ssl_ship_to_state truncate(shipping_address[:state], 2)
276
- 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)
277
277
  end
278
278
  end
279
279
 
@@ -293,9 +293,12 @@ module ActiveMerchant #:nodoc:
293
293
  xml.ssl_cardholder_ip options[:ip] if options.has_key?(:ip)
294
294
  end
295
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
296
297
  def add_auth_purchase_params(xml, options)
297
298
  xml.ssl_dynamic_dba options[:dba] if options.has_key?(:dba)
298
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[:ssl_token]
299
302
  xml.ssl_customer_code options[:customer] if options.has_key?(:customer)
300
303
  xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number)
301
304
  xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
@@ -367,7 +370,7 @@ module ActiveMerchant #:nodoc:
367
370
 
368
371
  def merchant_initiated_unscheduled(options)
369
372
  return options[:merchant_initiated_unscheduled] if options[:merchant_initiated_unscheduled]
370
- 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'
371
374
  end
372
375
 
373
376
  def entry_mode(options)
@@ -390,15 +393,17 @@ module ActiveMerchant #:nodoc:
390
393
 
391
394
  def commit(request)
392
395
  request = "xmldata=#{request}".delete('&')
396
+ store_action = request.match?('CCGETTOKEN')
393
397
 
394
398
  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
399
+ response = hash_html_decode(response)
395
400
 
396
401
  Response.new(
397
402
  response[:result] == '0',
398
403
  response[:result_message] || response[:errorMessage],
399
404
  response,
400
405
  test: @options[:test] || test?,
401
- authorization: authorization_from(response),
406
+ authorization: authorization_from(response, store_action),
402
407
  error_code: response[:errorCode],
403
408
  avs_result: { code: response[:avs_response] },
404
409
  cvv_result: response[:cvv2_response],
@@ -413,7 +418,7 @@ module ActiveMerchant #:nodoc:
413
418
  def headers
414
419
  {
415
420
  'Accept' => 'application/xml',
416
- 'Content-type' => 'application/x-www-form-urlencoded'
421
+ 'Content-type' => 'application/x-www-form-urlencoded;charset=utf8'
417
422
  }
418
423
  end
419
424
 
@@ -424,16 +429,46 @@ module ActiveMerchant #:nodoc:
424
429
  response.deep_transform_keys { |key| key.gsub('ssl_', '').to_sym }
425
430
  end
426
431
 
427
- def authorization_from(response)
432
+ def authorization_from(response, store_action)
433
+ return response[:token] if store_action
434
+
428
435
  [response[:approval_code], response[:txn_id]].join(';')
429
436
  end
430
437
 
431
- def truncate(value, size)
438
+ def url_encode_truncate(value, size)
432
439
  return nil unless value
433
440
 
434
- difference = value.force_encoding('iso-8859-1').length - value.length
441
+ encoded = url_encode(value)
442
+
443
+ while encoded.length > size
444
+ value.chop!
445
+ encoded = url_encode(value)
446
+ end
447
+ encoded
448
+ end
449
+
450
+ def url_encode(value)
451
+ if value.is_a?(String)
452
+ encoded = CGI.escape(value)
453
+ encoded = encoded.tr('+', ' ') # don't encode spaces
454
+ encoded = encoded.gsub('%26', '%26amp;') # account for Elavon's weird '&' handling
455
+ encoded
456
+ else
457
+ value.to_s
458
+ end
459
+ end
435
460
 
436
- return value.delete('&"<>').to_s[0, (size - difference)]
461
+ def hash_html_decode(hash)
462
+ hash.each do |k, v|
463
+ if v.is_a?(String)
464
+ # decode all string params
465
+ v = v.gsub('&amp;amp;', '&amp;') # account for Elavon's weird '&' handling
466
+ hash[k] = CGI.unescape_html(v)
467
+ elsif v.is_a?(Hash)
468
+ hash_html_decode(v)
469
+ end
470
+ end
471
+ hash
437
472
  end
438
473
  end
439
474
  end
@@ -82,6 +82,19 @@ module ActiveMerchant #:nodoc:
82
82
  commit('CreditCardReturn', request, money)
83
83
  end
84
84
 
85
+ def credit(money, payment, options = {})
86
+ request = build_soap_request do |xml|
87
+ xml.CreditCardCredit(xmlns: 'https://transaction.elementexpress.com') do
88
+ add_credentials(xml)
89
+ add_payment_method(xml, payment)
90
+ add_transaction(xml, money, options)
91
+ add_terminal(xml, options)
92
+ end
93
+ end
94
+
95
+ commit('CreditCardCredit', request, money)
96
+ end
97
+
85
98
  def void(authorization, options = {})
86
99
  trans_id, trans_amount = split_authorization(authorization)
87
100
  options.merge!({ trans_id: trans_id, trans_amount: trans_amount, reversal_type: 'Full' })
@@ -186,15 +199,22 @@ module ActiveMerchant #:nodoc:
186
199
  xml.ReversalType options[:reversal_type] if options[:reversal_type]
187
200
  xml.TransactionID options[:trans_id] if options[:trans_id]
188
201
  xml.TransactionAmount amount(money.to_i) if money
189
- xml.MarketCode 'Default' if money
202
+ xml.MarketCode market_code(money, options) if options[:market_code] || money
190
203
  xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20)
191
-
204
+ xml.TicketNumber options[:ticket_number] if options[:ticket_number]
205
+ xml.MerchantSuppliedTransactionId options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id]
192
206
  xml.PaymentType options[:payment_type] if options[:payment_type]
193
207
  xml.SubmissionType options[:submission_type] if options[:submission_type]
194
208
  xml.DuplicateCheckDisableFlag options[:duplicate_check_disable_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_check_disable_flag].nil?
209
+ xml.DuplicateOverrideFlag options[:duplicate_override_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_override_flag].nil?
210
+ xml.MerchantDescriptor options[:merchant_descriptor] if options[:merchant_descriptor]
195
211
  end
196
212
  end
197
213
 
214
+ def market_code(money, options)
215
+ options[:market_code] || 'Default'
216
+ end
217
+
198
218
  def add_terminal(xml, options)
199
219
  xml.terminal do
200
220
  xml.TerminalID options[:terminal_id] || '01'