activemerchant 1.98.0 → 1.99.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acfbb780d39dd416b823a9abef4cdcf4de902ec75210ed78e6771314d00b069b
4
- data.tar.gz: 95e9bf94b3610b33d843fd82af8e4f204380e7c53557d24141b654c553160d9f
3
+ metadata.gz: cbb76da56e87639a3f9129d70297b78a0edabe9faf2300e682e06e872e019419
4
+ data.tar.gz: 94cb0693e867b03071fe55a7e71b3cfbaf53a7c340d480f24108948822e9e1e7
5
5
  SHA512:
6
- metadata.gz: 6222cdad43482972e2a799981936dc1d8114ccbede88604880d350650f7b81422b6551a07375dd1a7e06ef562d4f128c332dabc2fc16ebb7b635f774de18a54e
7
- data.tar.gz: 2f9fe3fe9dfe17fc045f0a3e97940ae4b38a461c48f0416a26ed4b5a4e2bb549464911fbe7527392c8dfbcbbee16bfa82266e3ad644060199ee9552b5b82659b
6
+ metadata.gz: aa96ed1337c57fa00d0405ff07a70b9423bac6a26ec69fde450d73bc9cabfc0ae27b75a46faddcdf3e79f2198db8214e8e6c5926468f0acb999bef43fc220a73
7
+ data.tar.gz: f672640992309d545629a41e4f89d505732d0bb88559648a96935ef01293bcff737de41c8a7b8992993dafdfdb71d29bcbf8b85133c3d9ae5311f7b720b7c9c7
data/CHANGELOG CHANGED
@@ -1,6 +1,31 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
3
  == HEAD
4
+ == Version 1.99.0 (Sep 26, 2019)
5
+ * Adyen: Add functionality to set 3DS exemptions via API [britth] #3331
6
+ * Adyen: Send "NA" instead of "N/A" [leila-alderman] #3339
7
+ * Stripe Payment Intents: Set application fee or transfer amount on capture [britth] #3340
8
+ * TNS: Support Europe endpoint [curiousepic] #3346
9
+ * Redsys: Add 3DS support to gateway [britth] #3336
10
+ * Worldpay: Allow multiple refunds per authorization [jknipp] #3349
11
+ * MercadoPago: Add remote and unit tests for Naranja card [hdeters] #3345
12
+ * CyberSource: Pass commerce indicator if present [curiousepic] #3350
13
+ * Worldpay: Add 3DS2 Support [nfarve] #3344
14
+ * Credorax: Add 3DS 2.0 [nfarve] #3342
15
+ * TNS: Update verison and support pay mode [curiousepic] #3355
16
+ * Stripe: Add supported countries [therufs] #3358
17
+ * Stripe Payment Intents: Add supported countries [therufs] #3359
18
+ * Mundipagg: Append error messages to the message response field [jasonxp] #3353
19
+ * Redsys: Add ability to pass sca_exemption and moto fields to request exemptions [britth] #3354
20
+ * Credorax: Add A Mandatory 3DS field [nfarve] #3360
21
+ * CyberSource: Support 3DS2 pass-through fields [curiousepic] #3363
22
+ * Credorax: Add support for MOTO flagging [britth] #3366
23
+ * Credorax: Enable selecting a processor [leila-alderman] #3302
24
+ * Adyen: Add Cabal card [leila-alderman] #3361
25
+ * Decidir: Add remote tests for Cabal and Naranja [leila-alderman] #3337
26
+ * Payflow: Pass correct field in Status for 3DS in Payflow [nebdil] #3362
27
+ * CyberSource: Use 3DS hash for enrolled field [curiousepic] #3371
28
+
4
29
  == Version 1.98.0 (Sep 9, 2019)
5
30
  * Stripe Payment Intents: Add new gateway [britth] #3290
6
31
  * Stripe: Send cardholder name and address when creating sources for 3DS 1.0 [jknipp] #3300
@@ -10,7 +10,7 @@ module ActiveMerchant #:nodoc:
10
10
  self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US']
11
11
  self.default_currency = 'USD'
12
12
  self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
13
- self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja]
13
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja, :cabal]
14
14
 
15
15
  self.money_format = :cents
16
16
 
@@ -97,7 +97,14 @@ module ActiveMerchant #:nodoc:
97
97
  add_stored_credentials(post, credit_card, options)
98
98
  add_recurring_contract(post, options)
99
99
  add_address(post, options)
100
- commit('authorise', post, options)
100
+
101
+ initial_response = commit('authorise', post, options)
102
+
103
+ if initial_response.success? && card_not_stored?(initial_response)
104
+ unsupported_failure_response(initial_response)
105
+ else
106
+ initial_response
107
+ end
101
108
  end
102
109
 
103
110
  def verify(credit_card, options={})
@@ -229,17 +236,17 @@ module ActiveMerchant #:nodoc:
229
236
  return unless post[:card]&.kind_of?(Hash)
230
237
  if (address = options[:billing_address] || options[:address]) && address[:country]
231
238
  post[:billingAddress] = {}
