buckaroo-ideal 0.0.1
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +48 -0
- data/.travis.yml +5 -0
- data/.yardopts +3 -0
- data/Gemfile +29 -0
- data/Guardfile +13 -0
- data/LICENSE +22 -0
- data/README.md +45 -0
- data/Rakefile +19 -0
- data/buckaroo-ideal.gemspec +20 -0
- data/files/statuscodes.csv +213 -0
- data/lib/buckaroo-ideal.rb +26 -0
- data/lib/buckaroo-ideal/config.rb +101 -0
- data/lib/buckaroo-ideal/order.rb +46 -0
- data/lib/buckaroo-ideal/request.rb +164 -0
- data/lib/buckaroo-ideal/request_signature.rb +74 -0
- data/lib/buckaroo-ideal/response.rb +68 -0
- data/lib/buckaroo-ideal/response_signature.rb +54 -0
- data/lib/buckaroo-ideal/status.rb +67 -0
- data/lib/buckaroo-ideal/util.rb +41 -0
- data/lib/buckaroo-ideal/version.rb +5 -0
- data/spec/buckaroo-ideal/config_spec.rb +72 -0
- data/spec/buckaroo-ideal/order_spec.rb +43 -0
- data/spec/buckaroo-ideal/request_signature_spec.rb +34 -0
- data/spec/buckaroo-ideal/request_spec.rb +170 -0
- data/spec/buckaroo-ideal/response_signature_spec.rb +59 -0
- data/spec/buckaroo-ideal/response_spec.rb +77 -0
- data/spec/buckaroo-ideal/status_spec.rb +132 -0
- data/spec/buckaroo-ideal/util_spec.rb +62 -0
- data/spec/fixtures/statuscodes.csv +2 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/.gitkeep +0 -0
- metadata +113 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module Buckaroo
|
5
|
+
module Ideal
|
6
|
+
class ResponseSignature
|
7
|
+
|
8
|
+
# @return [String] The configured merchant_key in +Buckaroo::Ideal::Config+
|
9
|
+
delegate :merchant_key, to: Config
|
10
|
+
|
11
|
+
# @return [String] The configured secret_key in +Buckaroo::Ideal::Config+
|
12
|
+
delegate :secret_key, to: Config
|
13
|
+
|
14
|
+
# @return [String] The signature that was given in the response
|
15
|
+
attr_reader :signature
|
16
|
+
|
17
|
+
# @return [Buckaroo::Ideal::Response] The response that was signed.
|
18
|
+
attr_reader :response
|
19
|
+
|
20
|
+
# @return [String] The secret key that is used to sign the order.
|
21
|
+
attr_reader :secret
|
22
|
+
|
23
|
+
def initialize(response, signature = '')
|
24
|
+
@response = response
|
25
|
+
@signature = signature
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
signature == generate_signature
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_signature
|
33
|
+
salt = [
|
34
|
+
response.transaction_id,
|
35
|
+
response.timestamp,
|
36
|
+
merchant_key,
|
37
|
+
response.invoice_number,
|
38
|
+
response.reference,
|
39
|
+
response.currency,
|
40
|
+
to_cents(response.amount),
|
41
|
+
response.status.code,
|
42
|
+
to_numeric_boolean(response.test_mode),
|
43
|
+
secret_key
|
44
|
+
].join
|
45
|
+
|
46
|
+
Digest::MD5.hexdigest(salt)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
include Util
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Buckaroo
|
4
|
+
module Ideal
|
5
|
+
class Status
|
6
|
+
class UnknownStatusCode < StandardError; end
|
7
|
+
|
8
|
+
STATES = {
|
9
|
+
completed: %w(071 121 151 171 190 242 243 244 245 246 247 254 255 301
|
10
|
+
401 461 462 463 464 551 601 701 801),
|
11
|
+
pending: %w(000 001 070 090 091 100 105 120 126 135 136 150 156 157
|
12
|
+
170 176 177 253 300 400 460 500 550 600 700 710 790 791
|
13
|
+
792 793 800 811 814 815 831 834),
|
14
|
+
failed: %w(072 073 074 075 076 101 102 103 104 106 122 123 124 125
|
15
|
+
137 138 139 152 153 155 158 159 172 173 175 178 179 201
|
16
|
+
203 204 205 206 207 251 252 260 261 262 302 303 304 305
|
17
|
+
306 309 402 409 410 411 414 421 422 425 466 468 490 491
|
18
|
+
492 501 552 553 554 555 556 560 581 590 602 605 609 610
|
19
|
+
612 690 702 703 704 705 706 707 708 711 712 720 721 802
|
20
|
+
803 804 810 812 813 816 820 821 822 823 824 830 833 835
|
21
|
+
836 890 891 900 901 910 931 932 933 934 935 940 941 942
|
22
|
+
943 944 945 946 947 948 949 950 951 952 953 954 955 956
|
23
|
+
960 961 962 963 964 971 972 973 974 975 976 977 978 980
|
24
|
+
981 982 983 990 991 992 993 999)
|
25
|
+
}
|
26
|
+
|
27
|
+
CSV_FILE = File.expand_path('../../../files/statuscodes.csv', __FILE__)
|
28
|
+
|
29
|
+
attr_reader :code, :message
|
30
|
+
|
31
|
+
def self.status_codes(csv_file = CSV_FILE)
|
32
|
+
codes = {}
|
33
|
+
|
34
|
+
CSV.foreach(csv_file, col_sep: ';') do |row|
|
35
|
+
codes[row[0]] = row[1]
|
36
|
+
end
|
37
|
+
|
38
|
+
codes
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(code)
|
42
|
+
if message = self.class.status_codes[code]
|
43
|
+
@code = code
|
44
|
+
@message = message
|
45
|
+
else
|
46
|
+
raise UnknownStatusCode
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def state
|
51
|
+
STATES.each do |state, codes|
|
52
|
+
return state if codes.include? @code
|
53
|
+
end
|
54
|
+
|
55
|
+
:unknown
|
56
|
+
end
|
57
|
+
|
58
|
+
def completed?; state == :completed; end
|
59
|
+
def pending?; state == :pending; end
|
60
|
+
def failed?; state == :failed; end
|
61
|
+
|
62
|
+
def ==(other)
|
63
|
+
other.respond_to?(:code) && other.code == code
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'transliterator'
|
2
|
+
|
3
|
+
module Buckaroo
|
4
|
+
module Ideal
|
5
|
+
module Util
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def to_normalized_string(string)
|
9
|
+
Transliterator.asciify(string.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_cents(amount)
|
13
|
+
(amount.to_f * 100).round
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_cents(cents)
|
17
|
+
cents.to_f / 100
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_numeric_boolean(boolean)
|
21
|
+
boolean ? 1 : 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def from_numeric_boolean(number)
|
25
|
+
number.to_i == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def compact(subject)
|
29
|
+
subject = subject.dup
|
30
|
+
|
31
|
+
if subject.is_a?(Hash)
|
32
|
+
subject.each do |key, value|
|
33
|
+
subject.delete(key) unless value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
subject
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buckaroo::Ideal::Config do
|
4
|
+
before do
|
5
|
+
Buckaroo::Ideal::Config.configure(
|
6
|
+
merchant_key: 'merchant_key',
|
7
|
+
secret_key: 'secret_key',
|
8
|
+
test_mode: true,
|
9
|
+
success_url: 'http://example.com/transaction/success',
|
10
|
+
reject_url: 'http://example.com/transaction/reject',
|
11
|
+
error_url: 'http://example.com/transaction/error',
|
12
|
+
return_method: 'GET',
|
13
|
+
style: 'POPUP',
|
14
|
+
autoclose_popup: true
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
subject { Buckaroo::Ideal::Config }
|
19
|
+
|
20
|
+
it 'has a gateway_url' do
|
21
|
+
subject.gateway_url.should == 'https://payment.buckaroo.nl/gateway/payment.asp'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has a merchant_key' do
|
25
|
+
subject.merchant_key.should == 'merchant_key'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has a secret_key' do
|
29
|
+
subject.secret_key.should == 'secret_key'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has a test_mode' do
|
33
|
+
subject.test_mode.should be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has a success_url' do
|
37
|
+
subject.success_url.should == 'http://example.com/transaction/success'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has a reject_url' do
|
41
|
+
subject.reject_url.should == 'http://example.com/transaction/reject'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'has a error_url' do
|
45
|
+
subject.error_url.should == 'http://example.com/transaction/error'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'has a return_method' do
|
49
|
+
subject.return_method.should == 'GET'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'has a style' do
|
53
|
+
subject.style.should == 'POPUP'
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'has a autoclose_popup setting' do
|
57
|
+
subject.autoclose_popup.should be_true
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can be reset to default values' do
|
61
|
+
subject.reset
|
62
|
+
subject.merchant_key.should be_nil
|
63
|
+
subject.secret_key.should be_nil
|
64
|
+
subject.test_mode.should be_false
|
65
|
+
subject.success_url.should be_nil
|
66
|
+
subject.reject_url.should be_nil
|
67
|
+
subject.error_url.should be_nil
|
68
|
+
subject.return_method.should == 'POST'
|
69
|
+
subject.style.should == 'PAGE'
|
70
|
+
subject.autoclose_popup.should be_false
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buckaroo::Ideal::Order do
|
4
|
+
let(:order) { Buckaroo::Ideal::Order.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
Buckaroo::Ideal::Config.configure(
|
8
|
+
merchant_key: 'merchant_key',
|
9
|
+
secret_key: 'secret_key',
|
10
|
+
test_mode: true,
|
11
|
+
success_url: 'http://example.com/transaction/success',
|
12
|
+
reject_url: 'http://example.com/transaction/reject',
|
13
|
+
error_url: 'http://example.com/transaction/error',
|
14
|
+
return_method: 'GET',
|
15
|
+
style: 'POPUP',
|
16
|
+
autoclose_popup: true
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has a default currency' do
|
21
|
+
order.currency.should == 'EUR'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'does not have a default amount' do
|
25
|
+
order.amount.should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not have a default bank' do
|
29
|
+
order.bank.should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not have a default description' do
|
33
|
+
order.description.should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not have a default reference' do
|
37
|
+
order.reference.should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'does not have a default invoice_number' do
|
41
|
+
order.invoice_number.should be_nil
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buckaroo::Ideal::RequestSignature do
|
4
|
+
it 'generates a signature for the given order' do
|
5
|
+
order = mock invoice_number: 'EETNU-12345',
|
6
|
+
amount: 12.50,
|
7
|
+
currency: 'EUR'
|
8
|
+
|
9
|
+
Buckaroo::Ideal::Config.stub(:test_mode)
|
10
|
+
.and_return(true)
|
11
|
+
|
12
|
+
Buckaroo::Ideal::Config.stub(:merchant_key)
|
13
|
+
.and_return('merchant_key')
|
14
|
+
|
15
|
+
Buckaroo::Ideal::Config.stub(:secret_key)
|
16
|
+
.and_return('secret_key')
|
17
|
+
|
18
|
+
signature = Buckaroo::Ideal::RequestSignature.new(order)
|
19
|
+
|
20
|
+
expected_salt = [
|
21
|
+
'merchant_key', # config.merchant_key
|
22
|
+
'EETNU-12345', # order.invoice_number
|
23
|
+
1250, # order.amount in cents
|
24
|
+
'EUR', # order.currency
|
25
|
+
1, # config.test_mode
|
26
|
+
'secret_key' # config.secret_key
|
27
|
+
].join
|
28
|
+
|
29
|
+
Digest::MD5.should_receive(:hexdigest)
|
30
|
+
.with(expected_salt)
|
31
|
+
|
32
|
+
signature.to_s
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buckaroo::Ideal::Request do
|
4
|
+
let(:order) { Buckaroo::Ideal::Order.new }
|
5
|
+
let(:request) { Buckaroo::Ideal::Request.new(order) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
Buckaroo::Ideal::Config.configure(
|
9
|
+
merchant_key: 'merchant_key',
|
10
|
+
secret_key: 'secret_key',
|
11
|
+
test_mode: true,
|
12
|
+
success_url: 'http://example.com/transaction/success',
|
13
|
+
reject_url: 'http://example.com/transaction/reject',
|
14
|
+
error_url: 'http://example.com/transaction/error',
|
15
|
+
return_method: 'GET',
|
16
|
+
style: 'POPUP',
|
17
|
+
autoclose_popup: true
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has a default language' do
|
22
|
+
request.language.should == 'NL'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'has a default success_url from the configuration' do
|
26
|
+
request.success_url.should == 'http://example.com/transaction/success'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'has a default reject_url from the configuration' do
|
30
|
+
request.reject_url.should == 'http://example.com/transaction/reject'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has a default error_url from the configuration' do
|
34
|
+
request.error_url.should == 'http://example.com/transaction/error'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has a default return_method from the configuration' do
|
38
|
+
request.return_method.should == 'GET'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has a default style from the configuration' do
|
42
|
+
request.style.should == 'POPUP'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'has a default autoclose_popup from the configuration' do
|
46
|
+
request.autoclose_popup.should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#gateway_url' do
|
50
|
+
it 'returns the configured gateway_url' do
|
51
|
+
request.gateway_url.should == Buckaroo::Ideal::Config.gateway_url
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#parameters' do
|
56
|
+
def parameters; request.parameters; end
|
57
|
+
|
58
|
+
it 'has a BPE_Merchant with the configured merchant_key' do
|
59
|
+
parameters['BPE_Merchant'].should == 'merchant_key'
|
60
|
+
|
61
|
+
Buckaroo::Ideal::Config.merchant_key = 'new_merchant_key'
|
62
|
+
parameters['BPE_Merchant'].should == 'new_merchant_key'
|
63
|
+
end
|
64
|
+
|
65
|
+
it "has a BPE_Amount with the order's amount in cents" do
|
66
|
+
order.amount = 19.95
|
67
|
+
parameters['BPE_Amount'].should == 1995
|
68
|
+
end
|
69
|
+
|
70
|
+
it "has a BPE_Currency with the order's currency" do
|
71
|
+
parameters['BPE_Currency'].should == 'EUR'
|
72
|
+
|
73
|
+
order.currency = 'BHT'
|
74
|
+
parameters['BPE_Currency'].should == 'BHT'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "has a BPE_Invoice with the order's invoice_number" do
|
78
|
+
order.invoice_number = 'INV001'
|
79
|
+
parameters['BPE_Invoice'].should == 'INV001'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'has a BPE_Return_Method with the return_method' do
|
83
|
+
parameters['BPE_Return_Method'].should == 'GET'
|
84
|
+
|
85
|
+
request.return_method = 'POST'
|
86
|
+
parameters['BPE_Return_Method'].should == 'POST'
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has a BPE_Style if the style is set' do
|
90
|
+
parameters['BPE_Style'].should == 'POPUP'
|
91
|
+
|
92
|
+
request.style = 'PAGE'
|
93
|
+
parameters['BPE_Style'].should == 'PAGE'
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'has a BPE_Autoclose_Popup if autoclose_popup is set' do
|
97
|
+
parameters['BPE_Autoclose_Popup'].should == 1
|
98
|
+
|
99
|
+
request.autoclose_popup = false
|
100
|
+
parameters['BPE_Autoclose_Popup'].should == 0
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'has a generated BPE_Signature2' do
|
104
|
+
parameters['BPE_Signature2'].length.should == 32
|
105
|
+
|
106
|
+
request.stub(:signature).and_return('signature')
|
107
|
+
|
108
|
+
parameters['BPE_Signature2'].should == 'signature'
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'has a BPE_Language with the language' do
|
112
|
+
parameters['BPE_Language'].should == 'NL'
|
113
|
+
|
114
|
+
request.language = 'DE'
|
115
|
+
parameters['BPE_Language'].should == 'DE'
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'has a BPE_Mode with the configured test_mode' do
|
119
|
+
parameters['BPE_Mode'].should == 1
|
120
|
+
|
121
|
+
Buckaroo::Ideal::Config.test_mode = false
|
122
|
+
parameters['BPE_Mode'].should == 0
|
123
|
+
end
|
124
|
+
|
125
|
+
it "has a BPE_Issuer if the order's bank is set" do
|
126
|
+
parameters.keys.should_not include 'BPE_Issuer'
|
127
|
+
|
128
|
+
order.bank = 'ABNAMRO'
|
129
|
+
parameters['BPE_Issuer'].should == 'ABNAMRO'
|
130
|
+
end
|
131
|
+
|
132
|
+
it "has a BPE_Description if the order's description is set" do
|
133
|
+
parameters.keys.should_not include 'BPE_Description'
|
134
|
+
|
135
|
+
order.description = 'Your Order Description'
|
136
|
+
parameters['BPE_Description'].should == 'Your Order Description'
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'has a BPE_Reference if the reference is set' do
|
140
|
+
parameters.keys.should_not include 'BPE_Reference'
|
141
|
+
|
142
|
+
order.reference = 'Reference'
|
143
|
+
parameters['BPE_Reference'].should == 'Reference'
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'has a BPE_Return_Success if the success_url is set' do
|
147
|
+
request.success_url = nil
|
148
|
+
parameters.keys.should_not include 'BPE_Return_Success'
|
149
|
+
|
150
|
+
request.success_url = 'http://example.org/'
|
151
|
+
parameters['BPE_Return_Success'].should == 'http://example.org/'
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'has a BPE_Return_Reject if the reject_url is set' do
|
155
|
+
request.reject_url = nil
|
156
|
+
parameters.keys.should_not include 'BPE_Return_Reject'
|
157
|
+
|
158
|
+
request.reject_url = 'http://example.org/'
|
159
|
+
parameters['BPE_Return_Reject'].should == 'http://example.org/'
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'has a BPE_Return_Error if the error_url is set' do
|
163
|
+
request.error_url = nil
|
164
|
+
parameters.keys.should_not include 'BPE_Return_Error'
|
165
|
+
|
166
|
+
request.error_url = 'http://example.org/'
|
167
|
+
parameters['BPE_Return_Error'].should == 'http://example.org/'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|