activemerchant 1.90.0 → 1.99.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +202 -0
  3. data/README.md +6 -2
  4. data/lib/active_merchant/billing/avs_result.rb +4 -5
  5. data/lib/active_merchant/billing/credit_card.rb +8 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +81 -5
  7. data/lib/active_merchant/billing/gateway.rb +10 -0
  8. data/lib/active_merchant/billing/gateways/adyen.rb +206 -54
  9. data/lib/active_merchant/billing/gateways/bambora_apac.rb +226 -0
  10. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +43 -10
  11. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +3 -0
  12. data/lib/active_merchant/billing/gateways/beanstream.rb +11 -6
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +10 -8
  14. data/lib/active_merchant/billing/gateways/blue_snap.rb +211 -36
  15. data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +79 -18
  17. data/lib/active_merchant/billing/gateways/card_connect.rb +6 -1
  18. data/lib/active_merchant/billing/gateways/cecabank.rb +20 -9
  19. data/lib/active_merchant/billing/gateways/checkout_v2.rb +98 -61
  20. data/lib/active_merchant/billing/gateways/credorax.rb +69 -4
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +85 -14
  22. data/lib/active_merchant/billing/gateways/d_local.rb +3 -3
  23. data/lib/active_merchant/billing/gateways/decidir.rb +245 -0
  24. data/lib/active_merchant/billing/gateways/elavon.rb +9 -0
  25. data/lib/active_merchant/billing/gateways/epay.rb +13 -2
  26. data/lib/active_merchant/billing/gateways/eway_rapid.rb +42 -12
  27. data/lib/active_merchant/billing/gateways/fat_zebra.rb +26 -7
  28. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +42 -3
  29. data/lib/active_merchant/billing/gateways/global_collect.rb +3 -7
  30. data/lib/active_merchant/billing/gateways/hps.rb +46 -1
  31. data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
  32. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  33. data/lib/active_merchant/billing/gateways/litle.rb +61 -3
  34. data/lib/active_merchant/billing/gateways/mastercard.rb +30 -5
  35. data/lib/active_merchant/billing/gateways/mercado_pago.rb +1 -1
  36. data/lib/active_merchant/billing/gateways/migs.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/monei.rb +31 -0
  38. data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
  39. data/lib/active_merchant/billing/gateways/mundipagg.rb +37 -9
  40. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
  42. data/lib/active_merchant/billing/gateways/nmi.rb +45 -5
  43. data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
  44. data/lib/active_merchant/billing/gateways/opp.rb +20 -1
  45. data/lib/active_merchant/billing/gateways/orbital.rb +92 -11
  46. data/lib/active_merchant/billing/gateways/payflow.rb +64 -14
  47. data/lib/active_merchant/billing/gateways/payment_express.rb +7 -0
  48. data/lib/active_merchant/billing/gateways/paymentez.rb +7 -11
  49. data/lib/active_merchant/billing/gateways/paymill.rb +5 -0
  50. data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
  51. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -1
  52. data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -2
  53. data/lib/active_merchant/billing/gateways/pin.rb +19 -6
  54. data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
  55. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +7 -1
  56. data/lib/active_merchant/billing/gateways/qvalent.rb +54 -1
  57. data/lib/active_merchant/billing/gateways/realex.rb +42 -5
  58. data/lib/active_merchant/billing/gateways/redsys.rb +113 -30
  59. data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
  60. data/lib/active_merchant/billing/gateways/stripe.rb +66 -34
  61. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +271 -0
  62. data/lib/active_merchant/billing/gateways/tns.rb +10 -5
  63. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +5 -5
  64. data/lib/active_merchant/billing/gateways/trust_commerce.rb +46 -6
  65. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +19 -8
  66. data/lib/active_merchant/billing/gateways/visanet_peru.rb +22 -10
  67. data/lib/active_merchant/billing/gateways/worldpay.rb +237 -34
  68. data/lib/active_merchant/country.rb +2 -1
  69. data/lib/active_merchant/version.rb +1 -1
  70. metadata +20 -4
@@ -133,6 +133,7 @@ module ActiveMerchant #:nodoc:
133
133
  add_payment(xml, action, money, options)
134
134
  add_account(xml, credit_card)