232
- post[:billingAddress][:street] = address[:address1] || 'N/A'
233
- post[:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A'
239
+ post[:billingAddress][:street] = address[:address1] || 'NA'
240
+ post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
234
241
  post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
235
- post[:billingAddress][:city] = address[:city] || 'N/A'
242
+ post[:billingAddress][:city] = address[:city] || 'NA'
236
243
  post[:billingAddress][:stateOrProvince] = get_state(address)
237
244
  post[:billingAddress][:country] = address[:country] if address[:country]
238
245
  end
239
246
  end
240
247
 
241
248
  def get_state(address)
242
- address[:state] && !address[:state].blank? ? address[:state] : 'N/A'
249
+ address[:state] && !address[:state].blank? ? address[:state] : 'NA'
243
250
  end
244
251
 
245
252
  def add_invoice(post, money, options)
@@ -336,6 +343,11 @@ module ActiveMerchant #:nodoc:
336
343
  add_browser_info(three_ds_2_options[:browser_info], post)
337
344
  post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] }
338
345
  end
346
+
347
+ if options.has_key?(:execute_threed)
348
+ post[:additionalData][:executeThreeD] = options[:execute_threed]
349
+ post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption]
350
+ end
339
351
  else
340
352
  return unless options[:execute_threed] || options[:threed_dynamic]
341
353
  post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
@@ -498,6 +510,23 @@ module ActiveMerchant #:nodoc:
498
510
  userAgent: browser_info[:user_agent]
499
511
  }
500
512
  end
513
+
514
+ def unsupported_failure_response(initial_response)
515
+ Response.new(
516
+ false,
517
+ 'Recurring transactions are not supported for this card type.',
518
+ initial_response.params,
519
+ authorization: initial_response.authorization,
520
+ test: initial_response.test,
521
+ error_code: initial_response.error_code,
522
+ avs_result: initial_response.avs_result,
523
+ cvv_result: initial_response.cvv_result[:code]
524
+ )
525
+ end
526
+
527
+ def card_not_stored?(response)
528
+ response.authorization ? response.authorization.split('#')[2].nil? : true
529
+ end
501
530
  end
502
531
  end
503
532
  end
@@ -93,12 +93,15 @@ module ActiveMerchant #:nodoc:
93
93
 
94
94
  def void(authorization, options = {})
95
95
  reference, amount, type = split_auth(authorization)
96
-
97
- post = {}
98
- add_reference(post, reference)
99
- add_original_amount(post, amount)
100
- add_transaction_type(post, void_action(type))
101
- commit(post)
96
+ if type == TRANSACTIONS[:authorization]
97
+ capture(0, authorization, options)
98
+ else
99
+ post = {}
100
+ add_reference(post, reference)
101
+ add_original_amount(post, amount)
102
+ add_transaction_type(post, void_action(type))
103
+ commit(post)
104
+ end
102
105
  end
103
106
 
104
107
  def verify(source, options={})
@@ -317,7 +317,7 @@ module ActiveMerchant #:nodoc:
317
317
  post[:status] = options[:status]
318
318
 
319
319
  billing_address = options[:billing_address] || options[:address]
320
- post[:trnCardOwner] = billing_address[:name]
320
+ post[:trnCardOwner] = billing_address ? billing_address[:name] : nil
321
321
  end
322
322
 
323
323
  def add_recurring_amount(post, money)
@@ -135,6 +135,7 @@ module ActiveMerchant #:nodoc:
135
135
  add_echo(post, options)
136
136
  add_submerchant_id(post, options)
137
137
  add_transaction_type(post, options)
138
+ add_processor(post, options)
138
139
 
139
140
  commit(:purchase, post)
140
141
  end
@@ -149,6 +150,7 @@ module ActiveMerchant #:nodoc:
149
150
  add_echo(post, options)
150
151
  add_submerchant_id(post, options)
151
152
  add_transaction_type(post, options)
153
+ add_processor(post, options)
152
154
 
153
155
  commit(:authorize, post)
154
156
  end
@@ -160,6 +162,7 @@ module ActiveMerchant #:nodoc:
160
162
  add_customer_data(post, options)
161
163
  add_echo(post, options)
162
164
  add_submerchant_id(post, options)
165
+ add_processor(post, options)
163
166
 
164
167
  commit(:capture, post)
165
168
  end
@@ -171,6 +174,7 @@ module ActiveMerchant #:nodoc:
171
174
  add_echo(post, options)
172
175
  add_submerchant_id(post, options)
173
176
  post[:a1] = generate_unique_id
177
+ add_processor(post, options)
174
178
 
175
179
  commit(:void, post, reference_action)
176
180
  end
@@ -182,6 +186,7 @@ module ActiveMerchant #:nodoc:
182
186
  add_customer_data(post, options)
183
187
  add_echo(post, options)
184
188
  add_submerchant_id(post, options)
189
+ add_processor(post, options)
185
190
 
186
191
  commit(:refund, post)
187
192
  end
@@ -195,6 +200,7 @@ module ActiveMerchant #:nodoc:
195
200
  add_echo(post, options)
196
201
  add_submerchant_id(post, options)
197
202
  add_transaction_type(post, options)
203
+ add_processor(post, options)
198
204
 
199
205
  commit(:credit, post)
200
206
  end
@@ -270,6 +276,32 @@ module ActiveMerchant #:nodoc:
270
276
  def add_3d_secure(post, options)
