activemerchant 1.119.0 → 1.124.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +216 -1
  3. data/README.md +4 -2
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +3 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +32 -14
  8. data/lib/active_merchant/billing/gateways/adyen.rb +94 -25
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +3 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +52 -8
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  17. data/lib/active_merchant/billing/gateways/credorax.rb +15 -9
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +53 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +9 -2
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/elavon.rb +70 -28
  22. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  23. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  24. data/lib/active_merchant/billing/gateways/global_collect.rb +24 -10
  25. data/lib/active_merchant/billing/gateways/hps.rb +55 -1
  26. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  27. data/lib/active_merchant/billing/gateways/litle.rb +1 -1
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  29. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  30. data/lib/active_merchant/billing/gateways/mit.rb +260 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +290 -0
  32. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  33. data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
  34. data/lib/active_merchant/billing/gateways/netbanx.rb +26 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +27 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +99 -59
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  38. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  39. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  40. data/lib/active_merchant/billing/gateways/payeezy.rb +34 -6
  41. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  42. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  43. data/lib/active_merchant/billing/gateways/payment_express.rb +5 -5
  44. data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
  45. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  46. data/lib/active_merchant/billing/gateways/paysafe.rb +376 -0
  47. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  48. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  49. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  50. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +42 -24
  52. data/lib/active_merchant/billing/gateways/safe_charge.rb +25 -13
  53. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  54. data/lib/active_merchant/billing/gateways/stripe.rb +18 -8
  55. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +126 -48
  56. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  58. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  59. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +78 -18
  61. data/lib/active_merchant/billing/response.rb +4 -0
  62. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  63. data/lib/active_merchant/billing.rb +1 -0
  64. data/lib/active_merchant/version.rb +1 -1
  65. data/lib/certs/cacert.pem +1582 -2431
  66. metadata +11 -3
