activemerchant 1.126.0 → 1.129.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +241 -0
  3. data/lib/active_merchant/billing/check.rb +40 -8
  4. data/lib/active_merchant/billing/credit_card.rb +28 -1
  5. data/lib/active_merchant/billing/credit_card_methods.rb +79 -23
  6. data/lib/active_merchant/billing/gateways/adyen.rb +67 -8
  7. data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
  8. data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -4
  10. data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
  12. data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
  13. data/lib/active_merchant/billing/gateways/borgun.rb +56 -16
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +64 -17
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
  17. data/lib/active_merchant/billing/gateways/checkout_v2.rb +228 -57
  18. data/lib/active_merchant/billing/gateways/commerce_hub.rb +361 -0
  19. data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
  20. data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +100 -26
  22. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +456 -0
  23. data/lib/active_merchant/billing/gateways/d_local.rb +44 -5
  24. data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
  25. data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
  26. data/lib/active_merchant/billing/gateways/element.rb +21 -1
  27. data/lib/active_merchant/billing/gateways/global_collect.rb +73 -22
  28. data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
  29. data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
  30. data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
  31. data/lib/active_merchant/billing/gateways/litle.rb +25 -5
  32. data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
  33. data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
  34. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
  35. data/lib/active_merchant/billing/gateways/monei.rb +2 -0
  36. data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
  37. data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
  38. data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
  39. data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
  40. data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
  41. data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
  42. data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  45. data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
  46. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -0
  47. data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
  48. data/lib/active_merchant/billing/gateways/priority.rb +29 -6
  49. data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
  50. data/lib/active_merchant/billing/gateways/reach.rb +277 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +9 -5
  52. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
  53. data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
  54. data/lib/active_merchant/billing/gateways/shift4.rb +342 -0
  55. data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
  56. data/lib/active_merchant/billing/gateways/stripe.rb +21 -1
  57. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
  58. data/lib/active_merchant/billing/gateways/tns.rb +2 -5
  59. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
  60. data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
  61. data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
  62. data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
  63. data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
  64. data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
  65. data/lib/active_merchant/billing/gateways/worldpay.rb +117 -9
  66. data/lib/active_merchant/billing/response.rb +15 -1
  67. data/lib/active_merchant/connection.rb +0 -2
  68. data/lib/active_merchant/country.rb +1 -0
  69. data/lib/active_merchant/errors.rb +4 -1
  70. data/lib/active_merchant/version.rb +1 -1
  71. metadata +24 -3
@@ -42,6 +42,7 @@ module ActiveMerchant #:nodoc:
42
42
  add_error_on_requires_action(post, options)
43
43
  add_fulfillment_date(post, options)
44
44
  request_three_d_secure(post, options)
45
+ add_level_three(post, options)
45
46
 
46
47
  CREATE_INTENT_ATTRIBUTES.each do |attribute|
47
48
  add_whitelisted_attribute(post, options, attribute)
@@ -55,6 +56,11 @@ module ActiveMerchant #:nodoc:
55
56
  commit(:get, "payment_intents/#{intent_id}", nil, options)
56
57
  end
57
58
 
59
+ def create_test_customer
60
+ response = api_request(:post, 'customers')
61
+ response['id']
62
+ end
63
+
58
64
  def confirm_intent(intent_id, payment_method, options = {})
59
65
  post = {}
60
66
  result = add_payment_method_token(post, payment_method, options)
@@ -116,23 +122,27 @@ module ActiveMerchant #:nodoc:
116
122
  end
117
123
 
118
124
  def create_setup_intent(payment_method, options = {})
119
- post = {}
120
- add_customer(post, options)
121
- result = add_payment_method_token(post, payment_method, options)
122
- return result if result.is_a?(ActiveMerchant::Billing::Response)
125
+ MultiResponse.run do |r|
126
+ r.process do
127
+ post = {}
128
+ add_customer(post, options)
129
+ result = add_payment_method_token(post, payment_method, options, r)
130
+ return result if result.is_a?(ActiveMerchant::Billing::Response)
123
131
 
124
- add_metadata(post, options)
125
- add_return_url(post, options)
126
- add_fulfillment_date(post, options)
127
- request_three_d_secure(post, options)
128
- post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
129
- post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
130
- post[:description] = options[:description] if options[:description]
132
+ add_metadata(post, options)
133
+ add_return_url(post, options)
134
+ add_fulfillment_date(post, options)
135
+ request_three_d_secure(post, options)
136
+ post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
137
+ post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
138
+ post[:description] = options[:description] if options[:description]
131
139
 
