lib/active_merchant/billing/gateways/redsys.rb in activemerchant-1.55.0 vs lib/active_merchant/billing/gateways/redsys.rb in activemerchant-1.56.0
- old
+ new
@@ -22,10 +22,20 @@
# integration gateway which uses essentially the same API but with the
# banks own payment screen.
#
# Written by Samuel Lown for Cabify. For implementation questions, or
# test access details please get in touch: sam@cabify.com.
+ #
+ # *** SHA256 Authentication Update ***
+ #
+ # Redsys is dropping support for the SHA1 authentication method. This
+ # adapter has been updated to work with the new SHA256 authentication
+ # method, however in your initialization options hash you will need to
+ # specify the key/value :signature_algorithm => "sha256" to use the
+ # SHA256 method. Otherwise it will default to using the SHA1.
+ #
+ #
class RedsysGateway < Gateway
self.live_url = "https://sis.sermepa.es/sis/operaciones"
self.test_url = "https://sis-t.redsys.es:25443/sis/operaciones"
self.supported_countries = ['ES']
@@ -159,13 +169,15 @@
#
# * <tt>:login</tt> -- The Redsys Merchant ID (REQUIRED)
# * <tt>:secret_key</tt> -- The Redsys Secret Key. (REQUIRED)
# * <tt>:terminal</tt> -- The Redsys Terminal. Defaults to 1. (OPTIONAL)
# * <tt>:test</tt> -- +true+ or +false+. Defaults to +false+. (OPTIONAL)
+ # * <tt>:signature_algorithm</tt> -- +"sha256"+ Defaults to +"sha1"+. (OPTIONAL)
def initialize(options = {})
requires!(options, :login, :secret_key)
options[:terminal] ||= 1
+ options[:signature_algorithm] ||= "sha1"
super
end
def purchase(money, payment, options = {})
requires!(options, :order_id)
@@ -245,10 +257,11 @@
gsub(%r((%3CDS_MERCHANT_PAN%3E)\d+(%3C%2FDS_MERCHANT_PAN%3E))i, '\1[FILTERED]\2').
gsub(%r((%3CDS_MERCHANT_CVV2%3E)\d+(%3C%2FDS_MERCHANT_CVV2%3E))i, '\1[FILTERED]\2').
gsub(%r((<DS_MERCHANT_PAN>)\d+(</DS_MERCHANT_PAN>))i, '\1[FILTERED]\2').
gsub(%r((<DS_MERCHANT_CVV2>)\d+(</DS_MERCHANT_CVV2>))i, '\1[FILTERED]\2').
gsub(%r((DS_MERCHANT_CVV2)%2F%3E%0A%3C%2F)i, '\1[BLANK]').
+ gsub(%r((DS_MERCHANT_CVV2)%2F%3E%3C)i, '\1[BLANK]').
gsub(%r((DS_MERCHANT_CVV2%3E)(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2').
gsub(%r((<DS_MERCHANT_CVV2>)(</DS_MERCHANT_CVV2>))i, '\1[BLANK]\2').
gsub(%r((DS_MERCHANT_CVV2%3E)\++(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2').
gsub(%r((<DS_MERCHANT_CVV2>)\s+(</DS_MERCHANT_CVV2>))i, '\1[BLANK]\2')
end
@@ -287,22 +300,32 @@
}
end
end
def commit(data)
- headers = {
+ parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data))}", headers))
+ end
+
+ def headers
+ {
'Content-Type' => 'application/x-www-form-urlencoded'
}
- xml = build_xml_request(data)
- parse(ssl_post(url, "entrada=#{CGI.escape(xml)}", headers))
end
+ def xml_request_from(data)
+ if sha256_authentication?
+ build_sha256_xml_request(data)
+ else
+ build_sha1_xml_request(data)
+ end
+ end
+
def build_signature(data)
str = data[:amount] +
- data[:order_id].to_s +
- @options[:login].to_s +
- data[:currency]
+ data[:order_id].to_s +
+ @options[:login].to_s +
+ data[:currency]
if card = data[:card]
str << card[:pan]
str << card[:cvv] if card[:cvv]
end
@@ -316,23 +339,45 @@
str << @options[:secret_key]
Digest::SHA1.hexdigest(str)
end
- def build_xml_request(data)
+ def build_sha256_xml_request(data)
+ xml = Builder::XmlMarkup.new
+ xml.instruct!
+ xml.REQUEST do
+ build_merchant_data(xml, data)
+ xml.DS_SIGNATUREVERSION 'HMAC_SHA256_V1'
+ xml.DS_SIGNATURE sign_request(merchant_data_xml(data), data[:order_id])
+ end
+ xml.target!
+ end
+
+ def build_sha1_xml_request(data)
xml = Builder::XmlMarkup.new :indent => 2
+ build_merchant_data(xml, data)
+ xml.target!
+ end
+
+ def merchant_data_xml(data)
+ xml = Builder::XmlMarkup.new
+ build_merchant_data(xml, data)
+ xml.target!
+ end
+
+ def build_merchant_data(xml, data)
xml.DATOSENTRADA do
# Basic elements
xml.DS_Version 0.1
xml.DS_MERCHANT_CURRENCY data[:currency]
xml.DS_MERCHANT_AMOUNT data[:amount]
xml.DS_MERCHANT_ORDER data[:order_id]
xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
xml.DS_MERCHANT_TERMINAL @options[:terminal]
xml.DS_MERCHANT_MERCHANTCODE @options[:login]
- xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data)
+ xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication?
# Only when card is present
if data[:card]
xml.DS_MERCHANT_TITULAR data[:card][:name]
xml.DS_MERCHANT_PAN data[:card][:pan]
@@ -341,11 +386,10 @@
xml.DS_MERCHANT_IDENTIFIER 'REQUIRED' if data[:store_in_vault]
elsif data[:credit_card_token]
xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token]
end
end
- xml.target!
end
def parse(data)
params = {}
success = false
@@ -373,22 +417,27 @@
Response.new(success, message, params, options)
end
def validate_signature(data)
- str = data[:ds_amount] +
- data[:ds_order].to_s +
- data[:ds_merchantcode] +
- data[:ds_currency] +
- data[:ds_response] +
- data[:ds_cardnumber].to_s +
- data[:ds_transactiontype].to_s +
- data[:ds_securepayment].to_s +
- @options[:secret_key]
+ if sha256_authentication?
+ sig = Base64.strict_encode64(mac256(get_key(data[:ds_order].to_s), xml_signed_fields(data)))
+ sig.upcase == data[:ds_signature].to_s.upcase
+ else
+ str = data[:ds_amount] +
+ data[:ds_order].to_s +
+ data[:ds_merchantcode] +
+ data[:ds_currency] +
+ data[:ds_response] +
+ data[:ds_cardnumber].to_s +
+ data[:ds_transactiontype].to_s +
+ data[:ds_securepayment].to_s +
+ @options[:secret_key]
- sig = Digest::SHA1.hexdigest(str)
- data[:ds_signature].to_s.downcase == sig
+ sig = Digest::SHA1.hexdigest(str)
+ data[:ds_signature].to_s.downcase == sig
+ end
end
def build_authorization(params)
[params[:ds_order], params[:ds_amount], params[:ds_currency]].join("|")
end
@@ -423,9 +472,46 @@
if cleansed =~ /^\d{4}/
cleansed[0..11]
else
"%04d%s" % [rand(0..9999), cleansed[0...8]]
end
+ end
+
+ def sha256_authentication?
+ @options[:signature_algorithm] == "sha256"
+ end
+
+ def sign_request(xml_request_string, order_id)
+ key = encrypt(@options[:secret_key], order_id)
+ Base64.strict_encode64(mac256(key, xml_request_string))
+ end
+
+ def encrypt(key, order_id)
+ block_length = 8
+ cipher = OpenSSL::Cipher::Cipher.new('DES3')
+ cipher.encrypt
+
+ cipher.key = Base64.strict_decode64(key)
+ # The OpenSSL default of an all-zeroes ("\\0") IV is used.
+ cipher.padding = 0
+
+ order_id += "\0" until order_id.bytesize % block_length == 0 # Pad with zeros
+
+ output = cipher.update(order_id) + cipher.final
+ output
+ end
+
+ def mac256(key, data)
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data)
+ end
+
+ def xml_signed_fields(data)
+ data[:ds_amount] + data[:ds_order] + data[:ds_merchantcode] + data[:ds_currency] +
+ data[:ds_response] + data[:ds_transactiontype] + data[:ds_securepayment]
+ end
+
+ def get_key(order_id)
+ encrypt(@options[:secret_key], order_id)
end
end
end
end