activemerchant 1.121.0 → 1.125.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -19
  5. data/lib/active_merchant/billing/credit_card.rb +13 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +24 -12
  8. data/lib/active_merchant/billing/gateway.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/adyen.rb +75 -27
  10. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  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 +6 -3
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
  17. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +41 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +12 -6
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
  22. data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
  23. data/lib/active_merchant/billing/gateways/elavon.rb +65 -30
  24. data/lib/active_merchant/billing/gateways/element.rb +22 -2
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +130 -26
  26. data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
  27. data/lib/active_merchant/billing/gateways/kushki.rb +30 -0
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +6 -3
  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 +22 -11
  34. data/lib/active_merchant/billing/gateways/nmi.rb +29 -10
  35. data/lib/active_merchant/billing/gateways/orbital.rb +46 -8
  36. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  37. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
  44. data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
  45. data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
  46. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  47. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  48. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  49. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  50. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  51. data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
  52. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  53. data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
  54. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
  55. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  56. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
  58. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  59. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
  61. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  62. data/lib/active_merchant/billing/response.rb +4 -0
  63. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  64. data/lib/active_merchant/billing.rb +1 -0
  65. data/lib/active_merchant/version.rb +1 -1
  66. metadata +13 -3
@@ -0,0 +1,347 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class PriorityGateway < Gateway
4
+ # Sandbox and Production
5
+ self.test_url = 'https://sandbox.api.mxmerchant.com/checkout/v3/payment'
6
+ self.live_url = 'https://api.mxmerchant.com/checkout/v3/payment'
7
+
8
+ class_attribute :test_url_verify, :live_url_verify, :test_auth, :live_auth, :test_env_verify, :live_env_verify, :test_url_batch, :live_url_batch, :test_url_jwt, :live_url_jwt, :merchant
9
+
10
+ # Sandbox and Production - verify card
11
+ self.test_url_verify = 'https://sandbox-api2.mxmerchant.com/merchant/v1/bin'
12
+ self.live_url_verify = 'https://api2.mxmerchant.com/merchant/v1/bin'
13
+
14
+ # Sandbox and Production - check batch status
15
+ self.test_url_batch = 'https://sandbox.api.mxmerchant.com/checkout/v3/batch'
16
+ self.live_url_batch = 'https://api.mxmerchant.com/checkout/v3/batch'
17
+
18
+ # Sandbox and Production - generate jwt for verify card url
19
+ self.test_url_jwt = 'https://sandbox-api2.mxmerchant.com/security/v1/application/merchantId'
20
+ self.live_url_jwt = 'https://api2.mxmerchant.com/security/v1/application/merchantId'
21
+
22
+ self.supported_countries = ['US']
23
+ self.default_currency = 'USD'
24
+ self.supported_cardtypes = %i[visa master american_express discover]
25
+
26
+ self.homepage_url = 'https://mxmerchant.com/'
27
+ self.display_name = 'Priority'
28
+
29
+ def initialize(options = {})
30
+ requires!(options, :merchant_id, :key, :secret)
31
+ super
32
+ end
33
+
34
+ def basic_auth
35
+ Base64.strict_encode64("#{@options[:key]}:#{@options[:secret]}")
36
+ end
37
+
38
+ def request_headers
39
+ {
40
+ 'Content-Type' => 'application/json',
41
+ 'Authorization' => "Basic #{basic_auth}"
42
+ }
43
+ end
44
+
45
+ def request_verify_headers(jwt)
46
+ {
47
+ 'Authorization' => "Bearer #{jwt}"
48
+ }
49
+ end
50
+
51
+ def purchase(amount, credit_card, options = {})
52
+ params = {}
53
+ params['amount'] = localized_amount(amount.to_f, options[:currency])
54
+ params['authOnly'] = false
55
+
56
+ add_credit_card(params, credit_card, 'purchase', options)
57
+ add_type_merchant_purchase(params, @options[:merchant_id], true, options)
58
+ commit('purchase', params: params, jwt: options)
59
+ end
60
+
61
+ def authorize(amount, credit_card, options = {})
62
+ params = {}
63
+ params['amount'] = localized_amount(amount.to_f, options[:currency])
64
+ params['authOnly'] = true
65
+
66
+ add_credit_card(params, credit_card, 'purchase', options)
67
+ add_type_merchant_purchase(params, @options[:merchant_id], false, options)
68
+ commit('purchase', params: params, jwt: options)
69
+ end
70
+
71
+ def refund(amount, authorization, options = {})
72
+ params = {}
73
+ params['merchantId'] = @options[:merchant_id]
74
+ params['paymentToken'] = get_hash(authorization)['payment_token'] || options[:payment_token]
75
+ # refund amounts must be negative
76
+ params['amount'] = ('-' + localized_amount(amount.to_f, options[:currency])).to_f
77
+ commit('refund', params: params, jwt: options)
78
+ end
79
+
80
+ def capture(amount, authorization, options = {})
81
+ params = {}
82
+ params['amount'] = localized_amount(amount.to_f, options[:currency])
83
+ params['authCode'] = options[:authCode]
84
+ params['merchantId'] = @options[:merchant_id]
85
+ params['paymentToken'] = get_hash(authorization)['payment_token']
86
+ params['shouldGetCreditCardLevel'] = true
87
+ params['source'] = options[:source]
88
+ params['tenderType'] = 'Card'
89
+
90
+ commit('capture', params: params, jwt: options)
91
+ end
92
+
93
+ def void(authorization, options = {})
94
+ commit('void', iid: get_hash(authorization)['id'], jwt: options)
95
+ end
96
+
97
+ def verify(credit_card, options)
98
+ jwt = options[:jwt_token]
99
+ commit('verify', card_number: credit_card.number, jwt: jwt)
100
+ end
101
+
102
+ def supports_scrubbing?
103
+ true
104
+ end
105
+
106
+ def get_payment_status(batch_id, options)
107
+ commit('get_payment_status', params: batch_id, jwt: options)
108
+ end
109
+
110
+ def close_batch(batch_id, options)
111
+ commit('close_batch', params: batch_id, jwt: options)
112
+ end
113
+
114
+ def create_jwt(options)
115
+ commit('create_jwt', params: @options[:merchant_id], jwt: options)
116
+ end
117
+
118
+ def scrub(transcript)
119
+ transcript.
120
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
121
+ gsub(%r((number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
122
+ gsub(%r((cvv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
123
+ end
124
+
125
+ def add_credit_card(params, credit_card, action, options)
126
+ return unless credit_card&.is_a?(CreditCard)
127
+
128
+ card_details = {}
129
+
130
+ card_details['expiryMonth'] = format(credit_card.month, :two_digits).to_s
131
+ card_details['expiryYear'] = format(credit_card.year, :two_digits).to_s
132
+ card_details['expiryDate'] = exp_date(credit_card)
133
+ card_details['cardType'] = credit_card.brand
134
+ card_details['last4'] = credit_card.last_digits
135
+ card_details['cvv'] = credit_card.verification_value
136
+ card_details['number'] = credit_card.number
137
+
138
+ card_details['entryMode'] = options['entryMode'].blank? ? 'Keyed' : options['entryMode']
139
+
140
+ case action
141
+ when 'purchase'
142
+ card_details['avsStreet'] = options[:billing_address][:address1] if options[:billing_address]
143
+ card_details['avsZip'] = options[:billing_address][:zip] if options[:billing_address]
144
+ when 'refund'
145
+ card_details['cardId'] = options[:card_id]
146
+ card_details['cardPresent'] = options[:card_present]
147
+ card_details['hasContract'] = options[:has_contract]
148
+ card_details['isCorp'] = options[:is_corp]
149
+ card_details['isDebit'] = options[:is_debit]
150
+ card_details['token'] = options[:token]
151
+ else
152
+ card_details
153
+ end
154
+
155
+ params['cardAccount'] = card_details
156
+ end
157
+
158
+ def exp_date(credit_card)
159
+ "#{format(credit_card.month, :two_digits)}/#{format(credit_card.year, :two_digits)}"
160
+ end
161
+
162
+ def purchases
163
+ [{ taxRate: '0.0000', additionalTaxRate: nil, discountRate: nil }]
164
+ end
165
+
166
+ def add_type_merchant_purchase(params, merchant, is_settle_funds, options)
167
+ params['cardPresent'] = false
168
+ params['cardPresentType'] = 'CardNotPresent'
169
+ params['isAuth'] = true
170
+ params['isSettleFunds'] = is_settle_funds
171
+ params['isTicket'] = false
172
+
173
+ params['merchantId'] = merchant
174
+ params['mxAdvantageEnabled'] = false
175
+ params['paymentType'] = 'Sale'
176
+
177
+ params['purchases'] = purchases
178
+
179
+ params['shouldGetCreditCardLevel'] = true
180
+ params['shouldVaultCard'] = true
181
+ params['source'] = options[:source]
182
+ params['sourceZip'] = options[:billing_address][:zip] if options[:billing_address]
183
+ params['taxExempt'] = false
184
+ params['tenderType'] = 'Card'
185
+ end
186
+
187
+ def commit(action, params: '', iid: '', card_number: nil, jwt: '')
188
+ response =
189
+ begin
190
+ case action
191
+ when 'void'
192
+ parse(ssl_request(:delete, url(action, params, ref_number: iid), nil, request_headers))
193
+ when 'verify'
194
+ parse(ssl_get(url(action, params, credit_card_number: card_number), request_verify_headers(jwt)))
195
+ when 'get_payment_status', 'create_jwt'
196
+ parse(ssl_get(url(action, params, ref_number: iid), request_headers))
197
+ when 'close_batch'
198
+ parse(ssl_request(:put, url(action, params, ref_number: iid), nil, request_headers))
199
+ else
200
+ parse(ssl_post(url(action, params), post_data(params), request_headers))
201
+ end
202
+ rescue ResponseError => e
203
+ parse(e.response.body)
204
+ end
205
+ success = success_from(response, action)
206
+ response = { 'code' => '204' } if response == ''
207
+ Response.new(
208
+ success,
209
+ message_from(response),
210
+ response,
211
+ authorization: success ? authorization_from(response) : nil,
212
+ error_code: success || response == '' ? nil : error_from(response),
213
+ test: test?
214
+ )
215
+ end
216
+
217
+ def handle_response(response)
218
+ if response.code != '204' && (200...300).cover?(response.code.to_i)
219
+ response.body
220
+ elsif response.code == '204' || response == ''
221
+ response.body = { 'code' => '204' }
222
+ else
223
+ raise ResponseError.new(response)
224
+ end
225
+ end
226
+
227
+ def url(action, params, ref_number: '', credit_card_number: nil)
228
+ case action
229
+ when 'void'
230
+ base_url + "/#{ref_number}?force=true"
231
+ when 'verify'
232
+ (verify_url + '?search=') + credit_card_number.to_s[0..6]
233
+ when 'get_payment_status', 'close_batch'
234
+ batch_url + "/#{params}"
235
+ when 'create_jwt'
236
+ jwt_url + "/#{params}/token"
237
+ else
238
+ base_url + '?includeCustomerMatches=false&echo=true'
239
+ end
240
+ end
241
+
242
+ def base_url
243
+ test? ? test_url : live_url
244
+ end
245
+
246
+ def verify_url
247
+ test? ? self.test_url_verify : self.live_url_verify
248
+ end
249
+
250
+ def jwt_url
251
+ test? ? self.test_url_jwt : self.live_url_jwt
252
+ end
253
+
254
+ def batch_url
255
+ test? ? self.test_url_batch : self.live_url_batch
256
+ end
257
+
258
+ def parse(body)
259
+ return body if body['code'] == '204'
260
+
261
+ JSON.parse(body)
262
+ rescue JSON::ParserError
263
+ message = 'Invalid JSON response received from Priority Gateway. Please contact Priority Gateway if you continue to receive this message.'
264
+ message += " (The raw response returned by the API was #{body.inspect})"
265
+ {
266
+ 'message' => message
267
+ }
268
+ end
269
+
270
+ def success_from(response, action)
271
+ success = response['status'] == 'Approved' || response['status'] == 'Open' if response['status']
272
+ success = response['code'] == '204' if action == 'void'
273
+ success = !response['bank'].empty? if action == 'verify' && response['bank']
274
+ success
275
+ end
276
+
277
+ def message_from(response)
278
+ return response['details'][0] if response['details'] && response['details'][0]
279
+
280
+ response['authMessage'] || response['message'] || response['status']
281
+ end
282
+
283
+ def authorization_from(response)
284
+ {
285
+ 'payment_token' => response['paymentToken'],
286
+ 'id' => response['id']
287
+ }
288
+ end
289
+
290
+ def error_from(response)
291
+ response['errorCode'] || response['status']
292
+ end
293
+
294
+ def post_data(params)
295
+ params.to_json
296
+ end
297
+
298
+ def add_pos_data(options)
299
+ pos_options = {}
300
+ pos_options['panCaptureMethod'] = options[:pan_capture_method]
301
+
302
+ pos_options
303
+ end
304
+
305
+ def add_purchases_data(options)
306
+ purchases = {}
307
+
308
+ purchases['dateCreated'] = options[:date_created]
309
+ purchases['iId'] = options[:i_id]
310
+ purchases['transactionIId'] = options[:transaction_i_id]
311
+ purchases['transactionId'] = options[:transaction_id]
312
+ purchases['name'] = options[:name]
313
+ purchases['description'] = options[:description]
314
+ purchases['code'] = options[:code]
315
+ purchases['unitOfMeasure'] = options[:unit_of_measure]
316
+ purchases['unitPrice'] = options[:unit_price]
317
+ purchases['quantity'] = options[:quantity]
318
+ purchases['taxRate'] = options[:tax_rate]
319
+ purchases['taxAmount'] = options[:tax_amount]
320
+ purchases['discountRate'] = options[:discount_rate]
321
+ purchases['discountAmount'] = options[:discount_amt]
322
+ purchases['extendedAmount'] = options[:extended_amt]
323
+ purchases['lineItemId'] = options[:line_item_id]
324
+
325
+ purchase_arr = []
326
+ purchase_arr[0] = purchases
327
+ purchase_arr
328
+ end
329
+
330
+ def add_risk_data(options)
331
+ risk = {}
332
+ risk['cvvResponseCode'] = options[:cvv_response_code]
333
+ risk['cvvResponse'] = options[:cvv_response]
334
+ risk['cvvMatch'] = options[:cvv_match]
335
+ risk['avsResponse'] = options[:avs_response]
336
+ risk['avsAddressMatch'] = options[:avs_address_match]
337
+ risk['avsZipMatch'] = options[:avs_zip_match]
338
+
339
+ risk
340
+ end
341
+
342
+ def get_hash(string)
343
+ JSON.parse(string.gsub('=>', ':'))
344
+ end
345
+ end
346
+ end
347
+ end
@@ -151,6 +151,7 @@ module ActiveMerchant
151
151
  else
152
152
  add_three_d_secure(xml, options)
153
153
  end
154
+ add_stored_credential(xml, options)
154
155
  add_comments(xml, options)
155
156
  add_address_and_customer_info(xml, options)
156
157
  end
@@ -323,6 +324,23 @@ module ActiveMerchant
323
324
  end
324
325
  end
325
326
 
327
+ def add_stored_credential(xml, options)
328
+ return unless stored_credential = options[:stored_credential]
329
+
330
+ xml.tag! 'storedcredential' do
331
+ xml.tag! 'type', stored_credential_type(stored_credential[:reason_type])
332
+ xml.tag! 'initiator', stored_credential[:initiator]
333
+ xml.tag! 'sequence', stored_credential[:initial_transaction] ? 'first' : 'subsequent'
334
+ xml.tag! 'srd', stored_credential[:network_transaction_id]
335
+ end
336
+ end
337
+
338
+ def stored_credential_type(reason)
339
+ return 'oneoff' if reason == 'unscheduled'
340
+
341
+ reason
342
+ end
343
+
326
344
  def format_address_code(address)
327
345
  code = [address[:zip].to_s, address[:address1].to_s + address[:address2].to_s]
328
346
  code.collect { |e| e.gsub(/\D/, '') }.reject(&:empty?).join('|')
@@ -203,7 +203,7 @@ module ActiveMerchant #:nodoc:
203
203
  add_order(data, options[:order_id])
204
204
  add_payment(data, payment)
205
205
  add_external_mpi_fields(data, options)
206
- add_threeds(data, options) if options[:execute_threed]
206
+ add_three_ds_data(data, options) if options[:execute_threed]
207
207
  add_stored_credential_options(data, options)
208
208
  data[:description] = options[:description]
209
209
  data[:store_in_vault] = options[:store]
@@ -222,7 +222,7 @@ module ActiveMerchant #:nodoc:
222
222
  add_order(data, options[:order_id])
223
223
  add_payment(data, payment)
224
224
  add_external_mpi_fields(data, options)
225
- add_threeds(data, options) if options[:execute_threed]
225
+ add_three_ds_data(data, options) if options[:execute_threed]
226
226
  add_stored_credential_options(data, options)
227
227
  data[:description] = options[:description]
228
228
  data[:store_in_vault] = options[:store]
@@ -312,7 +312,7 @@ module ActiveMerchant #:nodoc:
312
312
  test? ? test_url : live_url
313
313
  end
314
314
 
315
- def threeds_url
315
+ def webservice_url
316
316
  test? ? 'https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2' : 'https://sis.redsys.es/sis/services/SerClsWSEntradaV2'
317
317
  end
318
318
 
@@ -372,43 +372,51 @@ module ActiveMerchant #:nodoc:
372
372
  end
373
373
  end
374
374
 
375
- def add_threeds(data, options)
376
- data[:threeds] = { threeDSInfo: 'CardData' } if options[:execute_threed] == true
375
+ def add_three_ds_data(data, options)
376
+ data[:three_ds_data] = { threeDSInfo: 'CardData' } if options[:execute_threed] == true
377
377
  end
378
378
 
379
- def determine_3ds_action(threeds_hash)
380
- return 'iniciaPeticion' if threeds_hash[:threeDSInfo] == 'CardData'
381
- return 'trataPeticion' if threeds_hash[:threeDSInfo] == 'AuthenticationData' ||
382
- threeds_hash[:threeDSInfo] == 'ChallengeResponse'
379
+ def determine_peticion_type(data)
380
+ three_ds_info = data.dig(:three_ds_data, :threeDSInfo)
381
+ return 'iniciaPeticion' if three_ds_info == 'CardData'
382
+ return 'trataPeticion' if three_ds_info == 'AuthenticationData' ||
383
+ three_ds_info == 'ChallengeResponse' ||
384
+ data[:sca_exemption] == 'MIT'
385
+ end
386
+
387
+ def use_webservice_endpoint?(data, options)
388
+ options[:use_webservice_endpoint].to_s == 'true' || data[:three_ds_data] || data[:sca_exemption] == 'MIT'
383
389
  end
384
390
 
385
391
  def commit(data, options = {})
386
- if data[:threeds]
387
- action = determine_3ds_action(data[:threeds])
392
+ xmlreq = xml_request_from(data, options)
393
+
394
+ if use_webservice_endpoint?(data, options)
395
+ peticion_type = determine_peticion_type(data)
396
+
388
397
  request = <<-REQUEST
389
398
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:impl="http://webservice.sis.sermepa.es" xmlns:intf="http://webservice.sis.sermepa.es" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
390
399
  <soapenv:Header/>
391
400
  <soapenv:Body>
392
- <intf:#{action} xmlns:intf="http://webservice.sis.sermepa.es">
401
+ <intf:#{peticion_type} xmlns:intf="http://webservice.sis.sermepa.es">
393
402
  <intf:datoEntrada>
394
- <![CDATA[#{xml_request_from(data, options)}]]>
403
+ <![CDATA[#{xmlreq}]]>
395
404
  </intf:datoEntrada>
396
- </intf:#{action}>
405
+ </intf:#{peticion_type}>
397
406
  </soapenv:Body>
398
407
  </soapenv:Envelope>
399
408
  REQUEST
400
- parse(ssl_post(threeds_url, request, headers(action)), action)
409
+ parse(ssl_post(webservice_url, request, headers(peticion_type)), peticion_type)
401
410
  else
402
- xmlreq = xml_request_from(data, options)
403
- parse(ssl_post(url, "entrada=#{CGI.escape(xmlreq)}", headers), action)
411
+ parse(ssl_post(url, "entrada=#{CGI.escape(xmlreq)}", headers), peticion_type)
404
412
  end
405
413
  end
406
414
 
407
- def headers(action = nil)
408
- if action
415
+ def headers(peticion_type = nil)
416
+ if peticion_type
409
417
  {
410
418
  'Content-Type' => 'text/xml',
411
- 'SOAPAction' => action
419
+ 'SOAPAction' => peticion_type
412
420
  }
413
421
  else
414
422
  {
@@ -470,14 +478,9 @@ module ActiveMerchant #:nodoc:
470
478
  xml.target!
471
479
  end
472
480
 
473
- # Template Method to allow AM API clients to override decision to escape, based on their own criteria.
474
- def escape_special_chars?(data, options = {})
475
- data[:threeds]
476
- end
477
-
478
481
  def build_merchant_data(xml, data, options = {})
479
482
  # See https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2/wsdl/SerClsWSEntradaV2.wsdl
480
- # (which results from calling #threeds_url + '?WSDL', https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2?WSDL)
483
+ # (which results from calling #webservice_url + '?WSDL', https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2?WSDL)
481
484
  xml.DATOSENTRADA do
482
485
  # Basic elements
483
486
  xml.DS_Version 0.1
@@ -485,7 +488,7 @@ module ActiveMerchant #:nodoc:
485
488
  xml.DS_MERCHANT_AMOUNT data[:amount]
486
489
  xml.DS_MERCHANT_ORDER data[:order_id]
487
490
  xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
488
- if data[:description] && escape_special_chars?(data, options)
491
+ if data[:description] && use_webservice_endpoint?(data, options)
489
492
  xml.DS_MERCHANT_PRODUCTDESCRIPTION CGI.escape(data[:description])
490
493
  else
491
494
  xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
@@ -494,17 +497,17 @@ module ActiveMerchant #:nodoc:
494
497
  xml.DS_MERCHANT_MERCHANTCODE @options[:login]
495
498
  xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication?
496
499
 
497
- action = determine_3ds_action(data[:threeds]) if data[:threeds]
498
- if action == 'iniciaPeticion' && data[:sca_exemption]
500
+ peticion_type = determine_peticion_type(data) if data[:three_ds_data]
501
+ if peticion_type == 'iniciaPeticion' && data[:sca_exemption]
499
502
  xml.DS_MERCHANT_EXCEP_SCA 'Y'
500
503
  else
501
504
  xml.DS_MERCHANT_EXCEP_SCA data[:sca_exemption] if data[:sca_exemption]
502
- xml.DS_MERCHANT_DIRECTPAYMENT data[:sca_exemption_direct_payment_enabled] if data[:sca_exemption_direct_payment_enabled]
505
+ xml.DS_MERCHANT_DIRECTPAYMENT data[:sca_exemption_direct_payment_enabled] || 'true' if data[:sca_exemption] == 'MIT'
503
506
  end
504
507
 
505
508
  # Only when card is present
506
509
  if data[:card]
507
- if data[:card][:name] && escape_special_chars?(data, options)
510
+ if data[:card][:name] && use_webservice_endpoint?(data, options)
508
511
  xml.DS_MERCHANT_TITULAR CGI.escape(data[:card][:name])
509
512
  else
510
513
  xml.DS_MERCHANT_TITULAR data[:card][:name]
@@ -525,7 +528,7 @@ module ActiveMerchant #:nodoc:
525
528
  # Requires account configuration to be able to use
526
529
  xml.DS_MERCHANT_DIRECTPAYMENT 'moto' if options.dig(:moto) && options.dig(:metadata, :manual_entry)
527
530
 
528
- xml.DS_MERCHANT_EMV3DS data[:threeds].to_json if data[:threeds]
531
+ xml.DS_MERCHANT_EMV3DS data[:three_ds_data].to_json if data[:three_ds_data]
529
532
 
530
533
  if options[:stored_credential]
531
534
  xml.DS_MERCHANT_COF_INI data[:DS_MERCHANT_COF_INI]
@@ -62,6 +62,7 @@ module ActiveMerchant #:nodoc:
62
62
  post[:sg_CCToken] = token
63
63
  post[:sg_ExpMonth] = exp_month
64
64
  post[:sg_ExpYear] = exp_year
65
+ post[:sg_Email] = options[:email]
65
66
 
66
67
  commit(post)
67
68
  end
@@ -126,9 +127,11 @@ module ActiveMerchant #:nodoc:
126
127
  private
127
128
 
128
129
  def add_transaction_data(trans_type, post, money, options)
130
+ currency = options[:currency] || currency(money)
131
+
129
132
  post[:sg_TransType] = trans_type
130
- post[:sg_Currency] = (options[:currency] || currency(money))
131
- post[:sg_Amount] = amount(money)
133
+ post[:sg_Currency] = currency
134
+ post[:sg_Amount] = localized_amount(money, currency)
132
135
  post[:sg_ClientLoginID] = @options[:client_login_id]
133
136
  post[:sg_ClientPassword] = @options[:client_password]
134
137
  post[:sg_ResponseFormat] = '4'
@@ -143,6 +146,8 @@ module ActiveMerchant #:nodoc:
143
146
  post[:sg_Descriptor] = options[:merchant_descriptor] if options[:merchant_descriptor]
144
147
  post[:sg_MerchantPhoneNumber] = options[:merchant_phone_number] if options[:merchant_phone_number]
145
148
  post[:sg_MerchantName] = options[:merchant_name] if options[:merchant_name]
149
+ post[:sg_ProductID] = options[:product_id] if options[:product_id]
150
+ post[:sg_NotUseCVV] = options[:not_use_cvv].to_s == 'true' ? 1 : 0 unless options[:not_use_cvv].nil?
146
151
  end
147
152
 
148
153
  def add_payment(post, payment, options = {})
@@ -185,6 +190,7 @@ module ActiveMerchant #:nodoc:
185
190
  post[:sg_Xid] = options[:three_d_secure][:xid]
186
191
  post[:sg_IsExternalMPI] = 1
187
192
  post[:sg_EnablePartialApproval] = options[:is_partial_approval]
193
+ post[:sg_challengePreference] = options[:three_d_secure][:challenge_preference] if options[:three_d_secure][:challenge_preference]
188
194
  end
189
195
 
190
196
  def parse(xml)
@@ -254,11 +254,20 @@ module ActiveMerchant #:nodoc:
254
254
  end
255
255
 
256
256
  def childnode_to_response(response, node, childnode)
257
- name = "#{node.name.downcase}_#{childnode.name.downcase}"
258
- if name == 'payment_method_data' && !childnode.elements.empty?
259
- response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first
257
+ node_name = node.name.downcase
258
+ childnode_name = childnode.name.downcase
259
+ composed_name = "#{node_name}_#{childnode_name}"
260
+
261
+ childnodes_present = !childnode.elements.empty?
262
+
263
+ if childnodes_present && composed_name == 'payment_method_data'
264
+ response[composed_name.to_sym] = Hash.from_xml(childnode.to_s).values.first
265
+ elsif childnodes_present && node_name == 'gateway_specific_response_fields'
266
+ response[node_name.to_sym] = {
267
+ childnode_name => Hash.from_xml(childnode.to_s).values.first
268
+ }
260
269
  else
261
- response[name.to_sym] = childnode.text
270
+ response[composed_name.to_sym] = childnode.text
262
271
  end
263
272
  end
264
273