require File.dirname(__FILE__) + '/ideal_response'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# Implementation contains some simplifications
# - does not support multiple subID per merchant
# - language is fixed to 'nl'
class IdealBaseGateway < Gateway
class_attribute :server_pem, :pem_password, :default_expiration_period
self.default_expiration_period = 'PT10M'
self.default_currency = 'EUR'
self.pem_password = true
self.abstract_class = true
# These constants will never change for most users
AUTHENTICATION_TYPE = 'SHA1_RSA'
LANGUAGE = 'nl'
SUB_ID = '0'
API_VERSION = '1.1.0'
attr_reader :url
def initialize(options = {})
requires!(options, :login, :password, :pem)
@options = options
@options[:pem_password] = options[:password]
@url = test? ? test_url : live_url
super
end
# Setup transaction. Get redirect_url from response.service_url
def setup_purchase(money, options = {})
requires!(options, :issuer_id, :return_url, :order_id, :currency, :description, :entrance_code)
commit(build_transaction_request(money, options))
end
# Check status of transaction and confirm payment
# transaction_id must be a valid transaction_id from a prior setup.
def capture(transaction, options = {})
options[:transaction_id] = transaction
commit(build_status_request(options))
end
# Get list of issuers from response.issuer_list
def issuers
commit(build_directory_request)
end
def test?
@options[:test] || Base.gateway_mode == :test
end
private
def token
if @token.nil?
@token = create_fingerprint(@options[:pem])
end
@token
end
#
#
# 2001-12-17T09:30:47.0Z
#
# 1003
#
#
# 000123456
# 0
# passkey
# 1
# 3823ad872eff23
# https://www.mijnwinkel.nl/betaalafhandeling
#
#
#
# iDEAL-aankoop 21
# 5999
# EUR
# PT3M30S
# nl
# Documentensuite
# D67tyx6rw9IhY71
#
#
def build_transaction_request(money, options)
date_time_stamp = create_time_stamp
message = date_time_stamp +
options[:issuer_id] +
@options[:login] +
SUB_ID +
options[:return_url] +
options[:order_id] +
money.to_s +
(options[:currency] || currency(money)) +
LANGUAGE +
options[:description] +
options[:entrance_code]
token_code = sign_message(@options[:pem], @options[:password], message)
xml = Builder::XmlMarkup.new(:indent => 2)
xml.instruct!
xml.tag! 'AcquirerTrxReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do
xml.tag! 'createDateTimeStamp', date_time_stamp
xml.tag! 'Issuer' do
xml.tag! 'issuerID', options[:issuer_id]
end
xml.tag! 'Merchant' do
xml.tag! 'merchantID', @options[:login]
xml.tag! 'subID', SUB_ID
xml.tag! 'authentication', AUTHENTICATION_TYPE
xml.tag! 'token', token
xml.tag! 'tokenCode', token_code
xml.tag! 'merchantReturnURL', options[:return_url]
end
xml.tag! 'Transaction' do
xml.tag! 'purchaseID', options[:order_id]
xml.tag! 'amount', money
xml.tag! 'currency', options[:currency]
xml.tag! 'expirationPeriod', options[:expiration_period] || default_expiration_period
xml.tag! 'language', LANGUAGE
xml.tag! 'description', options[:description]
xml.tag! 'entranceCode', options[:entrance_code]
end
xml.target!
end
end
#
#
# 2001-12-17T09:30:47.0Z
#
# 000123456
# 0
# keyed hash
# 1
# 3823ad872eff23
#
#
# 0001023456789112
#
#
def build_status_request(options)
datetimestamp = create_time_stamp
message = datetimestamp + @options[:login] + SUB_ID + options[:transaction_id]
tokenCode = sign_message(@options[:pem], @options[:password], message)
xml = Builder::XmlMarkup.new(:indent => 2)
xml.instruct!
xml.tag! 'AcquirerStatusReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do
xml.tag! 'createDateTimeStamp', datetimestamp
xml.tag! 'Merchant' do
xml.tag! 'merchantID', @options[:login]
xml.tag! 'subID', SUB_ID
xml.tag! 'authentication' , AUTHENTICATION_TYPE
xml.tag! 'token', token
xml.tag! 'tokenCode', tokenCode
end
xml.tag! 'Transaction' do
xml.tag! 'transactionID', options[:transaction_id]
end
end
xml.target!
end
#
#
# 2001-12-17T09:30:47.0Z
#
# 000000001
# 0
# 1
# hashkey
# WajqV1a3nDen0be2r196g9FGFF=
#
#
def build_directory_request
datetimestamp = create_time_stamp
message = datetimestamp + @options[:login] + SUB_ID
tokenCode = sign_message(@options[:pem], @options[:password], message)
xml = Builder::XmlMarkup.new(:indent => 2)
xml.instruct!
xml.tag! 'DirectoryReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do
xml.tag! 'createDateTimeStamp', datetimestamp
xml.tag! 'Merchant' do
xml.tag! 'merchantID', @options[:login]
xml.tag! 'subID', SUB_ID
xml.tag! 'authentication', AUTHENTICATION_TYPE
xml.tag! 'token', token
xml.tag! 'tokenCode', tokenCode
end
end
xml.target!
end
def commit(request)
raw_response = ssl_post(url, request)
response = Hash.from_xml(raw_response.to_s)
response_type = response.keys[0]
case response_type
when 'AcquirerTrxRes', 'DirectoryRes'
success = true
when 'ErrorRes'
success = false
when 'AcquirerStatusRes'
raise SecurityError, "Message verification failed.", caller unless status_response_verified?(response)
success = (response['AcquirerStatusRes']['Transaction']['status'] == 'Success')
else
raise ArgumentError, "Unknown response type.", caller
end
return IdealResponse.new(success, response.keys[0], response, :test => test?)
end
def create_fingerprint(cert_file)
cert_data = OpenSSL::X509::Certificate.new(cert_file).to_s
cert_data = cert_data.sub(/-----BEGIN CERTIFICATE-----/, '')
cert_data = cert_data.sub(/-----END CERTIFICATE-----/, '')
fingerprint = Base64.decode64(cert_data)
fingerprint = Digest::SHA1.hexdigest(fingerprint)
return fingerprint.upcase
end
def sign_message(private_key_data, password, data)
private_key = OpenSSL::PKey::RSA.new(private_key_data, password)
signature = private_key.sign(OpenSSL::Digest::SHA1.new, data.gsub('\s', ''))
return Base64.encode64(signature).gsub(/\n/, '')
end
def verify_message(cert_file, data, signature)
public_key = OpenSSL::X509::Certificate.new(cert_file).public_key
return public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), data)
end
def status_response_verified?(response)
transaction = response['AcquirerStatusRes']['Transaction']
message = response['AcquirerStatusRes']['createDateTimeStamp'] + transaction['transactionID' ] + transaction['status']
message << transaction['consumerAccountNumber'].to_s
verify_message(server_pem, message, response['AcquirerStatusRes']['Signature']['signatureValue'])
end
def create_time_stamp
Time.now.gmtime.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end
end
end
end