135
135
  add_customer(xml, credit_card, options)
136
+ add_three_d_secure(xml, options)
136
137
  end
137
138
 
138
139
  commit(request)
@@ -225,6 +226,36 @@ module ActiveMerchant #:nodoc:
225
226
  end
226
227
  end
227
228
 
229
+ # Private : Convert ECI to ResultIndicator
230
+ # Possible ECI values:
231
+ # 02 or 05 - Fully Authenticated Transaction
232
+ # 00 or 07 - Non 3D Secure Transaction
233
+ # Possible ResultIndicator values:
234
+ # 01 = MASTER_3D_ATTEMPT
235
+ # 02 = MASTER_3D_SUCCESS
236
+ # 05 = VISA_3D_SUCCESS
237
+ # 06 = VISA_3D_ATTEMPT
238
+ # 07 = DEFAULT_E_COMMERCE
239
+ def eci_to_result_indicator(eci)
240
+ case eci
241
+ when '02', '05'
242
+ return eci
243
+ else
244
+ return '07'
245
+ end
246
+ end
247
+
248
+ # Private : Add the 3DSecure infos to XML
249
+ def add_three_d_secure(xml, options)
250
+ if options[:three_d_secure]
251
+ xml.Authentication(:type => '3DSecure') do
252
+ xml.ResultIndicator eci_to_result_indicator options[:three_d_secure][:eci]
253
+ xml.Parameter(:name => 'VERIFICATION_ID') { xml.text options[:three_d_secure][:cavv] }
254
+ xml.Parameter(:name => 'XID') { xml.text options[:three_d_secure][:xid] }
255
+ end
256
+ end
257
+ end
258
+
228
259
  # Private: Parse XML response from Monei servers
229
260
  def parse(body)
230
261
  xml = Nokogiri::XML(body)
@@ -33,7 +33,6 @@ module ActiveMerchant #:nodoc:
33
33
  requires!(options, :login, :password)
34
34
  @cvv_enabled = options[:cvv_enabled]
35
35
  @avs_enabled = options[:avs_enabled]
36
- @cof_enabled = options[:cof_enabled]
37
36
  options[:crypt_type] = 7 unless options.has_key?(:crypt_type)
38
37
  super
39
38
  end
@@ -51,7 +50,7 @@ module ActiveMerchant #:nodoc:
51
50
  post[:order_id] = options[:order_id]
52
51
  post[:address] = options[:billing_address] || options[:address]
53
52
  post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
54
- add_cof(post, options) if @cof_enabled
53
+ add_cof(post, options)
55
54
  action = if post[:cavv]
56
55
  'cavv_preauth'
57
56
  elsif post[:data_key].blank?
@@ -74,7 +73,7 @@ module ActiveMerchant #:nodoc:
74
73
  post[:order_id] = options[:order_id]
75
74
  post[:address] = options[:billing_address] || options[:address]
76
75
  post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
77
- add_cof(post, options) if @cof_enabled
76
+ add_cof(post, options)
78
77
  action = if post[:cavv]
79
78
  'cavv_purchase'
80
79
  elsif post[:data_key].blank?
@@ -293,7 +292,7 @@ module ActiveMerchant #:nodoc:
293
292
  when :cvd_info
294
293
  transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled
295
294
  when :cof_info
296
- transaction.add_element(credential_on_file(parameters)) if @cof_enabled && cof_details_present?(parameters)
295
+ transaction.add_element(credential_on_file(parameters)) if cof_details_present?(parameters)
297
296
  else
298
297
  transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
299
298
  end
@@ -5,7 +5,7 @@ module ActiveMerchant #:nodoc:
5
5
 
6
6
  self.supported_countries = ['US']
7
7
  self.default_currency = 'USD'
8
- self.supported_cardtypes = [:visa, :master, :american_express, :discover]
8
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :alelo]
9
9
 
10
10
  self.homepage_url = 'https://www.mundipagg.com/'
11
11
  self.display_name = 'Mundipagg'
@@ -128,8 +128,8 @@ module ActiveMerchant #:nodoc:
128
128
  def add_shipping_address(post, options)
129
129
  if address = options[:shipping_address]
130
130
  post[:address] = {}
