ideal-payment 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/.travis.yml +14 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +21 -0
- data/README.md +148 -0
- data/Vagrantfile +35 -0
- data/certs/bestandsnaam.cer +25 -0
- data/certs/bestandsnaam.key +30 -0
- data/certs/ideal.cer +18 -0
- data/ideal.gemspec +31 -0
- data/init.rb +1 -0
- data/lib/ideal.rb +21 -0
- data/lib/ideal/acquirers.rb +18 -0
- data/lib/ideal/gateway.rb +377 -0
- data/lib/ideal/response.rb +274 -0
- data/lib/ideal/version.rb +5 -0
- data/provision.sh +24 -0
- data/spec/expectation_xml/directory_request.xml +25 -0
- data/spec/expectation_xml/status_request.xml +28 -0
- data/spec/expectation_xml/transaction_request.xml +38 -0
- data/spec/fixtures.yml +7 -0
- data/spec/remote_spec.rb +205 -0
- data/spec/spec.rb +500 -0
- data/spec/test_xml/error_response.xml +12 -0
- data/spec/test_xml/large_directory_response.xml +36 -0
- data/spec/test_xml/small_directory_response.xml +17 -0
- data/spec/test_xml/status_response_succeeded.xml +20 -0
- data/spec/test_xml/status_response_succeeded_incorrect.xml +18 -0
- data/spec/test_xml/transaction_response.xml +15 -0
- metadata +157 -0
data/provision.sh
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# This improves performance since the Google DNS is much quicker than the local Vagrant Virtualbox combination
|
4
|
+
printf "nameserver 8.8.8.8\nnameserver 8.8.4.4" > /etc/resolv.conf
|
5
|
+
|
6
|
+
echo "Updating the Operating System..."
|
7
|
+
sudo apt-get update >> /tmp/provision.log 2>&1
|
8
|
+
sudo apt-get upgrade -y >> /tmp/provision.log 2>&1
|
9
|
+
|
10
|
+
echo "Installing extra packages..."
|
11
|
+
sudo apt-get install curl wget git g++ libreadline6-dev zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 autoconf libgdbm-dev libncurses5-dev automake libtool bison pkg-config libffi-dev -y >> /tmp/provision.log 2>&1
|
12
|
+
|
13
|
+
echo "Installing rvm..."
|
14
|
+
curl -sSL https://get.rvm.io | bash -s stable --ruby >> /tmp/provision.log 2>&1
|
15
|
+
source /usr/local/rvm/scripts/rvm
|
16
|
+
|
17
|
+
echo "Installing Ruby-2.1.2..."
|
18
|
+
rvm install ruby-2.1.2 >> /tmp/provision.log 2>&1
|
19
|
+
rvm use ruby-2.1.2 --default >> /tmp/provision.log 2>&1
|
20
|
+
|
21
|
+
cd /vagrant && bundle install >> /tmp/provision.log 2>&1
|
22
|
+
|
23
|
+
echo ""
|
24
|
+
echo "And we are done! Check /tmp/provision.log for a full log file \`vagrant ssh -c \"cat /tmp/provision.log\"\`"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<DirectoryReq xmlns="http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1" version="3.3.1">
|
3
|
+
<createDateTimestamp>created_at_timestamp</createDateTimestamp>
|
4
|
+
<Merchant>
|
5
|
+
<merchantID>002054205</merchantID>
|
6
|
+
<subID>0</subID>
|
7
|
+
</Merchant>
|
8
|
+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
9
|
+
<SignedInfo>
|
10
|
+
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
11
|
+
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
12
|
+
<Reference URI="">
|
13
|
+
<Transforms>
|
14
|
+
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
15
|
+
</Transforms>
|
16
|
+
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
|
17
|
+
<DigestValue>digest_value</DigestValue>
|
18
|
+
</Reference>
|
19
|
+
</SignedInfo>
|
20
|
+
<SignatureValue>signature_value</SignatureValue>
|
21
|
+
<KeyInfo>
|
22
|
+
<KeyName>fingerprint</KeyName>
|
23
|
+
</KeyInfo>
|
24
|
+
</Signature>
|
25
|
+
</DirectoryReq>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<AcquirerStatusReq xmlns="http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1" version="3.3.1">
|
3
|
+
<createDateTimestamp>created_at_timestamp</createDateTimestamp>
|
4
|
+
<Merchant>
|
5
|
+
<merchantID>002054205</merchantID>
|
6
|
+
<subID>0</subID>
|
7
|
+
</Merchant>
|
8
|
+
<Transaction>
|
9
|
+
<transactionID>transaction_id</transactionID>
|
10
|
+
</Transaction>
|
11
|
+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
12
|
+
<SignedInfo>
|
13
|
+
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
14
|
+
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
15
|
+
<Reference URI="">
|
16
|
+
<Transforms>
|
17
|
+
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
18
|
+
</Transforms>
|
19
|
+
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
|
20
|
+
<DigestValue>digest_value</DigestValue>
|
21
|
+
</Reference>
|
22
|
+
</SignedInfo>
|
23
|
+
<SignatureValue>signature_value</SignatureValue>
|
24
|
+
<KeyInfo>
|
25
|
+
<KeyName>fingerprint</KeyName>
|
26
|
+
</KeyInfo>
|
27
|
+
</Signature>
|
28
|
+
</AcquirerStatusReq>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<AcquirerTrxReq xmlns="http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1" version="3.3.1">
|
3
|
+
<createDateTimestamp>created_at_timestamp</createDateTimestamp>
|
4
|
+
<Issuer>
|
5
|
+
<issuerID>issuer_id</issuerID>
|
6
|
+
</Issuer>
|
7
|
+
<Merchant>
|
8
|
+
<merchantID>002054205</merchantID>
|
9
|
+
<subID>0</subID>
|
10
|
+
<merchantReturnURL>return_url</merchantReturnURL>
|
11
|
+
</Merchant>
|
12
|
+
<Transaction>
|
13
|
+
<purchaseID>purchase_id</purchaseID>
|
14
|
+
<amount>amount</amount>
|
15
|
+
<currency>EUR</currency>
|
16
|
+
<expirationPeriod>expiration_period</expirationPeriod>
|
17
|
+
<language>nl</language>
|
18
|
+
<description>description</description>
|
19
|
+
<entranceCode>entrance_code</entranceCode>
|
20
|
+
</Transaction>
|
21
|
+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
22
|
+
<SignedInfo>
|
23
|
+
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
24
|
+
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
|
25
|
+
<Reference URI="">
|
26
|
+
<Transforms>
|
27
|
+
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
28
|
+
</Transforms>
|
29
|
+
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
|
30
|
+
<DigestValue>digest_value</DigestValue>
|
31
|
+
</Reference>
|
32
|
+
</SignedInfo>
|
33
|
+
<SignatureValue>signature_value</SignatureValue>
|
34
|
+
<KeyInfo>
|
35
|
+
<KeyName>fingerprint</KeyName>
|
36
|
+
</KeyInfo>
|
37
|
+
</Signature>
|
38
|
+
</AcquirerTrxReq>
|
data/spec/fixtures.yml
ADDED
data/spec/remote_spec.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'coveralls'
|
3
|
+
|
4
|
+
Coveralls.wear!
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'ideal'
|
8
|
+
require 'yaml'
|
9
|
+
require 'mocha'
|
10
|
+
|
11
|
+
class Idealtest
|
12
|
+
describe Ideal do
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
setup_ideal_gateway(fixtures('default'))
|
16
|
+
Ideal::Gateway.environment = :test
|
17
|
+
|
18
|
+
@gateway = Ideal::Gateway.new
|
19
|
+
|
20
|
+
@@issuer ||= {id: 'RABONL2U'}
|
21
|
+
|
22
|
+
@valid_options = {
|
23
|
+
:issuer_id => @@issuer[:id],
|
24
|
+
:expiration_period => 'PT10M',
|
25
|
+
:return_url => 'http://return_to.example.com',
|
26
|
+
:order_id => '123456789012',
|
27
|
+
:currency => 'EUR',
|
28
|
+
:description => 'A classic Dutch windmill',
|
29
|
+
:entrance_code => '1234'
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
describe 'requests' do
|
35
|
+
it 'should go to the test environment' do
|
36
|
+
expect(@gateway.issuers.test?).to be true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'valid requests' do
|
41
|
+
it 'should proceed' do
|
42
|
+
response = @gateway.setup_purchase(550, @valid_options)
|
43
|
+
expect(response.success?).to be true
|
44
|
+
expect(response.service_url).not_to be nil
|
45
|
+
expect(response.transaction_id).not_to be nil
|
46
|
+
expect(response.order_id).to eq(@valid_options[:order_id])
|
47
|
+
expect(response.verified?).to be true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'invalid requests' do
|
52
|
+
it 'should be rejected' do
|
53
|
+
response = @gateway.setup_purchase('0;5', @valid_options)
|
54
|
+
|
55
|
+
expect(response.success?).to eq false
|
56
|
+
expect(response.error_code).to eq('BR1210')
|
57
|
+
expect(response.error_message).not_to be nil
|
58
|
+
expect(response.consumer_error_message).not_to be nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'valid requests but incorrectly signed' do
|
63
|
+
it 'should be rejected' do
|
64
|
+
allow_any_instance_of(Xmldsig::SignedDocument).to receive(:validate).and_return(false)
|
65
|
+
response = capture_transaction(:success)
|
66
|
+
|
67
|
+
expect(response.verified?).to be false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
###
|
72
|
+
#
|
73
|
+
# These are the 7 integration tests of ING which need to be ran sucessfuly
|
74
|
+
# _before_ you'll get access to the live environment.
|
75
|
+
#
|
76
|
+
# See test_transaction_id for info on how the remote tests are ran.
|
77
|
+
#
|
78
|
+
|
79
|
+
describe '#issuers' do
|
80
|
+
it 'return a list of iDeal issuers' do
|
81
|
+
issuer_list = @gateway.issuers.list
|
82
|
+
expect(issuer_list.length).to eq(2)
|
83
|
+
expect(issuer_list[0][:id]).to eq('INGBNL2A')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
describe 'successful transaction' do
|
89
|
+
it 'should be successful' do
|
90
|
+
res = capture_transaction(:success)
|
91
|
+
expect(res.success?).to be true
|
92
|
+
expect(res.status).to eq(:success)
|
93
|
+
expect(res.verified?).to be true
|
94
|
+
expect(res.consumer_iban).to eq('NL17RABO0213698412')
|
95
|
+
expect(res.consumer_name).to eq('Hr E G H Küppers en/of MW M.J. Küppers-Veeneman')
|
96
|
+
expect(res.consumer_bic).to eq('RABONL2U')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe 'cancelled transaction' do
|
101
|
+
it 'should be cancelled' do
|
102
|
+
res = capture_transaction(:cancelled)
|
103
|
+
expect(res.success?).to be false
|
104
|
+
expect(res.status).to eq(:cancelled)
|
105
|
+
expect(res.verified?).to be true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'expired transaction' do
|
110
|
+
it 'should be expired' do
|
111
|
+
res = capture_transaction(:expired)
|
112
|
+
expect(res.success?).to be false
|
113
|
+
expect(res.status).to eq(:expired)
|
114
|
+
expect(res.verified?).to be true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'open transaction' do
|
119
|
+
it 'should be open' do
|
120
|
+
res = capture_transaction(:open)
|
121
|
+
expect(res.success?).to be false
|
122
|
+
expect(res.status).to eq(:open)
|
123
|
+
expect(res.verified?).to be true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe 'failed transaction' do
|
128
|
+
it 'should be failure' do
|
129
|
+
res = capture_transaction(:failure)
|
130
|
+
expect(res.success?).to be false
|
131
|
+
expect(res.status).to eq(:failure)
|
132
|
+
expect(res.verified?).to be true
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'server_error transaction' do
|
137
|
+
it 'should be server_error' do
|
138
|
+
res = capture_transaction(:server_error)
|
139
|
+
expect(res.success?).to be false
|
140
|
+
expect(res.verified?).to be true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Shortcut method which does a #setup_purchase through #test_transaction and
|
147
|
+
# captures the resulting transaction and returns the capture response.
|
148
|
+
def capture_transaction(type)
|
149
|
+
@gateway.capture test_transaction(type).transaction_id
|
150
|
+
end
|
151
|
+
|
152
|
+
# Calls #setup_purchase with the amount corresponding to the named test and
|
153
|
+
# returns the response. Before returning an assertion will be ran to test
|
154
|
+
# whether or not the transaction was successful.
|
155
|
+
def test_transaction(type)
|
156
|
+
amount = case type
|
157
|
+
when :success then
|
158
|
+
1.00
|
159
|
+
when :cancelled then
|
160
|
+
2.00
|
161
|
+
when :expired then
|
162
|
+
3.00
|
163
|
+
when :open then
|
164
|
+
4.00
|
165
|
+
when :failure then
|
166
|
+
5.00
|
167
|
+
when :server_error then
|
168
|
+
7.00
|
169
|
+
end
|
170
|
+
|
171
|
+
response = @gateway.setup_purchase(amount, @valid_options)
|
172
|
+
expect(response.success?).to be true
|
173
|
+
|
174
|
+
log('RESP', response.service_url)
|
175
|
+
|
176
|
+
response
|
177
|
+
end
|
178
|
+
|
179
|
+
def fixtures(key)
|
180
|
+
file = File.join(File.dirname(__FILE__), 'fixtures.yml')
|
181
|
+
fixtures ||= YAML.load(File.read(file))
|
182
|
+
@fixture = fixtures[key] || raise(StandardError, "No fixture data was found for key '#{key}'")
|
183
|
+
@fixture['private_key_file'] = File.join(File.dirname(__FILE__), @fixture['private_key_file'])
|
184
|
+
@fixture['private_certificate_file'] = File.join(File.dirname(__FILE__), @fixture['private_certificate_file'])
|
185
|
+
@fixture['ideal_certificate_file'] = File.join(File.dirname(__FILE__), @fixture['ideal_certificate_file'])
|
186
|
+
@fixture
|
187
|
+
end
|
188
|
+
|
189
|
+
# Setup the gateway by providing a hash of attributes and values.
|
190
|
+
def setup_ideal_gateway(fixture)
|
191
|
+
fixture = fixture.dup
|
192
|
+
# The passphrase needs to be set first, otherwise the key won't initialize properly
|
193
|
+
if passphrase = fixture.delete('passphrase')
|
194
|
+
Ideal::Gateway.passphrase = passphrase
|
195
|
+
end
|
196
|
+
fixture.each { |key, value| Ideal::Gateway.send("#{key}=", value) }
|
197
|
+
Ideal::Gateway.live_url = nil
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def log(a, b)
|
204
|
+
#$stderr.write("#{a}: #{b}")
|
205
|
+
end
|
data/spec/spec.rb
ADDED
@@ -0,0 +1,500 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
|
3
|
+
Coveralls.wear!
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'ideal'
|
7
|
+
require 'yaml'
|
8
|
+
require 'mocha'
|
9
|
+
|
10
|
+
|
11
|
+
def strip_whitespace(str)
|
12
|
+
str.gsub('/\s/m', '')
|
13
|
+
end
|
14
|
+
|
15
|
+
class GeneralMethodTest
|
16
|
+
describe Ideal do
|
17
|
+
|
18
|
+
before(:all) do
|
19
|
+
file = File.join(File.dirname(__FILE__), 'fixtures.yml')
|
20
|
+
fixtures ||= YAML.load(File.read(file))
|
21
|
+
@fixture = fixtures['default'] || raise(StandardError, 'No fixture data was found for key "default"')
|
22
|
+
@fixture['private_key_file'] = File.join(File.dirname(__FILE__), @fixture['private_key_file'])
|
23
|
+
@fixture['private_certificate_file'] = File.join(File.dirname(__FILE__), @fixture['private_certificate_file'])
|
24
|
+
@fixture['ideal_certificate_file'] = File.join(File.dirname(__FILE__), @fixture['ideal_certificate_file'])
|
25
|
+
# The passphrase needs to be set first, otherwise the key won't initialize properly
|
26
|
+
if (passphrase = @fixture.delete('passphrase')) # Yes the = is intended, not ==
|
27
|
+
Ideal::Gateway.passphrase = passphrase
|
28
|
+
end
|
29
|
+
@fixture.each { |key, value| Ideal::Gateway.send("#{key}=", value) }
|
30
|
+
Ideal::Gateway.live_url = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#merchant_id' do
|
34
|
+
it 'returns the set merchant id' do
|
35
|
+
expect(Ideal::Gateway.merchant_id).to eq('002054205')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#url' do
|
40
|
+
it 'returns the set test url from the fixture' do
|
41
|
+
expect(Ideal::Gateway.test_url).to eq('https://idealtest.rabobank.nl/ideal/iDEALv3')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#url' do
|
46
|
+
it 'returns the ideal url for issuer ING' do
|
47
|
+
Ideal::Gateway.acquirer = :ing
|
48
|
+
expect(Ideal::Gateway.live_url).to eq('https://ideal.secure-ing.com/ideal/iDEALv3')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#url' do
|
53
|
+
it 'returns the ideal url for issuer Rabobank' do
|
54
|
+
Ideal::Gateway.acquirer = :rabobank
|
55
|
+
expect(Ideal::Gateway.live_url).to eq('https://ideal.rabobank.nl/ideal/iDEALv3')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#url' do
|
60
|
+
it 'returns the ideal url for issuer ABN Amro' do
|
61
|
+
Ideal::Gateway.acquirer = :abnamro
|
62
|
+
expect(Ideal::Gateway.live_url).to eq('https://abnamro.ideal-payment.de/ideal/iDeal')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#acquirer' do
|
67
|
+
it 'throws an error if the acquirer is non existent' do
|
68
|
+
expect { Ideal::Gateway.acquirer = :nonexistent }.to raise_error(ArgumentError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#private_certificate' do
|
73
|
+
it 'returns the currently loaded private certificate' do
|
74
|
+
private_cert_file = File.read(@fixture['private_certificate_file'])
|
75
|
+
private_cert = OpenSSL::X509::Certificate.new(private_cert_file)
|
76
|
+
expect(Ideal::Gateway.private_certificate.to_text).to eq(private_cert.to_text)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#' do
|
81
|
+
it 'returns the private key' do
|
82
|
+
private_key_file = File.read(@fixture['private_key_file'])
|
83
|
+
private_key = OpenSSL::PKey::RSA.new(private_key_file, Ideal::Gateway.passphrase)
|
84
|
+
expect(Ideal::Gateway.private_key.to_text).to eq(private_key.to_text)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class GeneralTest
|
92
|
+
describe Ideal do
|
93
|
+
|
94
|
+
before(:each) do
|
95
|
+
file = File.join(File.dirname(__FILE__), 'fixtures.yml')
|
96
|
+
fixtures ||= YAML.load(File.read(file))
|
97
|
+
@fixture = fixtures['default'] || raise(StandardError, 'No fixture data was found for key "default"')
|
98
|
+
@fixture['private_key_file'] = File.join(File.dirname(__FILE__), @fixture['private_key_file'])
|
99
|
+
@fixture['private_certificate_file'] = File.join(File.dirname(__FILE__), @fixture['private_certificate_file'])
|
100
|
+
@fixture['ideal_certificate_file'] = File.join(File.dirname(__FILE__), @fixture['ideal_certificate_file'])
|
101
|
+
# The passphrase needs to be set first, otherwise the key won't initialize properly
|
102
|
+
if (passphrase = @fixture.delete('passphrase')) # Yes the = is intended, not ==
|
103
|
+
Ideal::Gateway.passphrase = passphrase
|
104
|
+
end
|
105
|
+
@fixture.each { |key, value| Ideal::Gateway.send("#{key}=", value) }
|
106
|
+
Ideal::Gateway.live_url = nil
|
107
|
+
@gateway = Ideal::Gateway.new
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#initialize' do
|
111
|
+
it 'has a sub_id of 0 when just initialized or picks the supplied value' do
|
112
|
+
expect(Ideal::Gateway.new.sub_id).to eq(0)
|
113
|
+
expect(Ideal::Gateway.new(:sub_id => 1).sub_id).to eq(1)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#send' do
|
118
|
+
it 'returns the test_url if the test mode is active' do
|
119
|
+
gateway = Ideal::Gateway.new
|
120
|
+
Ideal::Gateway.acquirer = :rabobank
|
121
|
+
Ideal::Gateway.environment = :test
|
122
|
+
expect(gateway.send(:request_url)).to eq(Ideal::Gateway.test_url)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#send' do
|
127
|
+
it 'returns the live_url if the test mode is inactive' do
|
128
|
+
gateway = Ideal::Gateway.new
|
129
|
+
Ideal::Gateway.acquirer = :rabobank
|
130
|
+
Ideal::Gateway.environment = :live
|
131
|
+
expect(gateway.send(:request_url)).to eq(Ideal::Gateway.live_url)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#send' do
|
136
|
+
it 'sends the correct created at datestamp' do
|
137
|
+
timestamp = '2011-11-28T16:30:00.000Z' # Exact time inventid was founded
|
138
|
+
allow_any_instance_of(Time).to receive(:gmtime).and_return(DateTime.parse(timestamp))
|
139
|
+
|
140
|
+
expect(@gateway.send(:created_at_timestamp)).to eq(timestamp)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#send' do
|
145
|
+
it 'generates a correct digest' do
|
146
|
+
sha256 = OpenSSL::Digest::SHA256.new
|
147
|
+
allow_any_instance_of(OpenSSL::Digest::SHA256).to receive(:new).and_return(sha256)
|
148
|
+
xml = Nokogiri::XML::Builder.new do |xml|
|
149
|
+
xml.request do |innerxml|
|
150
|
+
xml.content 'digest test'
|
151
|
+
@gateway.send(:sign!, innerxml)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
digest_value = xml.doc.at_xpath('//xmlns:DigestValue', 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#').text
|
155
|
+
xml.doc.at_xpath('//xmlns:Signature', 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#').remove
|
156
|
+
canonical = xml.doc.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
|
157
|
+
digest = sha256.digest canonical
|
158
|
+
expected_digest_value = strip_whitespace(Base64.encode64(digest.strip)) # .strip does not remove the \n
|
159
|
+
expect(expected_digest_value).to eq(digest_value)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
#=begin
|
164
|
+
describe '#send' do
|
165
|
+
it 'generates a correct signature' do
|
166
|
+
sha256 = OpenSSL::Digest::SHA256.new
|
167
|
+
allow_any_instance_of(OpenSSL::Digest::SHA256).to receive(:new).and_return(sha256)
|
168
|
+
xml1 = Nokogiri::XML::Builder.new do |xml|
|
169
|
+
xml.request do |innerxml|
|
170
|
+
xml.content 'signature test'
|
171
|
+
@gateway.send(:sign!, innerxml)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
signature_value = @gateway.send(:signature_value, xml1.doc)
|
175
|
+
xml2 = xml1.doc.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
|
176
|
+
signature = Ideal::Gateway.private_key.sign(sha256, xml2)
|
177
|
+
expected_signature_value = strip_whitespace(Base64.encode64(signature))
|
178
|
+
expect(signature_value).to eq(expected_signature_value)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
#=end
|
182
|
+
|
183
|
+
describe '#send' do
|
184
|
+
it 'generates a correct fingerprint' do
|
185
|
+
certificate_file = File.read(@fixture['private_certificate_file'])
|
186
|
+
expected_token = Digest::SHA1.hexdigest(OpenSSL::X509::Certificate.new(certificate_file).to_der)
|
187
|
+
expect(@gateway.send(:fingerprint)).to eq(expected_token)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe '#send' do
|
192
|
+
it 'performs an SSL POST and returns the correct response' do
|
193
|
+
Ideal::Gateway.environment = :test
|
194
|
+
allow_any_instance_of(Ideal::Response).to receive(:new).with('response', :test => true)
|
195
|
+
allow(@gateway).to receive(:ssl_post).with(@gateway.request_url, 'data').and_return('<x>response</x>')
|
196
|
+
@gateway.send(:post_data, @gateway.request_url, 'data', Ideal::Response)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class XmlTest
|
204
|
+
describe Ideal do
|
205
|
+
|
206
|
+
before(:each) do
|
207
|
+
@gateway = Ideal::Gateway.new
|
208
|
+
allow(@gateway).to receive(:created_at_timestamp).and_return('created_at_timestamp')
|
209
|
+
allow(@gateway).to receive(:digest_value).and_return('digest_value')
|
210
|
+
allow(@gateway).to receive(:signature_value).and_return('signature_value')
|
211
|
+
allow(@gateway).to receive(:fingerprint).and_return('fingerprint')
|
212
|
+
end
|
213
|
+
|
214
|
+
describe Ideal do
|
215
|
+
it 'creates a correct TransactionRequestXml' do
|
216
|
+
options = {
|
217
|
+
issuer_id: 'issuer_id',
|
218
|
+
return_url: 'return_url',
|
219
|
+
order_id: 'purchase_id',
|
220
|
+
expiration_period: 'expiration_period',
|
221
|
+
description: 'description',
|
222
|
+
entrance_code: 'entrance_code'
|
223
|
+
}
|
224
|
+
xml = @gateway.send(:build_transaction_request, 'amount', options)
|
225
|
+
expected_response = Nokogiri::XML.parse(File.read(File.join(File.dirname(__FILE__), 'expectation_xml/transaction_request.xml')))
|
226
|
+
actual_response = Nokogiri::XML.parse(xml)
|
227
|
+
expect(actual_response.to_s).to eq(expected_response.to_s)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe Ideal do
|
232
|
+
it 'creates a correct StatusRequestXml' do
|
233
|
+
options = {
|
234
|
+
transaction_id: 'transaction_id'
|
235
|
+
}
|
236
|
+
xml = @gateway.send(:build_status_request, options)
|
237
|
+
expected_response = Nokogiri::XML.parse(File.read(File.join(File.dirname(__FILE__), 'expectation_xml/status_request.xml')))
|
238
|
+
actual_response = Nokogiri::XML.parse(xml)
|
239
|
+
expect(actual_response.to_s).to eq(expected_response.to_s)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe Ideal do
|
244
|
+
it 'creates a correct DirectoryRequestXml' do
|
245
|
+
xml = @gateway.send(:build_directory_request)
|
246
|
+
expected_response = Nokogiri::XML.parse(File.read(File.join(File.dirname(__FILE__), 'expectation_xml/directory_request.xml')))
|
247
|
+
actual_response = Nokogiri::XML.parse(xml)
|
248
|
+
expect(actual_response.to_s).to eq(expected_response.to_s)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class ErrorHandlingTest
|
255
|
+
describe Ideal do
|
256
|
+
|
257
|
+
before(:each) do
|
258
|
+
@gateway = Ideal::Gateway.new
|
259
|
+
allow(@gateway).to receive(:created_at_timestamp).and_return('created_at_timestamp')
|
260
|
+
allow(@gateway).to receive(:digest_value).and_return('digest_value')
|
261
|
+
allow(@gateway).to receive(:signature_value).and_return('signature_value')
|
262
|
+
allow(@gateway).to receive(:fingerprint).and_return('fingerprint')
|
263
|
+
|
264
|
+
@transaction_id = '1234567890123456'
|
265
|
+
end
|
266
|
+
|
267
|
+
describe '#send' do
|
268
|
+
it 'accepts a valid request with valid options' do
|
269
|
+
expect(@gateway.send(:build_transaction_request, 4321, VALID_PURCHASE_OPTIONS)).to be_truthy
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe '#send' do
|
274
|
+
it 'should error on erroneously long fields in a transaction request' do
|
275
|
+
expect { @gateway.send(:build_transaction_request, 1234567890123, VALID_PURCHASE_OPTIONS) }.to raise_error(ArgumentError) # 13 chars
|
276
|
+
|
277
|
+
[
|
278
|
+
[:order_id, '12345678901234567'], # 17 chars,
|
279
|
+
[:description, '123456789012345678901234567890123'], # 33 chars
|
280
|
+
[:entrance_code, '12345678901234567890123456789012345678901'] # 41
|
281
|
+
].each do |key, value|
|
282
|
+
options = VALID_PURCHASE_OPTIONS.dup
|
283
|
+
options[key] = value
|
284
|
+
|
285
|
+
expect { @gateway.send(:build_transaction_request, 4321, options) }.to raise_error(ArgumentError)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe '#send' do
|
291
|
+
it 'should error on a missing required field in a transaction request' do
|
292
|
+
options = VALID_PURCHASE_OPTIONS.dup
|
293
|
+
options.keys.each do |key|
|
294
|
+
options.delete(key)
|
295
|
+
|
296
|
+
expect { @gateway.send(:build_transaction_request, 100, options) }.to raise_error(ArgumentError)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe '#send' do
|
302
|
+
it 'should error on inappropriate characters for a transaction request' do
|
303
|
+
expect { @gateway.send(:build_transaction_request, 'graphème', VALID_PURCHASE_OPTIONS) }.to raise_error(ArgumentError)
|
304
|
+
|
305
|
+
[:order_id, :description, :entrance_code].each do |key, value|
|
306
|
+
options = VALID_PURCHASE_OPTIONS.dup
|
307
|
+
options[key] = 'graphème'
|
308
|
+
|
309
|
+
expect { @gateway.send(:build_transaction_request, 4321, options) }.to raise_error(ArgumentError)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe '#send' do
|
315
|
+
it 'should error on missing required options for a status request' do
|
316
|
+
expect { @gateway.send(:build_status_request, {}) }.to raise_error(ArgumentError)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class ResponseTest
|
324
|
+
describe Ideal do
|
325
|
+
|
326
|
+
before(:each) do
|
327
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/large_directory_response.xml')
|
328
|
+
@xml = File.read(file)
|
329
|
+
@response = Ideal::Response.new(@xml)
|
330
|
+
end
|
331
|
+
|
332
|
+
describe '#test' do
|
333
|
+
it 'should set the environment correctly on instantiation or use the default' do
|
334
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/large_directory_response.xml')
|
335
|
+
xml = File.read(file)
|
336
|
+
expect(Ideal::Response.new(xml, :test => true).test?).to be true
|
337
|
+
expect(Ideal::Response.new(xml, :test => false).test?).to be false
|
338
|
+
expect(Ideal::Response.new(xml).test?).to be false
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
describe 'response' do
|
343
|
+
it 'return a correct response body' do
|
344
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/large_directory_response.xml')
|
345
|
+
xml = File.read(file)
|
346
|
+
response = Ideal::Response.new(xml)
|
347
|
+
expect(response.instance_variable_get(:@response).to_s).to eq(Nokogiri::XML.parse(xml).remove_namespaces!.root.to_s)
|
348
|
+
expect(response.success?).to be true
|
349
|
+
expect(response.error_message).to be nil
|
350
|
+
expect(response.error_code).to be nil
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
describe 'error' do
|
355
|
+
it 'gives an error if the response is incorrect' do
|
356
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/error_response.xml')
|
357
|
+
xml = File.read(file)
|
358
|
+
response = Ideal::Response.new(xml)
|
359
|
+
|
360
|
+
expect(response.success?).to be false
|
361
|
+
expect(response.error_message).to eq('Failure in system')
|
362
|
+
expect(response.error_details).to eq('System generating error: issuer')
|
363
|
+
expect(response.consumer_error_message).to eq('Betalen met iDEAL is nu niet mogelijk.')
|
364
|
+
expect(response.error_code).to eq('SO1000')
|
365
|
+
[
|
366
|
+
['IX1000', :xml],
|
367
|
+
['SO1000', :system],
|
368
|
+
['SE2000', :security],
|
369
|
+
['BR1200', :value],
|
370
|
+
['AP1000', :application]
|
371
|
+
].each do |code, type|
|
372
|
+
allow(response).to receive(:error_code).and_return(code)
|
373
|
+
expect(response.error_type).to eq(type)
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe 'directory' do
|
380
|
+
it 'returns the list of issuers' do
|
381
|
+
gateway = Ideal::Gateway.new
|
382
|
+
|
383
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/small_directory_response.xml')
|
384
|
+
xml = File.read(file)
|
385
|
+
|
386
|
+
allow(gateway).to receive(:build_directory_request).and_return('the request body')
|
387
|
+
expect(gateway).to receive(:ssl_post).with(gateway.request_url, 'the request body').and_return(xml)
|
388
|
+
|
389
|
+
expected_issuers = [{:id => '1006', :country => 'RogierIsGaafLand', :name => 'ABN AMRO Bank'}]
|
390
|
+
|
391
|
+
directory_response = gateway.issuers
|
392
|
+
expect(directory_response).to be_a Ideal::DirectoryResponse
|
393
|
+
expect(directory_response.list).to eq(expected_issuers)
|
394
|
+
|
395
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/large_directory_response.xml')
|
396
|
+
xml = File.read(file)
|
397
|
+
|
398
|
+
expected_issuers = [
|
399
|
+
{:id => '1006', :country => 'JoostIsCool', :name => 'ABN AMRO Bank'},
|
400
|
+
{:id => '1003', :country => 'JoostIsCool', :name => 'Postbank'},
|
401
|
+
{:id => '1005', :country => 'JoostIsCool', :name => 'Rabobank'},
|
402
|
+
{:id => '1017', :country => 'XmlIsCoolOhNee', :name => 'Asr bank'},
|
403
|
+
{:id => '1023', :country => 'XmlIsCoolOhNee', :name => 'Van Lanschot'}
|
404
|
+
]
|
405
|
+
|
406
|
+
expect(gateway).to receive(:ssl_post).with(gateway.request_url, 'the request body').and_return(xml)
|
407
|
+
|
408
|
+
directory_response = gateway.issuers
|
409
|
+
expect(directory_response).to be_a Ideal::DirectoryResponse
|
410
|
+
expect(directory_response.list).to eq(expected_issuers)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
describe 'purchase' do
|
415
|
+
it 'should return a valid transaction response' do
|
416
|
+
gateway = Ideal::Gateway.new
|
417
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/transaction_response.xml')
|
418
|
+
xml = File.read(file)
|
419
|
+
|
420
|
+
allow(gateway).to receive(:build_transaction_request).with(4321, VALID_PURCHASE_OPTIONS).and_return('the request body')
|
421
|
+
expect(gateway).to receive(:ssl_post).with(gateway.request_url, 'the request body').and_return(xml)
|
422
|
+
|
423
|
+
setup_purchase_response = gateway.setup_purchase(4321, VALID_PURCHASE_OPTIONS)
|
424
|
+
|
425
|
+
expect(setup_purchase_response).to be_a Ideal::TransactionResponse
|
426
|
+
|
427
|
+
expect(setup_purchase_response.service_url).to eq('https://ideal.example.com/long_service_url?X009=BETAAL&X010=20')
|
428
|
+
|
429
|
+
expect(setup_purchase_response.transaction_id).to eq('0001023456789112')
|
430
|
+
expect(setup_purchase_response.order_id).to eq('iDEAL-aankoop 21')
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|
435
|
+
|
436
|
+
describe 'purchase' do
|
437
|
+
it 'should start a transaction at the acquirer' do
|
438
|
+
|
439
|
+
gateway = Ideal::Gateway.new
|
440
|
+
file = File.join(File.dirname(__FILE__), 'test_xml/transaction_response.xml')
|
441
|
+
xml = File.read(file)
|
442
|
+
|
443
|
+
allow(gateway).to receive(:build_transaction_request).with(4321, VALID_PURCHASE_OPTIONS).and_return('the request body')
|
444
|
+
expect(gateway).to receive(:ssl_post).with(gateway.request_url, 'the request body').and_return(xml)
|
445
|
+
|
446
|
+
setup_purchase_response = gateway.setup_purchase(4321, VALID_PURCHASE_OPTIONS)
|
447
|
+
|
448
|
+
expect(setup_purchase_response).to be_a(Ideal::TransactionResponse)
|
449
|
+
|
450
|
+
expect(setup_purchase_response.service_url).to eq('https://ideal.example.com/long_service_url?X009=BETAAL&X010=20')
|
451
|
+
|
452
|
+
expect(setup_purchase_response.transaction_id).to eq('0001023456789112')
|
453
|
+
expect(setup_purchase_response.order_id).to eq('iDEAL-aankoop 21')
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
describe 'capturePurchase' do
|
458
|
+
it 'should do something correctly and not wrong' do
|
459
|
+
gateway = Ideal::Gateway.new
|
460
|
+
|
461
|
+
allow(gateway).to receive(:build_status_request).
|
462
|
+
with(:transaction_id => '0001023456789112').and_return('the request body')
|
463
|
+
|
464
|
+
allow(gateway).to receive(:ssl_post).with(gateway.request_url, 'the request body').and_return(File.read(File.join(File.dirname(__FILE__), 'test_xml/status_response_succeeded.xml')))
|
465
|
+
expect(gateway.capture('0001023456789112')).to be_a Ideal::StatusResponse
|
466
|
+
|
467
|
+
allow_any_instance_of(Ideal::StatusResponse).to receive(:verified?).and_return(true)
|
468
|
+
capture_response = gateway.capture('0001023456789112')
|
469
|
+
expect(capture_response.success?).to be true
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
describe 'capturePurchase' do
|
474
|
+
it 'tololol' do
|
475
|
+
|
476
|
+
gateway = Ideal::Gateway.new
|
477
|
+
allow(gateway).to receive(:build_status_request).
|
478
|
+
with(:transaction_id => '0001023456789112').and_return('the request body')
|
479
|
+
|
480
|
+
allow(gateway).to receive(:ssl_post).with(gateway.request_url, 'the request body').and_return(File.read(File.join(File.dirname(__FILE__), 'test_xml/status_response_succeeded_incorrect.xml')))
|
481
|
+
allow_any_instance_of(Ideal::StatusResponse).to receive(:verified?).and_return(false)
|
482
|
+
capture_response = gateway.capture('0001023456789112')
|
483
|
+
expect(capture_response.verified?).to be false
|
484
|
+
expect(capture_response.success?).to be false
|
485
|
+
end
|
486
|
+
|
487
|
+
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
# Defaults
|
492
|
+
|
493
|
+
VALID_PURCHASE_OPTIONS = {
|
494
|
+
:issuer_id => '99999IBAN',
|
495
|
+
:expiration_period => 'PT10M',
|
496
|
+
:return_url => 'https://example.inventid.net',
|
497
|
+
:order_id => '1234567890',
|
498
|
+
:description => 'A beautiful Eticket',
|
499
|
+
:entrance_code => '1234'
|
500
|
+
}
|