activemerchant 1.116.0 → 1.121.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +148 -1
  3. data/README.md +4 -2
  4. data/lib/active_merchant/billing/check.rb +10 -0
  5. data/lib/active_merchant/billing/credit_card.rb +3 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +80 -15
  7. data/lib/active_merchant/billing/gateways/adyen.rb +29 -8
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +37 -1
  9. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -0
  10. data/lib/active_merchant/billing/gateways/blue_snap.rb +3 -1
  11. data/lib/active_merchant/billing/gateways/braintree_blue.rb +54 -7
  12. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  13. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -2
  14. data/lib/active_merchant/billing/gateways/credorax.rb +30 -14
  15. data/lib/active_merchant/billing/gateways/cyber_source.rb +51 -8
  16. data/lib/active_merchant/billing/gateways/d_local.rb +1 -1
  17. data/lib/active_merchant/billing/gateways/decidir.rb +22 -2
  18. data/lib/active_merchant/billing/gateways/elavon.rb +54 -2
  19. data/lib/active_merchant/billing/gateways/eway_rapid.rb +13 -0
  20. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +17 -6
  21. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  22. data/lib/active_merchant/billing/gateways/global_collect.rb +25 -6
  23. data/lib/active_merchant/billing/gateways/hps.rb +65 -2
  24. data/lib/active_merchant/billing/gateways/litle.rb +21 -5
  25. data/lib/active_merchant/billing/gateways/mercado_pago.rb +2 -2
  26. data/lib/active_merchant/billing/gateways/netbanx.rb +37 -2
  27. data/lib/active_merchant/billing/gateways/orbital.rb +178 -45
  28. data/lib/active_merchant/billing/gateways/payeezy.rb +53 -11
  29. data/lib/active_merchant/billing/gateways/payment_express.rb +10 -5
  30. data/lib/active_merchant/billing/gateways/paymentez.rb +21 -1
  31. data/lib/active_merchant/billing/gateways/paypal.rb +10 -2
  32. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -0
  33. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  34. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  35. data/lib/active_merchant/billing/gateways/pin.rb +11 -0
  36. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  37. data/lib/active_merchant/billing/gateways/redsys.rb +101 -5
  38. data/lib/active_merchant/billing/gateways/safe_charge.rb +39 -6
  39. data/lib/active_merchant/billing/gateways/stripe.rb +9 -9
  40. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +82 -25
  41. data/lib/active_merchant/billing/gateways/vpos.rb +177 -0
  42. data/lib/active_merchant/billing/gateways/worldpay.rb +31 -14
  43. data/lib/active_merchant/billing/response.rb +2 -1
  44. data/lib/active_merchant/version.rb +1 -1
  45. data/lib/certs/cacert.pem +1582 -2431
  46. metadata +5 -3
@@ -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
 
@@ -287,15 +301,21 @@ module ActiveMerchant #:nodoc:
287
301
  error_code = nil
288
302
  if error = response.dig('status_details', 'error')
289
303
  code = error.dig('reason', 'id')
290
- error_code = STANDARD_ERROR_CODE_MAPPING[code]
304
+ standard_error_code = STANDARD_ERROR_CODE_MAPPING[code]
305
+ error_code = "#{code}, #{standard_error_code}"
291
306
  error_code ||= error['type']
292
307
  elsif response['error_type']
293
308
  error_code = response['error_type'] if response['validation_errors']
294
- elsif error = response.dig('error')
309
+ elsif response.dig('error', 'validation_errors')
310
+ error = response.dig('error')
295
311
  validation_errors = error.dig('validation_errors', 0)
296
312
  code = validation_errors['code'] if validation_errors && validation_errors['code']
297
313
  param = validation_errors['param'] if validation_errors && validation_errors['param']
298
314
  error_code = "#{error['error_type']} | #{code} | #{param}" if error['error_type']
315
+ elsif error = response.dig('error')
316
+ code = error.dig('reason', 'id')
317
+ standard_error_code = STANDARD_ERROR_CODE_MAPPING[code]
318
+ error_code = "#{code}, #{standard_error_code}"
299
319
  end
300
320
 
301
321
  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)
@@ -285,10 +295,12 @@ module ActiveMerchant #:nodoc:
285
295
 
