Skip to content

Commit

Permalink
Working on adding Sermepa support, not yet complete
Browse files Browse the repository at this point in the history
  • Loading branch information
samlown committed Aug 11, 2010
1 parent 6556e4f commit efb0890
Show file tree
Hide file tree
Showing 7 changed files with 640 additions and 0 deletions.
157 changes: 157 additions & 0 deletions lib/active_merchant/billing/integrations/sermepa.rb
@@ -0,0 +1,157 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
# See the BbvaTpv::Helper class for more generic information on usage of
# this integrated payment method.
module Sermepa

autoload :Helper, 'active_merchant/billing/integrations/sermepa/helper.rb'
autoload :Return, 'active_merchant/billing/integrations/sermepa/return.rb'
autoload :Notification, 'active_merchant/billing/integrations/sermepa/notification.rb'

mattr_accessor :service_test_url
self.service_test_url = "https://sis-t.sermepa.es:25443/sis/realizarPago"
mattr_accessor :service_production_url
self.service_production_url = "https://sis.sermepa.es/sis/realizarPago"

mattr_accessor :operations_test_url
self.operations_test_url = "https://sis-t.sermepa.es:25443/sis/operaciones"
mattr_accessor :operations_production_url
self.operations_production_url = "https://sis.sermepa.es/sis/operaciones"


def self.service_url
mode = ActiveMerchant::Billing::Base.integration_mode
case mode
when :production
self.service_production_url
when :test
self.service_test_url
else
raise StandardError, "Integration mode set to an invalid value: #{mode}"
end
end

def self.operations_url
mode = ActiveMerchant::Billing::Base.integration_mode
case mode
when :production
self.operations_production_url
when :test
self.operations_test_url
else
raise StandardError, "Integration mode set to an invalid value: #{mode}"
end

end

def self.notification(post)
Notification.new(post)
end


def self.currency_code( name )
row = supported_currencies.assoc(name)
row.nil? ? supported_currencies.first[1] : row[1]
end

def self.currency_from_code( code )
row = supported_currencies.rassoc(code)
row.nil? ? supported_currencies.first[0] : row[0]
end

def self.language_code(name)
row = supported_languages.assoc(name.to_s.downcase.to_sym)
row.nil? ? supported_languages.first[1] : row[1]
end

def self.language_from_code( code )
row = supported_languages.rassoc(code)
row.nil? ? supported_languages.first[0] : row[0]
end

def self.transaction_code(name)
row = supported_transactions.assoc(name.to_sym)
row.nil? ? supported_transactions.first[1] : row[1]
end
def self.transaction_from_code(code)
row = supported_transactions.rassoc(code.to_s)
row.nil? ? supported_languages.first[0] : row[0]
end

def self.supported_currencies
[ ['EUR', '978'] ]
end

def self.supported_languages
[
[:es, '001'],
[:en, '002'],
[:ca, '003'],
[:fr, '004'],
[:de, '005'],
[:pt, '009']
]
end

def self.supported_transactions
[
[:authorization, '0'],
[:preauthorization, '1'],
[:confirmation, '2'],
[:automatic_return, '3'],
[:reference_payment, '4'],
[:recurring_transaction, '5'],
[:successive_transaction, '6'],
[:authentication, '7'],
[:confirm_authentication, '8'],
[:cancel_preauthorization, '9'],
[:deferred_authorization, 'O'],
[:confirm_deferred_authorization, 'P'],
[:cancel_deferred_authorization, 'Q'],
[:inicial_recurring_authorization, 'R'],
[:successive_recurring_authorization, 'S']
]
end

def self.response_code_message(code)
case code.to_i
when 0..99
nil
when 900
"Transacción autorizada para devoluciones y confirmaciones"
when 101
"Tarjeta caducada"
when 102
"Tarjeta en excepción transitoria o bajo sospecha de fraude"
when 104
"Operación no permitida para esa tarjeta o terminal"
when 116
"Disponible insuficiente"
when 118
"Tarjeta no registrada o Método de pago no disponible para su tarjeta"
when 129
"Código de seguridad (CVV2/CVC2) incorrecto"
when 180
"Tarjeta no válida o Tarjeta ajena al servicio o Error en la llamada al MPI sin controlar."
when 184
"Error en la autenticación del titular"
when 190
"Denegación sin especificar Motivo"
when 191
"Fecha de caducidad errónea"
when 202
"Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de tarjeta"
when 912,9912
"Emisor no disponible"
when 913
"Pedido repetido"
else
"Transacción denegada"
end
end

end
end
end
end
169 changes: 169 additions & 0 deletions lib/active_merchant/billing/integrations/sermepa/helper.rb
@@ -0,0 +1,169 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module Sermepa
# Sermepa/Servired Spanish Virtual POS Gateway
#
# Support for the Spanish point of sale system provided by Sermepa, part of Servired,
# one of the main providers in Spain to Banks and Cajas.
#
# Requires the :terminal_id, :commercial_id, and :secret_key to be set in the credentials
# before the helper can be used.
#
class Helper < ActiveMerchant::Billing::Integrations::Helper
include PostsData

