forked from samlown/active_merchant
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Working on adding Sermepa support, not yet complete
- Loading branch information
Showing
7 changed files
with
640 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
169
lib/active_merchant/billing/integrations/sermepa/helper.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.