activemerchant 1.117.0 → 1.123.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +5 -3
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +6 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +96 -22
  8. data/lib/active_merchant/billing/gateways/adyen.rb +38 -21
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +5 -3
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +58 -8
  14. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  15. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  16. data/lib/active_merchant/billing/gateways/credorax.rb +16 -9
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +67 -9
  18. data/lib/active_merchant/billing/gateways/d_local.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/decidir.rb +29 -3
  20. data/lib/active_merchant/billing/gateways/elavon.rb +110 -26
  21. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  22. data/lib/active_merchant/billing/gateways/eway_rapid.rb +13 -0
  23. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +17 -6
  24. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +25 -16
  26. data/lib/active_merchant/billing/gateways/hps.rb +65 -2
  27. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  28. data/lib/active_merchant/billing/gateways/litle.rb +9 -4
  29. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  30. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +277 -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 +37 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +202 -47
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +57 -11
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +10 -5
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +26 -1
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -0
  45. data/lib/active_merchant/billing/gateways/paypal.rb +10 -2
  46. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  47. data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
  48. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  49. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  50. data/lib/active_merchant/billing/gateways/pin.rb +11 -0
  51. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  52. data/lib/active_merchant/billing/gateways/redsys.rb +78 -30
  53. data/lib/active_merchant/billing/gateways/safe_charge.rb +19 -8
  54. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  55. data/lib/active_merchant/billing/gateways/stripe.rb +8 -8
  56. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +86 -25
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  58. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  59. data/lib/active_merchant/billing/gateways/worldpay.rb +68 -20
  60. data/lib/active_merchant/billing/response.rb +2 -1
  61. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  62. data/lib/active_merchant/billing.rb +1 -0
  63. data/lib/active_merchant/version.rb +1 -1
  64. data/lib/certs/cacert.pem +1582 -2431
  65. metadata +10 -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,7 +75,19 @@ 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)
80
+
81
+ commit(params, options)
82
+ end
83
+
84
+ def credit(amount, payment_method, options = {})
85
+ params = { transaction_type: 'refund' }
76
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)
77
91
  commit(params, options)
78
92
  end
79
93
 
@@ -246,17 +260,42 @@ 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
- if options[:sequence]
271
+ if options[:sequence] || options[:stored_credential]
251
272
  params[:stored_credentials] = {}
252
- params[:stored_credentials][:cardbrand_original_transaction_id] = options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id]
253
- params[:stored_credentials][:sequence] = options[:sequence]
254
- params[:stored_credentials][:initiator] = options[:initiator] if options[:initiator]
255
- params[:stored_credentials][:is_scheduled] = options[:is_scheduled]
273
+ params[:stored_credentials][:cardbrand_original_transaction_id] = original_transaction_id(options) if original_transaction_id(options)
274
+ params[:stored_credentials][:initiator] = initiator(options) if initiator(options)
275
+ params[:stored_credentials][:sequence] = options[:sequence] || sequence(options[:stored_credential][:initial_transaction])
276
+ params[:stored_credentials][:is_scheduled] = options[:is_scheduled] || is_scheduled(options[:stored_credential][:reason_type])
256
277
  params[:stored_credentials][:auth_type_override] = options[:auth_type_override] if options[:auth_type_override]
257
278
  end
258
279
  end
259
280
 
281
+ def original_transaction_id(options)
282
+ return options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id]
283
+ return options[:stored_credential][:network_transaction_id] if options.dig(:stored_credential, :network_transaction_id)
284
+ end
285
+
286
+ def initiator(options)
287
+ return options[:initiator] if options[:initiator]
288
+ return options[:stored_credential][:initiator].upcase if options.dig(:stored_credential, :initiator)
289
+ end
290
+
291
+ def sequence(initial_transaction)
292
+ initial_transaction ? 'FIRST' : 'SUBSEQUENT'
293
+ end
294
+
295
+ def is_scheduled(reason_type)
296
+ reason_type == 'recurring' ? 'true' : 'false'
297
+ end
298
+
260
299
  def commit(params, options)
261
300
  url = base_url(options) + endpoint(params)
262
301
 
@@ -272,15 +311,16 @@ module ActiveMerchant
272
311
  response = json_error(e.response.body)
273
312
  end
274
313
 
314
+ success = success_from(response)
275
315
  Response.new(
276
- success_from(response),
277
- handle_message(response, success_from(response)),
316
+ success,
317
+ handle_message(response, success),
278
318
  response,
279
319
  test: test?,
280
320
  authorization: authorization_from(params, response),
281
321
  avs_result: { code: response['avs'] },
282
322
  cvv_result: response['cvv2'],
283
- error_code: error_code(response, success_from(response))
323
+ error_code: success ? nil : error_code_from(response)
284
324
  )
285
325
  end
286
326
 
@@ -334,10 +374,16 @@ module ActiveMerchant
334
374
  }
335
375
  end
336
376
 
337
- def error_code(response, success)
338
- 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
339
385
 
340
- response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ')
386
+ error_code
341
387
  end
342
388
 
343
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,6 +289,8 @@ 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
@@ -86,6 +86,11 @@ module ActiveMerchant #:nodoc:
86
86
  refund(money, identification, options)
87
87
  end
88
88
 
89
+ def verify(payment_source, options = {})
90
+ request = build_purchase_or_authorization_request(100, payment_source, options)
91
+ commit(:validate, request)
92
+ end
93
+
89
94
  # Token Based Billing
90
95
  #
91
96
  # Instead of storing the credit card details locally, you can store them inside the
@@ -149,7 +154,7 @@ module ActiveMerchant #:nodoc:
149
154
  add_credit_card(result, payment_source)
150
155
  end
151
156
 
152
- add_amount(result, money, options)
157
+ add_amount(result, money, options) if money
153
158
  add_invoice(result, options)
154
159
  add_address_verification_data(result, options)
155
160
  add_optional_elements(result, options)
@@ -229,8 +234,8 @@ module ActiveMerchant #:nodoc:
229
234
  address = options[:billing_address] || options[:address]
230
235
  return if address.nil?
231
236
 
232
- xml.add_element('EnableAvsData').text = 1
233
- 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
234
239
 
235
240
  xml.add_element('AvsStreetAddress').text = address[:address1]
236
241
  xml.add_element('AvsPostCode').text = address[:zip]
@@ -334,7 +339,7 @@ module ActiveMerchant #:nodoc:
334
339
  def authorization_from(action, response)
335
340
  case action
336
341
  when :validate
337
- (response[:billing_id] || response[:dps_billing_id])
342
+ (response[:billing_id] || response[:dps_billing_id] || response[:dps_txn_ref])
338
343
  else
339
344
  response[:dps_txn_ref]
340
345
  end
@@ -361,7 +366,7 @@ module ActiveMerchant #:nodoc:
361
366
  # add a method to response so we can easily get the token
362
367
  # for Validate transactions
363
368
  def token
364
- @params['billing_id'] || @params['dps_billing_id']
369
+ @params['billing_id'] || @params['dps_billing_id'] || @params['dps_txn_ref']
365
370
  end
366
371
  end
367
372
  end
@@ -9,7 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
 
10
10
  self.supported_countries = %w[MX EC CO BR CL PE]
11
11
  self.default_currency = 'USD'
12
- self.supported_cardtypes = %i[visa master american_express diners_club elo alia]
12
+ self.supported_cardtypes = %i[visa master american_express diners_club elo alia olimpica]
13
13
 
14
14
  self.homepage_url = 'https://secure.paymentez.com/'
15
15
  self.display_name = 'Paymentez'
@@ -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
@@ -175,9 +176,33 @@ module ActiveMerchant #:nodoc:
175
176
  extra_params = {}
176
177
  extra_params.merge!(options[:extra_params]) if options[:extra_params]
177
178
 
179
+ add_external_mpi_fields(extra_params, options)
180
+
178
181
  post['extra_params'] = extra_params unless extra_params.empty?
179
182
  end
180
183
 
184
+ def add_external_mpi_fields(extra_params, options)
185
+ three_d_secure_options = options[:three_d_secure]
186
+ return unless three_d_secure_options
187
+
188
+ auth_data = {
189
+ cavv: three_d_secure_options[:cavv],
190
+ xid: three_d_secure_options[:xid],
191
+ eci: three_d_secure_options[:eci],
192
+ version: three_d_secure_options[:version],
193
+ reference_id: three_d_secure_options[:three_ds_server_trans_id],
194
+ status: three_d_secure_options[:authentication_response_status] || three_d_secure_options[:directory_response_status]
195
+ }.compact
196
+
197
+ return if auth_data.empty?
198
+
199
+ extra_params[:auth_data] = auth_data
200
+ end
201
+
202
+ def add_more_info(post, options)
203
+ post[:more_info] = options[:more_info] if options[:more_info]
204
+ end
205
+
181
206
  def parse(body)
182
207
  JSON.parse(body)
183
208
  end
@@ -5,6 +5,7 @@ module ActiveMerchant #:nodoc:
5
5
  include Empty
6
6
 
7
7
  API_VERSION = '124'
8
+ API_VERSION_3DS2 = '214.0'
8
9
 
9
10
  URLS = {
10
11
  :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',