271
277
  if options[:eci] && options[:xid]
272
278
  add_3d_secure_1_data(post, options)
279
+ elsif options[:execute_threed] && options[:three_ds_2]
280
+ three_ds_2_options = options[:three_ds_2]
281
+ browser_info = three_ds_2_options[:browser_info]
282
+ post[:'3ds_initiate'] = options[:three_ds_initiate] || '01'
283
+ post[:'3ds_purchasedate'] = Time.now.utc.strftime('%Y%m%d%I%M%S')
284
+ post[:'3ds_channel'] = '02'
285
+ post[:'3ds_redirect_url'] = three_ds_2_options[:notification_url]
286
+ post[:'3ds_challengewindowsize'] = options[:three_ds_challenge_window_size] || '03'
287
+ post[:'3ds_version'] = options[:three_ds_version] if options[:three_ds_version]
288
+ post[:d5] = browser_info[:user_agent]
289
+ post[:'3ds_transtype'] = options[:transaction_type] || '01'
290
+ post[:'3ds_browsertz'] = browser_info[:timezone]
291
+ post[:'3ds_browserscreenwidth'] = browser_info[:width]
292
+ post[:'3ds_browserscreenheight'] = browser_info[:height]
293
+ post[:'3ds_browsercolordepth'] = browser_info[:depth]
294
+ post[:d6] = browser_info[:language]
295
+ post[:'3ds_browserjavaenabled'] = browser_info[:java]
296
+ post[:'3ds_browseracceptheader'] = browser_info[:accept_header]
297
+ if (shipping_address = options[:shipping_address])
298
+ post[:'3ds_shipaddrstate'] = shipping_address[:state]
299
+ post[:'3ds_shipaddrpostcode'] = shipping_address[:zip]
300
+ post[:'3ds_shipaddrline2'] = shipping_address[:address2]
301
+ post[:'3ds_shipaddrline1'] = shipping_address[:address1]
302
+ post[:'3ds_shipaddrcountry'] = shipping_address[:country]
303
+ post[:'3ds_shipaddrcity'] = shipping_address[:city]
304
+ end
273
305
  elsif options[:three_d_secure]
274
306
  add_normalized_3d_secure_2_data(post, options)
275
307
  end
@@ -306,6 +338,12 @@ module ActiveMerchant #:nodoc:
306
338
 
307
339
  def add_transaction_type(post, options)
308
340
  post[:a9] = options[:transaction_type] if options[:transaction_type]
341
+ post[:a2] = '3' if options.dig(:metadata, :manual_entry)
342
+ end
343
+
344
+ def add_processor(post, options)
345
+ post[:r1] = options[:processor] || 'CREDORAX'
346
+ post[:r2] = options[:processor_merchant_id] if options[:processor_merchant_id]
309
347
  end
310
348
 
311
349
  ACTIONS = {
@@ -317,7 +355,8 @@ module ActiveMerchant #:nodoc:
317
355
  credit: '6',
318
356
  purchase_void: '7',
319
357
  refund_void: '8',
320
- capture_void: '9'
358
+ capture_void: '9',
359
+ threeds_completion: '92'
321
360
  }
322
361
 
323
362
  def commit(action, params, reference_action = nil)
@@ -25,8 +25,8 @@ module ActiveMerchant #:nodoc:
25
25
  self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor'
26
26
 
27
27
  # Schema files can be found here: https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/
28
- TEST_XSD_VERSION = '1.156'
29
- PRODUCTION_XSD_VERSION = '1.155'
28
+ TEST_XSD_VERSION = '1.159'
29
+ PRODUCTION_XSD_VERSION = '1.159'
30
30
 
31
31
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro, :elo]
32
32
  self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK)
@@ -258,6 +258,7 @@ module ActiveMerchant #:nodoc:
258
258
  def build_auth_request(money, creditcard_or_reference, options)
259
259
  xml = Builder::XmlMarkup.new :indent => 2
260
260
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
261
+ add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
261
262
  add_decision_manager_fields(xml, options)
262
263
  add_mdd_fields(xml, options)
263
264
  add_auth_service(xml, creditcard_or_reference, options)
@@ -295,6 +296,7 @@ module ActiveMerchant #:nodoc:
295
296
  def build_purchase_request(money, payment_method_or_reference, options)
296
297
  xml = Builder::XmlMarkup.new :indent => 2
297
298
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
299
+ add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
298
300
  add_decision_manager_fields(xml, options)
299
301
  add_mdd_fields(xml, options)
300
302
  if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check'
@@ -530,19 +532,41 @@ module ActiveMerchant #:nodoc:
530
532
  add_auth_network_tokenization(xml, payment_method, options)
531
533
  else
532
534
  xml.tag! 'ccAuthService', {'run' => 'true'} do
533
- check_for_stored_cred_commerce_indicator(xml, options)
535
+ if options[:three_d_secure]
536
+ add_normalized_threeds_2_data(xml, payment_method, options)
537
+ else
538
+ indicator = options[:commerce_indicator] || stored_credential_commerce_indicator(options)
539
+ xml.tag!('commerceIndicator', indicator) if indicator
540
+ end
534
541
  end
535
542
  end
536
543
  end
537
544
 
538
- def check_for_stored_cred_commerce_indicator(xml, options)
539
- return unless options[:stored_credential]
540
- if commerce_indicator(options)
541
- xml.tag!('commerceIndicator', commerce_indicator(options))
545
+ def add_normalized_threeds_2_data(xml, payment_method, options)
546
+ threeds_2_options = options[:three_d_secure]
547
+
548
+ xml.tag!('cavv', threeds_2_options[:cavv]) if threeds_2_options[:cavv] && card_brand(payment_method).to_sym != :master
549
+ xml.tag!('cavvAlgorithm', threeds_2_options[:cavv_algorithm]) if threeds_2_options[:cavv_algorithm]
550
+ xml.tag!('paSpecificationVersion', threeds_2_options[:version]) if threeds_2_options[:version]
551
+ xml.tag!('directoryServerTransactionID', threeds_2_options[:ds_transaction_id]) if threeds_2_options[:ds_transaction_id]
552
+ xml.tag!('commerceIndicator', options[:commerce_indicator]) if options[:commerce_indicator]
553
+ xml.tag!('eciRaw', threeds_2_options[:eci]) if threeds_2_options[:eci]
554
+ xml.tag!('xid', threeds_2_options[:xid]) if threeds_2_options[:xid]
555
+ xml.tag!('veresEnrolled', threeds_2_options[:enrolled]) if threeds_2_options[:enrolled]
556
+ xml.tag!('paresStatus', threeds_2_options[:authentication_response_status]) if threeds_2_options[:authentication_response_status]
557
+ end
558
+
559
+ def add_threeds_2_ucaf_data(xml, payment_method, options)
560
+ return unless options[:three_d_secure] && card_brand(payment_method).to_sym == :master
561
+
562
+ xml.tag! 'ucaf' do
563
+ xml.tag!('authenticationData', options[:three_d_secure][:cavv])
564
+ xml.tag!('collectionIndicator', options[:collection_indicator]) if options[:collection_indicator]
542
565
  end
543
566
  end
544
567
 
545
- def commerce_indicator(options)
568
+ def stored_credential_commerce_indicator(options)
569
+ return unless options[:stored_credential]
546
570
  return if options[:stored_credential][:initial_transaction]
547
571
  case options[:stored_credential][:reason_type]
548
572
  when 'installment' then 'install'
@@ -106,7 +106,7 @@ module ActiveMerchant #:nodoc:
106
106
  private
107
107
 
108
108
  def add_auth_purchase_params(post, money, credit_card, options)
109
- post[:payment_method_id] = options[:payment_method_id] ? options[:payment_method_id].to_i : 1
109
+ post[:payment_method_id] = add_payment_method_id(credit_card)
110
110
  post[:site_transaction_id] = options[:order_id]
111
111
  post[:bin] = credit_card.number[0..5]
112
112
  post[:payment_type] = options[:payment_type] || 'single'
@@ -119,6 +119,18 @@ module ActiveMerchant #:nodoc:
119
119
  add_payment(post, credit_card, options)
120
120
  end
121
121
 
122
+ def add_payment_method_id(credit_card)
123
+ if options[:payment_method_id]
124
+ options[:payment_method_id].to_i
125
+ elsif CreditCard.brand?(credit_card.number) == 'cabal'
126
+ 63
127
+ elsif CreditCard.brand?(credit_card.number) == 'naranja'
128
+ 24
129
+ else
130
+ 1
131
+ end
132
+ end
133
+
122
134
  def add_invoice(post, money, options)
123
135
  add_amount(post, money, options)
124
136
  post[:currency] = (options[:currency] || currency(money))
@@ -7,9 +7,20 @@ module ActiveMerchant
7
7
  end
8
8
 
9
9
  def purchase(amount, payment_method, options={})
10
- MultiResponse.run do |r|
11
- r.process { authorize(amount, payment_method, options) }
12
- r.process { capture(amount, r.authorization, options) }
10
+ if options[:pay_mode]
11
+ post = new_post
12
+ add_invoice(post, amount, options)
13
+ add_reference(post, *new_authorization)
14
+ add_payment_method(post, payment_method)
15
+ add_customer_data(post, payment_method, options)
16
+ add_3dsecure_id(post, options)
17
+
18
+ commit('pay', post)
19
+ else
20
+ MultiResponse.run do |r|
21
+ r.process { authorize(amount, payment_method, options) }
22
+ r.process { capture(amount, r.authorization, options) }
23
+ end
13
24
  end
14
25
  end
15
26
 
@@ -206,9 +217,23 @@ module ActiveMerchant
206
217
 
207
218
  def base_url
208
219
  if test?
209
- @options[:region] == 'asia_pacific' ? test_ap_url : test_na_url
220
+ case @options[:region]
221
+ when 'asia_pacific'
222
+ test_ap_url
223
+ when 'europe'
224
+ test_eu_url
225
+ when 'north_america', nil
226
+ test_na_url
227
+ end
210
228
  else
211
- @options[:region] == 'asia_pacific' ? live_ap_url : live_na_url
229
+ case @options[:region]
230
+ when 'asia_pacific'
231
+ live_ap_url
232
+ when 'europe'
233
+ live_eu_url
234
+ when 'north_america', nil
235
+ live_na_url
236
+ end
212
237
  end
213
238
  end
214
239
 
@@ -249,7 +249,8 @@ module ActiveMerchant #:nodoc:
249
249
  error_code: error_code_from(response)
250
250
  )
251
251
  rescue ResponseError => e
252
- message = get_error_message(e)
252
+ message = get_error_messages(e)
253
+
253
254
  return Response.new(
254
255
  false,
255
256
  "#{STANDARD_ERROR_MESSAGE_MAPPING[e.response.code]} #{message}",
@@ -263,15 +264,41 @@ module ActiveMerchant #:nodoc:
263
264
  %w[pending paid processing canceled active].include? response['status']
264
265
  end
265
266
 
266
- def get_error_message(error)
267
- JSON.parse(error.response.body)['message']
268
- end
269
-
270
267
  def message_from(response)
268
+ return gateway_response_errors(response) if gateway_response_errors?(response)
271
269
  return response['message'] if response['message']
272
270
  return response['last_transaction']['acquirer_message'] if response['last_transaction']
273
271
  end
274
272
 
273
+ def get_error_messages(error)
274
+ parsed_response_body = parse(error.response.body)
275
+ message = parsed_response_body['message']
276
+
277
+ parsed_response_body['errors']&.each do |type, descriptions|
278
+ message += ' | '
279
+ message += descriptions.join(', ')
280
+ end
281
+
282
+ message
283
+ end
284
+
285
+ def gateway_response_errors?(response)
286
+ response.try(:[], 'last_transaction').try(:[], 'gateway_response').try(:[], 'errors').present?
287
+ end
288
+
289
+ def gateway_response_errors(response)
290
+ error_string = ''
291
+
292
+ response['last_transaction']['gateway_response']['errors']&.each do |error|
293
+ error.each do |key, value|
294
+ error_string += ' | ' unless error_string.blank?
295
+ error_string += value
296
+ end
297
+ end
298
+
299
+ error_string
300
+ end
301
+
275
302
  def authorization_from(response, action)
276
303
  return "#{response['customer']['id']}|#{response['id']}" if action == 'store'
277
304
  response['id']
@@ -262,23 +262,35 @@ module ActiveMerchant #:nodoc:
262
262
  xml.tag! 'NameOnCard', credit_card.first_name
263
263
  xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value?
264
264
 
265
- if options[:three_d_secure]
266
- three_d_secure = options[:three_d_secure]
267
- xml.tag! 'BuyerAuthResult' do
268
- xml.tag! 'Status', three_d_secure[:status] unless three_d_secure[:status].blank?
269
- xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank?
270
- xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank?
271
- xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank?
272
- xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank?
273
- xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
274
- xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank?
275
- end
276
- end
265
+ add_three_d_secure(options, xml)
277
266
 
278
267
  xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name
279
268
  end
280
269
  end
281
270
 
271
+ def add_three_d_secure(options, xml)
272
+ if options[:three_d_secure]
273
+ three_d_secure = options[:three_d_secure]
274
+ xml.tag! 'BuyerAuthResult' do
275
+ authentication_status(three_d_secure, xml)
276
+ xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank?
277
+ xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank?
278
+ xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank?
279
+ xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank?
280
+ xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
281
+ xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank?
282
+ end
283
+ end
284
+ end
285
+
286
+ def authentication_status(three_d_secure, xml)
287
+ if three_d_secure[:authentication_response_status].present?
288
+ xml.tag! 'Status', three_d_secure[:authentication_response_status]
289
+ elsif three_d_secure[:directory_response_status].present?
290
+ xml.tag! 'Status', three_d_secure[:directory_response_status]
291
+ end
292
+ end
293
+
282
294
  def credit_card_type(credit_card)
283
295
  return '' if card_brand(credit_card).blank?
284
296
 
@@ -193,28 +193,32 @@ module ActiveMerchant #:nodoc:
193
193
  requires!(options, :order_id)
194
194
 
195
195
  data = {}
196
- add_action(data, :purchase)
196
+ add_action(data, :purchase, options)
197
197
  add_amount(data, money, options)
198
198
  add_order(data, options[:order_id])
199
199
  add_payment(data, payment)
200
+ add_threeds(data, options) if options[:execute_threed]
200
201
  data[:description] = options[:description]
201
202
  data[:store_in_vault] = options[:store]
203
+ data[:sca_exemption] = options[:sca_exemption]
202
204
 
203
- commit data
205
+ commit data, options
204
206
  end
205
207
 
206
208
  def authorize(money, payment, options = {})
207
209
  requires!(options, :order_id)
208
210
 
209
211
  data = {}
210
- add_action(data, :authorize)
212
+ add_action(data, :authorize, options)
211
213
  add_amount(data, money, options)
212
214
  add_order(data, options[:order_id])
213
215
  add_payment(data, payment)
216
+ add_threeds(data, options) if options[:execute_threed]
214
217
  data[:description] = options[:description]
215
218
  data[:store_in_vault] = options[:store]
219
+ data[:sca_exemption] = options[:sca_exemption]
216
220
 
217
- commit data
221
+ commit data, options
218
222
  end
219
223
 
220
224
  def capture(money, authorization, options = {})
@@ -225,7 +229,7 @@ module ActiveMerchant #:nodoc:
225
229
  add_order(data, order_id)
226
230
  data[:description] = options[:description]
227
231
 
228
- commit data
232
+ commit data, options
229
233
  end
230
234
 
231
235
  def void(authorization, options = {})
@@ -236,7 +240,7 @@ module ActiveMerchant #:nodoc:
236
240
  add_order(data, order_id)
237
241
  data[:description] = options[:description]
238
242
 
239
- commit data
243
+ commit data, options
240
244
  end
241
245
 
242
246
  def refund(money, authorization, options = {})
@@ -247,7 +251,7 @@ module ActiveMerchant #:nodoc:
247
251
  add_order(data, order_id)
248
252
  data[:description] = options[:description]
249
253
 
250
- commit data
254
+ commit data, options
251
255
  end
252
256
 
253
257
  def verify(creditcard, options = {})
@@ -278,8 +282,8 @@ module ActiveMerchant #:nodoc:
278
282
 
279
283
  private
280
284
 
281
- def add_action(data, action)
282
- data[:action] = transaction_code(action)
285
+ def add_action(data, action, options = {})
286
+ data[:action] = options[:execute_threed].present? ? '0' : transaction_code(action)
283
287
  end
284
288
 
285
289
  def add_amount(data, money, options)
@@ -295,6 +299,10 @@ module ActiveMerchant #:nodoc:
295
299
  test? ? test_url : live_url
296
300
  end
297
301
 
302
+ def threeds_url
303
+ test? ? 'https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2': 'https://sis.redsys.es/sis/services/SerClsWSEntradaV2'
304
+ end
305
+
298
306
  def add_payment(data, card)
299
307
  if card.is_a?(String)
300
308
  data[:credit_card_token] = card
@@ -311,21 +319,57 @@ module ActiveMerchant #:nodoc:
311
319
  end
312
320
  end
313
321
 
314
- def commit(data)
315
- parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data))}", headers))
322
+ def add_threeds(data, options)
323
+ if options[:execute_threed] == true
324
+ data[:threeds] = {threeDSInfo: 'CardData'}
325
+ end
326
+ end
327
+
328
+ def determine_3ds_action(threeds_hash)
329
+ return 'iniciaPeticion' if threeds_hash[:threeDSInfo] == 'CardData'
330
+ return 'trataPeticion' if threeds_hash[:threeDSInfo] == 'AuthenticationData' ||
331
+ threeds_hash[:threeDSInfo] == 'ChallengeResponse'
332
+ end
333
+
334
+ def commit(data, options = {})
335
+ if data[:threeds]
336
+ action = determine_3ds_action(data[:threeds])
337
+ request = <<-EOS
338
+ <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" >
339
+ <soapenv:Header/>
340
+ <soapenv:Body>
341
+ <intf:#{action} xmlns:intf="http://webservice.sis.sermepa.es">
342
+ <intf:datoEntrada>
343
+ <![CDATA[#{xml_request_from(data, options)}]]>
344
+ </intf:datoEntrada>
345
+ </intf:#{action}>
346
+ </soapenv:Body>
347
+ </soapenv:Envelope>
348
+ EOS
349
+ parse(ssl_post(threeds_url, request, headers(action)), action)
350
+ else
351
+ parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data, options))}", headers), action)
352
+ end
316
353
  end
