diff --git a/.gitignore b/.gitignore index c1e0daf..4b729f0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,8 @@ tmtags coverage rdoc pkg +vendor +.bundle +.ideal ## PROJECT::SPECIFIC diff --git a/Gemfile b/Gemfile index 855cf03..b82fe25 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,8 @@ source "http://rubygems.org" gem 'rake' gemspec + + +group :test do + gem 'activesupport' +end \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 4c1eb88..b73992a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,26 @@ PATH remote: . specs: - ideal (0.2.0) - builder + ideal (0.9.0) nap + nokogiri GEM remote: http://rubygems.org/ specs: - builder (3.0.0) + activesupport (3.0.0) metaclass (0.0.1) mocha (0.10.0) metaclass (~> 0.0.1) - nap (0.4) + nap (0.5.1) + nokogiri (1.5.5) rake (0.9.2.2) PLATFORMS ruby DEPENDENCIES + activesupport ideal! mocha rake diff --git a/ideal.gemspec b/ideal.gemspec index f7b6f01..61bd9eb 100644 --- a/ideal.gemspec +++ b/ideal.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency "builder" + s.add_dependency "nokogiri" s.add_dependency "nap" s.add_development_dependency "mocha" end diff --git a/lib/ideal.rb b/lib/ideal.rb index 56fce30..46e7d52 100644 --- a/lib/ideal.rb +++ b/lib/ideal.rb @@ -1,6 +1,6 @@ # encoding: utf-8 -require 'builder' +require 'nokogiri' require 'rest' require 'ideal/acquirers' diff --git a/lib/ideal/acquirers.rb b/lib/ideal/acquirers.rb index 9a8ad84..d6d7570 100644 --- a/lib/ideal/acquirers.rb +++ b/lib/ideal/acquirers.rb @@ -7,12 +7,12 @@ module Ideal 'test_url' => 'https://idealtest.secure-ing.com/ideal/iDeal' }, 'rabobank' => { - 'live_url' => 'https://ideal.rabobank.nl/ideal/iDeal', - 'test_url' => 'https://idealtest.rabobank.nl/ideal/iDeal' + 'live_url' => 'https://ideal.rabobank.nl/ideal/iDEALv3', + 'test_url' => 'https://idealtest.rabobank.nl/ideal/iDEALv3' }, 'abnamro' => { 'live_url' => 'https://abnamro.ideal-payment.de/ideal/iDeal', 'test_url' => 'https://abnamro-test.ideal-payment.de/ideal/iDeal' } } -end \ No newline at end of file +end diff --git a/lib/ideal/gateway.rb b/lib/ideal/gateway.rb index 996972f..e937f86 100644 --- a/lib/ideal/gateway.rb +++ b/lib/ideal/gateway.rb @@ -3,7 +3,7 @@ require 'openssl' require 'net/https' require 'base64' -require 'digest/sha1' +require 'digest/sha2' module Ideal # === Response classes @@ -15,11 +15,10 @@ module Ideal # # See the Response class for more information on errors. class Gateway - AUTHENTICATION_TYPE = 'SHA1_RSA' LANGUAGE = 'nl' CURRENCY = 'EUR' - API_VERSION = '1.1.0' - XML_NAMESPACE = 'http://www.idealdesk.com/Message' + API_VERSION = '3.3.1' + XML_NAMESPACE = 'http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1' def self.acquirers Ideal::ACQUIRERS @@ -138,7 +137,7 @@ def request_url # # gateway.issuers.list # => [{ :id => '1006', :name => 'ABN AMRO Bank' }, …] def issuers - post_data request_url, build_directory_request_body, DirectoryResponse + post_data request_url, build_directory_request, DirectoryResponse end # Starts a purchase by sending an acquirer transaction request for the @@ -186,7 +185,7 @@ def issuers # # See the Gateway class description for a more elaborate example. def setup_purchase(money, options) - post_data request_url, build_transaction_request_body(money, options), TransactionResponse + post_data request_url, build_transaction_request(money, options), TransactionResponse end # Sends a acquirer status request for the specified +transaction_id+ and @@ -206,7 +205,7 @@ def setup_purchase(money, options) # # See the Gateway class description for a more elaborate example. def capture(transaction_id) - post_data request_url, build_status_request_body(:transaction_id => transaction_id), StatusResponse + post_data request_url, build_status_request(:transaction_id => transaction_id), StatusResponse end private @@ -240,22 +239,48 @@ def enforce_maximum_length(key, string, max_length) raise ArgumentError, "The value for `#{key}' contains diacritical characters `#{string}'." if string =~ DIACRITICAL_CHARACTERS end - # Returns the +token+ as specified in section 2.8.4 of the iDeal specs. - # - # This is the params['AcquirerStatusRes']['Signature']['fingerprint'] in - # a StatusResponse instance. - def token - Digest::SHA1.hexdigest(self.class.private_certificate.to_der).upcase - end - def strip_whitespace(str) str.gsub(/\s/m,'') end + + #signs the xml + def sign!(xml) + digest_val = digest_value(xml) + xml.Signature(xmlns: 'http://www.w3.org/2000/09/xmldsig#') do |xml| + xml.SignedInfo do |xml| + xml.CanonicalizationMethod(Algorithm: 'http://www.w3.org/2001/10/xml-exc-c14n#') + xml.SignatureMethod(Algorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') + xml.Reference(URI: '') do |xml| + xml.Transforms do |xml| + xml.Transform(Algorithm: 'http://www.w3.org/2000/09/xmldsig#enveloped-signature') + end + xml.DigestMethod(Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256') + xml.DigestValue digest_val + end + end + xml.SignatureValue signature_value(digest_val) + xml.KeyInfo do |xml| + xml.KeyName fingerprint + end + end + end - # Creates a +tokenCode+ from the specified +message+. - def token_code(message) - signature = self.class.private_key.sign(OpenSSL::Digest::SHA1.new, strip_whitespace(message)) - strip_whitespace(Base64.encode64(signature)) + # Creates a +signatureValue+ from the xml+. + def signature_value(digest_value) + signature = Ideal::Gateway.private_key.sign(OpenSSL::Digest::SHA256.new, digest_value) + strip_whitespace(Base64.encode64(strip_whitespace(signature))) + end + + # Creates a +digestValue+ from the xml+. + def digest_value(xml) + canonical = xml.doc.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0) + digest = OpenSSL::Digest::SHA256.new.digest canonical + strip_whitespace(Base64.encode64(strip_whitespace(digest))) + end + + # Creates a keyName value for the XML signature + def fingerprint + Digest::SHA1.hexdigest(Ideal::Gateway.private_certificate.to_der).upcase end # Returns a string containing the current UTC time, formatted as per the @@ -264,62 +289,6 @@ def created_at_timestamp Time.now.gmtime.strftime("%Y-%m-%dT%H:%M:%S.000Z") end - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } - else - lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] - end - end - - # iDeal doesn't really seem to care about nice looking keys in their XML. - # Probably some Java XML class, hence the method name. - def javaize_key(key) - key = key.to_s - case key - when 'acquirer_transaction_request' - 'AcquirerTrxReq' - when 'acquirer_status_request' - 'AcquirerStatusReq' - when 'directory_request' - 'DirectoryReq' - when 'issuer', 'merchant', 'transaction' - key.capitalize - when 'created_at' - 'createDateTimeStamp' - when 'merchant_return_url' - 'merchantReturnURL' - when 'token_code', 'expiration_period', 'entrance_code' - key[0,1] + camelize(key)[1..-1] - when /^(\w+)_id$/ - "#{$1}ID" - else - key - end - end - - # Creates xml with a given hash of tag-value pairs according to the iDeal - # requirements. - def xml_for(name, tags_and_values) - xml = Builder::XmlMarkup.new - xml.instruct! - xml.tag!(javaize_key(name), 'xmlns' => XML_NAMESPACE, 'version' => API_VERSION) { xml_from_array(xml, tags_and_values) } - xml.target! - end - - # Recursively creates xml for a given hash of tag-value pair. Uses - # javaize_key on the tags to create the tags needed by iDeal. - def xml_from_array(builder, tags_and_values) - tags_and_values.each do |tag, value| - tag = javaize_key(tag) - if value.is_a?(Array) - builder.tag!(tag) { xml_from_array(builder, value) } - else - builder.tag!(tag, value) - end - end - end - def requires!(options, *keys) missing = keys - options.keys unless missing.empty? @@ -327,45 +296,40 @@ def requires!(options, *keys) end end - def build_status_request_body(options) + def build_status_request(options) requires!(options, :transaction_id) timestamp = created_at_timestamp - message = "#{timestamp}#{self.class.merchant_id}#{@sub_id}#{options[:transaction_id]}" - - xml_for(:acquirer_status_request, [ - [:created_at, timestamp], - [:merchant, [ - [:merchant_id, self.class.merchant_id], - [:sub_id, @sub_id], - [:authentication, AUTHENTICATION_TYPE], - [:token, token], - [:token_code, token_code(message)] - ]], - - [:transaction, [ - [:transaction_id, options[:transaction_id]] - ]] - ]) + Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.AcquirerStatusReq(xmlns: XML_NAMESPACE, version: API_VERSION) do |xml| + xml.createDateTimestamp created_at_timestamp + xml.Merchant do |xml| + xml.merchantID self.class.merchant_id + xml.subID @sub_id + end + xml.Transaction do |xml| + xml.transactionID options[:transaction_id] + end + sign!(xml) + end + end.to_xml end - def build_directory_request_body + def build_directory_request timestamp = created_at_timestamp - message = "#{timestamp}#{self.class.merchant_id}#{@sub_id}" - - xml_for(:directory_request, [ - [:created_at, timestamp], - [:merchant, [ - [:merchant_id, self.class.merchant_id], - [:sub_id, @sub_id], - [:authentication, AUTHENTICATION_TYPE], - [:token, token], - [:token_code, token_code(message)] - ]] - ]) + Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.DirectoryReq(xmlns: XML_NAMESPACE, version: API_VERSION) do |xml| + xml.createDateTimestamp created_at_timestamp + xml.Merchant do |xml| + xml.merchantID self.class.merchant_id + xml.subID @sub_id + end + sign!(xml) + end + end.to_xml end - def build_transaction_request_body(money, options) + def build_transaction_request(money, options) requires!(options, :issuer_id, :expiration_period, :return_url, :order_id, :description, :entrance_code) enforce_maximum_length(:money, money.to_s, 12) @@ -374,41 +338,30 @@ def build_transaction_request_body(money, options) enforce_maximum_length(:entrance_code, options[:entrance_code], 40) timestamp = created_at_timestamp - message = timestamp + - options[:issuer_id] + - self.class.merchant_id + - @sub_id.to_s + - options[:return_url] + - options[:order_id] + - money.to_s + - CURRENCY + - LANGUAGE + - options[:description] + - options[:entrance_code] - - xml_for(:acquirer_transaction_request, [ - [:created_at, timestamp], - [:issuer, [[:issuer_id, options[:issuer_id]]]], - - [:merchant, [ - [:merchant_id, self.class.merchant_id], - [:sub_id, @sub_id], - [:authentication, AUTHENTICATION_TYPE], - [:token, token], - [:token_code, token_code(message)], - [:merchant_return_url, options[:return_url]] - ]], - - [:transaction, [ - [:purchase_id, options[:order_id]], - [:amount, money], - [:currency, CURRENCY], - [:expiration_period, options[:expiration_period]], - [:language, LANGUAGE], - [:description, options[:description]], - [:entrance_code, options[:entrance_code]] - ]] - ]) + + Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.AcquirerTrxReq(xmlns: XML_NAMESPACE, version: API_VERSION) do |xml| + xml.createDateTimestamp created_at_timestamp + xml.Issuer do |xml| + xml.issuerID options[:issuer_id] + end + xml.Merchant do |xml| + xml.merchantID self.class.merchant_id + xml.subID 0 + xml.merchantReturnURL options[:return_url] + end + xml.Transaction do |xml| + xml.purchaseID options[:order_id] + xml.amount money + xml.currency CURRENCY + xml.expirationPeriod options[:expiration_period] + xml.language LANGUAGE + xml.description options[:description] + xml.entranceCode options[:entrance_code] + end + sign!(xml) + end + end.to_xml end def log(thing, contents) diff --git a/test/gateway_test.rb b/test/gateway_test.rb index db29aaa..dfdcfa7 100644 --- a/test/gateway_test.rb +++ b/test/gateway_test.rb @@ -6,7 +6,7 @@ module IdealTestCases # This method is called at the end of the file when all fixture data has been loaded. def self.setup_ideal_gateway! Ideal::Gateway.class_eval do - self.acquirer = :ing + self.acquirer = :rabobank self.merchant_id = '123456789' @@ -18,13 +18,14 @@ def self.setup_ideal_gateway! end VALID_PURCHASE_OPTIONS = { - :issuer_id => '0001', + :issuer_id => '99999IBAN', :expiration_period => 'PT10M', :return_url => 'http://return_to.example.com', :order_id => '12345678901', :description => 'A classic Dutch windmill', :entrance_code => '1234' } + class ClassMethodsTest < Test::Unit::TestCase def test_merchant_id @@ -38,7 +39,7 @@ def test_verify_live_url_for_ing def test_verify_live_url_for_rabobank Ideal::Gateway.acquirer = :rabobank - assert_equal 'https://ideal.rabobank.nl/ideal/iDeal', Ideal::Gateway.live_url + assert_equal 'https://ideal.rabobank.nl/ideal/iDEALv3', Ideal::Gateway.live_url end def test_verify_live_urls_for_abnamro @@ -53,7 +54,7 @@ def test_does_not_allow_configuration_of_unknown_acquirers end def test_acquirers - assert_equal 'https://ideal.rabobank.nl/ideal/iDeal', Ideal::Gateway.acquirers['rabobank']['live_url'] + assert_equal 'https://ideal.rabobank.nl/ideal/iDEALv3', Ideal::Gateway.acquirers['rabobank']['live_url'] assert_equal 'https://ideal.secure-ing.com/ideal/iDeal', Ideal::Gateway.acquirers['ing']['live_url'] assert_equal 'https://abnamro.ideal-payment.de/ideal/iDeal', Ideal::Gateway.acquirers['abnamro']['live_url'] end @@ -103,52 +104,52 @@ def test_returns_created_at_timestamp assert_equal timestamp, @gateway.send(:created_at_timestamp) end - def test_ruby_to_java_keys_conversion - keys = [ - [:acquirer_transaction_request, 'AcquirerTrxReq'], - [:acquirer_status_request, 'AcquirerStatusReq'], - [:directory_request, 'DirectoryReq'], - [:created_at, 'createDateTimeStamp'], - [:issuer, 'Issuer'], - [:merchant, 'Merchant'], - [:transaction, 'Transaction'], - [:issuer_id, 'issuerID'], - [:merchant_id, 'merchantID'], - [:sub_id, 'subID'], - [:token_code, 'tokenCode'], - [:merchant_return_url, 'merchantReturnURL'], - [:purchase_id, 'purchaseID'], - [:expiration_period, 'expirationPeriod'], - [:entrance_code, 'entranceCode'] - ] - - keys.each do |key, expected_key| - assert_equal expected_key, @gateway.send(:javaize_key, key) + def test_digest_value_generation + sha256 = OpenSSL::Digest::SHA256.new + OpenSSL::Digest::SHA256.stubs(:new).returns(sha256) + xml = Nokogiri::XML::Builder.new do |xml| + xml.request do |xml| + xml.content 'digest test' + @gateway.send(:sign!, xml) + end end + digest_value = xml.doc.at_xpath('//xmlns:DigestValue', 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#').text + xml.doc.at_xpath('//xmlns:Signature', 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#').remove + canonical = xml.doc.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0) + digest = sha256.digest canonical + expected_digest_value = strip_whitespace(Base64.encode64(strip_whitespace(digest))) + assert_equal expected_digest_value, digest_value end - def test_does_not_convert_unknown_key_to_java_key - assert_equal 'not_a_registered_key', @gateway.send(:javaize_key, :not_a_registered_key) + + def test_signature_value_generation + sha256 = OpenSSL::Digest::SHA256.new + OpenSSL::Digest::SHA256.stubs(:new).returns(sha256) + signature_value = @gateway.send(:signature_value, 'foo') + signature = Ideal::Gateway.private_key.sign(sha256, 'foo') + expected_signature_value = strip_whitespace(Base64.encode64(strip_whitespace(signature))) + assert_equal expected_signature_value, signature_value end - def test_token_generation + def test_key_name_generation expected_token = Digest::SHA1.hexdigest(OpenSSL::X509::Certificate.new(PRIVATE_CERTIFICATE).to_der).upcase - assert_equal expected_token, @gateway.send(:token) + assert_equal expected_token, @gateway.send(:fingerprint) end - def test_token_code_generation - Ideal::Gateway.acquirer = :ing - message = "Top\tsecret\tman.\nI could tell you, but then I'd have to kill you…" - stripped_message = message.gsub(/\s/m, '') - - sha1 = OpenSSL::Digest::SHA1.new - OpenSSL::Digest::SHA1.stubs(:new).returns(sha1) - signature = Ideal::Gateway.private_key.sign(sha1, stripped_message) - encoded_signature = Base64.encode64(signature).strip.gsub(/\n/, '') - - assert_equal encoded_signature, @gateway.send(:token_code, message) - end + # def test_token_code_generation + # Ideal::Gateway.acquirer = :ing + # message = "Top\tsecret\tman.\nI could tell you, but then I'd have to kill you…" + # stripped_message = message.gsub(/\s/m, '') + # + # sha1 = OpenSSL::Digest::SHA1.new + # OpenSSL::Digest::SHA1.stubs(:new).returns(sha1) + # + # signature = Ideal::Gateway.private_key.sign(sha1, stripped_message) + # encoded_signature = Base64.encode64(signature).strip.gsub(/\n/, '') + # + # assert_equal encoded_signature, @gateway.send(:token_code, message) + # end def test_posts_data_with_ssl_to_request_url_and_return_the_correct_response_for_test Ideal::Gateway.environment = :test @@ -168,60 +169,63 @@ def test_posts_data_with_ssl_to_request_url_and_return_the_correct_response_for_ class XMLBuildingTest < Test::Unit::TestCase def setup @gateway = Ideal::Gateway.new - end - - def test_contains_correct_info_in_root_node - expected_xml = Builder::XmlMarkup.new - expected_xml.instruct! - expected_xml.tag!('AcquirerTrxReq', 'xmlns' => Ideal::Gateway::XML_NAMESPACE, 'version' => Ideal::Gateway::API_VERSION) {} - - assert_equal expected_xml.target!, @gateway.send(:xml_for, :acquirer_transaction_request, []) - end - - def test_creates_correct_xml_with_java_keys_from_array_with_ruby_keys - expected_xml = Builder::XmlMarkup.new - expected_xml.instruct! - expected_xml.tag!('AcquirerTrxReq', 'xmlns' => Ideal::Gateway::XML_NAMESPACE, 'version' => Ideal::Gateway::API_VERSION) do - expected_xml.tag!('a_parent') do - expected_xml.tag!('createDateTimeStamp', '2009-01-26') - end - end - - assert_equal expected_xml.target!, @gateway.send(:xml_for, :acquirer_transaction_request, [[:a_parent, [[:created_at, '2009-01-26']]]]) - end + @gateway.stubs(:created_at_timestamp).returns('created_at_timestamp') + @gateway.stubs(:digest_value).returns('digest_value') + @gateway.stubs(:signature_value).returns('signature_value') + @gateway.stubs(:fingerprint).returns('fingerprint') + end + + def test_transaction_request_xml + options = { + issuer_id: 'issuer_id', + return_url: 'return_url', + order_id: 'purchase_id', + expiration_period: 'expiration_period', + description: 'description', + entrance_code: 'entrance_code' + } + xml = @gateway.send(:build_transaction_request, 'amount', options) + assert_equal xml, TRANSACTION_REQUEST + end + + def test_status_request_xml + options = { + transaction_id: 'transaction_id', + } + xml = @gateway.send(:build_status_request, options) + assert_equal xml, STATUS_REQUEST + end + + def test_directory_request_xml + xml = @gateway.send(:build_directory_request) + assert_equal xml, DIRECTORY_REQUEST + end + + end - class RequestBodyBuildingTest < Test::Unit::TestCase + class ErroneousInputTest < Test::Unit::TestCase + def setup @gateway = Ideal::Gateway.new - @gateway.stubs(:created_at_timestamp).returns('created_at_timestamp') - @gateway.stubs(:token).returns('the_token') - @gateway.stubs(:token_code) - + @gateway.stubs(:digest_value).returns('digest_value') + @gateway.stubs(:signature_value).returns('signature_value') + @gateway.stubs(:fingerprint).returns('fingerprint') + @transaction_id = '0001023456789112' - end - def test_build_transaction_request_body_raises_ArgumentError_with_missing_required_options - options = VALID_PURCHASE_OPTIONS.dup - options.keys.each do |key| - options.delete(key) - - assert_raise(ArgumentError) do - @gateway.send(:build_transaction_request_body, 100, options) - end - end end - + def test_valid_with_valid_options - assert_not_nil @gateway.send(:build_transaction_request_body, 4321, VALID_PURCHASE_OPTIONS) + assert_not_nil @gateway.send(:build_transaction_request, 4321, VALID_PURCHASE_OPTIONS) end - + def test_checks_that_fields_are_not_too_long assert_raise ArgumentError do - @gateway.send(:build_transaction_request_body, 1234567890123, VALID_PURCHASE_OPTIONS) # 13 chars + @gateway.send(:build_transaction_request, 1234567890123, VALID_PURCHASE_OPTIONS) # 13 chars end - + [ [:order_id, '12345678901234567'], # 17 chars, [:description, '123456789012345678901234567890123'], # 33 chars @@ -229,119 +233,45 @@ def test_checks_that_fields_are_not_too_long ].each do |key, value| options = VALID_PURCHASE_OPTIONS.dup options[key] = value - + assert_raise ArgumentError do - @gateway.send(:build_transaction_request_body, 4321, options) + @gateway.send(:build_transaction_request, 4321, options) end end end - + + def test_build_transaction_request_body_raises_ArgumentError_with_missing_required_options + options = VALID_PURCHASE_OPTIONS.dup + options.keys.each do |key| + options.delete(key) + + assert_raise(ArgumentError) do + @gateway.send(:build_transaction_request, 100, options) + end + end + end + def test_checks_that_fields_do_not_contain_diacritical_characters assert_raise ArgumentError do - @gateway.send(:build_transaction_request_body, 'graphème', VALID_PURCHASE_OPTIONS) + @gateway.send(:build_transaction_request, 'graphème', VALID_PURCHASE_OPTIONS) end - + [:order_id, :description, :entrance_code].each do |key, value| options = VALID_PURCHASE_OPTIONS.dup options[key] = 'graphème' - + assert_raise ArgumentError do - @gateway.send(:build_transaction_request_body, 4321, options) + @gateway.send(:build_transaction_request, 4321, options) end end end - - def test_builds_a_transaction_request_body - money = 4321 - - message = 'created_at_timestamp' + - VALID_PURCHASE_OPTIONS[:issuer_id] + - Ideal::Gateway.merchant_id + - @gateway.sub_id.to_s + - VALID_PURCHASE_OPTIONS[:return_url] + - VALID_PURCHASE_OPTIONS[:order_id] + - money.to_s + - Ideal::Gateway::CURRENCY + - Ideal::Gateway::LANGUAGE + - VALID_PURCHASE_OPTIONS[:description] + - VALID_PURCHASE_OPTIONS[:entrance_code] - - @gateway.expects(:token_code).with(message).returns('the_token_code') - - @gateway.expects(:xml_for).with(:acquirer_transaction_request, [ - [:created_at, 'created_at_timestamp'], - [:issuer, [[:issuer_id, VALID_PURCHASE_OPTIONS[:issuer_id]]]], - - [:merchant, [ - [:merchant_id, Ideal::Gateway.merchant_id], - [:sub_id, @gateway.sub_id], - [:authentication, Ideal::Gateway::AUTHENTICATION_TYPE], - [:token, 'the_token'], - [:token_code, 'the_token_code'], - [:merchant_return_url, VALID_PURCHASE_OPTIONS[:return_url]] - ]], - - [:transaction, [ - [:purchase_id, VALID_PURCHASE_OPTIONS[:order_id]], - [:amount, money], - [:currency, Ideal::Gateway::CURRENCY], - [:expiration_period, VALID_PURCHASE_OPTIONS[:expiration_period]], - [:language, Ideal::Gateway::LANGUAGE], - [:description, VALID_PURCHASE_OPTIONS[:description]], - [:entrance_code, VALID_PURCHASE_OPTIONS[:entrance_code]] - ]] - ]) - - @gateway.send(:build_transaction_request_body, money, VALID_PURCHASE_OPTIONS) - end - - def test_builds_a_directory_request_body - message = 'created_at_timestamp' + Ideal::Gateway.merchant_id + @gateway.sub_id.to_s - @gateway.expects(:token_code).with(message).returns('the_token_code') - - @gateway.expects(:xml_for).with(:directory_request, [ - [:created_at, 'created_at_timestamp'], - [:merchant, [ - [:merchant_id, Ideal::Gateway.merchant_id], - [:sub_id, @gateway.sub_id], - [:authentication, Ideal::Gateway::AUTHENTICATION_TYPE], - [:token, 'the_token'], - [:token_code, 'the_token_code'] - ]] - ]) - - @gateway.send(:build_directory_request_body) - end - + def test_builds_a_status_request_body_raises_ArgumentError_with_missing_required_options assert_raise(ArgumentError) do - @gateway.send(:build_status_request_body, {}) + @gateway.send(:build_status_request, {}) end end - - def test_builds_a_status_request_body - options = { :transaction_id => @transaction_id } - - message = 'created_at_timestamp' + Ideal::Gateway.merchant_id + @gateway.sub_id.to_s + options[:transaction_id] - @gateway.expects(:token_code).with(message).returns('the_token_code') - - @gateway.expects(:xml_for).with(:acquirer_status_request, [ - [:created_at, 'created_at_timestamp'], - [:merchant, [ - [:merchant_id, Ideal::Gateway.merchant_id], - [:sub_id, @gateway.sub_id], - [:authentication, Ideal::Gateway::AUTHENTICATION_TYPE], - [:token, 'the_token'], - [:token_code, 'the_token_code'] - ]], - - [:transaction, [ - [:transaction_id, options[:transaction_id]] - ]], - ]) - - @gateway.send(:build_status_request_body, options) - end + end class GeneralResponseTest < Test::Unit::TestCase @@ -415,7 +345,7 @@ def setup end def test_returns_a_list_with_only_one_issuer - @gateway.stubs(:build_directory_request_body).returns('the request body') + @gateway.stubs(:build_directory_request).returns('the request body') @gateway.expects(:ssl_post).with(@gateway.request_url, 'the request body').returns(DIRECTORY_RESPONSE_WITH_ONE_ISSUER) expected_issuers = [{ :id => '1006', :name => 'ABN AMRO Bank' }] @@ -426,7 +356,7 @@ def test_returns_a_list_with_only_one_issuer end def test_returns_list_of_issuers_from_response - @gateway.stubs(:build_directory_request_body).returns('the request body') + @gateway.stubs(:build_directory_request).returns('the request body') @gateway.expects(:ssl_post).with(@gateway.request_url, 'the request body').returns(DIRECTORY_RESPONSE_WITH_MULTIPLE_ISSUERS) expected_issuers = [ @@ -447,7 +377,7 @@ class SetupPurchaseTest < Test::Unit::TestCase def setup @gateway = Ideal::Gateway.new - @gateway.stubs(:build_transaction_request_body).with(4321, VALID_PURCHASE_OPTIONS).returns('the request body') + @gateway.stubs(:build_transaction_request).with(4321, VALID_PURCHASE_OPTIONS).returns('the request body') @gateway.expects(:ssl_post).with(@gateway.request_url, 'the request body').returns(ACQUIRER_TRANSACTION_RESPONSE) @setup_purchase_response = @gateway.setup_purchase(4321, VALID_PURCHASE_OPTIONS) @@ -471,7 +401,7 @@ class CapturePurchaseTest < Test::Unit::TestCase def setup @gateway = Ideal::Gateway.new - @gateway.stubs(:build_status_request_body). + @gateway.stubs(:build_status_request). with(:transaction_id => '0001023456789112').returns('the request body') end @@ -738,5 +668,101 @@ def expects_request_and_returns(str) } + TRANSACTION_REQUEST = @transaction_xml = %{ + + created_at_timestamp + + issuer_id + + + 123456789 + 0 + return_url + + + purchase_id + amount + EUR + expiration_period + nl + description + entrance_code + + + + + + + + + + + digest_value + + + signature_value + + fingerprint + + + +} + + DIRECTORY_REQUEST = %{ + + created_at_timestamp + + 123456789 + 0 + + + + + + + + + + + digest_value + + + signature_value + + fingerprint + + + +} + + STATUS_REQUEST = %{ + + created_at_timestamp + + 123456789 + 0 + + + transaction_id + + + + + + + + + + + digest_value + + + signature_value + + fingerprint + + + +} setup_ideal_gateway! end \ No newline at end of file diff --git a/test/helper.rb b/test/helper.rb index b5f8bf1..4897d0a 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,6 +1,7 @@ require 'rubygems' require 'test/unit' require 'mocha' +require 'active_support/test_case' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) @@ -9,13 +10,21 @@ $stdout.sync = true +unless defined?(Test::Unit::AssertionFailedError) + Test::Unit::AssertionFailedError = ActiveSupport::TestCase::Assertion +end + module Test module Unit class TestCase HOME_DIR = RUBY_PLATFORM =~ /mswin32/ ? ENV['HOMEPATH'] : ENV['HOME'] unless defined?(HOME_DIR) LOCAL_CREDENTIALS = File.join(HOME_DIR.to_s, '.ideal/fixtures.yml') unless defined?(LOCAL_CREDENTIALS) DEFAULT_CREDENTIALS = File.join(File.dirname(__FILE__), 'fixtures.yml') unless defined?(DEFAULT_CREDENTIALS) - + + def strip_whitespace(str) + str.gsub(/\s/m,'') + end + private def all_fixtures diff --git a/test/remote_test.rb b/test/remote_test.rb index 4a0200d..e79c4cc 100644 --- a/test/remote_test.rb +++ b/test/remote_test.rb @@ -9,7 +9,7 @@ def setup @gateway = Ideal::Gateway.new - @@issuer ||= @gateway.issuers.list[0] + @@issuer ||= {id: 'RABONL2UXXX'} @valid_options = { :issuer_id => @@issuer[:id],