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