317
354
 
318
- def headers
319
- {
320
- 'Content-Type' => 'application/x-www-form-urlencoded'
321
- }
355
+ def headers(action=nil)
356
+ if action
357
+ {
358
+ 'Content-Type' => 'text/xml',
359
+ 'SOAPAction' => action
360
+ }
361
+ else
362
+ {
363
+ 'Content-Type' => 'application/x-www-form-urlencoded'
364
+ }
365
+ end
322
366
  end
323
367
 
324
- def xml_request_from(data)
368
+ def xml_request_from(data, options = {})
325
369
  if sha256_authentication?
326
- build_sha256_xml_request(data)
370
+ build_sha256_xml_request(data, options)
327
371
  else
328
- build_sha1_xml_request(data)
372
+ build_sha1_xml_request(data, options)
329
373
  end
330
374
  end
331
375
 
@@ -351,30 +395,30 @@ module ActiveMerchant #:nodoc:
351
395
  Digest::SHA1.hexdigest(str)
352
396
  end
353
397
 
354
- def build_sha256_xml_request(data)
398
+ def build_sha256_xml_request(data, options = {})
355
399
  xml = Builder::XmlMarkup.new
356
400
  xml.instruct!
357
401
  xml.REQUEST do
358
- build_merchant_data(xml, data)
402
+ build_merchant_data(xml, data, options)
359
403
  xml.DS_SIGNATUREVERSION 'HMAC_SHA256_V1'
360
- xml.DS_SIGNATURE sign_request(merchant_data_xml(data), data[:order_id])
404
+ xml.DS_SIGNATURE sign_request(merchant_data_xml(data, options), data[:order_id])
361
405
  end
362
406
  xml.target!
363
407
  end
364
408
 
365
- def build_sha1_xml_request(data)
409
+ def build_sha1_xml_request(data, options = {})
366
410
  xml = Builder::XmlMarkup.new :indent => 2
367
- build_merchant_data(xml, data)
411
+ build_merchant_data(xml, data, options)
368
412
  xml.target!
369
413
  end
370
414
 
371
- def merchant_data_xml(data)
415
+ def merchant_data_xml(data, options = {})
372
416
  xml = Builder::XmlMarkup.new
373
- build_merchant_data(xml, data)
417
+ build_merchant_data(xml, data, options)
374
418
  xml.target!
375
419
  end
376
420
 
377
- def build_merchant_data(xml, data)
421
+ def build_merchant_data(xml, data, options = {})
378
422
  xml.DATOSENTRADA do
379
423
  # Basic elements
380
424
  xml.DS_Version 0.1
@@ -383,9 +427,10 @@ module ActiveMerchant #:nodoc:
383
427
  xml.DS_MERCHANT_ORDER data[:order_id]
384
428
  xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
385
429
  xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
386
- xml.DS_MERCHANT_TERMINAL @options[:terminal]
430
+ xml.DS_MERCHANT_TERMINAL options[:terminal] || @options[:terminal]
387
431
  xml.DS_MERCHANT_MERCHANTCODE @options[:login]
388
432
  xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication?
433
+ xml.DS_MERCHANT_EXCEP_SCA data[:sca_exemption] if data[:sca_exemption]
389
434
 
390
435
  # Only when card is present
391
436
  if data[:card]
@@ -398,22 +443,42 @@ module ActiveMerchant #:nodoc:
398
443
  xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token]
399
444
  xml.DS_MERCHANT_DIRECTPAYMENT 'true'
400
445
  end
446
+
447
+ # Set moto flag only if explicitly requested via moto field
448
+ # Requires account configuration to be able to use
449
+ if options.dig(:moto) && options.dig(:metadata, :manual_entry)
450
+ xml.DS_MERCHANT_DIRECTPAYMENT 'moto'
451
+ end
452
+
453
+ if data[:threeds]
454
+ xml.DS_MERCHANT_EMV3DS data[:threeds].to_json
455
+ end
401
456
  end
402
457
  end
403
458
 
404
- def parse(data)
459
+ def parse(data, action)
405
460
  params = {}
406
461
  success = false
407
462
  message = ''
408
463
  options = @options.merge(:test => test?)
409
464
  xml = Nokogiri::XML(data)
410
465
  code = xml.xpath('//RETORNOXML/CODIGO').text
411
- if code == '0'
466
+
467
+ if ['iniciaPeticion', 'trataPeticion'].include?(action)
468
+ vxml = Nokogiri::XML(data).remove_namespaces!.xpath("//Envelope/Body/#{action}Response/#{action}Return").inner_text
469
+ xml = Nokogiri::XML(vxml)
470
+ node = (action == 'iniciaPeticion' ? 'INFOTARJETA' : 'OPERACION')
471
+ op = xml.xpath("//RETORNOXML/#{node}")
472
+ op.children.each do |element|
473
+ params[element.name.downcase.to_sym] = element.text
474
+ end
475
+ message = response_text_3ds(xml, params)
476
+ success = params.size > 0 && is_success_response?(params[:ds_response])
477
+ elsif code == '0'
412
478
  op = xml.xpath('//RETORNOXML/OPERACION')
413
479
  op.children.each do |element|
414
480
  params[element.name.downcase.to_sym] = element.text
415
481
  end
416
-
417
482
  if validate_signature(params)
418
483
  message = response_text(params[:ds_response])
419
484
  options[:authorization] = build_authorization(params)
@@ -474,6 +539,20 @@ module ActiveMerchant #:nodoc:
474
539
  RESPONSE_TEXTS[code] || 'Unkown code, please check in manual'
475
540
  end
476
541
 
542
+ def response_text_3ds(xml, params)
543
+ code = xml.xpath('//RETORNOXML/CODIGO').text
544
+ message = ''
545
+ if code != '0'
546
+ message = "#{code} ERROR"
547
+ elsif params[:ds_emv3ds]
548
+ three_ds_data = JSON.parse(params[:ds_emv3ds])
549
+ message = three_ds_data['threeDSInfo']
550
+ elsif params[:ds_response]
551
+ message = response_text(params[:ds_response])
552
+ end
553
+ message
554
+ end
555
+
477
556
  def is_success_response?(code)
478
557
  (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
479
558
  end
@@ -523,6 +602,10 @@ module ActiveMerchant #:nodoc:
523
602
  xml_signed_fields += data[:ds_cardnumber]
524
603
  end
525
604
 
605
+ if data[:ds_emv3ds]
606
+ xml_signed_fields += data[:ds_emv3ds]
607
+ end
608
+
526
609
  xml_signed_fields + data[:ds_transactiontype] + data[:ds_securepayment]
527
610
  end
528
611
 
@@ -25,7 +25,7 @@ module ActiveMerchant #:nodoc:
25
25
 
26
26
  DEFAULT_API_VERSION = '2015-04-07'
27
27
 
28
- self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US)
28
+ self.supported_countries = %w(AT AU BE BR CA CH DE DK EE ES FI FR GB GR HK IE IT JP LT LU LV MX NL NO NZ PL PT SE SG SI SK US)
29
29
  self.default_currency = 'USD'