286
296
  def add_auth_purchase_params(xml, options)
287
297
  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)
298
+ xml.ssl_merchant_initiated_unscheduled merchant_initiated_unscheduled(options) if merchant_initiated_unscheduled(options)
289
299
  xml.ssl_customer_code options[:customer] if options.has_key?(:customer)
290
300
  xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number)
301
+ xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
291
302
  add_custom_fields(xml, options) if options[:custom_fields]
303
+ add_stored_credential(xml, options) if options[:stored_credential]
292
304
  end
293
305
 
294
306
  def add_custom_fields(xml, options)
@@ -337,6 +349,32 @@ module ActiveMerchant #:nodoc:
337
349
  }
338
350
  end
339
351
 
352
+ def add_stored_credential(xml, options)
353
+ network_transaction_id = options.dig(:stored_credential, :network_transaction_id)
354
+ case
355
+ when network_transaction_id.nil?
356
+ return
357
+ when network_transaction_id.to_s.include?('|')
358
+ oar_data, ps2000_data = options[:stored_credential][:network_transaction_id].split('|')
359
+ xml.ssl_oar_data oar_data unless oar_data.nil? || oar_data.empty?
360
+ xml.ssl_ps2000_data ps2000_data unless ps2000_data.nil? || ps2000_data.empty?
361
+ when network_transaction_id.to_s.length > 22
362
+ xml.ssl_oar_data options.dig(:stored_credential, :network_transaction_id)
363
+ else
364
+ xml.ssl_ps2000_data options.dig(:stored_credential, :network_transaction_id)
365
+ end
366
+ end
367
+
368
+ def merchant_initiated_unscheduled(options)
369
+ 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'
371
+ end
372
+
373
+ def entry_mode(options)
374
+ return options[:entry_mode] if options[:entry_mode]
375
+ return 12 if options[:stored_credential]
376
+ end
377
+
340
378
  def build_xml_request
341
379
  builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
342
380
  xml.txn do
@@ -352,6 +390,7 @@ module ActiveMerchant #:nodoc:
352
390
 
353
391
  def commit(request)
354
392
  request = "xmldata=#{request}".delete('&')
393
+
355
394
  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
356
395
 
357
396
  Response.new(
@@ -362,10 +401,15 @@ module ActiveMerchant #:nodoc:
362
401
  authorization: authorization_from(response),
363
402
  error_code: response[:errorCode],
364
403
  avs_result: { code: response[:avs_response] },
365
- cvv_result: response[:cvv2_response]
404
+ cvv_result: response[:cvv2_response],
405
+ network_transaction_id: build_network_transaction_id(response)
366
406
  )
367
407
  end
368
408
 
409
+ def build_network_transaction_id(response)
410
+ "#{response[:oar_data]}|#{response[:ps2000_data]}"
411
+ end
412
+
369
413
  def headers
370
414
  {
371
415
  'Accept' => 'application/xml',
@@ -383,6 +427,14 @@ module ActiveMerchant #:nodoc:
383
427
  def authorization_from(response)
384
428
  [response[:approval_code], response[:txn_id]].join(';')
385
429
  end
430
+
431
+ def truncate(value, size)
432
+ return nil unless value
433
+
434
+ difference = value.force_encoding('iso-8859-1').length - value.length
435
+
436
+ return value.delete('&"<>').to_s[0, (size - difference)]
437
+ end
386
438
  end
387
439
  end
388
440
  end
@@ -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]
@@ -32,6 +32,7 @@ module ActiveMerchant #:nodoc:
32
32
  add_address(post, payment, options)
33
33
  add_creator_info(post, options)
34
34
  add_fraud_fields(post, options)
35
+ add_external_cardholder_authentication_data(post, options)
35
36
  commit(:authorize, post)
36
37
  end
37
38
 
@@ -200,21 +201,21 @@ module ActiveMerchant #:nodoc:
200
201
  shipping_address = options[:shipping_address]
201
202
  if billing_address = options[:billing_address] || options[:address]
202
203
  post['order']['customer']['billingAddress'] = {
203
- 'street' => billing_address[:address1],
204
- 'additionalInfo' => billing_address[:address2],
204
+ 'street' => truncate(billing_address[:address1], 50),
205
+ 'additionalInfo' => truncate(billing_address[:address2], 50),
205
206
  'zip' => billing_address[:zip],
206
207
  'city' => billing_address[:city],
207
- 'state' => billing_address[:state],
208
+ 'state' => truncate(billing_address[:state], 35),
208
209
  'countryCode' => billing_address[:country]
209
210
  }
