activemerchant 1.55.0 → 1.56.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.
@@ -1,6 +1,8 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class CashnetGateway < Gateway
4
+ include Empty
5
+
4
6
  self.live_url = "https://commerce.cashnet.com/"
5
7
 
6
8
  self.supported_countries = ["US"]
@@ -47,6 +49,7 @@ module ActiveMerchant #:nodoc:
47
49
  post = {}
48
50
  post[:origtx] = identification
49
51
  add_invoice(post, options)
52
+ add_customer_data(post, options)
50
53
  commit('REFUND', money, post)
51
54
  end
52
55
 
@@ -106,6 +109,7 @@ module ActiveMerchant #:nodoc:
106
109
 
107
110
  def add_customer_data(post, options)
108
111
  post[:email_g] = options[:email]
112
+ post[:custcode] = options[:custcode] unless empty?(options[:custcode])
109
113
  end
110
114
 
111
115
  def expdate(creditcard)
@@ -3,6 +3,8 @@ require 'nokogiri'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class CreditcallGateway < Gateway
6
+ include Empty
7
+
6
8
  self.test_url = 'https://test.cardeasexml.com/generic.cex'
7
9
  self.live_url = 'https://live.cardeasexml.com/generic.cex'
8
10
 
@@ -121,7 +123,7 @@ module ActiveMerchant #:nodoc:
121
123
  xml.Manual(type: "cnp") do
122
124
  xml.PAN payment_method.number
123
125
  xml.ExpiryDate exp_date(payment_method)
124
- xml.CSC payment_method.verification_value
126
+ xml.CSC payment_method.verification_value unless empty?(payment_method.verification_value)
125
127
  end
126
128
 
127
129
  if address = options[:billing_address]
@@ -82,6 +82,7 @@ module ActiveMerchant #:nodoc:
82
82
  add_address(form, options)
83
83
  add_customer_data(form, options)
84
84
  add_test_mode(form, options)
85
+ add_ip(form, options)
85
86
  commit(:purchase, money, form)
86
87
  end
87
88
 
@@ -100,6 +101,7 @@ module ActiveMerchant #:nodoc:
100
101
  add_address(form, options)
101
102
  add_customer_data(form, options)
102
103
  add_test_mode(form, options)
104
+ add_ip(form, options)
103
105
  commit(:authorize, money, form)
104
106
  end
105
107
 
@@ -296,6 +298,10 @@ module ActiveMerchant #:nodoc:
296
298
  form[:partial_shipment_flag] = 'Y' if options[:partial_shipment_flag]
297
299
  end
298
300
 
301
+ def add_ip(form, options)
302
+ form[:cardholder_ip] = options[:ip] if options.has_key?(:ip)
303
+ end
304
+
299
305
  def message_from(response)
300
306
  success?(response) ? response['result_message'] : response['errorMessage']
301
307
  end
@@ -356,7 +356,7 @@ module ActiveMerchant #:nodoc:
356
356
  [
357
357
  response[:authorization_num],
358
358
  response[:transaction_tag],
359
- response[:dollar_amount].sub(".", "")
359
+ (response[:dollar_amount].to_f * 100).round
360
360
  ].join(";")
361
361
  else
362
362
  ""
@@ -1,7 +1,8 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class GarantiGateway < Gateway
4
- self.live_url = self.test_url = 'https://sanalposprov.garanti.com.tr/VPServlet'
4
+ self.live_url = 'https://sanalposprov.garanti.com.tr/VPServlet'
5
+ self.test_url = 'https://sanalposprovtest.garanti.com.tr/VPServlet'
5
6
 
6
7
  # The countries the gateway supports merchants from as 2 digit ISO country codes
7
8
  self.supported_countries = ['US','TR']
@@ -217,7 +218,8 @@ module ActiveMerchant #:nodoc:
217
218
  end
218
219
 
219
220
  def commit(money,request)
220
- raw_response = ssl_post(self.live_url, "data=" + request)
221
+ url = test? ? self.test_url : self.live_url
222
+ raw_response = ssl_post(url, "data=" + request)
221
223
  response = parse(raw_response)
222
224
 
223
225
  success = success?(response)
@@ -162,7 +162,7 @@ module ActiveMerchant #:nodoc:
162
162
  end
163
163
 
164
164
  def capture(money, reference, options = {})
