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 :test_url, :live_url, :server_pem, :pem_password, :default_expiration_period self.default_expiration_period = 'PT10M' self.default_currency = 'EUR' self.pem_password = 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 # <?xml version="1.0" encoding="UTF-8"?> # <AcquirerTrxReq xmlns="http://www.idealdesk.com/Message" version="1.1.0"> # <createDateTimeStamp>2001-12-17T09:30:47.0Z</createDateTimeStamp> # <Issuer> # <issuerID>1003</issuerID> # </Issuer> # <Merchant> # <merchantID>000123456</merchantID> # <subID>0</subID> # <authentication>passkey</authentication> # <token>1</token> # <tokenCode>3823ad872eff23</tokenCode> # <merchantReturnURL>https://www.mijnwinkel.nl/betaalafhandeling # </merchantReturnURL> # </Merchant> # <Transaction> # <purchaseID>iDEAL-aankoop 21</purchaseID> # <amount>5999</amount> # <currency>EUR</currency> # <expirationPeriod>PT3M30S</expirationPeriod> # <language>nl</language> # <description>Documentensuite</description> # <entranceCode>D67tyx6rw9IhY71</entranceCode> # </Transaction> # </AcquirerTrxReq> 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 # <?xml version="1.0" encoding="UTF-8"?> # <AcquirerStatusReq xmlns="http://www.idealdesk.com/Message" version="1.1.0"> # <createDateTimeStamp>2001-12-17T09:30:47.0Z</createDateTimeStamp> # <Merchant> # <merchantID>000123456</merchantID> # <subID>0</subID> # <authentication>keyed hash</authentication> # <token>1</token> # <tokenCode>3823ad872eff23</tokenCode> # </Merchant> # <Transaction> # <transactionID>0001023456789112</transactionID> # </Transaction> # </AcquirerStatusReq> 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 # <?xml version="1.0" encoding="UTF-8"?> # <DirectoryReq xmlns="http://www.idealdesk.com/Message" version="1.1.0"> # <createDateTimeStamp>2001-12-17T09:30:47.0Z</createDateTimeStamp> # <Merchant> # <merchantID>000000001</merchantID> # <subID>0</subID> # <authentication>1</authentication> # <token>hashkey</token> # <tokenCode>WajqV1a3nDen0be2r196g9FGFF=</tokenCode> # </Merchant> # </DirectoryReq> 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