activemerchant 1.90.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +202 -0
  3. data/README.md +6 -2
  4. data/lib/active_merchant/billing/avs_result.rb +4 -5
  5. data/lib/active_merchant/billing/credit_card.rb +8 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +81 -5
  7. data/lib/active_merchant/billing/gateway.rb +10 -0
  8. data/lib/active_merchant/billing/gateways/adyen.rb +206 -54
  9. data/lib/active_merchant/billing/gateways/bambora_apac.rb +226 -0
  10. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +43 -10
  11. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +3 -0
  12. data/lib/active_merchant/billing/gateways/beanstream.rb +11 -6
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +10 -8
  14. data/lib/active_merchant/billing/gateways/blue_snap.rb +211 -36
  15. data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +79 -18
  17. data/lib/active_merchant/billing/gateways/card_connect.rb +6 -1
  18. data/lib/active_merchant/billing/gateways/cecabank.rb +20 -9
  19. data/lib/active_merchant/billing/gateways/checkout_v2.rb +98 -61
  20. data/lib/active_merchant/billing/gateways/credorax.rb +69 -4
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +85 -14
  22. data/lib/active_merchant/billing/gateways/d_local.rb +3 -3
  23. data/lib/active_merchant/billing/gateways/decidir.rb +245 -0
  24. data/lib/active_merchant/billing/gateways/elavon.rb +9 -0
  25. data/lib/active_merchant/billing/gateways/epay.rb +13 -2
  26. data/lib/active_merchant/billing/gateways/eway_rapid.rb +42 -12
  27. data/lib/active_merchant/billing/gateways/fat_zebra.rb +26 -7
  28. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +42 -3
  29. data/lib/active_merchant/billing/gateways/global_collect.rb +3 -7
  30. data/lib/active_merchant/billing/gateways/hps.rb +46 -1
  31. data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
  32. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  33. data/lib/active_merchant/billing/gateways/litle.rb +61 -3
  34. data/lib/active_merchant/billing/gateways/mastercard.rb +30 -5
  35. data/lib/active_merchant/billing/gateways/mercado_pago.rb +1 -1
  36. data/lib/active_merchant/billing/gateways/migs.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/monei.rb +31 -0
  38. data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
  39. data/lib/active_merchant/billing/gateways/mundipagg.rb +37 -9
  40. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
  42. data/lib/active_merchant/billing/gateways/nmi.rb +45 -5
  43. data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
  44. data/lib/active_merchant/billing/gateways/opp.rb +20 -1
  45. data/lib/active_merchant/billing/gateways/orbital.rb +92 -11
  46. data/lib/active_merchant/billing/gateways/payflow.rb +64 -14
  47. data/lib/active_merchant/billing/gateways/payment_express.rb +7 -0
  48. data/lib/active_merchant/billing/gateways/paymentez.rb +7 -11
  49. data/lib/active_merchant/billing/gateways/paymill.rb +5 -0
  50. data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
  51. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -1
  52. data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -2
  53. data/lib/active_merchant/billing/gateways/pin.rb +19 -6
  54. data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
  55. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +7 -1
  56. data/lib/active_merchant/billing/gateways/qvalent.rb +54 -1
  57. data/lib/active_merchant/billing/gateways/realex.rb +42 -5
  58. data/lib/active_merchant/billing/gateways/redsys.rb +113 -30
  59. data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
  60. data/lib/active_merchant/billing/gateways/stripe.rb +66 -34
  61. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +271 -0
  62. data/lib/active_merchant/billing/gateways/tns.rb +10 -5
  63. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +5 -5
  64. data/lib/active_merchant/billing/gateways/trust_commerce.rb +46 -6
  65. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +19 -8
  66. data/lib/active_merchant/billing/gateways/visanet_peru.rb +22 -10
  67. data/lib/active_merchant/billing/gateways/worldpay.rb +237 -34
  68. data/lib/active_merchant/country.rb +2 -1
  69. data/lib/active_merchant/version.rb +1 -1
  70. metadata +20 -4
