activemerchant 1.126.0 → 1.130.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +263 -0
  3. data/lib/active_merchant/billing/check.rb +40 -8
  4. data/lib/active_merchant/billing/credit_card.rb +28 -1
  5. data/lib/active_merchant/billing/credit_card_methods.rb +80 -24
  6. data/lib/active_merchant/billing/gateways/adyen.rb +69 -10
  7. data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
  8. data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +23 -5
  10. data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
  12. data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
  13. data/lib/active_merchant/billing/gateways/borgun.rb +57 -16
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +72 -24
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
  17. data/lib/active_merchant/billing/gateways/checkout_v2.rb +228 -57
  18. data/lib/active_merchant/billing/gateways/commerce_hub.rb +366 -0
  19. data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
  20. data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +119 -33
  22. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +454 -0
  23. data/lib/active_merchant/billing/gateways/d_local.rb +45 -5
  24. data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
  25. data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
  26. data/lib/active_merchant/billing/gateways/element.rb +21 -1
  27. data/lib/active_merchant/billing/gateways/global_collect.rb +113 -40
  28. data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
  29. data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
  30. data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
  31. data/lib/active_merchant/billing/gateways/litle.rb +25 -5
  32. data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
  33. data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
  34. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
  35. data/lib/active_merchant/billing/gateways/monei.rb +2 -0
  36. data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
  37. data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
  38. data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
  39. data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
  40. data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
  41. data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
  42. data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  45. data/lib/active_merchant/billing/gateways/paypal_express.rb +2 -0
  46. data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
  47. data/lib/active_merchant/billing/gateways/payu_latam.rb +4 -1
  48. data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
  49. data/lib/active_merchant/billing/gateways/priority.rb +29 -6
  50. data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
  51. data/lib/active_merchant/billing/gateways/reach.rb +277 -0
  52. data/lib/active_merchant/billing/gateways/redsys.rb +10 -5
  53. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
  54. data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
  55. data/lib/active_merchant/billing/gateways/shift4.rb +345 -0
  56. data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
  57. data/lib/active_merchant/billing/gateways/stripe.rb +30 -6
  58. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
  59. data/lib/active_merchant/billing/gateways/tns.rb +2 -5
  60. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
  61. data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
  62. data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
  63. data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
  64. data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
  65. data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
  66. data/lib/active_merchant/billing/gateways/worldpay.rb +122 -13
  67. data/lib/active_merchant/billing/response.rb +15 -1
  68. data/lib/active_merchant/connection.rb +0 -2
  69. data/lib/active_merchant/country.rb +1 -0
  70. data/lib/active_merchant/errors.rb +4 -1
  71. data/lib/active_merchant/version.rb +1 -1
  72. metadata +24 -3
@@ -6,18 +6,12 @@ module ActiveMerchant #:nodoc:
6
6
 
7
7
  self.supported_countries = %w(BR MX CO CL AR PE)
8
8
  self.default_currency = 'USD'
9
- self.supported_cardtypes = %i[visa master american_express discover diners_club]
9
+ self.supported_cardtypes = %i[visa master american_express discover diners_club elo hipercard]
10
10
 
11
11
  self.homepage_url = 'http://www.ebanx.com/'
12
12
  self.display_name = 'EBANX'
13
13
 
14
- CARD_BRAND = {
15
- visa: 'visa',
16
- master: 'master_card',
17
- american_express: 'amex',
18
- discover: 'discover',
19
- diners_club: 'diners'
20
- }
14
+ TAGS = ['Spreedly']
21
15
 
22
16
  URL_MAP = {
23
17
  purchase: 'direct',
@@ -25,7 +19,8 @@ module ActiveMerchant #:nodoc:
25
19
  capture: 'capture',
26
20
  refund: 'refund',
27
21
  void: 'cancel',
28
- store: 'token'
22
+ store: 'token',
23
+ inquire: 'query'
29
24
  }
30
25
 
31
26
  HTTP_METHOD = {
@@ -34,13 +29,14 @@ module ActiveMerchant #:nodoc:
34
29
  capture: :get,
35
30
  refund: :post,
36
31
  void: :get,
37
- store: :post
32
+ store: :post,
33
+ inquire: :get
38
34
  }
39
35
 