132
- commit(:post, 'setup_intents', post, options)
140
+ commit(:post, 'setup_intents', post, options)
141
+ end
142
+ end
133
143
  end
134
144
 
135
- def retrieve_setup_intent(setup_intent_id)
145
+ def retrieve_setup_intent(setup_intent_id, options = {})
136
146
  # Retrieving a setup_intent passing 'expand[]=latest_attempt' allows the caller to
137
147
  # check for a network_transaction_id and ds_transaction_id
138
148
  # eg (latest_attempt -> payment_method_details -> card -> network_transaction_id)
@@ -140,7 +150,7 @@ module ActiveMerchant #:nodoc:
140
150
  # Being able to retrieve these fields enables payment flows that rely on MIT exemptions, e.g: off_session
141
151
  commit(:post, "setup_intents/#{setup_intent_id}", {
142
152
  'expand[]': 'latest_attempt'
143
- }, {})
153
+ }, options)
144
154
  end
145
155
 
146
156
  def authorize(money, payment_method, options = {})
@@ -231,7 +241,7 @@ module ActiveMerchant #:nodoc:
231
241
  end
232
242
 
233
243
  def verify(payment_method, options = {})
234
- create_setup_intent(payment_method, options.merge!(confirm: true))
244
+ create_setup_intent(payment_method, options.merge!({ confirm: true, verify: true }))
235
245
  end
236
246
 
237
247
  def setup_purchase(money, options = {})
@@ -284,6 +294,19 @@ module ActiveMerchant #:nodoc:
284
294
  post[:metadata][:event_type] = options[:event_type] if options[:event_type]
285
295
  end
286
296
 
297
+ def add_level_three(post, options = {})
298
+ level_three = {}
299
+
300
+ level_three[:merchant_reference] = options[:merchant_reference] if options[:merchant_reference]
301
+ level_three[:customer_reference] = options[:customer_reference] if options[:customer_reference]
302
+ level_three[:shipping_address_zip] = options[:shipping_address_zip] if options[:shipping_address_zip]
303
+ level_three[:shipping_from_zip] = options[:shipping_from_zip] if options[:shipping_from_zip]
304
+ level_three[:shipping_amount] = options[:shipping_amount] if options[:shipping_amount]
305
+ level_three[:line_items] = options[:line_items] if options[:line_items]
306
+
307
+ post[:level3] = level_three unless level_three.empty?
308
+ end
309
+
287
310
  def add_return_url(post, options)
288
311
  return unless options[:confirm]
289
312
 
@@ -291,7 +314,7 @@ module ActiveMerchant #:nodoc:
291
314
  post[:return_url] = options[:return_url] if options[:return_url]
292
315
  end
293
316
 
294
- def add_payment_method_token(post, payment_method, options)
317
+ def add_payment_method_token(post, payment_method, options, responses = [])
295
318
  case payment_method
296
319
  when StripePaymentToken
297
320
  post[:payment_method_data] = {
@@ -304,7 +327,9 @@ module ActiveMerchant #:nodoc:
304
327
  when String
305
328
  extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
306
329
  when ActiveMerchant::Billing::CreditCard
307
- get_payment_method_data_from_card(post, payment_method, options)
330
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) if options[:verify]
331
+
332
+ get_payment_method_data_from_card(post, payment_method, options, responses)
308
333
  end
309
334
  end
310
335
 
@@ -333,6 +358,7 @@ module ActiveMerchant #:nodoc:
333
358
  cryptogram: payment.payment_cryptogram
334
359
  }
335
360
  }
361
+ add_billing_address_for_card_tokenization(post, options) if %i(apple_pay android_pay).include?(tokenization_method)
336
362
  token_response = api_request(:post, 'tokens', post, options)
337
363
  success = token_response['error'].nil?
338
364
  if success && token_response['id']
@@ -344,16 +370,17 @@ module ActiveMerchant #:nodoc:
344
370
  end
345
371
  end
346
372
 
347
- def get_payment_method_data_from_card(post, payment_method, options)
348
- return create_payment_method_and_extract_token(post, payment_method, options) unless off_session_request?(options)
373
+ def get_payment_method_data_from_card(post, payment_method, options, responses)
374
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options)
349
375
 