165
- commit(money, build_capture_request(reference.split(";").first, money))
165
+ commit(money, build_capture_request(reference.split(";").first, money, options))
166
166
  end
167
167
 
168
168
  def void(reference, options = {})
@@ -242,9 +242,10 @@ module ActiveMerchant #:nodoc:
242
242
  end
243
243
  end
244
244
 
245
- def build_capture_request(transaction_id, money)
245
+ def build_capture_request(transaction_id, money, options)
246
246
  build_xml_request('CAPT', transaction_id) do |xml|
247
247
  xml.tag! 'TotalAmount', amount(money)
248
+ add_user_defined_fields(xml, options)
248
249
  end
249
250
  end
250
251
 
@@ -195,7 +195,16 @@ module ActiveMerchant #:nodoc:
195
195
  def add_credit_card(xml, credit_card, action)
196
196
  xml.tag! 'Account' do
197
197
  if credit_card.track_data.present?
198
- xml.tag! 'Track1', credit_card.track_data
198
+ # Track 1 has a start sentinel (STX) of '%' and track 2 is ';'
199
+ # Track 1 and 2 have identical end sentinels (ETX) of '?'
200
+ # If the track has no STX or is corrupt, we send it as track 1, to let Mercury
201
+ #handle with the validation error as it sees fit.
202
+ # Track 2 requires having the start and end sentinels stripped. Track 1 does not.
203
+ if credit_card.track_data[0] == ';' # track 2 start sentinel (STX)
204
+ xml.tag! 'Track2', credit_card.track_data[1..-2]
205
+ else # track 1 or a corrupt track
206
+ xml.tag! 'Track1', credit_card.track_data
207
+ end
199
208
  else
200
209
  xml.tag! 'AcctNo', credit_card.number
201
210
  xml.tag! 'ExpDate', expdate(credit_card)
@@ -23,6 +23,7 @@ module ActiveMerchant #:nodoc:
23
23
  add_invoice(post, amount, options)
24
24
  add_payment_method(post, payment_method, options)
25
25
  add_customer_data(post, options)
26
+ add_address(post, options)
26
27
  commit("purchase", post)
27
28
  end
28
29
 
@@ -31,6 +32,7 @@ module ActiveMerchant #:nodoc:
31
32
  add_invoice(post, amount, options)
32
33
  add_payment_method(post, payment_method, options)
33
34
  add_customer_data(post, options)
35
+ add_address(post, options)
34
36
  commit("authorize", post)
35
37
  end
36
38
 
@@ -81,22 +83,34 @@ module ActiveMerchant #:nodoc:
81
83
  post[:currency] = options[:currency] || currency(money)
82
84
  end
83
85
  post[:project] = options[:project] if options[:project]
86
+ post["params[title]"] = options[:description] if options[:description]
84
87
  end
85
88
 
86
89
  def add_payment_method(post, payment_method, options={})
87
- post[:firstname] = payment_method.first_name
88
- post[:surname] = payment_method.last_name
89
90
  post[:number] = payment_method.number
90
91
  post[:recurring] = 1 if options[:recurring] == true
91
92
  post[:cvc2] = payment_method.verification_value
92
93
  post[:expiryYear] = format(payment_method.year, :four_digits)
93
94
  post[:expiryMonth] = format(payment_method.month, :two_digits)
95
+
96
+ post["params[firstname]"] = payment_method.first_name
97
+ post["params[surname]"] = payment_method.last_name
94
98
  end
95
99
 
96
100
  def add_customer_data(post, options)
97
- post[:email] = options[:email] if options[:email]
98
- post[:ip] = options[:ip] || "1.1.1.1"
99
- post[:sendMail] = options[:send_mail] || 'false'
101
+ post["params[email]"] = options[:email] if options[:email]
102
+ post["params[ip]"] = options[:ip] || "1.1.1.1"
103
+ post["params[sendMail]"] = options[:send_mail] || 'false'
104
+ end
105
+
106
+ def add_address(post, options)
107
+ address = options[:billing_address]
108
+ return unless address
109
+
110
+ post["params[address]"] = address[:address1] if address[:address1]
111
+ post["params[zipcode]"] = address[:zip] if address[:zip]
112
+ post["params[town]"] = address[:city] if address[:city]
113
+ post["params[country]"] = address[:country] if address[:country]
100
114
  end
101
115
 
102
116
  def add_reference(post, authorization)