@@ -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
 
@@ -35,19 +35,13 @@ module ActiveMerchant #:nodoc:
35
35
  # Public: Run a purchase transaction.
36
36
  #
37
37
  # money - The monetary amount of the transaction in cents.
38
- # payment_method - The CreditCard or the Spreedly payment method token.
38
+ # payment_method - The CreditCard or Check or the Spreedly payment method token.
39
39
  # options - A hash of options:
40
40
  # :store - Retain the payment method if the purchase
41
41
  # succeeds. Defaults to false. (optional)
42
42
  def purchase(money, payment_method, options = {})
43
- if payment_method.is_a?(String)
44
- purchase_with_token(money, payment_method, options)
45
- else
46
- MultiResponse.run do |r|
47
- r.process { save_card(options[:store], payment_method, options) }
48
- r.process { purchase_with_token(money, r.authorization, options) }
49
- end
50
- end
43
+ request = build_transaction_request(money, payment_method, options)
44
+ commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request)
51
45
  end
52
46
 
53
47
  # Public: Run an authorize transaction.
@@ -58,14 +52,8 @@ module ActiveMerchant #:nodoc:
58
52
  # :store - Retain the payment method if the authorize
59
53
  # succeeds. Defaults to false. (optional)
60
54
  def authorize(money, payment_method, options = {})
61
- if payment_method.is_a?(String)
62
- authorize_with_token(money, payment_method, options)
63
- else
64
- MultiResponse.run do |r|
65
- r.process { save_card(options[:store], payment_method, options) }
66
- r.process { authorize_with_token(money, r.authorization, options) }
67
- end
68
- end
55
+ request = build_transaction_request(money, payment_method, options)
56
+ commit("gateways/#{@options[:gateway_token]}/authorize.xml", request)
69
57
  end
70
58
 
71
59
  def capture(money, authorization, options={})
@@ -79,6 +67,7 @@ module ActiveMerchant #:nodoc:
79
67
  def refund(money, authorization, options={})
80
68
  request = build_xml_request('transaction') do |doc|
81
69
  add_invoice(doc, money, options)
70
+ add_extra_options(:gateway_specific_fields, doc, options)
82
71
  end
83
72
 
84
73
  commit("transactions/#{authorization}/credit.xml", request)
@@ -155,32 +144,25 @@ module ActiveMerchant #:nodoc:
155
144
  end
156
145
 
157
146
  def purchase_with_token(money, payment_method_token, options)
158
- request = auth_purchase_request(money, payment_method_token, options)
147
+ request = build_transaction_request(money, payment_method_token, options)
159
148
  commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request)
160
149
  end
161
150
 
162
151
  def authorize_with_token(money, payment_method_token, options)
163
- request = auth_purchase_request(money, payment_method_token, options)
152
+ request = build_transaction_request(money, payment_method_token, options)
164
153
  commit("gateways/#{@options[:gateway_token]}/authorize.xml", request)
165
154
  end
166
155
 
167
156
  def verify_with_token(payment_method_token, options)
168
- request = build_xml_request('transaction') do |doc|
169
- add_invoice(doc, nil, options)
170
- doc.payment_method_token(payment_method_token)
171
- doc.retain_on_success(true) if options[:store]
172
- add_extra_options(:gateway_specific_fields, doc, options)
173
- end
174
-
157
+ request = build_transaction_request(nil, payment_method_token, options)
175
158
  commit("gateways/#{@options[:gateway_token]}/verify.xml", request)
176
159
  end
177
160
 
178
- def auth_purchase_request(money, payment_method_token, options)
161
+ def build_transaction_request(money, payment_method, options)
179
162
  build_xml_request('transaction') do |doc|
180
163
  add_invoice(doc, money, options)
164
+ add_payment_method(doc, payment_method, options)
181
165
  add_extra_options(:gateway_specific_fields, doc, options)
182
- doc.payment_method_token(payment_method_token)
183
- doc.retain_on_success(true) if options[:store]
184
166
  end
185
167
  end
186
168
 
@@ -190,6 +172,27 @@ module ActiveMerchant #:nodoc:
190
172
  doc.order_id(options[:order_id])
191
173
  doc.ip(options[:ip]) if options[:ip]
192
174
  doc.description(options[:description]) if options[:description]
175
+
176
+ if options[:merchant_name_descriptor]
177
+ doc.merchant_name_descriptor(options[:merchant_name_descriptor])
178
+ end
179
+ if options[:merchant_location_descriptor]
180
+ doc.merchant_location_descriptor(options[:merchant_location_descriptor])
181
+ end
182
+ end
183
+
184
+ def add_payment_method(doc, payment_method, options)
185
+ doc.retain_on_success(true) if options[:store]
186
+
187
+ if payment_method.is_a?(String)
188
+ doc.payment_method_token(payment_method)
189
+ elsif payment_method.is_a?(CreditCard)
190
+ add_credit_card(doc, payment_method, options)
191
+ elsif payment_method.is_a?(Check)
192
+ add_bank_account(doc, payment_method, options)
193
+ else
194
+ raise TypeError, 'Payment method not supported'
195
+ end
193
196
  end
194
197
 
195
198
  def add_credit_card(doc, credit_card, options)
@@ -210,6 +213,17 @@ module ActiveMerchant #:nodoc:
210
213
  end
211
214
  end
212
215
 
216
+ def add_bank_account(doc, bank_account, options)
217
+ doc.bank_account do
218
+ doc.first_name(bank_account.first_name)
219
+ doc.last_name(bank_account.last_name)
220
+ doc.bank_routing_number(bank_account.routing_number)
221
+ doc.bank_account_number(bank_account.account_number)
222
+ doc.bank_account_type(bank_account.account_type)
223
+ doc.bank_account_holder_type(bank_account.account_holder_type)
224
+ end
225
+ end
226
+
213
227
  def add_extra_options(type, doc, options)
214
228
  doc.send(type) do
215
229
  extra_options_to_doc(doc, options[type])
@@ -2,6 +2,8 @@ require 'active_support/core_ext/hash/slice'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
+ # This gateway uses an older version of the Stripe API.
6
+ # To utilize the updated {Payment Intents API}[https://stripe.com/docs/api/payment_intents], integrate with the StripePaymentIntents gateway
5
7
  class StripeGateway < Gateway
6
8
  self.live_url = 'https://api.stripe.com/v1/'
7
9
 
@@ -21,7 +23,9 @@ module ActiveMerchant #:nodoc:
21
23
  'unchecked' => 'P'
22
24
  }
23
25
 
24
- 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)
26
+ DEFAULT_API_VERSION = '2015-04-07'
27
+
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)
25
29
  self.default_currency = 'USD'
26
30
  self.money_format = :cents
27
31
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
@@ -150,14 +154,21 @@ module ActiveMerchant #:nodoc:
150
154
  post[:reason] = options[:reason] if options[:reason]
151
155
  post[:expand] = [:charge]
152
156
 
153
- MultiResponse.run(:first) do |r|
154
- r.process { commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options) }
157
+ response = commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options)
158
+
159
+ if response.success? && options[:refund_fee_amount] && options[:refund_fee_amount].to_s != '0'
160
+ charge = api_request(:get, "charges/#{CGI.escape(identification)}", nil, options)
155
161
 
156
- if options[:refund_fee_amount] && options[:refund_fee_amount].to_s != '0'
157
- r.process { fetch_application_fee(identification, options) }
158
- r.process { refund_application_fee(options[:refund_fee_amount].to_i, application_fee_from_response(r.responses.last), options) }
162
+ if application_fee = charge['application_fee']
163
+ fee_refund_options = {
164
+ currency: options[:currency], # currency isn't used by Stripe here, but we need it for #add_amount
165
+ key: @fee_refund_api_key
166
+ }
167
+ refund_application_fee(options[:refund_fee_amount].to_i, application_fee, fee_refund_options)
159
168
  end