131
- post[:address][:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]
132
- post[:address][:number] = address[:address1].match(/\d+/)[0] if address[:address1]
131
+ post[:address][:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]&.match(/\D+/)
132
+ post[:address][:number] = address[:address1].match(/\d+/)[0] if address[:address1]&.match(/\d+/)
133
133
  post[:address][:compliment] = address[:address2] if address[:address2]
134
134
  post[:address][:city] = address[:city] if address[:city]
135
135
  post[:address][:state] = address[:state] if address[:state]
@@ -155,7 +155,8 @@ module ActiveMerchant #:nodoc:
155
155
  post[:customer][:name] = payment.name if post[:customer]
156
156
  post[:customer_id] = parse_auth(payment)[0] if payment.is_a?(String)
157
157
  post[:payment] = {}
158
- post[:payment][:gateway_affiliation_id] = @options[:gateway_id] if @options[:gateway_id]
158
+ affiliation = options[:gateway_affiliation_id] || @options[:gateway_id]
159
+ post[:payment][:gateway_affiliation_id] = affiliation if affiliation
159
160
  post[:payment][:metadata] = { mundipagg_payment_method_code: '1' } if test?
160
161
  if voucher?(payment)
161
162
  add_voucher(post, payment, options)
@@ -248,7 +249,8 @@ module ActiveMerchant #:nodoc:
248
249
  error_code: error_code_from(response)
249
250
  )
250
251
  rescue ResponseError => e
