ideal-payment 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|