activemerchant 1.116.0 → 1.121.0

Sign up to get free protection for your applications and to get access to all the features.
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.',