160
169
  end
170
+
171
+ response
161
172
  end
162
173
 
163
174
  def verify(payment, options = {})
@@ -168,21 +179,10 @@ module ActiveMerchant #:nodoc:
168
179
  end
169
180
  end
170
181
 
171
- def application_fee_from_response(response)
172
- return unless response.success?
173
- response.params['application_fee'] unless response.params['application_fee'].empty?
174
- end
175
-
176
182
  def refund_application_fee(money, identification, options = {})
177
- return Response.new(false, 'Application fee id could not be found') unless identification
178
-
179
183
  post = {}
180
184
  add_amount(post, money, options)
181
- options[:key] = @fee_refund_api_key if @fee_refund_api_key
182
- options.delete(:stripe_account)
183
-
184
- refund_fee = commit(:post, "application_fees/#{CGI.escape(identification)}/refunds", post, options)
185
- application_fee_response!(refund_fee, "Application fee could not be refunded: #{refund_fee.message}")
185
+ commit(:post, "application_fees/#{CGI.escape(identification)}/refunds", post, options)
186
186
  end
187
187
 
188
188
  # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true)
@@ -304,8 +304,8 @@ module ActiveMerchant #:nodoc:
304
304
  add_amount(post, money, options, true)
305
305
  post[:type] = type
306
306
  if type == 'card'
307
- add_creditcard(post, payment, options)
308
- post[:card].delete(:name)
307
+ add_creditcard(post, payment, options, true)
308
+ add_source_owner(post, payment, options)
309
309
  elsif type == 'three_d_secure'
310
310
  post[:three_d_secure] = {card: payment}
311
311
  post[:redirect] = {return_url: options[:redirect_url]}
@@ -313,10 +313,16 @@ module ActiveMerchant #:nodoc:
313
313
  commit(:post, 'sources', post, options)
314
314
  end
315
315
 
316
+ def show_source(source_id, options)
317
+ commit(:get, "sources/#{source_id}", nil, options)
318
+ end
319
+
316
320
  def create_webhook_endpoint(options, events)
317
321
  post = {}
318
322
  post[:url] = options[:callback_url]
319
323
  post[:enabled_events] = events
324
+ post[:connect] = true if options[:stripe_account]
325
+ options.delete(:stripe_account)
320
326
  commit(:post, 'webhook_endpoints', post, options)
321
327
  end
322
328
 
@@ -324,6 +330,18 @@ module ActiveMerchant #:nodoc:
324
330
  commit(:delete, "webhook_endpoints/#{options[:webhook_id]}", {}, options)
325
331
  end
326
332
 
333
+ def show_webhook_endpoint(options)
334
+ options.delete(:stripe_account)
335
+ commit(:get, "webhook_endpoints/#{options[:webhook_id]}", nil, options)
336
+ end
337
+
338
+ def list_webhook_endpoints(options)
339
+ params = {}
340
+ params[:limit] = options[:limit] if options[:limit]
341
+ options.delete(:stripe_account)
342
+ commit(:get, "webhook_endpoints?#{post_data(params)}", nil, options)
343
+ end
344
+
327
345
  def create_post_for_auth_or_purchase(money, payment, options)
328
346
  post = {}
329
347
 
@@ -333,6 +351,12 @@ module ActiveMerchant #:nodoc:
333
351
  add_creditcard(post, payment, options)
334
352
  end
335
353
 
354
+ add_charge_details(post, money, payment, options)
355
+ post
356
+ end
357
+
358
+ # Used internally by Spreedly to populate the charge object for 3DS 1.0 transactions
359
+ def add_charge_details(post, money, payment, options)
336
360
  if emv_payment?(payment)
337
361
  add_statement_address(post, options)
338
362
  add_emv_metadata(post, payment)