@@ -128,7 +142,7 @@ module ActiveMerchant #:nodoc:
128
142
  end
129
143
 
130
144
  def post_data(action, params)
131
- params.map {|k, v| "#{k}=#{CGI.escape(v.to_s)}"}.join('&')
145
+ params.map {|k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join('&')
132
146
  end
133
147
 
134
148
  def url(action)
@@ -3,7 +3,6 @@ require 'active_merchant/billing/rails'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class OmiseGateway < Gateway
6
- API_VERSION = '1.0'
7
6
  API_URL = 'https://api.omise.co/'
8
7
  VAULT_URL = 'https://vault.omise.co/'
9
8
 
@@ -45,8 +44,9 @@ module ActiveMerchant #:nodoc:
45
44
 
46
45
  def initialize(options={})
47
46
  requires!(options, :public_key, :secret_key)
48
- @public_key = options[:public_key]
49
- @secret_key = options[:secret_key]
47
+ @public_key = options[:public_key]
48
+ @secret_key = options[:secret_key]
49
+ @api_version = options[:api_version]
50
50
  super
51
51
  end
52
52
 
@@ -178,7 +178,8 @@ module ActiveMerchant #:nodoc:
178
178
  key = options[:key] || @secret_key
179
179
  {
180
180
  'Content-Type' => 'application/json;utf-8',
181
- 'User-Agent' => "Omise/v#{API_VERSION} ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
181
+ 'Omise-Version' => @api_version || "2014-07-27",
182
+ 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION} Ruby/#{RUBY_VERSION}",
182
183
  'Authorization' => 'Basic ' + Base64.encode64(key.to_s + ':').strip,
183
184
  'Accept-Encoding' => 'utf-8'
184
185
  }
@@ -24,6 +24,16 @@ module ActiveMerchant #:nodoc:
24
24
  #
25
25
  # Written by Samuel Lown for Cabify. For implementation questions, or
26
26
  # test access details please get in touch: sam@cabify.com.
27
+ #
28
+ # *** SHA256 Authentication Update ***
29
+ #
30
+ # Redsys is dropping support for the SHA1 authentication method. This
31
+ # adapter has been updated to work with the new SHA256 authentication
32
+ # method, however in your initialization options hash you will need to
33
+ # specify the key/value :signature_algorithm => "sha256" to use the
34
+ # SHA256 method. Otherwise it will default to using the SHA1.
35
+ #
36
+ #
27
37
  class RedsysGateway < Gateway
28
38
  self.live_url = "https://sis.sermepa.es/sis/operaciones"
29
39
  self.test_url = "https://sis-t.redsys.es:25443/sis/operaciones"
@@ -161,9 +171,11 @@ module ActiveMerchant #:nodoc:
161
171
  # * <tt>:secret_key</tt> -- The Redsys Secret Key. (REQUIRED)
162
172
  # * <tt>:terminal</tt> -- The Redsys Terminal. Defaults to 1. (OPTIONAL)
163
173
  # * <tt>:test</tt> -- +true+ or +false+. Defaults to +false+. (OPTIONAL)
174
+ # * <tt>:signature_algorithm</tt> -- +"sha256"+ Defaults to +"sha1"+. (OPTIONAL)
164
175
  def initialize(options = {})
165
176
  requires!(options, :login, :secret_key)
166
177
  options[:terminal] ||= 1
178
+ options[:signature_algorithm] ||= "sha1"
167
179
  super
168
180
  end
169
181
 
@@ -247,6 +259,7 @@ module ActiveMerchant #:nodoc:
247
259
  gsub(%r((<DS_MERCHANT_PAN>)\d+(</DS_MERCHANT_PAN>))i, '\1[FILTERED]\2').
248
260
  gsub(%r((<DS_MERCHANT_CVV2>)\d+(</DS_MERCHANT_CVV2>))i, '\1[FILTERED]\2').
249
261
  gsub(%r((DS_MERCHANT_CVV2)%2F%3E%0A%3C%2F)i, '\1[BLANK]').
262
+ gsub(%r((DS_MERCHANT_CVV2)%2F%3E%3C)i, '\1[BLANK]').
250
263
  gsub(%r((DS_MERCHANT_CVV2%3E)(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2').
251
264
  gsub(%r((<DS_MERCHANT_CVV2>)(</DS_MERCHANT_CVV2>))i, '\1[BLANK]\2').
252
265
  gsub(%r((DS_MERCHANT_CVV2%3E)\++(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2').
@@ -289,18 +302,28 @@ module ActiveMerchant #:nodoc:
289
302
  end
290
303
 
291
304
  def commit(data)
292
- headers = {
305
+ parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data))}", headers))
306
+ end
307
+
308
+ def headers
309
+ {
293
310
  'Content-Type' => 'application/x-www-form-urlencoded'
294
311
  }
295
- xml = build_xml_request(data)
296
- parse(ssl_post(url, "entrada=#{CGI.escape(xml)}", headers))
312
+ end
313
+
314
+ def xml_request_from(data)
315
+ if sha256_authentication?
316
+ build_sha256_xml_request(data)
317
+ else
318
+ build_sha1_xml_request(data)
319
+ end
297
320
  end
298
321
 
299
322
  def build_signature(data)
300
323
  str = data[:amount] +
301
- data[:order_id].to_s +
302
- @options[:login].to_s +
303
- data[:currency]
324
+ data[:order_id].to_s +
325
+ @options[:login].to_s +
326
+ data[:currency]
304
327
 
305
328
  if card = data[:card]
306
329
  str << card[:pan]
@@ -318,8 +341,30 @@ module ActiveMerchant #:nodoc:
318
341
  Digest::SHA1.hexdigest(str)
319
342
  end
320
343
 
321
- def build_xml_request(data)
344
+ def build_sha256_xml_request(data)
345
+ xml = Builder::XmlMarkup.new
346
+ xml.instruct!
347
+ xml.REQUEST do
348
+ build_merchant_data(xml, data)
349
+ xml.DS_SIGNATUREVERSION 'HMAC_SHA256_V1'
350
+ xml.DS_SIGNATURE sign_request(merchant_data_xml(data), data[:order_id])
351
+ end
352
+ xml.target!
353
+ end
354
+
355
+ def build_sha1_xml_request(data)
322
356
  xml = Builder::XmlMarkup.new :indent => 2
357
+ build_merchant_data(xml, data)
358
+ xml.target!
359
+ end
360
+
361
+ def merchant_data_xml(data)
362
+ xml = Builder::XmlMarkup.new
363
+ build_merchant_data(xml, data)
364
+ xml.target!
365
+ end
366
+
367
+ def build_merchant_data(xml, data)
323
368
  xml.DATOSENTRADA do
324
369
  # Basic elements
325
370
  xml.DS_Version 0.1
@@ -330,7 +375,7 @@ module ActiveMerchant #:nodoc:
330
375
  xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
331
376
  xml.DS_MERCHANT_TERMINAL @options[:terminal]
332
377
  xml.DS_MERCHANT_MERCHANTCODE @options[:login]
333
- xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data)
378
+ xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication?
334
379
 
335
380
  # Only when card is present
336
381
  if data[:card]
@@ -343,7 +388,6 @@ module ActiveMerchant #:nodoc:
343
388
  xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token]
344
389
  end
345
390
  end
346
- xml.target!
347
391
  end
348
392
 
349
393
  def parse(data)
@@ -375,18 +419,23 @@ module ActiveMerchant #:nodoc:
375
419
  end
376
420
 
377
421
  def validate_signature(data)
378
- str = data[:ds_amount] +
379
- data[:ds_order].to_s +
380
- data[:ds_merchantcode] +
381
- data[:ds_currency] +
382
- data[:ds_response] +
383
- data[:ds_cardnumber].to_s +
384
- data[:ds_transactiontype].to_s +
385
- data[:ds_securepayment].to_s +
386
- @options[:secret_key]
387
-
388
- sig = Digest::SHA1.hexdigest(str)
389
- data[:ds_signature].to_s.downcase == sig
422
+ if sha256_authentication?
423
+ sig = Base64.strict_encode64(mac256(get_key(data[:ds_order].to_s), xml_signed_fields(data)))
424
+ sig.upcase == data[:ds_signature].to_s.upcase
425
+ else
426
+ str = data[:ds_amount] +
427
+ data[:ds_order].to_s +
428
+ data[:ds_merchantcode] +
429
+ data[:ds_currency] +
430
+ data[:ds_response] +
431
+ data[:ds_cardnumber].to_s +
432
+ data[:ds_transactiontype].to_s +
433
+ data[:ds_securepayment].to_s +
434
+ @options[:secret_key]
435
+
436
+ sig = Digest::SHA1.hexdigest(str)
437
+ data[:ds_signature].to_s.downcase == sig
438
+ end
390
439
  end
391
440
 
392
441
  def build_authorization(params)
@@ -426,6 +475,43 @@ module ActiveMerchant #:nodoc:
426
475
  "%04d%s" % [rand(0..9999), cleansed[0...8]]
427
476
  end
428
477
  end
478
+
479
+ def sha256_authentication?
480
+ @options[:signature_algorithm] == "sha256"
481
+ end
482
+
483
+ def sign_request(xml_request_string, order_id)
484
+ key = encrypt(@options[:secret_key], order_id)
485
+ Base64.strict_encode64(mac256(key, xml_request_string))
486
+ end
487
+
488
+ def encrypt(key, order_id)
489
+ block_length = 8
490
+ cipher = OpenSSL::Cipher::Cipher.new('DES3')
491
+ cipher.encrypt
492
+
493
+ cipher.key = Base64.strict_decode64(key)
494
+ # The OpenSSL default of an all-zeroes ("\\0") IV is used.
495
+ cipher.padding = 0
496
+
497
+ order_id += "\0" until order_id.bytesize % block_length == 0 # Pad with zeros
498
+
499
+ output = cipher.update(order_id) + cipher.final
500
+ output
501
+ end
502
+
503
+ def mac256(key, data)
504
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data)
505
+ end
506
+
507
+ def xml_signed_fields(data)
508
+ data[:ds_amount] + data[:ds_order] + data[:ds_merchantcode] + data[:ds_currency] +
509
+ data[:ds_response] + data[:ds_transactiontype] + data[:ds_securepayment]
510
+ end
511
+
512
+ def get_key(order_id)
513
+ encrypt(@options[:secret_key], order_id)
514
+ end
429
515
  end
430
516
  end
431
517
  end
@@ -106,7 +106,9 @@ module ActiveMerchant #:nodoc:
106
106
  end
107
107
 
108
108
  def void(identification, options = {})
109
- commit(:post, "charges/#{CGI.escape(identification)}/refunds", {}, options)
109
+ post = {}
110
+ post[:expand] = [:charge]
111
+ commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options)
110
112
  end
