lib/active_merchant/billing/gateways/redsys.rb in activemerchant-1.44.1 vs lib/active_merchant/billing/gateways/redsys.rb in activemerchant-1.45.0
- old
+ new
@@ -7,57 +7,36 @@
#
# 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.
#
- # Standard ActiveMerchant methods are supported, with one notable exception:
- # :order_id must be provided and must conform to a very specific format.
+ # Redsys requires an order_id be provided with each transaction and it must
+ # follow a specific format. The rules are as follows:
#
- # == Example use:
- #
- # gateway = ActiveMerchant::Billing::RedsysGateway.new(
- # :login => "091358382",
- # :secret_key => "qwertyasdf0123456789"
- # )
- #
- # # Run a purchase for 10 euros
- # response = gateway.purchase(1000, creditcard, :order_id => "123456")
- # puts reponse.success? # => true
- #
- # # Partially refund the purchase
- # response = gateway.refund(500, response.authorization)
- #
- # Redsys requires an order_id be provided with each transaction of a
- # specific format. The rules are as follows:
- #
- # * Minimum length: 4
- # * Maximum length: 12
# * 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.
class RedsysGateway < Gateway
self.live_url = "https://sis.sermepa.es/sis/operaciones"
self.test_url = "https://sis-t.sermepa.es:25443/sis/operaciones"
- # Sensible region specific defaults.
self.supported_countries = ['ES']
self.default_currency = 'EUR'
self.money_format = :cents
# Not all card types may be activated by the bank!
self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club]
-
- # Homepage URL of the gateway for reference
self.homepage_url = "http://www.redsys.es/"
-
- # What to call this gateway
self.display_name = "Redsys"
CURRENCY_CODES = {
"ARS" => '032',
"AUD" => '036',
@@ -73,10 +52,11 @@
"JPY" => '392',
"MXN" => '484',
"NZD" => '554',
"PEN" => '604',
"RUB" => '643',
+ "SGD" => '702',
"USD" => '840',
"UYU" => '858'
}
# The set of supported transactions for this gateway.
@@ -92,18 +72,16 @@
# 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.
RESPONSE_TEXTS = {
- # Accepted Codes
0 => "Transaction Approved",
400 => "Cancellation Accepted",
481 => "Cancellation Accepted",
500 => "Reconciliation Accepted",
900 => "Refund / Confirmation approved",
- # Declined error codes
101 => "Card expired",
102 => "Card blocked temporarily or under susciption of fraud",
104 => "Transaction not permitted",
107 => "Contact the card issuer",
109 => "Invalid identification by merchant or POS terminal",
@@ -119,27 +97,24 @@
182 => "Card with credit or debit restrictions",
184 => "Authentication error",
190 => "Refusal with no specific reason",
191 => "Expiry date incorrect",
- # Declined, and suspected of fraud
201 => "Card expired",
202 => "Card blocked temporarily or under suscipition of fraud",
204 => "Transaction not permitted",
207 => "Contact the card issuer",
208 => "Lost or stolen card",
209 => "Lost or stolen card",
280 => "CVV2/CVC2 Error",
290 => "Declined with no specific reason",
- # More general codes for specific types of transaction
480 => "Original transaction not located, or time-out exceeded",
501 => "Original transaction not located, or time-out exceeded",
502 => "Original transaction not located, or time-out exceeded",
503 => "Original transaction not located, or time-out exceeded",
- # Declined transactions by the bank
904 => "Merchant not registered at FUC",
909 => "System error",
912 => "Issuer not available",
913 => "Duplicate transmission",
916 => "Amount too low",
@@ -190,10 +165,11 @@
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
def authorize(money, creditcard, options = {})
@@ -202,44 +178,55 @@
data = {}
add_action(data, :authorize)
add_amount(data, money, options)
add_order(data, options[:order_id])
add_creditcard(data, creditcard)
+ data[:description] = options[:description]
commit data
end
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
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
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
+ 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
+
private
def add_action(data, action)
data[:action] = transaction_code(action)
end
@@ -248,12 +235,11 @@
data[:amount] = amount(money).to_s
data[:currency] = currency_code(options[:currency] || currency(money))
end
def add_order(data, order_id)
- raise ArgumentError.new("Invalid order_id format") unless(/^\d{4}[\da-zA-Z]{0,8}$/ =~ order_id)
- data[:order_id] = order_id
+ data[:order_id] = clean_order_id(order_id)
end
def url
test? ? test_url : live_url
end
@@ -298,17 +284,18 @@
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_TERMINAL @options[:terminal]
- xml.DS_MERCHANT_MERCHANTCODE @options[:login]
- xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data)
+ 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]
@@ -371,10 +358,11 @@
[order_id, amount.to_i, currency]
end
def currency_code(currency)
return currency if currency =~ /^\d+$/
+ raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency]
CURRENCY_CODES[currency]
end
def transaction_code(type)
SUPPORTED_TRANSACTIONS[type]
@@ -386,9 +374,18 @@
RESPONSE_TEXTS[code] || "Unkown code, please check in manual"
end
def is_success_response?(code)
(code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
+ end
+
+ 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
end
end
end