# 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