@@ -0,0 +1,404 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class PayTraceGateway < Gateway
4
+ self.test_url = 'https://api.paytrace.com'
5
+ self.live_url = 'https://api.paytrace.com'
6
+
7
+ self.supported_countries = ['US']
8
+ self.default_currency = 'USD'
9
+ self.supported_cardtypes = %i[visa master american_express discover]
10
+
11
+ self.homepage_url = 'https://paytrace.com/'
12
+ self.display_name = 'PayTrace'
13
+
14
+ # Response codes based on API Response Codes found here: https://developers.paytrace.com/support/home#14000041297
15
+ STANDARD_ERROR_CODE_MAPPING = {
16
+ '1' => STANDARD_ERROR_CODE[:error_occurred],
17
+ '102' => STANDARD_ERROR_CODE[:declined],
18
+ '103' => STANDARD_ERROR_CODE[:auto_voided],
19
+ '107' => STANDARD_ERROR_CODE[:unsuccessful_refund],
20
+ '108' => STANDARD_ERROR_CODE[:test_refund],
21
+ '110' => STANDARD_ERROR_CODE[:unsuccessful_void],
22
+ '113' => STANDARD_ERROR_CODE[:unsuccessful_capture]
23
+ }
24
+
25
+ ENDPOINTS = {
26
+ customer_id_sale: 'transactions/sale/by_customer',
27
+ keyed_sale: 'transactions/sale/keyed',
28
+ customer_id_auth: 'transactions/authorization/by_customer',
29
+ keyed_auth: 'transactions/authorization/keyed',
30
+ capture: 'transactions/authorization/capture',
31
+ transaction_refund: 'transactions/refund/for_transaction',
32
+ transaction_void: 'transactions/void',
33
+ store: 'customer/create',
34
+ redact: 'customer/delete',
35
+ level_3_visa: 'level_three/visa',
36
+ level_3_mastercard: 'level_three/mastercard'
37
+ }
38
+
39
+ def initialize(options = {})
40
+ requires!(options, :username, :password, :integrator_id)
41
+ super
42
+ acquire_access_token
43
+ end
44
+
45
+ def purchase(money, payment_or_customer_id, options = {})
46
+ if visa_or_mastercard?(options)
47
+ MultiResponse.run(:use_first_response) do |r|
48
+ endpoint = customer_id?(payment_or_customer_id) ? ENDPOINTS[:customer_id_sale] : ENDPOINTS[:keyed_sale]
49
+
50
+ r.process { commit(endpoint, build_purchase_request(money, payment_or_customer_id, options)) }
51
+ r.process { commit(ENDPOINTS[:"level_3_#{options[:visa_or_mastercard]}"], send_level_3_data(r, options)) }
52
+ end
53
+ else
54
+ post = build_purchase_request(money, payment_or_customer_id, options)
55
+ post[:customer_id] ? endpoint = ENDPOINTS[:customer_id_sale] : endpoint = ENDPOINTS[:keyed_sale]
56
+ response = commit(endpoint, post)
57
+ check_token_response(response, endpoint, post, options)
58
+ end
59
+ end
60
+
61
+ def authorize(money, payment_or_customer_id, options = {})
62
+ post = {}
63
+ add_amount(post, money, options)
64
+ if customer_id?(payment_or_customer_id)
65
+ post[:customer_id] = payment_or_customer_id
66
+ endpoint = ENDPOINTS[:customer_id_auth]
67
+ else
68
+ add_payment(post, payment_or_customer_id)
69
+ add_address(post, payment_or_customer_id, options)
70
+ add_customer_data(post, options)
71
+ endpoint = ENDPOINTS[:keyed_auth]
72
+ end
73
+ response = commit(endpoint, post)
74
+ check_token_response(response, endpoint, post, options)
75
+ end
76
+
77
+ def capture(money, authorization, options = {})
78
+ if visa_or_mastercard?(options)
79
+ MultiResponse.run do |r|
80
+ r.process { commit(ENDPOINTS[:capture], build_capture_request(money, authorization, options)) }
81
+ r.process { commit(ENDPOINTS[:"level_3_#{options[:visa_or_mastercard]}"], send_level_3_data(r, options)) }
82
+ end
83
+ else
84
+ post = build_capture_request(money, authorization, options)
85
+ response = commit(ENDPOINTS[:capture], post)
86
+ check_token_response(response, ENDPOINTS[:capture], post, options)
87
+ end
88
+ end
89
+
90
+ def refund(money, authorization, options = {})
91
+ # currently only support full and partial refunds of settled transactions via a transaction ID
92
+ post = {}
93
+ add_amount(post, money, options)
94
+ post[:transaction_id] = authorization
95
+ response = commit(ENDPOINTS[:transaction_refund], post)
96
+ check_token_response(response, ENDPOINTS[:transaction_refund], post, options)
97
+ end
98
+
99
+ def void(authorization, options = {})
100
+ post = {}
101
+ post[:transaction_id] = authorization
102
+
103
+ response = commit(ENDPOINTS[:transaction_void], post)
104
+ check_token_response(response, ENDPOINTS[:transaction_void], post, options)
105
+ end
106
+
107
+ def verify(credit_card, options = {})
108
+ authorize(0, credit_card, options)
109
+ end
110
+
111
+ # The customer_IDs that come from storing cards can be used for auth and purchase transaction types
112
+ def store(credit_card, options = {})
113
+ post = {}
114
+ post[:customer_id] = options[:customer_id] || SecureRandom.hex(12)
115
+ add_payment(post, credit_card)
116
+ add_address(post, credit_card, options)
117
+ response = commit(ENDPOINTS[:store], post)
118
+ check_token_response(response, ENDPOINTS[:store], post, options)
119
+ end
120
+
121
+ def redact(customer_id)
122
+ post = {}
123
+ post[:customer_id] = customer_id
124
+ response = commit(ENDPOINTS[:redact], post)
125
+ check_token_response(response, ENDPOINTS[:redact], post, options)
126
+ end
127
+
128
+ def supports_scrubbing?
129
+ true
130
+ end
131
+
132
+ def scrub(transcript)
133
+ transcript.
134
+ gsub(%r((Authorization: Bearer )[a-zA-Z0-9:_]+), '\1[FILTERED]').
135
+ gsub(%r(("credit_card\\?":{\\?"number\\?":\\?")\d+), '\1[FILTERED]').
136
+ gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]').
137
+ gsub(%r(("username\\?":\\?")\w+@+\w+.+\w+), '\1[FILTERED]').
138
+ gsub(%r(("password\\?":\\?")\w+), '\1[FILTERED]').
139
+ gsub(%r(("integrator_id\\?":\\?")\w+), '\1[FILTERED]')
140
+ end
141
+
142
+ def acquire_access_token
143
+ post = {}
144
+ post[:grant_type] = 'password'
145
+ post[:username] = @options[:username]
146
+ post[:password] = @options[:password]
147
+ data = post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
148
+ url = live_url + '/oauth/token'
149
+ oauth_headers = {
150
+ 'Accept' => '*/*',
151
+ 'Content-Type' => 'application/x-www-form-urlencoded'
152
+ }
153
+ response = ssl_post(url, data, oauth_headers)
154
+ json_response = JSON.parse(response)
155
+
156
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
157
+ response
158
+ end
159
+
160
+ private
161
+
162
+ def build_purchase_request(money, payment_or_customer_id, options)
163
+ post = {}
164
+ add_amount(post, money, options)
165
+ if customer_id?(payment_or_customer_id)
166
+ post[:customer_id] = payment_or_customer_id
167
+ else
168
+ add_payment(post, payment_or_customer_id)
169
+ add_address(post, payment_or_customer_id, options)
170
+ add_customer_data(post, options)
171
+ end
172
+
173
+ post
174
+ end
175
+
176
+ def build_capture_request(money, authorization, options)
177
+ post = {}
178
+ post[:transaction_id] = authorization
179
+ add_amount(post, money, options)
180
+
181
+ post
182
+ end
183
+
184
+ # method can only be used to add level 3 data to any approved and unsettled sale transaction so it is built into the standard purchase workflow above
185
+ def send_level_3_data(response, options)
186
+ post = {}
187
+ post[:transaction_id] = response.authorization
188
+ add_level_3_data(post, options)
189
+
190
+ post
191
+ end
192
+
193
+ def visa_or_mastercard?(options)
194
+ return false unless options[:visa_or_mastercard]
195
+
196
+ options[:visa_or_mastercard] == 'visa' || options[:visa_or_mastercard] == 'mastercard'
197
+ end
198
+
199
+ def customer_id?(payment_or_customer_id)
200
+ payment_or_customer_id.class == String
201
+ end
202
+
203
+ def string_literal_to_boolean(value)
204
+ return value unless value.class == String
205
+
206
+ if value.casecmp('true').zero?
207
+ true
208
+ elsif value.casecmp('false').zero?
209
+ false
210
+ else return nil
211
+ end
212
+ end
213
+
214
+ def add_customer_data(post, options)
215
+ return unless options[:email]
216
+
217
+ post[:email] = options[:email]
218
+ end
219
+
220
+ def add_address(post, creditcard, options)
221
+ return unless options[:billing_address] || options[:address]
222
+
223
+ address = options[:billing_address] || options[:address]
224
+ post[:billing_address] = {}
225
+ post[:billing_address][:name] = creditcard.name
226
+ post[:billing_address][:street_address] = address[:address1]
227
+ post[:billing_address][:city] = address[:city]
228
+ post[:billing_address][:state] = address[:state]
229
+ post[:billing_address][:zip] = address[:zip]
230
+ end
231
+
232
+ def add_amount(post, money, options)
233
+ post[:amount] = amount(money)
234
+ end
235
+
236
+ def add_payment(post, payment)
237
+ post[:credit_card] = {}
238
+ post[:credit_card][:number] = payment.number
239
+ post[:credit_card][:expiration_month] = payment.month
240
+ post[:credit_card][:expiration_year] = payment.year
241
+ end
242
+
243
+ def add_level_3_data(post, options)
244
+ post[:invoice_id] = options[:invoice_id] if options[:invoice_id]
245
+ post[:customer_reference_id] = options[:customer_reference_id] if options[:customer_reference_id]
246
+ post[:tax_amount] = options[:tax_amount].to_i if options[:tax_amount]
247
+ post[:national_tax_amount] = options[:national_tax_amount].to_i if options[:national_tax_amount]
248
+ post[:merchant_tax_id] = options[:merchant_tax_id] if options[:merchant_tax_id]
249
+ post[:customer_tax_id] = options[:customer_tax_id] if options[:customer_tax_id]
250
+ post[:commodity_code] = options[:commodity_code] if options[:commodity_code]
251
+ post[:discount_amount] = options[:discount_amount].to_i if options[:discount_amount]
252
+ post[:freight_amount] = options[:freight_amount].to_i if options[:freight_amount]
253
+ post[:duty_amount] = options[:duty_amount].to_i if options[:duty_amount]
254
+ post[:additional_tax_amount] = options[:additional_tax_amount].to_i if options[:additional_tax_amount]
255
+ post[:additional_tax_rate] = options[:additional_tax_rate].to_i if options[:additional_tax_rate]
256
+
257
+ add_source_address(post, options)
258
+ add_shipping_address(post, options)
259
+ add_line_items(post, options)
260
+ end
261
+
262
+ def add_source_address(post, options)
263
+ return unless source_address = options[:source_address] ||
264
+ options[:billing_address] ||
265
+ options[:address]
266
+
267
+ post[:source_address] = {}
268
+ post[:source_address][:zip] = source_address[:zip] if source_address[:zip]
269
+ end
270
+
271
+ def add_shipping_address(post, options)
272
+ return unless shipping_address = options[:shipping_address]
273
+
274
+ post[:shipping_address] = {}
275
+ post[:shipping_address][:name] = shipping_address[:name] if shipping_address[:name]
276
+ post[:shipping_address][:street_address] = shipping_address[:address1] if shipping_address[:address1]
277
+ post[:shipping_address][:street_address2] = shipping_address[:address2] if shipping_address[:address2]
278
+ post[:shipping_address][:city] = shipping_address[:city] if shipping_address[:city]
279
+ post[:shipping_address][:state] = shipping_address[:state] if shipping_address[:state]
280
+ post[:shipping_address][:zip] = shipping_address[:zip] if shipping_address[:zip]
281
+ post[:shipping_address][:country] = shipping_address[:country] if shipping_address[:country]
282
+ end
283
+
284
+ def add_line_items(post, options)
285
+ return unless options[:line_items]
286
+
287
+ line_items = []
288
+ options[:line_items].each do |li|
289
+ obj = {}
290
+
291
+ obj[:additional_tax_amount] = li[:additional_tax_amount].to_i if li[:additional_tax_amount]
292
+ obj[:additional_tax_included] = string_literal_to_boolean(li[:additional_tax_included]) if li[:additional_tax_included]
293
+ obj[:additional_tax_rate] = li[:additional_tax_rate].to_i if li[:additional_tax_rate]
294
+ obj[:amount] = li[:amount].to_i if li[:amount]
295
+ obj[:commodity_code] = li[:commodity_code] if li[:commodity_code]
296
+ obj[:debit_or_credit] = li[:debit_or_credit] if li[:debit_or_credit]
297
+ obj[:description] = li[:description] if li[:description]
298
+ obj[:discount_amount] = li[:discount_amount].to_i if li[:discount_amount]
299
+ obj[:discount_rate] = li[:discount_rate].to_i if li[:discount_rate]
300
+ obj[:discount_included] = string_literal_to_boolean(li[:discount_included]) if li[:discount_included]
301
+ obj[:merchant_tax_id] = li[:merchant_tax_id] if li[:merchant_tax_id]
302
+ obj[:product_id] = li[:product_id] if li[:product_id]
303
+ obj[:quantity] = li[:quantity] if li[:quantity]
304
+ obj[:transaction_id] = li[:transaction_id] if li[:transaction_id]
305
+ obj[:tax_included] = string_literal_to_boolean(li[:tax_included]) if li[:tax_included]
306
+ obj[:unit_of_measure] = li[:unit_of_measure] if li[:unit_of_measure]
307
+ obj[:unit_cost] = li[:unit_cost].to_i if li[:unit_cost]
308
+
309
+ line_items << obj
310
+ end
311
+ post[:line_items] = line_items
312
+ end
313
+
314
+ def check_token_response(response, endpoint, body = {}, options = {})
315
+ return response unless response.params['error'] == 'invalid_token'
316
+
317
+ acquire_access_token
318
+ commit(endpoint, body)
319
+ end
320
+
321
+ def parse(body)
322
+ JSON.parse(body)
323
+ end
324
+
325
+ def commit(action, parameters)
326
+ base_url = (test? ? test_url : live_url)
327
+ url = base_url + '/v1/' + action
328
+ raw_response = ssl_post(url, post_data(parameters), headers)
329
+ response = parse(raw_response)
330
+ success = success_from(response)
331
+
332
+ Response.new(
333
+ success,
334
+ message_from(success, response),
335
+ response,
336
+ authorization: authorization_from(action, response),
337
+ avs_result: AVSResult.new(code: response['avs_response']),
338
+ cvv_result: response['csc_response'],
339
+ test: test?,
340
+ error_code: success ? nil : error_code_from(response)
341
+ )
342
+ rescue JSON::ParserError
343
+ unparsable_response(raw_response)
344
+ end
345
+
346
+ def unparsable_response(raw_response)
347
+ message = 'Unparsable response received from PayTrace. Please contact PayTrace if you continue to receive this message.'
348
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
349
+ return Response.new(false, message)
350
+ end
351
+
352
+ def headers
353
+ {
354
+ 'Content-type' => 'application/json',
355
+ 'Authorization' => 'Bearer ' + @options[:access_token]
356
+ }
357
+ end
358
+
359
+ def success_from(response)
360
+ response['success']
361
+ end
362
+
363
+ def message_from(success, response)
364
+ return response['status_message'] if success
365
+
366
+ if error = response['errors']
367
+ message = 'Errors-'
368
+ error.each do |k, v|
369
+ message.concat(" code:#{k}, message:#{v}")
370
+ end
371
+ else
372
+ message = response['status_message'].to_s + " #{response['approval_message']}"
373
+ end
374
+
375
+ message
376
+ end
377
+
378
+ # store transactions do not return a transaction_id, but they return a customer_id that will then be used as the third_party_token for the stored payment method
379
+ def authorization_from(action, response)
380
+ if action == ENDPOINTS[:store]
381
+ response['customer_id']
382
+ else
383
+ response['transaction_id']
384
+ end
385
+ end
386
+
387
+ def post_data(parameters = {})
388
+ parameters[:password] = @options[:password]
389
+ parameters[:username] = @options[:username]
390
+ parameters[:integrator_id] = @options[:integrator_id]
391
+
392
+ parameters.to_json
393
+ end
394
+
395
+ def error_code_from(response)
396
+ STANDARD_ERROR_CODE_MAPPING[response['response_code']]
397
+ end
398
+
399
+ def handle_response(response)
400
+ response.body
401
+ end
402
+ end
403
+ end
404
+ end
@@ -39,6 +39,7 @@ module ActiveMerchant
39
39
  add_address(params, options)
40
40
  add_amount(params, amount, options)
41
41
  add_soft_descriptors(params, options)
42
+ add_level2_data(params, options)
42
43
  add_stored_credentials(params, options)
43
44
 
44
45
  commit(params, options)
@@ -53,6 +54,7 @@ module ActiveMerchant
53
54
  add_address(params, options)
54
55
  add_amount(params, amount, options)
55
56
  add_soft_descriptors(params, options)
57
+ add_level2_data(params, options)
56
58
  add_stored_credentials(params, options)
57
59
 
58
60
  commit(params, options)
@@ -73,10 +75,22 @@ module ActiveMerchant
73
75
 
74
76
  add_authorization_info(params, authorization)
75
77
  add_amount(params, (amount || amount_from_authorization(authorization)), options)
78
+ add_soft_descriptors(params, options)
79
+ add_invoice(params, options)
76
80
 
77
81
  commit(params, options)
78
82
  end
79
83
 
84
+ def credit(amount, payment_method, options = {})
85
+ params = { transaction_type: 'refund' }
86
+
87
+ add_amount(params, amount, options)
88
+ add_payment_method(params, payment_method, options)
89
+ add_soft_descriptors(params, options)
90
+ add_invoice(params, options)
91
+ commit(params, options)
92
+ end
93
+
80
94
  def store(payment_method, options = {})
81
95
  params = { transaction_type: 'store' }
82
96
 
@@ -246,6 +260,13 @@ module ActiveMerchant
246
260
  params[:soft_descriptors] = options[:soft_descriptors] if options[:soft_descriptors]
247
261
  end
248
262
 
263
+ def add_level2_data(params, options)
264
+ return unless level2_data = options[:level2]
265
+
266
+ params[:level2] = {}
267
+ params[:level2][:customer_ref] = level2_data[:customer_ref]
268
+ end
269
+
249
270
  def add_stored_credentials(params, options)
250
271
  if options[:sequence] || options[:stored_credential]
251
272
  params[:stored_credentials] = {}
@@ -290,15 +311,16 @@ module ActiveMerchant
290
311
  response = json_error(e.response.body)
291
312
  end
292
313
 
314
+ success = success_from(response)
293
315
  Response.new(
294
- success_from(response),
295
- handle_message(response, success_from(response)),
316
+ success,
317
+ handle_message(response, success),
296
318
  response,
297
319
  test: test?,
298
320
  authorization: authorization_from(params, response),
299
321
  avs_result: { code: response['avs'] },
300
322
  cvv_result: response['cvv2'],
301
- error_code: error_code(response, success_from(response))
323
+ error_code: success ? nil : error_code_from(response)
302
324
  )
303
325
  end
304
326
 
@@ -352,10 +374,16 @@ module ActiveMerchant
352
374
  }
353
375
  end
354
376
 
355
- def error_code(response, success)
356
- return if success
377
+ def error_code_from(response)
378
+ error_code = nil
379
+ if response['bank_resp_code'] == '100'
380
+ return
381
+ elsif response['bank_resp_code']
382
+ error_code = response['bank_resp_code']
383
+ elsif error_code = response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ')
384
+ end
357
385
 
358
- response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ')
386
+ error_code
359
387
  end
360
388
 
361
389
  def success_from(response)
@@ -118,6 +118,7 @@ module ActiveMerchant #:nodoc:
118
118
  xml.tag!('Description', options[:description]) unless options[:description].blank?
119
119
  xml.tag!('Comment', options[:comment]) unless options[:comment].blank?
120
120
  xml.tag!('ExtData', 'Name' => 'COMMENT2', 'Value' => options[:comment2]) unless options[:comment2].blank?
121
+ xml.tag!('MerchDescr', options[:merch_descr]) unless options[:merch_descr].blank?
121
122
  xml.tag!(
122
123
  'ExtData',
123
124
  'Name' => 'CAPTURECOMPLETE',
@@ -56,6 +56,10 @@ module ActiveMerchant #:nodoc:
56
56
  end
57
57
  end
58
58
 
59
+ def store(payment, options = {})
60
+ raise ArgumentError, 'Store is not supported on Payflow gateways'
61
+ end
62
+
59
63
  def verify_credentials
60
64
  response = void('0')
61
65
  response.params['result'] != '26'
@@ -141,6 +145,7 @@ module ActiveMerchant #:nodoc:
141
145
  xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank?
142
146
  xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank?
143
147
  xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank?
148
+ xml.tag! 'MerchDescr', options[:merch_descr] unless options[:merch_descr].blank?
144
149
 
145
150
  billing_address = options[:billing_address] || options[:address]
146
151
  add_address(xml, 'BillTo', billing_address, options) if billing_address
@@ -176,6 +181,7 @@ module ActiveMerchant #:nodoc:
176
181
  xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank?
177
182
  xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank?
178
183
  xml.tag! 'EMail', options[:email] unless options[:email].nil?
184
+ xml.tag! 'MerchDescr', options[:merch_descr] unless options[:merch_descr].blank?
179
185
 
180
186
  billing_address = options[:billing_address] || options[:address]
181
187
  add_address(xml, 'BillTo', billing_address, options) if billing_address
@@ -239,6 +245,7 @@ module ActiveMerchant #:nodoc:
239
245
  xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank?
240
246
  xml.tag! 'Description', options[:description] unless options[:description].blank?
241
247
  xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank?
248
+ xml.tag! 'MerchDescr', options[:merch_descr] unless options[:merch_descr].blank?
242
249
  xml.tag! 'BillTo' do
243
250
  xml.tag! 'Name', check.name
244
251
  end
@@ -282,18 +289,28 @@ module ActiveMerchant #:nodoc:
282
289
  xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank?
283
290
  xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
284
291
  xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank?
292
+ xml.tag! 'ThreeDSVersion', three_d_secure[:version] unless three_d_secure[:version].blank?
293
+ xml.tag! 'DSTransactionID', three_d_secure[:ds_transaction_id] unless three_d_secure[:ds_transaction_id].blank?
285
294
  end
286
295
  end
287
296
  end
288
297
 
289
298
  def authentication_status(three_d_secure, xml)
290
- if three_d_secure[:authentication_response_status].present?
291
- xml.tag! 'Status', three_d_secure[:authentication_response_status]
292
- elsif three_d_secure[:directory_response_status].present?
293
- xml.tag! 'Status', three_d_secure[:directory_response_status]
299
+ status = if three_d_secure[:authentication_response_status].present?
300
+ three_d_secure[:authentication_response_status]
301
+ elsif three_d_secure[:directory_response_status].present?
302
+ three_d_secure[:directory_response_status]
303
+ end
304
+ if status.present?
305
+ xml.tag! 'Status', status
306
+ xml.tag! 'AuthenticationStatus', status if version_2_or_newer?(three_d_secure)
294
307
  end
295
308
  end
296
309
 
310
+ def version_2_or_newer?(three_d_secure)
311
+ three_d_secure[:version]&.start_with?('2')
312
+ end
313
+
297
314
  def credit_card_type(credit_card)
298
315
  return '' if card_brand(credit_card).blank?
299
316
 
@@ -86,8 +86,8 @@ module ActiveMerchant #:nodoc:
86
86
  refund(money, identification, options)
87
87
  end
88
88
 
89
- def verify(money, payment_source, options = {})
90
- request = build_purchase_or_authorization_request(money, payment_source, options)
89
+ def verify(payment_source, options = {})
90
+ request = build_purchase_or_authorization_request(100, payment_source, options)
91
91
  commit(:validate, request)
92
92
  end
93
93
 
@@ -154,7 +154,7 @@ module ActiveMerchant #:nodoc:
154
154
  add_credit_card(result, payment_source)
155
155
  end
156
156
 
157
- add_amount(result, money, options)
157
+ add_amount(result, money, options) if money
158
158
  add_invoice(result, options)
159
159
  add_address_verification_data(result, options)
160
160
  add_optional_elements(result, options)
@@ -234,8 +234,8 @@ module ActiveMerchant #:nodoc:
234
234
  address = options[:billing_address] || options[:address]
235
235
  return if address.nil?
236
236
 
237
- xml.add_element('EnableAvsData').text = 1
238
- xml.add_element('AvsAction').text = 1
237
+ xml.add_element('EnableAvsData').text = options[:enable_avs_data] || 1
238
+ xml.add_element('AvsAction').text = options[:avs_action] || 1
239
239
 
240
240
  xml.add_element('AvsStreetAddress').text = address[:address1]
241
241
  xml.add_element('AvsPostCode').text = address[:zip]
@@ -82,6 +82,7 @@ module ActiveMerchant #:nodoc:
82
82
  def refund(money, authorization, options = {})
83
83
  post = { transaction: { id: authorization } }
84
84
  post[:order] = { amount: amount(money).to_f } if money
85
+ add_more_info(post, options)
85
86
 
86
87
  commit_transaction('refund', post)
87
88
  end
@@ -198,6 +199,10 @@ module ActiveMerchant #:nodoc:
198
199
  extra_params[:auth_data] = auth_data
199
200
  end
200
201
 
202
+ def add_more_info(post, options)
203
+ post[:more_info] = options[:more_info] if options[:more_info]
204
+ end
205
+
201
206
  def parse(body)
202
207
  JSON.parse(body)
203
208
  end
@@ -250,6 +250,7 @@ module ActiveMerchant #:nodoc:
250
250
  xml.tag! 'n2:PaymentType', options[:payment_type] || 'Any'
251
251
  add_payment_details(xml, money, currency_code, options)
252
252
  xml.tag! 'n2:IPAddress', options[:ip]
253
+ xml.tag! 'n2:MerchantSessionId', options[:merchant_session_id] if options[:merchant_session_id].present?
253
254
  end
254
255
  end
255
256
  end