210
211
  end
211
212
  if shipping_address
212
213
  post['order']['customer']['shippingAddress'] = {
213
- 'street' => shipping_address[:address1],
214
- 'additionalInfo' => shipping_address[:address2],
214
+ 'street' => truncate(shipping_address[:address1], 50),
215
+ 'additionalInfo' => truncate(shipping_address[:address2], 50),
215
216
  'zip' => shipping_address[:zip],
216
217
  'city' => shipping_address[:city],
217
- 'state' => shipping_address[:state],
218
+ 'state' => truncate(shipping_address[:state], 35),
218
219
  'countryCode' => shipping_address[:country]
219
220
  }
220
221
  post['order']['customer']['shippingAddress']['name'] = {
@@ -232,6 +233,24 @@ module ActiveMerchant #:nodoc:
232
233
  post['fraudFields'] = fraud_fields unless fraud_fields.empty?
233
234
  end
234
235
 
236
+ def add_external_cardholder_authentication_data(post, options)
237
+ return unless threeds_2_options = options[:three_d_secure]
238
+
239
+ authentication_data = {}
240
+ authentication_data[:acsTransactionId] = threeds_2_options[:acs_transaction_id] if threeds_2_options[:acs_transaction_id]
241
+ authentication_data[:cavv] = threeds_2_options[:cavv] if threeds_2_options[:cavv]
242
+ authentication_data[:cavvAlgorithm] = threeds_2_options[:cavv_algorithm] if threeds_2_options[:cavv_algorithm]
243
+ authentication_data[:directoryServerTransactionId] = threeds_2_options[:ds_transaction_id] if threeds_2_options[:ds_transaction_id]
244
+ authentication_data[:eci] = threeds_2_options[:eci] if threeds_2_options[:eci]
245
+ authentication_data[:threeDSecureVersion] = threeds_2_options[:version] if threeds_2_options[:version]
246
+ authentication_data[:validationResult] = threeds_2_options[:authentication_response_status] if threeds_2_options[:authentication_response_status]
247
+ authentication_data[:xid] = threeds_2_options[:xid] if threeds_2_options[:xid]
248
+
249
+ post['cardPaymentMethodSpecificInput'] ||= {}
250
+ post['cardPaymentMethodSpecificInput']['threeDSecure'] ||= {}
251
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['externalCardholderAuthenticationData'] = authentication_data unless authentication_data.empty?
252
+ end
253
+
235
254
  def add_number_of_installments(post, options)
236
255
  post['order']['additionalInput']['numberOfInstallments'] = options[:number_of_installments] if options[:number_of_installments]
237
256
  end
@@ -39,6 +39,7 @@ module ActiveMerchant #:nodoc:
39
39
  add_descriptor_name(xml, options)
40
40
  add_card_or_token_payment(xml, card_or_token, options)
41
41
  add_three_d_secure(xml, card_or_token, options)
42
+ add_stored_credentials(xml, options)
42
43
  end
43
44
  end
44
45
 
@@ -52,6 +53,8 @@ module ActiveMerchant #:nodoc:
52
53
  def purchase(money, payment_method, options = {})
53
54
  if payment_method.is_a?(Check)
54
55
  commit_check_sale(money, payment_method, options)
56
+ elsif options.dig(:stored_credential, :reason_type) == 'recurring'
57
+ commit_recurring_billing_sale(money, payment_method, options)
55
58
  else
56
59
  commit_credit_sale(money, payment_method, options)
57
60
  end
@@ -67,6 +70,15 @@ module ActiveMerchant #:nodoc:
67
70
  end
68
71
  end
69
72
 
73
+ def credit(money, payment_method, options = {})
74
+ commit('CreditReturn') do |xml|
75
+ add_amount(xml, money)
76
+ add_allow_dup(xml)
77
+ add_card_or_token_payment(xml, payment_method, options)
78
+ add_details(xml, options)
79
+ end
80
+ end
81
+
70
82
  def verify(card_or_token, options = {})
71
83
  commit('CreditAccountVerify') do |xml|
72
84
  add_card_or_token_customer_data(xml, card_or_token, options)
@@ -122,6 +134,21 @@ module ActiveMerchant #:nodoc:
122
134
  add_descriptor_name(xml, options)
123
135
  add_card_or_token_payment(xml, card_or_token, options)
124
136
  add_three_d_secure(xml, card_or_token, options)
137
+ add_stored_credentials(xml, options)
138
+ end
139
+ end
140
+
141
+ def commit_recurring_billing_sale(money, card_or_token, options)
142
+ commit('RecurringBilling') do |xml|
143
+ add_amount(xml, money)
144
+ add_allow_dup(xml)
145
+ add_card_or_token_customer_data(xml, card_or_token, options)
146
+ add_details(xml, options)
147
+ add_descriptor_name(xml, options)
148
+ add_card_or_token_payment(xml, card_or_token, options)
149
+ add_three_d_secure(xml, card_or_token, options)
150
+ add_stored_credentials(xml, options)
151
+ add_stored_credentials_for_recurring_billing(xml, options)
125
152
  end
126
153
  end
127
154
 
@@ -148,7 +175,7 @@ module ActiveMerchant #:nodoc:
148
175
  xml.hps :CardHolderAddr, billing_address[:address1] if billing_address[:address1]
149
176
  xml.hps :CardHolderCity, billing_address[:city] if billing_address[:city]
150
177
  xml.hps :CardHolderState, billing_address[:state] if billing_address[:state]
151
- xml.hps :CardHolderZip, billing_address[:zip] if billing_address[:zip]
178
+ xml.hps :CardHolderZip, alphanumeric_zip(billing_address[:zip]) if billing_address[:zip]
152
179
  end
153
180
  end
154
181
  end
@@ -210,7 +237,7 @@ module ActiveMerchant #:nodoc:
210
237
  def add_details(xml, options)
211
238
  xml.hps :AdditionalTxnFields do
212
239
  xml.hps :Description, options[:description] if options[:description]
213
- xml.hps :InvoiceNbr, options[:order_id] if options[:order_id]
240
+ xml.hps :InvoiceNbr, options[:order_id][0..59] if options[:order_id]
214
241
  xml.hps :CustomerID, options[:customer_id] if options[:customer_id]
215
242
  end
216
243
  end
@@ -256,6 +283,38 @@ module ActiveMerchant #:nodoc:
256
283
  end
257
284
  end
258
285
 
286
+ # We do not currently support installments on this gateway.
287
+ # The HPS gateway treats recurring transactions as a seperate transaction type
288
+ def add_stored_credentials(xml, options)
289
+ return unless options[:stored_credential]
290
+
291
+ xml.hps :CardOnFileData do
292
+ if options[:stored_credential][:initiator] == 'customer'
293
+ xml.hps :CardOnFile, 'C'
294
+ elsif options[:stored_credential][:initiator] == 'merchant'
295
+ xml.hps :CardOnFile, 'M'
296
+ else
297
+ return
298
+ end
299
+
300
+ if options[:stored_credential][:network_transaction_id]
301
+ xml.hps :CardBrandTxnId, options[:stored_credential][:network_transaction_id]
302
+ else
303
+ return
304
+ end
305
+ end
306
+ end
307
+
308
+ def add_stored_credentials_for_recurring_billing(xml, options)
309
+ xml.hps :RecurringData do
310
+ if options[:stored_credential][:reason_type] = 'recurring'
311
+ xml.hps :OneTime, 'N'
312
+ else
313
+ xml.hps :OneTime, 'Y'
314
+ end
315
+ end
316
+ end
317
+
259
318
  def strip_leading_zero(value)
260
319
  return value unless value[0] == '0'
261
320
 
@@ -380,6 +439,10 @@ module ActiveMerchant #:nodoc:
380
439
  @options[:secret_api_key]&.include?('_cert_')
381
440
  end
382
441
 
442
+ def alphanumeric_zip(zip)
443
+ zip.gsub(/[^0-9a-z]/i, '')
444
+ end
445
+
383
446
  ISSUER_MESSAGES = {
384
447
  '13' => 'Must be greater than or equal 0.',
385
448
  '14' => 'The card number is incorrect.',