30
30
  self.money_format = :cents
31
31
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
@@ -6,7 +6,7 @@ module ActiveMerchant #:nodoc:
6
6
  # For the legacy API, see the Stripe gateway
7
7
  class StripePaymentIntentsGateway < StripeGateway
8
8
 
9
- self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US)
9
+ self.supported_countries = %w(AT AU BE BR CA CH DE DK EE ES FI FR GB GR HK IE IT JP LT LU LV MX NL NO NZ PL PT SE SG SI SK US)
10
10
 
11
11
  ALLOWED_METHOD_STATES = %w[automatic manual].freeze
12
12
  ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze
@@ -92,7 +92,11 @@ module ActiveMerchant #:nodoc:
92
92
  def capture(money, intent_id, options = {})
93
93
  post = {}
94
94
  post[:amount_to_capture] = money
95
- add_connected_account(post, options)
95
+ if options[:transfer_amount]
96
+ post[:transfer_data] = {}
97
+ post[:transfer_data][:amount] = options[:transfer_amount]
98
+ end
99
+ post[:application_fee_amount] = options[:application_fee] if options[:application_fee]
96
100
  commit(:post, "payment_intents/#{intent_id}/capture", post, options)
97
101
  end
98
102
 
@@ -3,13 +3,18 @@ module ActiveMerchant
3
3
  class TnsGateway < Gateway
4
4
  include MastercardGateway
5
5
 
6
- class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url
6
+ class_attribute :live_na_url, :live_ap_url, :live_eu_url, :test_na_url, :test_ap_url, :test_eu_url
7
7
 
8
- self.live_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/'
9
- self.test_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/'
8
+ VERSION = '52'
10
9
 
11
- self.live_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/'
12
- self.test_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/'
10
+ self.live_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
11
+ self.test_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
12
+
13
+ self.live_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
14
+ self.test_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
15
+
16
+ self.live_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
17
+ self.test_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
13
18
 
14
19
  self.display_name = 'TNS'
15
20
  self.homepage_url = 'http://www.tnsi.com/'
@@ -88,7 +88,7 @@ module ActiveMerchant #:nodoc:
88
88
  def refund(money, authorization, options = {})
89
89
  authorization = order_id_from_authorization(authorization.to_s)
90
90
  response = MultiResponse.run do |r|
91
- r.process { inquire_request(authorization, options, 'CAPTURED', 'SETTLED', 'SETTLED_BY_MERCHANT') }
91
+ r.process { inquire_request(authorization, options, 'CAPTURED', 'SETTLED', 'SETTLED_BY_MERCHANT') } unless options[:authorization_validated]
92
92
  r.process { refund_request(money, authorization, options) }
93
93
  end
94
94
 
@@ -208,6 +208,8 @@ module ActiveMerchant #:nodoc:
208
208
  add_instalments_data(xml, options)
209
209
  end
210
210
  add_moto_flag(xml, options) if options.dig(:metadata, :manual_entry)
211
+ add_additional_3ds_data(xml, options) if options[:execute_threed] && options[:three_ds_version] && options[:three_ds_version] =~ /^2/
212
+ add_3ds_exemption(xml, options) if options[:exemption_type]
211
213
  end
212
214
  end
213
215
  end
@@ -257,6 +259,14 @@ module ActiveMerchant #:nodoc:
257
259
  end
258
260
  end
259
261
 
262
+ def add_additional_3ds_data(xml, options)
263
+ xml.tag! 'additional3DSData', 'dfReferenceId' => options[:session_id]
264
+ end
265
+
266
+ def add_3ds_exemption(xml, options)
267
+ xml.tag! 'exemption', 'type' => options[:exemption_type], 'placement' => options[:exemption_placement] || 'AUTHORISATION'
268
+ end
269
+
260
270
  def add_amount(xml, money, options)
261
271
  currency = options[:currency] || currency(money)
262
272
 
@@ -329,7 +339,7 @@ module ActiveMerchant #:nodoc:
329
339
  xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits)
330
340
  end
331
341
 
332
- xml.tag! 'cardHolderName', options[:execute_threed] ? '3D' : payment_method.name
342
+ xml.tag! 'cardHolderName', options[:execute_threed] && (options[:three_ds_version] =~ /[^2]/).nil? ? '3D' : payment_method.name
333
343
  xml.tag! 'cvc', payment_method.verification_value
334
344
 
335
345
  add_address(xml, (options[:billing_address] || options[:address]))
@@ -481,6 +491,7 @@ module ActiveMerchant #:nodoc:
481
491
  if options[:execute_threed]
482
492
  raw[:cookie] = @cookie
483
493
  raw[:session_id] = options[:session_id]
494
+ raw[:is3DSOrder] = true
484
495
  end
485
496
  success = success_from(action, raw, success_criteria)
486
497
  message = message_from(success, raw, success_criteria)
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.98.0'
2
+ VERSION = '1.99.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.98.0
4
+ version: 1.99.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-09 00:00:00.000000000 Z
11
+ date: 2019-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport