# encoding: utf-8 require 'cgi' require 'openssl' require 'base64' #require 'rexml/document' module Ideal # === Response classes # # * Response # * TransactionResponse # * StatusResponse # * DirectoryResponse # # See the Response class for more information on errors. class Response attr_accessor :response def initialize(response_body, options = {}) #@response = REXML::Document.new(response_body).root @body = response_body doc = Nokogiri::XML(response_body) doc.remove_namespaces! @response = doc.root @success = !error_occured? @test = options[:test] ? options[:test] : false end # Returns whether we're running in test mode def test? @test end # Returns whether the request was a success def success? @success end # Returns a technical error message. def error_message text('//Error/errorMessage') unless success? end # Returns a consumer friendly error message. def consumer_error_message text('//Error/consumerMessage') unless success? end # Returns details on the error if available. def error_details text('//Error/errorDetail') unless success? end # Returns an error type inflected from the first two characters of the # error code. See error_code for a full list of errors. # # Error code to type mappings: # # * +IX+ - :xml # * +SO+ - :system # * +SE+ - :security # * +BR+ - :value # * +AP+ - :application def error_type unless success? case error_code[0, 2] when 'IX' then :xml when 'SO' then :system when 'SE' then :security when 'BR' then :value when 'AP' then :application end end end # Returns the code of the error that occured. # # === Codes # # ==== IX: Invalid XML and all related problems # # Such as incorrect encoding, invalid version, or otherwise unreadable: # # * IX1000 - Received XML not well-formed. # * IX1100 - Received XML not valid. # * IX1200 - Encoding type not UTF-8. # * IX1300 - XML version number invalid. # * IX1400 - Unknown message. # * IX1500 - Mandatory main value missing. (Merchant ID ?) # * IX1600 - Mandatory value missing. # # ==== SO: System maintenance or failure # # The errors that are communicated in the event of system maintenance or # system failure. Also covers the situation where new requests are no # longer being accepted but requests already submitted will be dealt with # (until a certain time): # # * SO1000 - Failure in system. # * SO1200 - System busy. Try again later. # * SO1400 - Unavailable due to maintenance. # # ==== SE: Security and authentication errors # # Incorrect authentication methods and expired certificates: # # * SE2000 - Authentication error. # * SE2100 - Authentication method not supported. # * SE2700 - Invalid electronic signature. # # ==== BR: Field errors # # Extra information on incorrect fields: # # * BR1200 - iDEAL version number invalid. # * BR1210 - Value contains non-permitted character. # * BR1220 - Value too long. # * BR1230 - Value too short. # * BR1240 - Value too high. # * BR1250 - Value too low. # * BR1250 - Unknown entry in list. # * BR1270 - Invalid date/time. # * BR1280 - Invalid URL. # # ==== AP: Application errors # # Errors relating to IDs, account numbers, time zones, transactions: # # * AP1000 - Acquirer ID unknown. # * AP1100 - Merchant ID unknown. # * AP1200 - Issuer ID unknown. # * AP1300 - Sub ID unknown. # * AP1500 - Merchant ID not active. # * AP2600 - Transaction does not exist. # * AP2620 - Transaction already submitted. # * AP2700 - Bank account number not 11-proof. # * AP2900 - Selected currency not supported. # * AP2910 - Maximum amount exceeded. (Detailed record states the maximum amount). # * AP2915 - Amount too low. (Detailed record states the minimum amount). # * AP2920 - Please adjust expiration period. See suggested expiration period. def error_code text('//errorCode') unless success? end private def error_occured? @response.name == 'AcquirerErrorRes' end def text(path) @response.xpath(path)[0].text() unless @response.xpath(path)[0].nil? end end # An instance of TransactionResponse is returned from # Gateway#setup_purchase which returns the service_url to where the # user should be redirected to perform the transaction _and_ the # transaction ID. class TransactionResponse < Response # Returns the URL to the issuer’s page where the consumer should be # redirected to in order to perform the payment. def service_url CGI::unescapeHTML(text('//issuerAuthenticationURL')).strip end def verified? signed_document = Xmldsig::SignedDocument.new(@body) @verified ||= signed_document.validate(Ideal::Gateway.ideal_certificate) end # Returns the transaction ID which is needed for requesting the status # of a transaction. See Gateway#capture. def transaction_id text('//transactionID') end # Returns the :order_id for this transaction. def order_id text('//purchaseID') end def signature Base64.decode64(text('//SignatureValue')) end end # An instance of StatusResponse is returned from Gateway#capture # which returns whether or not the transaction that was started with # Gateway#setup_purchase was successful. # # It takes care of checking if the message was authentic by verifying the # the message and its signature against the iDEAL certificate. # # If success? returns +false+ because the authenticity wasn't verified # there will be no error_code, error_message, and error_type. Use verified? # to check if the authenticity has been verified. class StatusResponse < Response def initialize(response_body, options = {}) super @success = transaction_successful? end # Returns the status message, which is one of: :success, # :cancelled, :expired, :open, or # :failure. def status status = text('//status') status.downcase.to_sym unless (status.nil? || status.strip == '') end # Returns whether or not the authenticity of the message could be # verified. def verified? signed_document = Xmldsig::SignedDocument.new(@body) @verified ||= signed_document.validate(Ideal::Gateway.ideal_certificate) end # Returns the bankaccount number when the transaction was successful. def consumer_iban text('//consumerIBAN') end # Returns the name on the bankaccount of the customer when the # transaction was successful. def consumer_name text('//consumerName') end # Returns the BIC of the bankaccount of the customer when the # transaction was succesfull def consumer_bic text('//consumerBIC') end def signature Base64.decode64(text('//SignatureValue')) end private # Checks if no errors occured _and_ if the message was authentic. def transaction_successful? !error_occured? && status == :success && verified? end end # An instance of DirectoryResponse is returned from # Gateway#issuers which returns the list of issuers available at the # acquirer. class DirectoryResponse < Response # Returns a list of issuers available at the acquirer. # # gateway.issuers.list # => [{ :id => '1006', :name => 'ABN AMRO Bank' }] def list list = Array.new @response.xpath(".//Country").each { |country| country_name = country.xpath(".//countryNames").first.text() country.xpath(".//Issuer").each { |issuer| list << {:id => issuer.xpath(".//issuerID").first.text(), :country => country_name, :name => issuer.xpath(".//issuerName").first.text()} } } list end end end