350
376
  post[:payment_method_data] = add_payment_method_data(payment_method, options)
351
377
  end
352
378
 
353
- def create_payment_method_and_extract_token(post, payment_method, options)
379
+ def create_payment_method_and_extract_token(post, payment_method, options, responses)
354
380
  payment_method_response = create_payment_method(payment_method, options)
355
381
  return payment_method_response if payment_method_response.failure?
356
382
 
383
+ responses << payment_method_response
357
384
  add_payment_method_token(post, payment_method_response.params['id'], options)
358
385
  end
359
386
 
@@ -448,9 +475,22 @@ module ActiveMerchant #:nodoc:
448
475
  post
449
476
  end
450
477
 
478
+ def add_billing_address_for_card_tokenization(post, options = {})
479
+ return unless (billing = options[:billing_address] || options[:address])
480
+
481
+ post[:card][:address_city] = billing[:city] if billing[:city]
482
+ post[:card][:address_country] = billing[:country] if billing[:country]
483
+ post[:card][:address_line1] = billing[:address1] if billing[:address1]
484
+ post[:card][:address_line2] = billing[:address2] if billing[:address2]
485
+ post[:card][:address_zip] = billing[:zip] if billing[:zip]
486
+ post[:card][:address_state] = billing[:state] if billing[:state]
487
+ end
488
+
451
489
  def add_billing_address(post, options = {})
452
490
  return unless billing = options[:billing_address] || options[:address]
453
491
 
492
+ email = billing[:email] || options[:email]
493
+
454
494
  post[:billing_details] = {}
455
495
  post[:billing_details][:address] = {}
456
496
  post[:billing_details][:address][:city] = billing[:city] if billing[:city]
@@ -459,7 +499,7 @@ module ActiveMerchant #:nodoc:
459
499
  post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
460
500
  post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
461
501
  post[:billing_details][:address][:state] = billing[:state] if billing[:state]
462
- post[:billing_details][:email] = billing[:email] if billing[:email]
502
+ post[:billing_details][:email] = email if email
463
503
  post[:billing_details][:name] = billing[:name] if billing[:name]
464
504
  post[:billing_details][:phone] = billing[:phone] if billing[:phone]
465
505
  end
@@ -501,7 +541,7 @@ module ActiveMerchant #:nodoc:
501
541
  def success_from(response, options)
502
542
  if response['status'] == 'requires_action' && !options[:execute_threed]
503
543
  response['error'] = {}
504
- response['error']['message'] = 'Received unexpected 3DS authentication response. Use the execute_threed option to initiate a proper 3DS flow.'
544
+ response['error']['message'] = 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.'
505
545
  return false
506
546
  end
507
547
 
@@ -8,13 +8,10 @@ module ActiveMerchant
8
8
  VERSION = '52'
9
9
 
10
10
  self.live_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
11
- self.test_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
12
-
13
11
  self.live_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
14
- self.test_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
15
-
16
12
  self.live_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
17
- self.test_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
13
+
14
+ self.test_url = "https://secure.uat.tnspayments.com/api/rest/version/#{VERSION}/"
18
15
 
19
16
  self.display_name = 'TNS'
20
17
  self.homepage_url = 'http://www.tnsi.com/'
@@ -482,7 +482,7 @@ module ActiveMerchant #:nodoc:
482
482
 
483
483
  doc = Nokogiri::XML::Document.parse(request)
484
484
  merc_nodeset = doc.xpath('//v1:merc', 'v1' => V1_NAMESPACE)
485
- merc_nodeset.after "<tranCode>#{TRANSACTION_CODES[action]}</tranCode>"
485
+ merc_nodeset.after "<v1:tranCode>#{TRANSACTION_CODES[action]}</v1:tranCode>"
486
486
  doc.root.to_xml
487
487
  end
488
488
 
@@ -248,6 +248,12 @@ module ActiveMerchant #:nodoc:
248
248
  commit(action, parameters)
249
249
  end
250
250
 
251
+ def verify(credit_card, options = {})
252
+ parameters = {}
253
+ add_creditcard(parameters, credit_card)
254
+ commit('verify', parameters)
255
+ end
256
+
251
257
  # recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's
252
258
  # hosted customer billing info database.
253
259
  #
@@ -476,9 +482,14 @@ module ActiveMerchant #:nodoc:
476
482
  end
477
483
 
478
484
  def authorization_from(action, data)
479
- authorization = data['transid']
480
- authorization = "#{authorization}|#{action}" if authorization && VOIDABLE_ACTIONS.include?(action)
481
- authorization
485
+ case action
486
+ when 'store'
487
+ data['billingid']
488
+ when *VOIDABLE_ACTIONS
489
+ "#{data['transid']}|#{action}"
490
+ else
491
+ data['transid']
492
+ end
482
493
  end
483
494
 
484
495
  def split_authorization(authorization)
@@ -15,15 +15,24 @@ module ActiveMerchant
15
15
  self.homepage_url = 'http://vancopayments.com/'
16
16
  self.display_name = 'Vanco Payment Solutions'
17
17
 
18
+ SECONDS_PER_DAY = 3600 * 24
19
+ BUFFER_TIME_IN_SECS = 60 * 3
20
+
18
21
  def initialize(options = {})
19
22
  requires!(options, :user_id, :password, :client_id)
20
23
  super
21
24
  end
22
25
 
23
26
  def purchase(money, payment_method, options = {})
24
- MultiResponse.run do |r|
25
- r.process { login }
26
- r.process { commit(purchase_request(money, payment_method, r.params['response_sessionid'], options), :response_transactionref) }
27
+ moment_less_than_24_hours_ago = Time.now - SECONDS_PER_DAY - BUFFER_TIME_IN_SECS
28
+
29
+ if options[:session_id] && options[:session_id][:created_at] >= moment_less_than_24_hours_ago
30
+ commit(purchase_request(money, payment_method, options[:session_id][:id], options), :response_transactionref)
31
+ else
32
+ MultiResponse.run do |r|
33
+ r.process { login }
34
+ r.process { commit(purchase_request(money, payment_method, r.params['response_sessionid'], options), :response_transactionref) }
35
+ end
27
36
  end
28
37
  end
29
38
 
@@ -143,7 +143,7 @@ module ActiveMerchant #:nodoc:
143
143
  end
144
144
 
145
145
  def generate_purchase_number_stamp
146
- (Time.now.to_f.round(2) * 100).to_i.to_s
146
+ Time.now.to_f.to_s.delete('.')[1..10] + rand(99).to_s
147
147
  end
148
148
 
149
149
  def commit(action, params, options = {})
@@ -27,6 +27,7 @@ module ActiveMerchant #:nodoc:
27
27
  requires!(options, :private_key, :public_key)
28
28
  @private_key = options[:private_key]
29
29
  @public_key = options[:public_key]
30
+ @encryption_key = OpenSSL::PKey::RSA.new(options[:encryption_key]) if options[:encryption_key]
30
31
  @shop_process_id = options[:shop_process_id] || SecureRandom.random_number(10**15)
31
32
  super
32
33
  end
@@ -114,15 +115,15 @@ module ActiveMerchant #:nodoc:
114
115
  transcript.encode('UTF-8', 'binary', undef: :replace, replace: '')
115
116
  end
116
117
 
117
- private
118
-
119
118
  # Required to encrypt PAN data.
120
119
  def one_time_public_key
121
120
  token = generate_token('get_encription_public_key', @public_key)
122
121
  response = commit(:pci_encryption_key, token: token)
123
- OpenSSL::PKey::RSA.new(response.params['encryption_key'])
122
+ response.params['encryption_key']
124
123
  end
125
124
 
125
+ private
126
+
126
127
  def generate_token(*elements)
127
128
  Digest::MD5.hexdigest(@private_key + elements.join)
128
129
  end
@@ -138,7 +139,9 @@ module ActiveMerchant #:nodoc:
138
139
 
139
140
  payload = { card_number: card_number, 'cvv': cvv }.to_json
140
141
 
141
- post[:card_encrypted_data] = JWE.encrypt(payload, one_time_public_key)
142
+ encryption_key = @encryption_key || OpenSSL::PKey::RSA.new(one_time_public_key)
143
+
144
+ post[:card_encrypted_data] = JWE.encrypt(payload, encryption_key)
142
145
  post[:card_month_expiration] = format(payment.month, :two_digits)
143
146
  post[:card_year_expiration] = format(payment.year, :two_digits)
144
147
  end
@@ -61,12 +61,16 @@ module ActiveMerchant #:nodoc:
61
61
  end
62
62
 
63
63
  def refund(money, authorization, options = {})
64
- post = { amount_in_cents: amount(money).to_i, transaction_id: authorization.to_s }
65
- commit('refund', post, '/refunds_sync')
64
+ # post = { amount_in_cents: amount(money).to_i, transaction_id: authorization.to_s }
65
+ # commit('refund', post, '/refunds_sync')
66
+
67
+ # All refunds will instead be voided. This is temporary.
68
+ void(authorization, options, money)
66
69
  end
67
70
 
68
- def void(authorization, options = {})
69
- commit('void', {}, "/transactions/#{authorization}/void_sync")
71
+ def void(authorization, options = {}, money = nil)
72
+ post = money ? { amount_in_cents: amount(money).to_i } : {}
73
+ commit('void', post, "/transactions/#{authorization}/void_sync")
70
74
  end
71
75
 
72
76
  def supports_scrubbing?
@@ -144,6 +144,11 @@ module ActiveMerchant #:nodoc:
144
144
  store_request(credit_card, options)
145
145
  end
146
146
 
147
+ def inquire(authorization, options = {})
148
+ order_id = order_id_from_authorization(authorization.to_s) || options[:order_id]
149
+ commit('direct_inquiry', build_order_inquiry_request(order_id, options), :ok, options)
150
+ end
151
+
147
152
  def supports_scrubbing
148
153
  true
149
154
  end
@@ -237,6 +242,7 @@ module ActiveMerchant #:nodoc:
237
242
  add_sub_merchant_data(xml, options[:sub_merchant_data]) if options[:sub_merchant_data]
238
243
  add_hcg_additional_data(xml, options) if options[:hcg_additional_data]
239
244
  add_instalments_data(xml, options) if options[:instalments]
245
+ add_additional_data(xml, money, options) if options[:level_2_data] || options[:level_3_data]
240
246
  add_moto_flag(xml, options) if options.dig(:metadata, :manual_entry)
241
247
  add_additional_3ds_data(xml, options) if options[:execute_threed] && options[:three_ds_version] && options[:three_ds_version] =~ /^2/
242
248
  add_3ds_exemption(xml, options) if options[:exemption_type]
@@ -245,6 +251,91 @@ module ActiveMerchant #:nodoc:
245
251
  end
246
252
  end
247
253
 
254
+ def add_additional_data(xml, amount, options)
255
+ level_two_data = options[:level_2_data] || {}
256
+ level_three_data = options[:level_3_data] || {}
257
+ level_two_and_three_data = level_two_data.merge(level_three_data).symbolize_keys
258
+
259
+ xml.branchSpecificExtension do
260
+ xml.purchase do
261
+ add_level_two_and_three_data(xml, amount, level_two_and_three_data)
262
+ end
263
+ end
264
+ end
265
+
266
+ def add_level_two_and_three_data(xml, amount, data)
267
+ xml.invoiceReferenceNumber data[:invoice_reference_number] if data.include?(:invoice_reference_number)
268
+ xml.customerReference data[:customer_reference] if data.include?(:customer_reference)
269
+ xml.cardAcceptorTaxId data[:card_acceptor_tax_id] if data.include?(:card_acceptor_tax_id)
270
+
271
+ {
272
+ sales_tax: 'salesTax',
273
+ discount_amount: 'discountAmount',
274
+ shipping_amount: 'shippingAmount',
275
+ duty_amount: 'dutyAmount'
276
+ }.each do |key, tag|
277
+ next unless data.include?(key)
278
+
279
+ xml.tag! tag do
280
+ data_amount = data[key].symbolize_keys
281
+ add_amount(xml, data_amount[:amount].to_i, data_amount)
282
+ end
283
+ end
284
+
285
+ xml.discountName data[:discount_name] if data.include?(:discount_name)
286
+ xml.discountCode data[:discount_code] if data.include?(:discount_code)
287
+
288
+ add_date_element(xml, 'shippingDate', data[:shipping_date]) if data.include?(:shipping_date)
289
+
290
+ if data.include?(:shipping_courier)
291
+ xml.shippingCourier(
292
+ data[:shipping_courier][:priority],
293
+ data[:shipping_courier][:tracking_number],
294
+ data[:shipping_courier][:name]
295
+ )
296
+ end
297
+
298
+ add_optional_data_level_two_and_three(xml, data)
299
+
300
+ if data.include?(:item) && data[:item].kind_of?(Array)
301
+ data[:item].each { |item| add_items_into_level_three_data(xml, item.symbolize_keys) }
302
+ elsif data.include?(:item)
303
+ add_items_into_level_three_data(xml, data[:item].symbolize_keys)
304
+ end
305
+ end
306
+
307
+ def add_items_into_level_three_data(xml, item)
308
+ xml.item do
309
+ xml.description item[:description] if item[:description]
310
+ xml.productCode item[:product_code] if item[:product_code]
311
+ xml.commodityCode item[:commodity_code] if item[:commodity_code]
312
+ xml.quantity item[:quantity] if item[:quantity]
313
+
314
+ {
315
+ unit_cost: 'unitCost',
316
+ item_total: 'itemTotal',
317
+ item_total_with_tax: 'itemTotalWithTax',
318
+ item_discount_amount: 'itemDiscountAmount',
319
+ tax_amount: 'taxAmount'
320
+ }.each do |key, tag|
321
+ next unless item.include?(key)
322
+
323
+ xml.tag! tag do
324
+ data_amount = item[key].symbolize_keys
325
+ add_amount(xml, data_amount[:amount].to_i, data_amount)
326
+ end
327
+ end
328
+ end
329
+ end
330
+
331
+ def add_optional_data_level_two_and_three(xml, data)
332
+ xml.shipFromPostalCode data[:ship_from_postal_code] if data.include?(:ship_from_postal_code)
333
+ xml.destinationPostalCode data[:destination_postal_code] if data.include?(:destination_postal_code)
334
+ xml.destinationCountryCode data[:destination_country_code] if data.include?(:destination_country_code)
335
+ add_date_element(xml, 'orderDate', data[:order_date].symbolize_keys) if data.include?(:order_date)
336
+ xml.taxExempt data[:tax_exempt] if data.include?(:tax_exempt)
337
+ end
338
+
248
339
  def order_tag_attributes(options)
249
340
  { 'orderCode' => clean_order_id(options[:order_id]), 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v.present? }
250
341
  end
@@ -718,9 +809,18 @@ module ActiveMerchant #:nodoc:
718
809
  resp_params = { action: action }
719
810
 
720
811
  parse_elements(doc.root, resp_params)
812
+ extract_issuer_response(doc.root, resp_params)
813
+
721
814
  resp_params
722
815
  end
723
816
 
817
+ def extract_issuer_response(doc, response)
818
+ return unless issuer_response = doc.at_xpath('//paymentService//reply//orderStatus//payment//IssuerResponseCode')
819
+
820
+ response[:issuer_response_code] = issuer_response['code']
821
+ response[:issuer_response_description] = issuer_response['description']
822
+ end
823
+
724
824
  def parse_elements(node, response)
725
825
  node_name = node.name.underscore
726
826
  node.attributes.each do |k, v|
@@ -743,9 +843,11 @@ module ActiveMerchant #:nodoc:
743
843
  'Content-Type' => 'text/xml',
744
844
  'Authorization' => encoded_credentials
745
845
  }
746
- if options[:cookie]
747
- headers['Cookie'] = options[:cookie] if options[:cookie]
748
- end
846
+
847
+ # ensure cookie included on follow-up '3ds' and 'capture_request' calls, using the cookie saved from the preceding response
848
+ # cookie should be present in options on the 3ds and capture calls, but also still saved in the instance var in case
849
+ cookie = options[:cookie] || @cookie || nil
850
+ headers['Cookie'] = cookie if cookie
749
851
 
750
852
  headers['Idempotency-Key'] = idempotency_key if idempotency_key
751
853
  headers
@@ -761,7 +863,7 @@ module ActiveMerchant #:nodoc:
761
863
  raw[:is3DSOrder] = true
762
864
  end
763
865
  success = success_from(action, raw, success_criteria)
764
- message = message_from(success, raw, success_criteria)
866
+ message = message_from(success, raw, success_criteria, action)
765
867
 
766
868
  Response.new(
767
869
  success,
@@ -798,7 +900,8 @@ module ActiveMerchant #:nodoc:
798
900
  def handle_response(response)
799
901
  case response.code.to_i
800
902
  when 200...300
801
- @cookie = response['Set-Cookie']
903
+ cookie = response.header['Set-Cookie']&.match('^[^;]*')
904
+ @cookie = cookie[0] if cookie
802
905
  response.body
803
906
  else
804
907
  raise ResponseError.new(response)
@@ -809,10 +912,10 @@ module ActiveMerchant #:nodoc:
809
912
  success_criteria_success?(raw, success_criteria) || action_success?(action, raw)
810
913
  end
811
914
 
812
- def message_from(success, raw, success_criteria)
915
+ def message_from(success, raw, success_criteria, action)
813
916
  return 'SUCCESS' if success
814
917
 
815
- raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria)
918
+ raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria, action) || raw[:issuer_response_description]
816
919
  end
