class ActiveMerchant::Billing::RedsysGateway
Redsys Merchant Gateway
¶ ↑
Gateway
support for the Spanish “Redsys” payment gateway system. This is used by many banks in Spain and is particularly well supported by Catalunya Caixa’s ecommerce department.
Redsys requires an order_id be provided with each transaction and it must follow a specific format. The rules are as follows:
* First 4 digits must be numerical * Remaining 8 digits may be alphanumeric * Max length: 12 If an invalid order_id is provided, we do our best to clean it up.
Much of the code for this library is based on the active_merchant_sermepa 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.
Constants
- CURRENCY_CODES
- RESPONSE_TEXTS
These are the text meanings sent back by the acquirer when a card has been rejected. Syntax or general request errors are not covered here.
- SUPPORTED_TRANSACTIONS
The set of supported transactions for this gateway. More operations are supported by the gateway itself, but are not supported in this library.
Public Class Methods
Creates a new instance
Redsys requires a login and secret_key, and optionally also accepts a non-default terminal.
Options¶ ↑
-
:login
– The Redsys Merchant ID (REQUIRED) -
:secret_key
– The Redsys Secret Key. (REQUIRED) -
:terminal
– The Redsys Terminal. Defaults to 1. (OPTIONAL) -
:test
–true
orfalse
. Defaults tofalse
. (OPTIONAL)
ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/redsys.rb, line 156 def initialize(options = {}) requires!(options, :login, :secret_key) options[:terminal] ||= 1 super end
Public Instance Methods
# File lib/active_merchant/billing/gateways/redsys.rb, line 188 def capture(money, authorization, options = {}) data = {} add_action(data, :capture) add_amount(data, money, options) order_id, _, _ = split_authorization(authorization) add_order(data, order_id) data[:description] = options[:description] commit data end
# File lib/active_merchant/billing/gateways/redsys.rb, line 162 def purchase(money, creditcard, options = {}) requires!(options, :order_id) data = {} add_action(data, :purchase) add_amount(data, money, options) add_order(data, options[:order_id]) add_creditcard(data, creditcard) data[:description] = options[:description] commit data end
# File lib/active_merchant/billing/gateways/redsys.rb, line 210 def refund(money, authorization, options = {}) data = {} add_action(data, :refund) add_amount(data, money, options) order_id, _, _ = split_authorization(authorization) add_order(data, order_id) data[:description] = options[:description] commit data end
# File lib/active_merchant/billing/gateways/redsys.rb, line 221 def verify(creditcard, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, creditcard, options) } r.process(:ignore_result) { void(r.authorization, options) } end end
# File lib/active_merchant/billing/gateways/redsys.rb, line 199 def void(authorization, options = {}) data = {} add_action(data, :cancel) order_id, amount, currency = split_authorization(authorization) add_amount(data, amount, :currency => currency) add_order(data, order_id) data[:description] = options[:description] commit data end
Private Instance Methods
# File lib/active_merchant/billing/gateways/redsys.rb, line 230 def add_action(data, action) data[:action] = transaction_code(action) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 234 def add_amount(data, money, options) data[:amount] = amount(money).to_s data[:currency] = currency_code(options[:currency] || currency(money)) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 247 def add_creditcard(data, card) name = [card.first_name, card.last_name].join(' ').slice(0, 60) year = sprintf("%.4i", card.year) month = sprintf("%.2i", card.month) data[:card] = { :name => name, :pan => card.number, :date => "#{year[2..3]}#{month}", :cvv => card.verification_value } end
# File lib/active_merchant/billing/gateways/redsys.rb, line 239 def add_order(data, order_id) data[:order_id] = clean_order_id(order_id) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 267 def build_signature(data) str = data[:amount] + 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 str << data[:action] str << @options[:secret_key] Digest::SHA1.hexdigest(str) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 284 def build_xml_request(data) xml = Builder::XmlMarkup.new :indent => 2 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) # Only when card is present if data[:card] xml.DS_MERCHANT_TITULAR data[:card][:name] xml.DS_MERCHANT_PAN data[:card][:pan] xml.DS_MERCHANT_EXPIRYDATE data[:card][:date] xml.DS_MERCHANT_CVV2 data[:card][:cvv] end end xml.target! end
# File lib/active_merchant/billing/gateways/redsys.rb, line 381 def clean_order_id(order_id) cleansed = order_id.gsub(/[^\da-zA-Z]/, '') if cleansed =~ /^\d{4}/ cleansed[0..12] else "%04d%s" % [rand(0..9999), cleansed[0...8]] end end
# File lib/active_merchant/billing/gateways/redsys.rb, line 259 def commit(data) headers = { 'Content-Type' => 'application/x-www-form-urlencoded' } xml = build_xml_request(data) parse(ssl_post(url, "entrada=#{CGI.escape(xml)}", headers)) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 361 def currency_code(currency) return currency if currency =~ /^\d+$/ raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency] CURRENCY_CODES[currency] end
# File lib/active_merchant/billing/gateways/redsys.rb, line 377 def is_success_response?(code) (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 309 def parse(data) params = {} success = false message = "" options = @options.merge(:test => test?) xml = Nokogiri::XML(data) code = xml.xpath("//RETORNOXML/CODIGO").text if code == "0" op = xml.xpath("//RETORNOXML/OPERACION") op.children.each do |element| params[element.name.downcase.to_sym] = element.text end if validate_signature(params) message = response_text(params[:ds_response]) options[:authorization] = build_authorization(params) success = is_success_response?(params[:ds_response]) else message = "Response failed validation check" end else # Some kind of programmer error with the request! message = "#{code} ERROR" end Response.new(success, message, params, options) end
# File lib/active_merchant/billing/gateways/redsys.rb, line 371 def response_text(code) code = code.to_i code = 0 if code < 100 RESPONSE_TEXTS[code] || "Unkown code, please check in manual" end
# File lib/active_merchant/billing/gateways/redsys.rb, line 367 def transaction_code(type) SUPPORTED_TRANSACTIONS[type] end
# File lib/active_merchant/billing/gateways/redsys.rb, line 243 def url test? ? test_url : live_url end
# File lib/active_merchant/billing/gateways/redsys.rb, line 337 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] sig = Digest::SHA1.hexdigest(str) data[:ds_signature].to_s.downcase == sig end