activemerchant 1.98.0 → 1.99.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.
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