251
- message = get_error_message(e)
252
+ message = get_error_messages(e)
253
+
252
254
  return Response.new(
253
255
  false,
254
256
  "#{STANDARD_ERROR_MESSAGE_MAPPING[e.response.code]} #{message}",
@@ -262,15 +264,41 @@ module ActiveMerchant #:nodoc:
262
264
  %w[pending paid processing canceled active].include? response['status']
263
265
  end
264
266
 
265
- def get_error_message(error)
266
- JSON.parse(error.response.body)['message']
267
- end
268
-
269
267
  def message_from(response)
268
+ return gateway_response_errors(response) if gateway_response_errors?(response)
270
269
  return response['message'] if response['message']
271
270
  return response['last_transaction']['acquirer_message'] if response['last_transaction']
272
271
  end
273
272
 
273
+ def get_error_messages(error)
274
+ parsed_response_body = parse(error.response.body)
275
+ message = parsed_response_body['message']
276
+
277
+ parsed_response_body['errors']&.each do |type, descriptions|
278
+ message += ' | '
279
+ message += descriptions.join(', ')
280
+ end
281
+
282
+ message
283
+ end
284
+
285
+ def gateway_response_errors?(response)
286
+ response.try(:[], 'last_transaction').try(:[], 'gateway_response').try(:[], 'errors').present?
287
+ end
288
+
289
+ def gateway_response_errors(response)
290
+ error_string = ''
291
+
292
+ response['last_transaction']['gateway_response']['errors']&.each do |error|
293
+ error.each do |key, value|
294
+ error_string += ' | ' unless error_string.blank?
295
+ error_string += value
296
+ end
297
+ end
298
+
299
+ error_string
300
+ end
301
+
274
302
  def authorization_from(response, action)
275
303
  return "#{response['customer']['id']}|#{response['id']}" if action == 'store'
276
304
  response['id']
@@ -12,7 +12,7 @@ module ActiveMerchant #:nodoc:
12
12
 
13
13
  self.test_url = 'https://demo.transact.nab.com.au/xmlapi/payment'
14
14
  self.live_url = 'https://transact.nab.com.au/live/xmlapi/payment'
15
- self.test_periodic_url = 'https://transact.nab.com.au/xmlapidemo/periodic'
15
+ self.test_periodic_url = 'https://demo.transact.nab.com.au/xmlapi/periodic'
16
16
  self.live_periodic_url = 'https://transact.nab.com.au/xmlapi/periodic'
17
17
 
18
18
  self.supported_countries = ['AU']
@@ -55,6 +55,10 @@ module ActiveMerchant #:nodoc:
55
55
  post = {}
56
56
  add_invoice(post, money, options)
57
57
 
58
+ # Setting merchantRefNumber to a unique id for each refund
59
+ # This is to support multiple partial refunds for the same order
60
+ post[:merchantRefNum] = SecureRandom.uuid
61
+
58
62
  commit(:post, "settlements/#{authorization}/refunds", post)
59
63
  end
60
64
 
@@ -31,9 +31,11 @@ module ActiveMerchant #:nodoc:
31
31
  post = {}
32
32
  add_invoice(post, amount, options)
33
33
  add_payment_method(post, payment_method, options)
34
+ add_stored_credential(post, options)
34
35
  add_customer_data(post, options)
35
36
  add_vendor_data(post, options)
36
37
  add_merchant_defined_fields(post, options)
38
+ add_level3_fields(post, options)
37
39
 
38
40
  commit('sale', post)
39
41
  end
@@ -42,9 +44,11 @@ module ActiveMerchant #:nodoc:
42
44
  post = {}
43
45
  add_invoice(post, amount, options)
44
46
  add_payment_method(post, payment_method, options)
47
+ add_stored_credential(post, options)
45
48
  add_customer_data(post, options)
46
49
  add_vendor_data(post, options)
47
50
  add_merchant_defined_fields(post, options)
51
+ add_level3_fields(post, options)
48
52
 
49
53
  commit('auth', post)
50
54
  end
@@ -81,6 +85,7 @@ module ActiveMerchant #:nodoc:
81
85
  add_payment_method(post, payment_method, options)
82
86
  add_customer_data(post, options)
83
87
  add_vendor_data(post, options)
88
+ add_level3_fields(post, options)
84
89
 
85
90
  commit('credit', post)
86
91
  end
@@ -91,6 +96,7 @@ module ActiveMerchant #:nodoc:
91
96
  add_customer_data(post, options)
92
97
  add_vendor_data(post, options)
93
98
  add_merchant_defined_fields(post, options)
99
+ add_level3_fields(post, options)
94
100
 
95
101
  commit('validate', post)
96
102
  end
@@ -117,7 +123,7 @@ module ActiveMerchant #:nodoc:
117
123
 
118
124
  def scrub(transcript)
119
125
  transcript.
120
- gsub(%r((password=)\w+), '\1[FILTERED]').
126
+ gsub(%r((password=)[^&\n]*), '\1[FILTERED]').
121
127
  gsub(%r((ccnumber=)\d+), '\1[FILTERED]').
122
128
  gsub(%r((cvv=)\d+), '\1[FILTERED]').
123
129
  gsub(%r((checkaba=)\d+), '\1[FILTERED]').
@@ -131,6 +137,10 @@ module ActiveMerchant #:nodoc:
131
137
 
132
138
  private
133
139
 
140
+ def add_level3_fields(post, options)
141
+ add_fields_to_post_if_present(post, options, [:tax, :shipping, :ponumber])
142
+ end
143
+
134
144
  def add_invoice(post, money, options)
135
145
  post[:amount] = amount(money)
136
146
  post[:orderid] = options[:order_id]
@@ -144,7 +154,8 @@ module ActiveMerchant #:nodoc:
144
154
 
145
155
  def add_payment_method(post, payment_method, options)
146
156
  if(payment_method.is_a?(String))
147
- post[:customer_vault_id] = payment_method
157
+ customer_vault_id, _ = split_authorization(payment_method)
158
+ post[:customer_vault_id] = customer_vault_id
148
159
  elsif payment_method.is_a?(NetworkTokenizationCreditCard)
149
160
  post[:ccnumber] = payment_method.number
150
161
  post[:ccexp] = exp_date(payment_method)
@@ -169,6 +180,34 @@ module ActiveMerchant #:nodoc:
169
180
  end
170
181
  end
171
182
 
183
+ def add_stored_credential(post, options)
184
+ return unless (stored_credential = options[:stored_credential])
185
+
186
+ if stored_credential[:initiator] == 'cardholder'
187
+ post[:initiated_by] = 'customer'
188
+ else
189
+ post[:initiated_by] = 'merchant'
190
+ end
191
+
192
+ # :reason_type, when provided, overrides anything previously set in
193
+ # post[:billing_method] (see `add_invoice` and the :recurring) option
194
+ case stored_credential[:reason_type]
195
+ when 'recurring'
196
+ post[:billing_method] = 'recurring'
197
+ when 'installment'
198
+ post[:billing_method] = 'installment'
199
+ when 'unscheduled'
200
+ post.delete(:billing_method)
201
+ end
202
+
203
+ if stored_credential[:initial_transaction]
204
+ post[:stored_credential_indicator] = 'stored'
205
+ else
206
+ post[:stored_credential_indicator] = 'used'
207
+ post[:initial_transaction_id] = stored_credential[:network_transaction_id]
208
+ end
209
+ end
210
+
172
211
  def add_customer_data(post, options)
173
212
  post[:email] = options[:email]
174
213
  post[:ipaddress] = options[:ip]
@@ -236,15 +275,16 @@ module ActiveMerchant #:nodoc:
236
275
  succeeded,
237
276
  message_from(succeeded, response),
238
277
  response,
239
- authorization: authorization_from(response, params[:payment]),
278
+ authorization: authorization_from(response, params[:payment], action),
240
279
  avs_result: AVSResult.new(code: response[:avsresponse]),
241
280
  cvv_result: CVVResult.new(response[:cvvresponse]),
242
281
  test: test?
243
282
  )
244
283
  end
245
284
 
246
- def authorization_from(response, payment_type)
247
- [ response[:transactionid], payment_type ].join('#')
285
+ def authorization_from(response, payment_type, action)
286
+ authorization = (action == 'add_customer' ? response[:customer_vault_id] : response[:transactionid])
287
+ [ authorization, payment_type ].join('#')
248
288
  end
249
289
 
250
290
  def split_authorization(authorization)
@@ -205,7 +205,7 @@ module ActiveMerchant #:nodoc:
205
205
  end
206
206
 
207
207
  def error?(response)
208
- response.key?('error_code')
208
+ response['error_code'] && !response['error_code'].blank?
209
209
  end
210
210
 
211
211
  def response_error(raw_response)
@@ -125,6 +125,9 @@ module ActiveMerchant #:nodoc:
125
125
 
126
126
  def purchase(money, payment, options={})
127
127
  # debit
128
+ if payment.is_a?(String)
129
+ options[:registrationId] = payment
130
+ end
128
131
  execute_dbpa(options[:risk_workflow] ? 'PA.CP': 'DB',
129
132
  money, payment, options)
130
133
  end
@@ -156,6 +159,10 @@ module ActiveMerchant #:nodoc:
156
159
  end
157
160
  end
158
161
 
162
+ def store(credit_card, options = {})
163
+ execute_store(credit_card, options.merge(store: true))
164
+ end
165
+
159
166
  def supports_scrubbing?
160
167
  true
161
168
  end
@@ -169,6 +176,15 @@ module ActiveMerchant #:nodoc:
169
176
 
170
177
  private
171
178
 
179
+ def execute_store(payment, options)
180
+ post = {}
181
+ add_payment_method(post, payment, options)
182
+ add_address(post, options)
183
+ add_options(post, options)
184
+ add_3d_secure(post, options)
185
+ commit(post, nil, options)
186
+ end
187
+
172
188
  def execute_dbpa(txtype, money, payment, options)
173
189
  post = {}
174
190
  post[:paymentType] = txtype
@@ -243,6 +259,7 @@ module ActiveMerchant #:nodoc:
243
259
  end
244
260
 
245
261
  def add_payment_method(post, payment, options)
262
+ return if payment.is_a?(String)
246
263
  if options[:registrationId]
