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

new(options = {}) click to toggle source

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)

  • :testtrue or false. Defaults to false. (OPTIONAL)

Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/redsys.rb, line 157
def initialize(options = {})
  requires!(options, :login, :secret_key)
  options[:terminal] ||= 1
  super
end

Public Instance Methods

authorize(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 177
def authorize(money, payment, options = {})
  requires!(options, :order_id)

  data = {}
  add_action(data, :authorize)
  add_amount(data, money, options)
  add_order(data, options[:order_id])
  add_payment(data, payment)
  data[:description] = options[:description]
  data[:store_in_vault] = options[:store]

  commit data
end
capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 191
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
purchase(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 163
def purchase(money, payment, options = {})
  requires!(options, :order_id)

  data = {}
  add_action(data, :purchase)
  add_amount(data, money, options)
  add_order(data, options[:order_id])
  add_payment(data, payment)
  data[:description] = options[:description]
  data[:store_in_vault] = options[:store]

  commit data
end
refund(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 213
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
verify(creditcard, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 224
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
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 202
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

add_action(data, action) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 233
def add_action(data, action)
  data[:action] = transaction_code(action)
end
add_amount(data, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 237
def add_amount(data, money, options)
  data[:amount] = amount(money).to_s
  data[:currency] = currency_code(options[:currency] || currency(money))
end
add_order(data, order_id) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 242
def add_order(data, order_id)
  data[:order_id] = clean_order_id(order_id)
end
add_payment(data, card) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 250
def add_payment(data, card)
  if card.is_a?(String)
    data[:credit_card_token] = card
  else
    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
end
build_authorization(params) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 367
def build_authorization(params)
  [params[:ds_order], params[:ds_amount], params[:ds_currency]].join("|")
end
build_signature(data) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 274
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]
  if data[:store_in_vault]
    str << 'REQUIRED'
  elsif data[:credit_card_token]
    str << data[:credit_card_token]
  end
  str << @options[:secret_key]

  Digest::SHA1.hexdigest(str)
end
build_xml_request(data) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 296
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]
      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
clean_order_id(order_id) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 396
def clean_order_id(order_id)
  cleansed = order_id.gsub(/[^\da-zA-Z]/, '')
  if cleansed =~ /^\d{4}/
    cleansed[0..11]
  else
    "%04d%s" % [rand(0..9999), cleansed[0...8]]
  end
end
commit(data) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 266
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
currency_code(currency) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 376
def currency_code(currency)
  return currency if currency =~ /^\d+$/
  raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency]
  CURRENCY_CODES[currency]
end
is_success_response?(code) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 392
def is_success_response?(code)
  (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
end
parse(data) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 324
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
response_text(code) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 386
def response_text(code)
  code = code.to_i
  code = 0 if code < 100
  RESPONSE_TEXTS[code] || "Unkown code, please check in manual"
end
split_authorization(authorization) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 371
def split_authorization(authorization)
  order_id, amount, currency = authorization.split("|")
  [order_id, amount.to_i, currency]
end
transaction_code(type) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 382
def transaction_code(type)
  SUPPORTED_TRANSACTIONS[type]
end
url() click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 246
def url
  test? ? test_url : live_url
end
validate_signature(data) click to toggle source
# File lib/active_merchant/billing/gateways/redsys.rb, line 352
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