111
113
 
112
114
  def refund(money, identification, options = {})
@@ -115,6 +117,7 @@ module ActiveMerchant #:nodoc:
115
117
  post[:refund_application_fee] = true if options[:refund_application_fee]
116
118
  post[:reverse_transfer] = options[:reverse_transfer] if options[:reverse_transfer]
117
119
  post[:metadata] = options[:metadata] if options[:metadata]
120
+ post[:expand] = [:charge]
118
121
 
119
122
  MultiResponse.run(:first) do |r|
120
123
  r.process { commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options) }
@@ -283,7 +286,8 @@ module ActiveMerchant #:nodoc:
283
286
  end
284
287
 
285
288
  def add_expand_parameters(post, options)
286
- post[:expand] = Array.wrap(options[:expand])
289
+ post[:expand] ||= []
290
+ post[:expand].concat(Array.wrap(options[:expand]).map(&:to_sym)).uniq!
287
291
  end
288
292
 
289
293
  def add_customer_data(post, options)
@@ -453,7 +457,7 @@ module ActiveMerchant #:nodoc:
453
457
  Response.new(success,
454
458
  success ? "Transaction approved" : response["error"]["message"],
455
459
  response,
456
- :test => response.has_key?("livemode") ? !response["livemode"] : false,
460
+ :test => response_is_test?(response),
457
461
  :authorization => authorization_from(success, url, method, response),
458
462
  :avs_result => { :code => avs_code },
459
463
  :cvv_result => cvc_code,
@@ -492,6 +496,16 @@ module ActiveMerchant #:nodoc:
492
496
  }
493
497
  end
494
498
 
499
+ def response_is_test?(response)
500
+ if response.has_key?('livemode')
501
+ !response['livemode']
502
+ elsif response['charge'].is_a?(Hash) && response['charge'].has_key?('livemode')
503
+ !response['charge']['livemode']
504
+ else
505
+ false
506
+ end
507
+ end
508
+
495
509
  def non_fractional_currency?(currency)
496
510
  CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
497
511
  end