817
920
 
818
921
  # success_criteria can be:
@@ -829,6 +932,8 @@ module ActiveMerchant #:nodoc:
829
932
  case action
830
933
  when 'store'
831
934
  raw[:token].present?
935
+ when 'direct_inquiry'
936
+ raw[:last_event].present?
832
937
  else
833
938
  false
834
939
  end
@@ -838,8 +943,11 @@ module ActiveMerchant #:nodoc:
838
943
  raw[:iso8583_return_code_code] || raw[:error_code] || nil unless success == 'SUCCESS'
839
944
  end
840
945
 
841
- def required_status_message(raw, success_criteria)
842
- "A transaction status of #{success_criteria.collect { |c| "'#{c}'" }.join(' or ')} is required." if !success_criteria.include?(raw[:last_event])
946
+ def required_status_message(raw, success_criteria, action)
947
+ return if success_criteria.include?(raw[:last_event])
948
+ return unless %w[cancel refund inquiry credit fast_credit].include?(action)
949
+
950
+ "A transaction status of #{success_criteria.collect { |c| "'#{c}'" }.join(' or ')} is required."
843
951
  end
844
952
 
845
953
  def authorization_from(action, raw, options)
@@ -85,7 +85,21 @@ module ActiveMerchant #:nodoc:
85
85
  (primary_response ? primary_response.success? : true)
86
86
  end
87
87
 
88
- %w(params message test authorization avs_result cvv_result error_code emv_authorization test? fraud_review?).each do |m|
88
+ def avs_result
89
+ return @primary_response.try(:avs_result) if @use_first_response
90
+
91
+ result = responses.reverse.find { |r| r.avs_result['code'].present? }
92
+ result.try(:avs_result) || responses.last.try(:avs_result)
93
+ end
94
+
95
+ def cvv_result
96
+ return @primary_response.try(:cvv_result) if @use_first_response
97
+
98
+ result = responses.reverse.find { |r| r.cvv_result['code'].present? }
99
+ result.try(:cvv_result) || responses.last.try(:cvv_result)
100
+ end
101
+
102
+ %w(params message test authorization error_code emv_authorization test? fraud_review?).each do |m|
89
103
  class_eval %(
90
104
  def #{m}
91
105
  (@responses.empty? ? nil : primary_response.#{m})
@@ -85,8 +85,6 @@ module ActiveMerchant
85
85
  result =
86
86
  case method
87
87
  when :get
88
- raise ArgumentError, 'GET requests do not support a request body' if body
89
-
90
88
  http.get(endpoint.request_uri, headers)
91
89
  when :post
92
90
  debug body
@@ -184,6 +184,7 @@ module ActiveMerchant #:nodoc:
184
184
  { alpha2: 'KP', name: 'Korea, Democratic People\'s Republic of', alpha3: 'PRK', numeric: '408' },
185
185
  { alpha2: 'KR', name: 'Korea, Republic of', alpha3: 'KOR', numeric: '410' },
186
186
  { alpha2: 'XK', name: 'Kosovo', alpha3: 'XKX', numeric: '900' },
187
+ { alpha2: 'QZ', name: 'Kosovo', alpha3: 'XKX', numeric: '900' },
187
188
  { alpha2: 'KW', name: 'Kuwait', alpha3: 'KWT', numeric: '414' },
188
189
  { alpha2: 'KG', name: 'Kyrgyzstan', alpha3: 'KGZ', numeric: '417' },
189
190
  { alpha2: 'LA', name: 'Lao People\'s Democratic Republic', alpha3: 'LAO', numeric: '418' },
@@ -23,10 +23,13 @@ module ActiveMerchant #:nodoc:
23
23
  end
24
24
 
25
25
  def to_s
26
- "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
26
+ "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
27
27
  end
28
28
  end
29
29
 
30
+ class OAuthResponseError < ResponseError # :nodoc:
31
+ end
32
+
30
33
  class ClientCertificateError < ActiveMerchantError # :nodoc
31
34
  end
32
35
 
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.126.0'
2
+ VERSION = '1.129.0'
3
3
  end