40
36
  VERIFY_AMOUNT_PER_COUNTRY = {
41
37
  'br' => 100,
42
38
  'ar' => 100,
43
- 'co' => 100,
39
+ 'co' => 50000,
44
40
  'pe' => 300,
45
41
  'mx' => 2000,
46
42
  'cl' => 80000
@@ -57,7 +53,7 @@ module ActiveMerchant #:nodoc:
57
53
  add_operation(post)
58
54
  add_invoice(post, money, options)
59
55
  add_customer_data(post, payment, options)
60
- add_card_or_token(post, payment)
56
+ add_card_or_token(post, payment, options)
61
57
  add_address(post, options)
62
58
  add_customer_responsible_person(post, payment, options)
63
59
  add_additional_data(post, options)
@@ -71,7 +67,7 @@ module ActiveMerchant #:nodoc:
71
67
  add_operation(post)
72
68
  add_invoice(post, money, options)
73
69
  add_customer_data(post, payment, options)
74
- add_card_or_token(post, payment)
70
+ add_card_or_token(post, payment, options)
75
71
  add_address(post, options)
76
72
  add_customer_responsible_person(post, payment, options)
77
73
  add_additional_data(post, options)
@@ -124,6 +120,14 @@ module ActiveMerchant #:nodoc:
124
120
  end
125
121
  end
126
122
 
123
+ def inquire(authorization, options = {})
124
+ post = {}
125
+ add_integration_key(post)
126
+ add_authorization(post, authorization)
127
+
128
+ commit(:inquire, post)
129
+ end
130
+
127
131
  def supports_scrubbing?
128
132
  true
129
133
  end
@@ -151,7 +155,7 @@ module ActiveMerchant #:nodoc:
151
155
 
152
156
  def add_customer_data(post, payment, options)
153
157
  post[:payment][:name] = customer_name(payment, options)
154
- post[:payment][:email] = options[:email] || 'unspecified@example.com'
158
+ post[:payment][:email] = options[:email]
155
159
  post[:payment][:document] = options[:document]
156
160
  post[:payment][:birth_date] = options[:birth_date] if options[:birth_date]
157
161
  end
@@ -186,14 +190,15 @@ module ActiveMerchant #:nodoc:
186
190
  post[:payment][:order_number] = options[:order_id][0..39] if options[:order_id]
187
191
  end
188
192
 
189
- def add_card_or_token(post, payment)
190
- payment, brand = payment.split('|') if payment.is_a?(String)
191
- post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym]
193
+ def add_card_or_token(post, payment, options)
194
+ payment = payment.split('|')[0] if payment.is_a?(String)
195
+ post[:payment][:payment_type_code] = 'creditcard'
192
196
  post[:payment][:creditcard] = payment_details(payment)
197
+ post[:payment][:creditcard][:soft_descriptor] = options[:soft_descriptor] if options[:soft_descriptor]
193
198
  end
194
199
 
195
200
  def add_payment_details(post, payment)
196
- post[:payment_type_code] = CARD_BRAND[payment.brand.to_sym]
201
+ post[:payment_type_code] = 'creditcard'
197
202
  post[:creditcard] = payment_details(payment)
198
203
  end
199
204
 
@@ -216,6 +221,7 @@ module ActiveMerchant #:nodoc:
216
221
  post[:metadata] = {} if post[:metadata].nil?
217
222
  post[:metadata][:merchant_payment_code] = options[:order_id] if options[:order_id]
218
223
  post[:processing_type] = options[:processing_type] if options[:processing_type]
224
+ post[:payment][:tags] = TAGS
219
225
  end
220
226
 
221
227
  def parse(body)
@@ -253,13 +259,15 @@ module ActiveMerchant #:nodoc:
253
259
  end
254
260
 
255
261
  def success_from(action, response)
262
+ payment_status = response.try(:[], 'payment').try(:[], 'status')
263
+
256
264
  if %i[purchase capture refund].include?(action)
257
- response.try(:[], 'payment').try(:[], 'status') == 'CO'
265
+ payment_status == 'CO'
258
266
  elsif action == :authorize
259
- response.try(:[], 'payment').try(:[], 'status') == 'PE'
267
+ payment_status == 'PE'
260
268
  elsif action == :void
261
- response.try(:[], 'payment').try(:[], 'status') == 'CA'
262
- elsif action == :store
269
+ payment_status == 'CA'
270
+ elsif %i[store inquire].include?(action)
263
271
  response.try(:[], 'status') == 'SUCCESS'
264
272
  else
265
273
  false
@@ -274,7 +282,11 @@ module ActiveMerchant #:nodoc:
274
282
 
