module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class CecabankGateway < Gateway
self.test_url = 'https://tpv.ceca.es'
self.live_url = 'https://pgw.ceca.es'
self.supported_countries = ['ES']
self.supported_cardtypes = [:visa, :master, :american_express]
self.homepage_url = 'http://www.ceca.es/es/'
self.display_name = 'Cecabank'
self.default_currency = 'EUR'
self.money_format = :cents
#### CECA's MAGIC NUMBERS
CECA_NOTIFICATIONS_URL = 'NONE'
CECA_ENCRIPTION = 'SHA2'
CECA_DECIMALS = '2'
CECA_MODE = 'SSL'
CECA_UI_LESS_LANGUAGE = 'XML'
CECA_UI_LESS_LANGUAGE_REFUND = '1'
CECA_UI_LESS_REFUND_PAGE = 'anulacion_xml'
CECA_ACTION_REFUND = 'anulaciones/anularParcial' # use partial refund's URL to avoid time frame limitations and decision logic on client side
CECA_ACTION_PURCHASE = 'tpv/compra'
CECA_CURRENCIES_DICTIONARY = {'EUR' => 978, 'USD' => 840, 'GBP' => 826}
# Creates a new CecabankGateway
#
# The gateway requires four values for connection to be passed
# in the +options+ hash.
#
# ==== Options
#
# * :merchant_id -- Cecabank's merchant_id (REQUIRED)
# * :acquirer_bin -- Cecabank's acquirer_bin (REQUIRED)
# * :terminal_id -- Cecabank's terminal_id (REQUIRED)
# * :key -- Cecabank's cypher key (REQUIRED)
# * :test -- +true+ or +false+. If true, perform transactions against the test server.
# Otherwise, perform transactions against the production server.
def initialize(options = {})
requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :key)
super
end
# Perform a purchase, which is essentially an authorization and capture in a single operation.
#
# ==== Parameters
#
# * money -- The amount to be purchased as an Integer value in cents.
# * creditcard -- The CreditCard details for the transaction.
# * options -- A hash of optional parameters.
#
# ==== Options
#
# * :order_id -- order_id passed used purchase. (REQUIRED)
# * :currency -- currency. Supported: EUR, USD, GBP.
# * :description -- description to be pased to the gateway.
def purchase(money, creditcard, options = {})
requires!(options, :order_id)
post = {'Descripcion' => options[:description],
'Num_operacion' => options[:order_id],
'Idioma' => CECA_UI_LESS_LANGUAGE,
'Pago_soportado' => CECA_MODE,
'URL_OK' => CECA_NOTIFICATIONS_URL,
'URL_NOK' => CECA_NOTIFICATIONS_URL,
'Importe' => amount(money),
'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)]}
add_creditcard(post, creditcard)
commit(CECA_ACTION_PURCHASE, post)
end
# Refund a transaction.
#
# This transaction indicates to the gateway that
# money should flow from the merchant to the customer.
#
# ==== Parameters
#
# * money -- The amount to be credited to the customer as an Integer value in cents.
# * identification -- The reference given from the gateway on purchase (reference, not operation).
# * options -- A hash of parameters.
def refund(money, identification, options = {})
reference, order_id = split_authorization(identification)
post = {'Referencia' => reference,
'Num_operacion' => order_id,
'Idioma' => CECA_UI_LESS_LANGUAGE_REFUND,
'Pagina' => CECA_UI_LESS_REFUND_PAGE,
'Importe' => amount(money),
'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)]}
commit(CECA_ACTION_REFUND, post)
end
def supports_scrubbing
true
end
def scrub(transcript)
transcript.
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
gsub(%r((&?pan=)[^&]*)i, '\1[FILTERED]').
gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]')
end
private
def add_creditcard(post, creditcard)
post['PAN'] = creditcard.number
post['Caducidad'] = expdate(creditcard)
post['CVV2'] = creditcard.verification_value
post['Pago_elegido'] = CECA_MODE
end
def expdate(creditcard)
"#{format(creditcard.year, :four_digits)}#{format(creditcard.month, :two_digits)}"
end
def parse(body)
response = {}
root = REXML::Document.new(body).root
response[:success] = (root.attributes['valor'] == 'OK')
response[:date] = root.attributes['fecha']
response[:operation_number] = root.attributes['numeroOperacion']
response[:message] = root.attributes['valor']
if root.elements['OPERACION']
response[:operation_type] = root.elements['OPERACION'].attributes['tipo']
response[:amount] = root.elements['OPERACION/importe'].text.strip
end
response[:description] = root.elements['OPERACION/descripcion'].text if root.elements['OPERACION/descripcion']
response[:authorization_number] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
response[:reference] = root.elements['OPERACION/referencia'].text if root.elements['OPERACION/referencia']
response[:pan] = root.elements['OPERACION/pan'].text if root.elements['OPERACION/pan']
if root.elements['ERROR']
response[:error_code] = root.elements['ERROR/codigo'].text
response[:error_message] = root.elements['ERROR/descripcion'].text
else
if root.elements['OPERACION'].attributes['numeroOperacion'] == '000'
if(root.elements['OPERACION/numeroAutorizacion'])
response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text
end
else
response[:authorization] = root.attributes['numeroOperacion']
end
end
return response
rescue REXML::ParseException => e
response[:success] = false
response[:message] = 'Unable to parse the response.'
response[:error_message] = e.message
response
end
def commit(action, parameters)
parameters.merge!(
'Cifrado' => CECA_ENCRIPTION,
'Firma' => generate_signature(action, parameters),
'Exponente' => CECA_DECIMALS,
'MerchantID' => options[:merchant_id],
'AcquirerBIN' => options[:acquirer_bin],
'TerminalID' => options[:terminal_id]
)
url = (test? ? self.test_url : self.live_url) + "/tpvweb/#{action}.action"
xml = ssl_post("#{url}?", post_data(parameters))
response = parse(xml)
Response.new(
response[:success],
message_from(response),
response,
:test => test?,
:authorization => build_authorization(response),
:error_code => response[:error_code]
)
end
def message_from(response)
if response[:message] == 'ERROR' && response[:error_message]
response[:error_message]
elsif response[:error_message]
"#{response[:message]} #{response[:error_message]}"
else
response[:message]
end
end
def post_data(params)
return nil unless params
params.map do |key, value|
next if value.blank?
if value.is_a?(Hash)
h = {}
value.each do |k, v|
h["#{key}.#{k}"] = v unless v.blank?
end
post_data(h)
else
"#{key}=#{CGI.escape(value.to_s)}"
end
end.compact.join('&')
end
def build_authorization(response)
[response[:reference], response[:authorization]].join('|')
end
def split_authorization(authorization)
authorization.split('|')
end
def generate_signature(action, parameters)
signature_fields = case action
when CECA_ACTION_REFUND
options[:key].to_s +
options[:merchant_id].to_s +
options[:acquirer_bin].to_s +
options[:terminal_id].to_s +
parameters['Num_operacion'].to_s +
parameters['Importe'].to_s +
parameters['TipoMoneda'].to_s +
CECA_DECIMALS +
parameters['Referencia'].to_s +
CECA_ENCRIPTION
else
options[:key].to_s +
options[:merchant_id].to_s +
options[:acquirer_bin].to_s +
options[:terminal_id].to_s +
parameters['Num_operacion'].to_s +
parameters['Importe'].to_s +
parameters['TipoMoneda'].to_s +
CECA_DECIMALS +
CECA_ENCRIPTION +
CECA_NOTIFICATIONS_URL +
CECA_NOTIFICATIONS_URL
end
Digest::SHA2.hexdigest(signature_fields)
end
end
end
end