@@ -435,7 +459,7 @@ module ActiveMerchant #:nodoc:
435
459
  post[:statement_address][:state] = statement_address[:state]
436
460
  end
437
461
 
438
- def add_creditcard(post, creditcard, options)
462
+ def add_creditcard(post, creditcard, options, use_sources = false)
439
463
  card = {}
440
464
  if emv_payment?(creditcard)
441
465
  add_emv_creditcard(post, creditcard.icc_data)
@@ -458,7 +482,7 @@ module ActiveMerchant #:nodoc:
458
482
  card[:exp_month] = creditcard.month
459
483
  card[:exp_year] = creditcard.year
460
484
  card[:cvc] = creditcard.verification_value if creditcard.verification_value?
461
- card[:name] = creditcard.name if creditcard.name
485
+ card[:name] = creditcard.name if creditcard.name && !use_sources
462
486
  end
463
487
 
464
488
  if creditcard.is_a?(NetworkTokenizationCreditCard)
@@ -468,7 +492,7 @@ module ActiveMerchant #:nodoc:
468
492
  end
469
493
  post[:card] = card
470
494
 
471
- add_address(post, options)
495
+ add_address(post, options) unless use_sources
472
496
  elsif creditcard.kind_of?(String)
473
497
  if options[:track_data]
474
498
  card[:swipe_data] = options[:track_data]
@@ -516,15 +540,23 @@ module ActiveMerchant #:nodoc:
516
540
  post[:metadata][:card_read_method] = creditcard.read_method if creditcard.respond_to?(:read_method)
517
541
  end
518
542
 
519
- def fetch_application_fee(identification, options = {})
520
- options[:key] = @fee_refund_api_key
521
-
522
- fetch_charge = commit(:get, "charges/#{CGI.escape(identification)}", nil, options)
523
- application_fee_response!(fetch_charge, "Application fee id could not be retrieved: #{fetch_charge.message}")
524
- end
543
+ def add_source_owner(post, creditcard, options)
544
+ post[:owner] = {}
545
+ post[:owner][:name] = creditcard.name if creditcard.name
546
+ post[:owner][:email] = options[:email] if options[:email]
525
547
 
526
- def application_fee_response!(response, message)
527
- response.success? ? response : Response.new(false, message)
548
+ if address = options[:billing_address] || options[:address]
549
+ owner_address = {}
550
+ owner_address[:line1] = address[:address1] if address[:address1]
551
+ owner_address[:line2] = address[:address2] if address[:address2]
552
+ owner_address[:country] = address[:country] if address[:country]
553
+ owner_address[:postal_code] = address[:zip] if address[:zip]
554
+ owner_address[:state] = address[:state] if address[:state]
555
+ owner_address[:city] = address[:city] if address[:city]
556
+
557
+ post[:owner][:phone] = address[:phone] if address[:phone]
558
+ post[:owner][:address] = owner_address
559
+ end
528
560
  end
529
561
 
530
562
  def parse(body)
@@ -586,7 +618,7 @@ module ActiveMerchant #:nodoc:
586
618
  end
587
619
 
588
620
  def api_version(options)
589
- options[:version] || @options[:version] || '2015-04-07'
621
+ options[:version] || @options[:version] || self.class::DEFAULT_API_VERSION
590
622
  end
591
623
 
592
624
  def api_request(method, endpoint, parameters = nil, options = {})
@@ -629,8 +661,8 @@ module ActiveMerchant #:nodoc:
629
661
  return response.fetch('error', {})['charge'] unless success
630
662
 
631
663
  if url == 'customers'
632
- [response['id'], response['sources']['data'].first['id']].join('|')
633
- elsif method == :post && url.match(/customers\/.*\/cards/)
664
+ [response['id'], response.dig('sources', 'data').first&.dig('id')].join('|')
665
+ elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/))
634
666
  [response['customer'], response['id']].join('|')
635
667
  else
636
668
  response['id']