activemerchant 1.126.0 → 1.129.0

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