247
264
  post[:card] = {
248
265
  cvv: payment.verification_value,
@@ -278,7 +295,9 @@ module ActiveMerchant #:nodoc:
278
295
  end
279
296
 
280
297
  def build_url(url, authorization, options)
281
- if options[:registrationId]
298
+ if options[:store]
299
+ url.gsub(/payments/, 'registrations')
300
+ elsif options[:registrationId]
282
301
  "#{url.gsub(/payments/, 'registrations')}/#{options[:registrationId]}/payments"
283
302
  elsif authorization
284
303
  "#{url}/#{authorization}"
@@ -30,7 +30,7 @@ module ActiveMerchant #:nodoc:
30
30
  class OrbitalGateway < Gateway
31
31
  include Empty
32
32
 
33
- API_VERSION = '7.1'
33
+ API_VERSION = '7.7'
34
34
 
35
35
  POST_HEADERS = {
36
36
  'MIME-Version' => '1.1',
@@ -465,16 +465,62 @@ module ActiveMerchant #:nodoc:
465
465
  end
466
466
  end
467
467
 
468
- def add_cdpt_eci_and_xid(xml, creditcard)
469
- xml.tag! :AuthenticationECIInd, creditcard.eci
470
- xml.tag! :XID, creditcard.transaction_id if creditcard.transaction_id
468
+ def add_eci(xml, creditcard, three_d_secure)
469
+ eci = if three_d_secure
470
+ three_d_secure[:eci]
471
+ elsif creditcard.is_a?(NetworkTokenizationCreditCard)
472
+ creditcard.eci
473
+ end
474
+
475
+ xml.tag!(:AuthenticationECIInd, eci) if eci
476
+ end
477
+
478
+ def add_xid(xml, creditcard, three_d_secure)
479
+ xid = if three_d_secure && creditcard.brand == 'visa'
480
+ three_d_secure[:xid]
481
+ elsif creditcard.is_a?(NetworkTokenizationCreditCard)
482
+ creditcard.transaction_id
483
+ end
484
+
485
+ xml.tag!(:XID, xid) if xid
486
+ end
487
+
488
+ def add_cavv(xml, creditcard, three_d_secure)
489
+ return unless three_d_secure && creditcard.brand == 'visa'
490
+
491
+ xml.tag!(:CAVV, three_d_secure[:cavv])
492
+ end
493
+
494
+ def add_aav(xml, creditcard, three_d_secure)
495
+ return unless three_d_secure && creditcard.brand == 'master'
496
+
497
+ xml.tag!(:AAV, three_d_secure[:cavv])
471
498
  end
472
499
 
473
- def add_cdpt_payment_cryptogram(xml, creditcard)
500
+ def add_dpanind(xml, creditcard)
501
+ return unless creditcard.is_a?(NetworkTokenizationCreditCard)
502
+
474
503
  xml.tag! :DPANInd, 'Y'
504
+ end
505
+
506
+ def add_digital_token_cryptogram(xml, creditcard)
507
+ return unless creditcard.is_a?(NetworkTokenizationCreditCard)
508
+
475
509
  xml.tag! :DigitalTokenCryptogram, creditcard.payment_cryptogram
476
510
  end
477
511
 
512
+ def add_aevv(xml, creditcard, three_d_secure)
513
+ return unless three_d_secure && creditcard.brand == 'american_express'
514
+
515
+ xml.tag!(:AEVV, three_d_secure[:cavv])
516
+ end
517
+
518
+ def add_pymt_brand_program_code(xml, creditcard, three_d_secure)
519
+ return unless three_d_secure && creditcard.brand == 'american_express'
520
+
521
+ xml.tag!(:PymtBrandProgramCode, 'ASK')
522
+ end
523
+
478
524
  def add_refund(xml, currency=nil)
479
525
  xml.tag! :AccountNum, nil
480
526
 
@@ -506,6 +552,36 @@ module ActiveMerchant #:nodoc:
506
552
  end
507
553
  end
508
554
 
555
+ def add_stored_credentials(xml, parameters)
556
+ return unless parameters[:mit_stored_credential_ind] == 'Y' || parameters[:stored_credential] && !parameters[:stored_credential].values.all?(&:nil?)
557
+ if msg_type = get_msg_type(parameters)
558
+ xml.tag! :MITMsgType, msg_type
559
+ end
560
+ xml.tag! :MITStoredCredentialInd, 'Y'
561
+ if parameters[:mit_submitted_transaction_id]
562
+ xml.tag! :MITSubmittedTransactionID, parameters[:mit_submitted_transaction_id]
563
+ elsif parameters.dig(:stored_credential, :network_transaction_id) && parameters.dig(:stored_credential, :initiator) == 'merchant'
564
+ xml.tag! :MITSubmittedTransactionID, parameters[:stored_credential][:network_transaction_id]
565
+ end
566
+ end
567
+
568
+ def get_msg_type(parameters)
569
+ return parameters[:mit_msg_type] if parameters[:mit_msg_type]
570
+ return 'CSTO' if parameters[:stored_credential][:initial_transaction]
571
+ return unless parameters[:stored_credential][:initiator] && parameters[:stored_credential][:reason_type]
572
+ initiator = case parameters[:stored_credential][:initiator]
573
+ when 'customer' then 'C'
574
+ when 'merchant' then 'M'
575
+ end
576
+ reason = case parameters[:stored_credential][:reason_type]
577
+ when 'recurring' then 'REC'
578
+ when 'installment' then 'INS'
579
+ when 'unscheduled' then 'USE'
580
+ end
581
+
582
+ "#{initiator}#{reason}"
583
+ end
584
+
509
585
  def parse(body)
510
586
  response = {}
511
587
  xml = REXML::Document.new(body)
@@ -603,9 +679,11 @@ module ActiveMerchant #:nodoc:
603
679
 
604
680
  yield xml if block_given?
605
681
 
606
- if creditcard.is_a?(NetworkTokenizationCreditCard)
607
- add_cdpt_eci_and_xid(xml, creditcard)
608
- end
682
+ three_d_secure = parameters[:three_d_secure]
683
+
684
+ add_eci(xml, creditcard, three_d_secure)
685
+ add_cavv(xml, creditcard, three_d_secure)
686
+ add_xid(xml, creditcard, three_d_secure)
609
687
 
610
688
  xml.tag! :OrderID, format_order_id(parameters[:order_id])
611
689
  xml.tag! :Amount, amount(money)
@@ -614,11 +692,12 @@ module ActiveMerchant #:nodoc:
614
692
  add_level_2_tax(xml, parameters)
615
693
  add_level_2_advice_addendum(xml, parameters)
616
694
 
695
+ add_aav(xml, creditcard, three_d_secure)
617
696
  # CustomerAni, AVSPhoneType and AVSDestPhoneType could be added here.
618
697
 
619
- if creditcard.is_a?(NetworkTokenizationCreditCard)
620
- add_cdpt_payment_cryptogram(xml, creditcard)
621
- end
698
+ add_dpanind(xml, creditcard)
699
+ add_aevv(xml, creditcard, three_d_secure)
700
+ add_digital_token_cryptogram(xml, creditcard)
622
701
 
623
702
  if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors)
624
703
  add_soft_descriptors(xml, parameters[:soft_descriptors])
@@ -635,6 +714,8 @@ module ActiveMerchant #:nodoc:
635
714
  end
636
715
 
637
716
  add_level_2_purchase(xml, parameters)
717
+ add_stored_credentials(xml, parameters)
718
+ add_pymt_brand_program_code(xml, creditcard, three_d_secure)
638
719
  end
639
720
  end
640
721
  xml.target!
@@ -1,3 +1,4 @@
1
+ require 'nokogiri'
1
2
  require 'active_merchant/billing/gateways/payflow/payflow_common_api'
2
3
  require 'active_merchant/billing/gateways/payflow/payflow_response'
3
4
  require 'active_merchant/billing/gateways/payflow_express'
@@ -187,7 +188,44 @@ module ActiveMerchant #:nodoc:
187
188
  end
188
189
  end
189
190
  end
190
- xml.target!
191
+ add_level_two_three_fields(xml.target!, options)
192
+ end
193
+
194
+ def add_level_two_three_fields(xml_string, options)
195
+ if options[:level_two_fields] || options[:level_three_fields]
196
+ xml_doc = Nokogiri::XML.parse(xml_string)
197
+ %i[level_two_fields level_three_fields].each do |fields|
198
+ xml_string = add_fields(xml_doc, options[fields]) if options[fields]
199
+ end
200
+ end
201
+ xml_string
202
+ end
203
+
204
+ def check_fields(parent, fields, xml_doc)
205
+ fields.each do |k, v|
206
+ if v.is_a? String
207
+ new_node = Nokogiri::XML::Node.new(k, xml_doc)
208
+ new_node.add_child(v)
209
+ xml_doc.at_css(parent).add_child(new_node)
210
+ else
211
+ check_subparent_before_continuing(parent, k, xml_doc)
212
+ check_fields(k, v, xml_doc)
213
+ end
214
+ end
215
+ xml_doc
216
+ end
217
+
218
+ def check_subparent_before_continuing(parent, subparent, xml_doc)
219
+ unless xml_doc.at_css(subparent)
220
+ subparent_node = Nokogiri::XML::Node.new(subparent, xml_doc)
221
+ xml_doc.at_css(parent).add_child(subparent_node)
222
+ end
223
+ end
224
+
225
+ def add_fields(xml_doc, options_fields)
226
+ fields_to_add = JSON.parse(options_fields)
227
+ check_fields('Invoice', fields_to_add, xml_doc)
228
+ xml_doc.root.to_s
191
229
  end
192
230
 
193
231
  def build_check_request(action, money, check, options)
@@ -213,7 +251,7 @@ module ActiveMerchant #:nodoc:
213
251
  end
214
252
  end
215
253
  end
216
- xml.target!
254
+ add_level_two_three_fields(xml.target!, options)
217
255
  end
218
256
 
219
257
  def add_credit_card(xml, credit_card, options = {})
@@ -224,23 +262,35 @@ module ActiveMerchant #:nodoc:
224
262
  xml.tag! 'NameOnCard', credit_card.first_name
225
263
  xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value?
226
264
 
227
- if options[:three_d_secure]
228
- three_d_secure = options[:three_d_secure]
229
- xml.tag! 'BuyerAuthResult' do
230
- xml.tag! 'Status', three_d_secure[:status] unless three_d_secure[:status].blank?
231
- xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank?
232
- xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank?
233
- xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank?
234
- xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank?
235
- xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
236
- xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank?
237
- end
238
- end
265
+ add_three_d_secure(options, xml)
239
266
 
240
267
  xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name
241
268
  end
242
269
  end
243
270
 
271
+ def add_three_d_secure(options, xml)
272
+ if options[:three_d_secure]
273
+ three_d_secure = options[:three_d_secure]
274
+ xml.tag! 'BuyerAuthResult' do
275
+ authentication_status(three_d_secure, xml)
276
+ xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank?
277
+ xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank?
278
+ xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank?
279
+ xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank?
280
+ xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
281
+ xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank?
282
+ end
283
+ end
284
+ end
285
+
286
+ def authentication_status(three_d_secure, xml)
287
+ if three_d_secure[:authentication_response_status].present?
288
+ xml.tag! 'Status', three_d_secure[:authentication_response_status]
289
+ elsif three_d_secure[:directory_response_status].present?
290
+ xml.tag! 'Status', three_d_secure[:directory_response_status]
291
+ end
292
+ end
293
+
244
294
  def credit_card_type(credit_card)
245
295
  return '' if card_brand(credit_card).blank?
246
296
 
@@ -153,6 +153,7 @@ module ActiveMerchant #:nodoc:
153
153
  add_invoice(result, options)
154
154
  add_address_verification_data(result, options)
155
155
  add_optional_elements(result, options)
156
+ add_ip(result, options)
156
157
  result
157
158
  end
158
159
 
@@ -163,6 +164,7 @@ module ActiveMerchant #:nodoc:
163
164
  add_invoice(result, options)
164
165
  add_reference(result, identification)
165
166
  add_optional_elements(result, options)
167
+ add_ip(result, options)
166
168
  result
167
169
  end
168
170
 
@@ -172,6 +174,7 @@ module ActiveMerchant #:nodoc:
172
174
  add_amount(result, 100, options) # need to make an auth request for $1
173
175
  add_token_request(result, options)
174
176
  add_optional_elements(result, options)
177
+ add_ip(result, options)
175
178
  result
176
179
  end
177
180
 
@@ -233,6 +236,10 @@ module ActiveMerchant #:nodoc:
233
236
  xml.add_element('AvsPostCode').text = address[:zip]
234
237
  end
235
238
 
239
+ def add_ip(xml, options)
240
+ xml.add_element('ClientInfo').text = options[:ip] if options[:ip]
241
+ end
242
+
236
243
  # The options hash may contain optional data which will be passed
237
244
  # through the specialized optional fields at PaymentExpress
238
245
  # as follows: