activemerchant 1.121.0 → 1.125.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +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