class << self
# Credentials should be set as a hash containing the fields:
# :terminal_id, :commercial_id, :secret_key
attr_accessor :credentials
end

mapping :account, 'Ds_Merchant_MerchantCode'

mapping :currency, 'Ds_Merchant_Currency'
mapping :amount, 'Ds_Merchant_Amount'

mapping :order, 'Ds_Merchant_Order'
mapping :description, 'Ds_Merchant_Product_Description'
mapping :client, 'Ds_Merchant_Titular'

mapping :notify_url, 'Ds_Merchant_MerchantURL'
mapping :success_url, 'Ds_Merchant_UrlOK'
mapping :failure_url, 'Ds_Merchant_UrlKO'

mapping :language, 'Ds_Merchant_ConsumerLanguage'

mapping :transaction, 'Ds_Merchant_TransactionType'

#### Special Request Specific Fields ####
mapping :signature, 'Ds_Merchant_MerchantSignature'
mapping :terminal, 'Ds_Merchant_Terminal'
########

# ammount should always be provided in cents!
def initialize(order, account, options = {})
self.credentials = options.delete(:credentials) if options[:credentials]

# Replace account with commercial_id
super(order, credentials[:commercial_id], options)

add_field mappings[:transaction], '0' # Default Transaction Type
add_field mappings[:terminal], credentials[:terminal_id]
end

# Allow credentials to be overwritten if needed
def credentials
@credentials || self.class.credentials
end
def credentials=(creds)
@credentials = (self.class.credentials || {}).dup.merge(creds)
end

def amount=(money)
cents = money.respond_to?(:cents) ? money.cents : money
if money.is_a?(String) || cents.to_i <= 0
raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
end
add_field mappings[:amount], sprintf("%.2f", cents.to_f/100)
end

def order=(order_id)
order_id = order_id.to_s
if order_id !~ /^[0-9]{4}/ && order_id.length <= 8
order_id = ('0' * 4) + order_id
end
regexp = /^[0-9]{4}[0-9a-zA-Z]{0,8}$/
raise "Invalid order number format! First 4 digits must be numbers" if order_id !~ regexp
add_field mappings[:order], order_id
end

def currency=( value )
add_field mappings[:currency], Sermepa.currency_code(value)
end

def language=(lang)
add_field mappings[:language], Sermepa.language_code(lang)
end

def transaction=(type)
add_field mappings[:transaction], (Sermepa.supported_transactions.assoc(type) || [])[1]
end

def form_fields
add_field mappings[:signature], sign_request
@fields
end


# Send a manual request for the notification object.
# This is used to confirm a purchase if one was not sent by the gateway.
def request_notification
body = build_xml_confirmation_request

headers = { }
headers['Content-Length'] = body.size.to_s
headers['User-Agent'] = "Active Merchant -- http://activemerchant.org"
headers['Content-Type'] = 'application/x-www-form-urlencoded'

response = ssl_post(Sermepa.operations_url, body, headers)
Notification.new response
end

protected

def build_xml_confirmation_request
self.transaction = :confirmation
xml = Builder::XmlMarkup.new :indent => 2
xml.datosentrada do
xml.ds_version 0.1
xml.ds_merchant_currency @fields['Ds_Merchant_Currency']
xml.ds_merchant_merchanturl @fields['Ds_Merchant_MerchantURL']
xml.ds_merchant_transactiontype @fields['Ds_Merchant_TransactionType']
xml.ds_merchant_merchantdata @fields['Ds_Merchant_Product_Description']
xml.ds_merchant_terminal credentials[:terminal_id]
xml.ds_merchant_merchantcode credentials[:commercial_id]
xml.ds_merchant_order @fields['Ds_Merchant_Order']
xml.ds_merchant_merchantsignature sign_request
end
xml.target!
end


# Generate a signature authenticating the current request.
# Values included in the signature are determined by the the type of
# transaction.
def sign_request(strength = :normal)
str = (@fields['Ds_Merchant_Amount'].to_f * 100).to_i.to_s +
@fields['Ds_Merchant_Order'].to_s +
@fields['Ds_Merchant_MerchantCode'].to_s +
@fields['Ds_Merchant_Currency'].to_s

case Sermepa.transaction_from_code(@fields['Ds_Merchant_TransactionType'])
when :recurring_transaction
str += @fields['Ds_Merchant_SumTotal']

# Add transaction type for the following requests performed only using XML
when :confirmation, :automatic_return, :successive_transaction,
:confirm_authentication, :cancel_preauthorization, :preauthorization,
:deferred_authorization, :confirm_deferred_authorization, :cancel_deferred_authorization,
:initial_recurring_authorization, :successive_recurring_authorization
str += @fields['Ds_Merchant_TransactionType']
strength = :normal # Force the strength!
end

if strength == :extended
str += @fields['Ds_Merchant_TransactionType'].to_s +
@fields['Ds_Merchant_MerchantURL'].to_s
end

str += credentials[:secret_key]

Digest::SHA1.hexdigest( str )
end

end
end
end
end
end

0 comments on commit efb0890

Please sign in to comment.