275
283
  def authorization_from(action, parameters, response)
276
284
  if action == :store
277
- "#{response.try(:[], 'token')}|#{CARD_BRAND[parameters[:payment_type_code].to_sym]}"
285
+ if success_from(action, response)
286
+ "#{response.try(:[], 'token')}|#{response['payment_type_code']}"
287
+ else
288
+ response.try(:[], 'token')
289
+ end
278
290
  else
279
291
  response.try(:[], 'payment').try(:[], 'hash')
280
292
  end
@@ -294,7 +306,7 @@ module ActiveMerchant #:nodoc:
294
306
  end
295
307
 
296
308
  def requires_http_get(action)
297
- return true if %i[capture void].include?(action)
309
+ return true if %i[capture void inquire].include?(action)
298
310
 
299
311
  false
300
312
  end
@@ -17,6 +17,11 @@ module ActiveMerchant #:nodoc:
17
17
  SERVICE_TEST_URL = 'https://certservices.elementexpress.com/express.asmx'
18
18
  SERVICE_LIVE_URL = 'https://services.elementexpress.com/express.asmx'
19
19
 
20
+ NETWORK_TOKEN_TYPE = {
21
+ apple_pay: '2',
22
+ google_pay: '1'
23
+ }
24
+
20
25
  def initialize(options = {})
21
26
  requires!(options, :account_id, :account_token, :application_id, :acceptor_id, :application_name, :application_version)
22
27
  super
@@ -171,6 +176,8 @@ module ActiveMerchant #:nodoc:
171
176
  add_payment_account_id(xml, payment)
172
177
  elsif payment.is_a?(Check)
173
178
  add_echeck(xml, payment)
179
+ elsif payment.is_a?(NetworkTokenizationCreditCard)
180
+ add_network_tokenization_card(xml, payment)
174
181
  else
175
182
  add_credit_card(xml, payment)
176
183
  end
@@ -200,7 +207,7 @@ module ActiveMerchant #:nodoc:
200
207
  xml.TransactionID options[:trans_id] if options[:trans_id]
201
208
  xml.TransactionAmount amount(money.to_i) if money
202
209
  xml.MarketCode market_code(money, options) if options[:market_code] || money
203
- xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20)
210
+ xml.ReferenceNumber options[:order_id].present? ? options[:order_id][0, 50] : SecureRandom.hex(20)
204
211
  xml.TicketNumber options[:ticket_number] if options[:ticket_number]
205
212
  xml.MerchantSuppliedTransactionId options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id]
206
213
  xml.PaymentType options[:payment_type] if options[:payment_type]
@@ -246,8 +253,21 @@ module ActiveMerchant #:nodoc:
246
253
  end
247
254
  end
248
255
 
256
+ def add_network_tokenization_card(xml, payment)
257
+ xml.card do
258
+ xml.CardNumber payment.number
259
+ xml.ExpirationMonth format(payment.month, :two_digits)
260
+ xml.ExpirationYear format(payment.year, :two_digits)
261
+ xml.CardholderName "#{payment.first_name} #{payment.last_name}"
262
+ xml.Cryptogram payment.payment_cryptogram
263
+ xml.ElectronicCommerceIndicator payment.eci if payment.eci.present?
264
+ xml.WalletType NETWORK_TOKEN_TYPE.fetch(payment.source, '0')
265
+ end
266
+ end
267
+
249
268
  def add_address(xml, options)
250
269
  if address = options[:billing_address] || options[:address]
270
+ address[:email] ||= options[:email]
251
271
  xml.address do
252
272
  xml.BillingAddress1 address[:address1] if address[:address1]
253
273
  xml.BillingAddress2 address[:address2] if address[:address2]
@@ -2,6 +2,8 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class GlobalCollectGateway < Gateway
4
4
  class_attribute :preproduction_url
5
+ class_attribute :ogone_direct_test
6
+ class_attribute :ogone_direct_live
5
7
 
6
8
  self.display_name = 'GlobalCollect'
7
9
  self.homepage_url = 'http://www.globalcollect.com/'
@@ -9,6 +11,8 @@ module ActiveMerchant #:nodoc:
9
11
  self.test_url = 'https://eu.sandbox.api-ingenico.com'
10
12
  self.preproduction_url = 'https://world.preprod.api-ingenico.com'
11
13
  self.live_url = 'https://world.api-ingenico.com'
14
+ self.ogone_direct_test = 'https://payment.preprod.direct.worldline-solutions.com'
15
+ self.ogone_direct_live = 'https://payment.direct.worldline-solutions.com'
12
16
 
13
17
  self.supported_countries = %w[AD AE AG AI AL AM AO AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BW BY BZ CA CC CD CF CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HN HR HT HU ID IE IL IM IN IS IT JM JO JP KE KG KH KI KM KN KR KW KY KZ LA LB LC LI LK LR LS LT LU LV MA MC MD ME MF MG MH MK MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PL PN PS PT PW QA RE RO RS RU RW SA SB SC SE SG SH SI SJ SK SL SM SN SR ST SV SZ TC TD TG TH TJ TL TM TN TO TR TT TV TW TZ UA UG US UY UZ VC VE VG VI VN WF WS ZA ZM ZW]
14
18
  self.default_currency = 'USD'
@@ -36,7 +40,7 @@ module ActiveMerchant #:nodoc:
36
40
  add_creator_info(post, options)
37
41
  add_fraud_fields(post, options)
38
42
  add_external_cardholder_authentication_data(post, options)
39
- commit(:authorize, post, options: options)
43
+ commit(:post, :authorize, post, options: options)
40
44
  end
41
45
 
42
46
  def capture(money, authorization, options = {})
@@ -44,7 +48,7 @@ module ActiveMerchant #:nodoc:
44
48
  add_order(post, money, options, capture: true)
45
49
  add_customer_data(post, options)
46
50
  add_creator_info(post, options)
47
- commit(:capture, post, authorization: authorization)
51
+ commit(:post, :capture, post, authorization: authorization)
48
52
  end
49
53
 
50
54
  def refund(money, authorization, options = {})
@@ -52,13 +56,13 @@ module ActiveMerchant #:nodoc:
52
56
  add_amount(post, money, options)
53
57
  add_refund_customer_data(post, options)
54
58
  add_creator_info(post, options)
55
- commit(:refund, post, authorization: authorization)
59
+ commit(:post, :refund, post, authorization: authorization)
56
60
  end
57
61
 
58
62
  def void(authorization, options = {})
59
63
  post = nestable_hash
60
64
  add_creator_info(post, options)
61
- commit(:void, post, authorization: authorization)
65
+ commit(:post, :void, post, authorization: authorization)
62
66
  end
63
67
 
64
68
  def verify(payment, options = {})
@@ -68,6 +72,10 @@ module ActiveMerchant #:nodoc:
68
72
  end
69
73
  end
70
74
 
75
+ def inquire(authorization, options = {})
76
+ commit(:get, :inquire, nil, authorization: authorization)
77
+ end
78
+
71
79
  def supports_scrubbing?
72
80
  true
73
81
  end
@@ -76,7 +84,10 @@ module ActiveMerchant #:nodoc:
76
84
  transcript.
77
85
  gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]').
78
86
  gsub(%r(("cardNumber\\+":\\+")\d+), '\1[FILTERED]').
79
- gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]')
87
+ gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]').
88
+ gsub(%r(("dpan\\+":\\+")\d+), '\1[FILTERED]').
89
+ gsub(%r(("pan\\+":\\+")\d+), '\1[FILTERED]').
90
+ gsub(%r(("cryptogram\\+":\\+"|("cavv\\+" : \\+"))[^\\]*), '\1[FILTERED]')
80
91
  end
81
92
 
82
93
  private
@@ -89,7 +100,9 @@ module ActiveMerchant #:nodoc:
89
100
  'jcb' => '125',
90
101
  'diners_club' => '132',
91
102
  'cabal' => '135',
92
- 'naranja' => '136'
103
+ 'naranja' => '136',
104
+ 'apple_pay': '302',
105
+ 'google_pay': '320'
93
106
  }
94
107
 
95
108
  def add_order(post, money, options, capture: false)
@@ -105,7 +118,7 @@ module ActiveMerchant #:nodoc:
105
118
  post['order']['references']['invoiceData'] = {
106
119
  'invoiceNumber' => options[:invoice]
107
120
  }
108
- add_airline_data(post, options)
121
+ add_airline_data(post, options) unless ogone_direct?
109
122
  add_lodging_data(post, options)
110
123
  add_number_of_installments(post, options) if options[:number_of_installments]
111
124
  end
@@ -239,9 +252,10 @@ module ActiveMerchant #:nodoc:
239
252
  end
240
253
 
241
254
  def add_amount(post, money, options = {})
255
+ currency_ogone = 'EUR' if ogone_direct?
242
256
  post['amountOfMoney'] = {
243
257
  'amount' => amount(money),
244
- 'currencyCode' => options[:currency] || currency(money)
258
+ 'currencyCode' => options[:currency] || currency_ogone || currency(money)
245
259
  }
246
260
  end
247
261
 
@@ -250,20 +264,58 @@ module ActiveMerchant #:nodoc:
250
264
  month = format(payment.month, :two_digits)
251
265
  expirydate = "#{month}#{year}"
252
266
  pre_authorization = options[:pre_authorization] ? 'PRE_AUTHORIZATION' : 'FINAL_AUTHORIZATION'
253
- post['cardPaymentMethodSpecificInput'] = {
254
- 'paymentProductId' => BRAND_MAP[payment.brand],
255
- 'skipAuthentication' => 'true', # refers to 3DSecure
267
+ product_id = options[:payment_product_id] || BRAND_MAP[payment.brand]
268
+ specifics_inputs = {
269
+ 'paymentProductId' => product_id,
270
+ 'skipAuthentication' => options[:skip_authentication] || 'true', # refers to 3DSecure
256
271
  'skipFraudService' => 'true',
257
272
  'authorizationMode' => pre_authorization
258
273
  }
259
- post['cardPaymentMethodSpecificInput']['requiresApproval'] = options[:requires_approval] unless options[:requires_approval].nil?
274
+ specifics_inputs['requiresApproval'] = options[:requires_approval] unless options[:requires_approval].nil?
275
+ if payment.is_a?(NetworkTokenizationCreditCard)
276
+ add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate)
277
+ elsif payment.is_a?(CreditCard)
278
+ options[:google_pay_pan_only] ? add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate) : add_credit_card(post, payment, specifics_inputs, expirydate)
279
+ end
280
+ end
260
281
 
261
- post['cardPaymentMethodSpecificInput']['card'] = {
262
- 'cvv' => payment.verification_value,
263
- 'cardNumber' => payment.number,
264
- 'expiryDate' => expirydate,
265
- 'cardholderName' => payment.name
266
- }
282
+ def add_credit_card(post, payment, specifics_inputs, expirydate)
283
+ post['cardPaymentMethodSpecificInput'] = specifics_inputs.merge({
284
+ 'card' => {
285
+ 'cvv' => payment.verification_value,
286
+ 'cardNumber' => payment.number,
287
+ 'expiryDate' => expirydate,
288
+ 'cardholderName' => payment.name
289
+ }
290
+ })
291
+ end
292
+
293
+ def add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate)
294
+ specifics_inputs['paymentProductId'] = options[:google_pay_pan_only] ? BRAND_MAP[:google_pay] : BRAND_MAP[payment.source]
295
+ post['mobilePaymentMethodSpecificInput'] = specifics_inputs
296
+ add_decrypted_payment_data(post, payment, options, expirydate)
297
+ end
298
+
299
+ def add_decrypted_payment_data(post, payment, options, expirydate)
300
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.payment_cryptogram
301
+ data = {
302
+ 'cardholderName' => payment.name,
303
+ 'cryptogram' => payment.payment_cryptogram,
304
+ 'eci' => payment.eci,
305
+ 'expiryDate' => expirydate,
306
+ 'dpan' => payment.number
307
+ }
308
+ data['paymentMethod'] = 'TOKENIZED_CARD' if payment.source == :google_pay
309
+ # else case when google payment is an ONLY_PAN, doesn't have cryptogram or eci.
310
+ elsif options[:google_pay_pan_only]
311
+ data = {
312
+ 'cardholderName' => payment.name,
313
+ 'expiryDate' => expirydate,
314
+ 'pan' => payment.number,
315
+ 'paymentMethod' => 'CARD'
316
+ }
317
+ end
318
+ post['mobilePaymentMethodSpecificInput']['decryptedPaymentData'] = data if data
267
319
  end
268
320
 
269
321
  def add_customer_data(post, options, payment = nil)
@@ -330,18 +382,24 @@ module ActiveMerchant #:nodoc:
330
382
  def add_external_cardholder_authentication_data(post, options)
331
383
  return unless threeds_2_options = options[:three_d_secure]
332
384
 
333
- authentication_data = {}
334
- authentication_data[:acsTransactionId] = threeds_2_options[:acs_transaction_id] if threeds_2_options[:acs_transaction_id]
335
- authentication_data[:cavv] = threeds_2_options[:cavv] if threeds_2_options[:cavv]
336
- authentication_data[:cavvAlgorithm] = threeds_2_options[:cavv_algorithm] if threeds_2_options[:cavv_algorithm]
337
- authentication_data[:directoryServerTransactionId] = threeds_2_options[:ds_transaction_id] if threeds_2_options[:ds_transaction_id]
338
- authentication_data[:eci] = threeds_2_options[:eci] if threeds_2_options[:eci]
339
- authentication_data[:threeDSecureVersion] = threeds_2_options[:version] if threeds_2_options[:version]
340
- authentication_data[:validationResult] = threeds_2_options[:authentication_response_status] if threeds_2_options[:authentication_response_status]
341
- authentication_data[:xid] = threeds_2_options[:xid] if threeds_2_options[:xid]
385
+ authentication_data = {
386
+ priorThreeDSecureData: { acsTransactionId: threeds_2_options[:acs_transaction_id] }.compact,
387
+ cavv: threeds_2_options[:cavv],
388
+ cavvAlgorithm: threeds_2_options[:cavv_algorithm],
389
+ directoryServerTransactionId: threeds_2_options[:ds_transaction_id],
390
+ eci: threeds_2_options[:eci],
391
+ threeDSecureVersion: threeds_2_options[:version] || options[:three_ds_version],
392
+ validationResult: threeds_2_options[:authentication_response_status],
393
+ xid: threeds_2_options[:xid],
394
+ acsTransactionId: threeds_2_options[:acs_transaction_id],
395
+ flow: threeds_2_options[:flow]
396
+ }.compact
342
397
 
343
398
  post['cardPaymentMethodSpecificInput'] ||= {}
344
399
  post['cardPaymentMethodSpecificInput']['threeDSecure'] ||= {}
400
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['merchantFraudRate'] = threeds_2_options[:merchant_fraud_rate]
401
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['exemptionRequest'] = threeds_2_options[:exemption_request]
402
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['secureCorporatePayment'] = threeds_2_options[:secure_corporate_payment]
345
403
  post['cardPaymentMethodSpecificInput']['threeDSecure']['externalCardholderAuthenticationData'] = authentication_data unless authentication_data.empty?
346
404
  end
347
405
 
@@ -355,31 +413,44 @@ module ActiveMerchant #:nodoc:
355
413
 
356
414
  def url(action, authorization)
357
415
  return preproduction_url + uri(action, authorization) if @options[:url_override].to_s == 'preproduction'
416
+ return ogone_direct_url(action, authorization) if ogone_direct?
358
417
 
359
418
  (test? ? test_url : live_url) + uri(action, authorization)
360
419
  end
361
420
 
421
+ def ogone_direct_url(action, authorization)
422
+ (test? ? ogone_direct_test : ogone_direct_live) + uri(action, authorization)
423
+ end
424
+
425
+ def ogone_direct?
426
+ @options[:url_override].to_s == 'ogone_direct'
427
+ end
428
+
362
429
  def uri(action, authorization)
363
- uri = "/v1/#{@options[:merchant_id]}/"
430
+ version = ogone_direct? ? 'v2' : 'v1'
431
+ uri = "/#{version}/#{@options[:merchant_id]}/"
364
432
  case action
365
433
  when :authorize
366
434
  uri + 'payments'
367
435
  when :capture
368
- uri + "payments/#{authorization}/approve"
436
+ capture_name = ogone_direct? ? 'capture' : 'approve'
437
+ uri + "payments/#{authorization}/#{capture_name}"
369
438
  when :refund
370
439
  uri + "payments/#{authorization}/refund"
371
440
  when :void
372
441
  uri + "payments/#{authorization}/cancel"
442
+ when :inquire
443
+ uri + "payments/#{authorization}"
373
444
  end
374
445
  end
375
446
 
376
447
  def idempotency_key_for_signature(options)
377
- "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
448
+ "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key] && !ogone_direct?
378
449
  end
379
450
 
380
- def commit(action, post, authorization: nil, options: {})
451
+ def commit(method, action, post, authorization: nil, options: {})
381
452
  begin
382
- raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization, options))
453
+ raw_response = ssl_request(method, url(action, authorization), post&.to_json, headers(method, action, post, authorization, options))
383
454
  response = parse(raw_response)
384
455
  rescue ResponseError => e
385
456
  response = parse(e.response.body) if e.response.code.to_i >= 400
@@ -405,33 +476,33 @@ module ActiveMerchant #:nodoc:
405
476
  }
406
477
  end
407
478
 
408
- def headers(action, post, authorization = nil, options = {})
479
+ def headers(method, action, post, authorization = nil, options = {})
409
480
  headers = {
410
481
  'Content-Type' => content_type,
411
- 'Authorization' => auth_digest(action, post, authorization, options),
482
+ 'Authorization' => auth_digest(method, action, post, authorization, options),
412
483
  'Date' => date
413
484
  }
414
485
 
415
- headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key]
486
+ headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key] && !ogone_direct?
416
487
  headers
417
488
  end
418
489
 
419
- def auth_digest(action, post, authorization = nil, options = {})
490
+ def auth_digest(method, action, post, authorization = nil, options = {})
420
491
  data = <<~REQUEST
421
- POST
492
+ #{method.to_s.upcase}
422
493
  #{content_type}
423
494
  #{date}
424
495
  #{idempotency_key_for_signature(options)}
425
496
  #{uri(action, authorization)}
426
497
  REQUEST
427
498
  data = data.each_line.reject { |line| line.strip == '' }.join
428
- digest = OpenSSL::Digest.new('sha256')
499
+ digest = OpenSSL::Digest.new('SHA256')
429
500
  key = @options[:secret_api_key]
430
- "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}"
501
+ "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data)).strip}"
431
502
  end
432
503
 
433
504
  def date
434
- @date ||= Time.now.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header
505
+ @date ||= Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')
435
506
  end
436
507
 
437
508
  def content_type
@@ -441,6 +512,8 @@ module ActiveMerchant #:nodoc:
441
512
  def success_from(action, response)
442
513
  return false if response['errorId'] || response['error_message']
443
514
 
515
+ return %w(CAPTURED CAPTURE_REQUESTED).include?(response.dig('payment', 'status')) if response.dig('payment', 'paymentOutput', 'paymentMethod') == 'mobile'
516
+
444
517
  case action
445
518
  when :authorize
446
519
  response.dig('payment', 'statusOutput', 'isAuthorized')
@@ -27,32 +27,32 @@ module ActiveMerchant #:nodoc:
27
27
  def purchase(money, payment, options = {})
28
28
  xml = build_purchase_and_authorize_request(money, payment, options)
29
29
 
30
- commit('sale', xml)
30
+ commit('sale', xml, options)
31
31
  end
32
32
 
33
33
  def authorize(money, payment, options = {})
34
34
  xml = build_purchase_and_authorize_request(money, payment, options)
35
35
 
36
- commit('preAuth', xml)
36
+ commit('preAuth', xml, options)
37
37
  end
38
38
 
39
39
  def capture(money, authorization, options = {})
40
40
  xml = build_capture_and_refund_request(money, authorization, options)
41
41
 
42
- commit('postAuth', xml)
42
+ commit('postAuth', xml, options)
43
43
  end
44
44
 
45
45
  def refund(money, authorization, options = {})
46
46
  xml = build_capture_and_refund_request(money, authorization, options)
47
47
 
48
- commit('return', xml)
48
+ commit('return', xml, options)
49
49
  end
50
50
 
51
51
  def void(authorization, options = {})
52
52
  xml = Builder::XmlMarkup.new(indent: 2)
53
53
  add_transaction_details(xml, options.merge!({ order_id: authorization }))
54
54
 
55
- commit('void', xml)
55
+ commit('void', xml, options)
56
56
  end
57
57
 
58
58
  def store(credit_card, options = {})
@@ -60,7 +60,7 @@ module ActiveMerchant #:nodoc:
60
60
  xml = Builder::XmlMarkup.new(indent: 2)
61
61
  add_storage_item(xml, credit_card, options)
62
62
 
63
- commit('vault', xml)
63
+ commit('vault', xml, options)
64
64
  end
65
65
 
66
66
  def unstore(hosted_data_id)
@@ -343,7 +343,12 @@ module ActiveMerchant #:nodoc:
343
343
  }
344
344
  end
345
345
 
346
- def commit(action, request)
346
+ def override_store_id(options)
347
+ @credentials[:store_id] = options[:store_id] if options[:store_id].present?
348
+ end
349
+
350
+ def commit(action, request, options = {})
351
+ override_store_id(options)
347
352
  url = (test? ? test_url : live_url)
348
353
  soap_request = build_soap_request(action, request)
349
354
  response = parse(ssl_post(url, soap_request, build_header))
@@ -391,7 +396,7 @@ module ActiveMerchant #:nodoc:
391
396
  end
392
397
 
393
398
  def message_from(response)
394
- response[:TransactionResult]
399
+ [response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ')
395
400
  end
396
401
 
397
402
  def authorization_from(action, response)
@@ -3,7 +3,10 @@ require 'nokogiri'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class IveriGateway < Gateway
6
+ class_attribute :iveri_url
7
+
6
8
  self.live_url = self.test_url = 'https://portal.nedsecure.co.za/iVeriWebService/Service.asmx'
9
+ self.iveri_url = 'https://portal.host.iveri.com/iVeriWebService/Service.asmx'
7
10
 
8
11
  self.supported_countries = %w[US ZA GB]
9
12
  self.default_currency = 'ZAR'
@@ -91,7 +94,7 @@ module ActiveMerchant #:nodoc:
91
94
  xml[:soap].Envelope 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
92
95
  xml[:soap].Body do
93
96
  xml.Execute 'xmlns' => 'http://iveri.com/' do
94
- xml.validateRequest 'true'
97
+ xml.validateRequest('false')
95
98
  xml.protocol 'V_XML'
96
99
  xml.protocolVersion '2.0'
97
100
  xml.request vxml
@@ -118,8 +121,9 @@ module ActiveMerchant #:nodoc:
118
121
  def add_auth_purchase_params(post, money, payment_method, options)
119
122
  add_card_holder_authentication(post, options)
120
123
  add_amount(post, money, options)
121
- add_electronic_commerce_indicator(post, options)
124
+ add_electronic_commerce_indicator(post, options) unless options[:three_d_secure]
122
125
  add_payment_method(post, payment_method, options)
126
+ add_three_ds(post, options)
123
127
  end
124
128
 
125
129
  def add_amount(post, money, options)
@@ -156,7 +160,7 @@ module ActiveMerchant #:nodoc:
156
160
  def commit(post)
157
161
  raw_response =
158
162
  begin
159
- ssl_post(live_url, build_xml_envelope(post), headers(post))
163
+ ssl_post(url, build_xml_envelope(post), headers(post))
160
164
  rescue ActiveMerchant::ResponseError => e
161
165
  e.response.body
162
166
  end
@@ -178,6 +182,10 @@ module ActiveMerchant #:nodoc:
178
182
  test? ? 'Test' : 'Live'
179
183
  end
180
184
 
185
+ def url
186
+ @options[:url_override].to_s == 'iveri' ? iveri_url : live_url
187
+ end
188
+
181
189
  def headers(post)
182
190
  {
183
191
  'Content-Type' => 'text/xml; charset=utf-8',
@@ -249,6 +257,34 @@ module ActiveMerchant #:nodoc:
249
257
  tr('-', '_').
250
258
  downcase
251
259
  end
260
+
261
+ def add_three_ds(post, options)
262
+ return unless three_d_secure = options[:three_d_secure]
263
+
264
+ post.ElectronicCommerceIndicator(formatted_three_ds_eci(three_d_secure[:eci])) if three_d_secure[:eci]
265
+ post.CardHolderAuthenticationID(three_d_secure[:xid]) if three_d_secure[:xid]
266
+ post.CardHolderAuthenticationData(three_d_secure[:cavv]) if three_d_secure[:cavv]
267
+ post.ThreeDSecure_ProtocolVersion(three_d_secure[:version]) if three_d_secure[:version]
268
+ post.ThreeDSecure_DSTransID(three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id]
269
+ post.ThreeDSecure_VEResEnrolled(formatted_enrollment(three_d_secure[:enrolled])) if three_d_secure[:enrolled]
270
+ end
271
+
272
+ def formatted_enrollment(val)
273
+ case val
274
+ when 'Y', 'N', 'U' then val
275
+ when true, 'true' then 'Y'
276
+ when false, 'false' then 'N'
277
+ end
278
+ end
279
+
280
+ def formatted_three_ds_eci(val)
281
+ case val
282
+ when '05', '02' then 'ThreeDSecure'
283
+ when '06', '01' then 'ThreeDSecureAttempted'
284
+ when '07' then 'SecureChannel'
285
+ else val
286
+ end
287
+ end
252
288
  end
253
289
  end
254
290
  end