ipizza 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +18 -0
  5. data/README.markdown +101 -0
  6. data/Rakefile +25 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +1 -0
  9. data/init.rb +1 -0
  10. data/ipizza.gemspec +103 -0
  11. data/lib/ipizza.rb +13 -0
  12. data/lib/ipizza/authentication_request.rb +4 -0
  13. data/lib/ipizza/authentication_response.rb +24 -0
  14. data/lib/ipizza/config.rb +54 -0
  15. data/lib/ipizza/payment.rb +15 -0
  16. data/lib/ipizza/payment_request.rb +4 -0
  17. data/lib/ipizza/payment_response.rb +23 -0
  18. data/lib/ipizza/provider/nordea.rb +77 -0
  19. data/lib/ipizza/provider/nordea/authentication_request.rb +29 -0
  20. data/lib/ipizza/provider/nordea/authentication_response.rb +38 -0
  21. data/lib/ipizza/provider/nordea/payment_request.rb +33 -0
  22. data/lib/ipizza/provider/nordea/payment_response.rb +43 -0
  23. data/lib/ipizza/provider/sampo.rb +49 -0
  24. data/lib/ipizza/provider/seb.rb +81 -0
  25. data/lib/ipizza/provider/swedbank.rb +69 -0
  26. data/lib/ipizza/request.rb +17 -0
  27. data/lib/ipizza/response.rb +24 -0
  28. data/lib/ipizza/util.rb +74 -0
  29. data/spec/certificates/bank.crt +21 -0
  30. data/spec/certificates/bank.csr +17 -0
  31. data/spec/certificates/bank.key +27 -0
  32. data/spec/certificates/bank.pub +1 -0
  33. data/spec/certificates/dealer.key +30 -0
  34. data/spec/certificates/dealer.pub +1 -0
  35. data/spec/certificates/nordea_test_priv +1 -0
  36. data/spec/certificates/seb_test_priv.pem +16 -0
  37. data/spec/certificates/seb_test_pub.crt +20 -0
  38. data/spec/certificates/seb_test_pub.pem +20 -0
  39. data/spec/config/config.yml +38 -0
  40. data/spec/config/plain_config.yml +13 -0
  41. data/spec/ipizza/authentication_response_spec.rb +23 -0
  42. data/spec/ipizza/config_spec.rb +58 -0
  43. data/spec/ipizza/provider/nordea/authentication_response_spec.rb +41 -0
  44. data/spec/ipizza/provider/nordea/payment_request_spec.rb +4 -0
  45. data/spec/ipizza/provider/nordea/payment_response_spec.rb +4 -0
  46. data/spec/ipizza/provider/nordea_spec.rb +29 -0
  47. data/spec/ipizza/provider/sampo_spec.rb +4 -0
  48. data/spec/ipizza/provider/seb_spec.rb +46 -0
  49. data/spec/ipizza/provider/swedbank_spec.rb +46 -0
  50. data/spec/ipizza/util_spec.rb +21 -0
  51. data/spec/spec_helper.rb +9 -0
  52. data/spec/support/pizza.rb +8 -0
  53. metadata +129 -0
@@ -0,0 +1,29 @@
1
+ require 'digest/md5'
2
+
3
+ module Ipizza::Provider
4
+ class Nordea::AuthenticationRequest < Ipizza::AuthenticationRequest
5
+
6
+ attr_accessor :params
7
+ attr_accessor :service_url
8
+
9
+ def sign(key_path)
10
+ key = File.read(key_path).strip
11
+ params['A01Y_MAC'] = Digest::MD5.hexdigest(mac_data_string(key)).upcase
12
+ end
13
+
14
+ def request_params
15
+ params.inject(Hash.new) { |h, v| h["A01Y_#{v.first}"] = v.last; h }
16
+ end
17
+
18
+ private
19
+
20
+ def mac_data_string(key)
21
+ order = ['ACTION_ID', 'VERS', 'RCVID', 'LANGCODE', 'STAMP', 'IDTYPE', 'RETLINK', 'CANLINK', 'REJLINK', 'KEYVERS', 'ALG']
22
+
23
+ datastr = order.inject('') do |memo, param|
24
+ memo << params[param].to_s << '&'
25
+ end
26
+ datastr << key << '&'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ require 'digest/md5'
2
+
3
+ module Ipizza::Provider
4
+ class Nordea::AuthenticationResponse < Ipizza::AuthenticationResponse
5
+
6
+ def initialize(params)
7
+ @params = params
8
+ end
9
+
10
+ def verify(key_path)
11
+ key = File.read(key_path).strip
12
+ @valid = @params['B02K_MAC'] == Digest::MD5.hexdigest(mac_data_string(key)).upcase
13
+ end
14
+
15
+ def success?
16
+ @valid && @params['B02K_CUSTID'].present?
17
+ end
18
+
19
+ def valid?
20
+ @valid
21
+ end
22
+
23
+ def user_info
24
+ valid? ? {'name' => @params['B02K_CUSTNAME'], 'custid' => @params['B02K_CUSTID']} : {}
25
+ end
26
+
27
+ private
28
+
29
+ def mac_data_string(key)
30
+ order = ['VERS', 'TIMESTMP', 'IDNBR', 'STAMP', 'CUSTNAME', 'KEYVERS', 'ALG', 'CUSTID', 'CUSTTYPE']
31
+
32
+ datastr = order.inject('') do |memo, param|
33
+ memo << @params["B02K_#{param}"].to_s << '&'
34
+ end
35
+ datastr << key << '&'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ require 'digest/md5'
2
+
3
+ module Ipizza::Provider
4
+ class Nordea::PaymentRequest < Ipizza::PaymentRequest
5
+
6
+ attr_accessor :params
7
+ attr_accessor :service_url
8
+
9
+ def sign(key_path)
10
+ key = File.read(key_path).strip
11
+ self.params['MAC'] = Digest::MD5.hexdigest(mac_data_string(key)).upcase
12
+ end
13
+
14
+ def request_params
15
+ params
16
+ end
17
+
18
+ private
19
+
20
+ def mac_data_string(key)
21
+ order = ['VERSION', 'STAMP', 'RCV_ID', 'AMOUNT', 'REF', 'DATE', 'CUR']
22
+
23
+ datastr = order.inject('') do |memo, param|
24
+ memo << params[param].to_s
25
+ memo << '&'
26
+ memo
27
+ end
28
+ datastr << key
29
+ datastr << '&'
30
+ datastr
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ require 'digest/md5'
2
+
3
+ module Ipizza::Provider
4
+ class Nordea::PaymentResponse < Ipizza::PaymentResponse
5
+
6
+ def initialize(params)
7
+ @params = params
8
+ end
9
+
10
+ def verify(key_path)
11
+ key = File.read(key_path)
12
+ @valid = @params['RETURN_MAC'] == Digest::MD5.hexdigest(mac_data_string(key)).upcase
13
+ end
14
+
15
+ def success?
16
+ if @valid && !@params['RETURN_PAID'].blank? then true else false end
17
+ end
18
+
19
+ def valid?
20
+ @valid
21
+ end
22
+
23
+ def payment_info
24
+ @payment_info ||= Ipizza::Payment.new(:stamp => @params['RETURN_STAMP'], :refnum => @params['RETURN_REF'])
25
+ end
26
+
27
+ private
28
+
29
+ def mac_data_string(key)
30
+ order = ['RETURN_VERSION', 'RETURN_STAMP', 'RETURN_REF', 'RETURN_PAID']
31
+
32
+ datastr = order.inject('') do |memo, param|
33
+ memo << @params[param].to_s
34
+ memo << '&'
35
+ memo
36
+ end
37
+ datastr << key
38
+ datastr << '&'
39
+
40
+ datastr
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+ module Ipizza::Provider
2
+ class Sampo
3
+
4
+ class << self
5
+ attr_accessor :service_url, :return_url, :file_key, :key_secret, :file_cert, :snd_id, :lang, :rec_acc, :rec_name
6
+ end
7
+
8
+ def payment_request(payment, service = 1002)
9
+ req = Ipizza::PaymentRequest.new
10
+ req.service_url = self.service_url
11
+ req.sign_params = {
12
+ 'VK_SERVICE' => service.to_s,
13
+ 'VK_VERSION' => '008',
14
+ 'VK_SND_ID' => self.snd_id,
15
+ 'VK_STAMP' => payment.stamp,
16
+ 'VK_AMOUNT' => sprintf('%.2f', payment.amount),
17
+ 'VK_CURR' => payment.currency,
18
+ 'VK_REF' => Ipizza::Util.sign_731(payment.refnum),
19
+ 'VK_MSG' => payment.message
20
+ }
21
+
22
+ if service == 1001
23
+ req.sign_params['VK_ACC'] = self.rec_acc
24
+ req.sign_params['VK_NAME'] = self.rec_name
25
+ end
26
+
27
+ req.extra_params = {
28
+ 'VK_RETURN' => self.return_url,
29
+ 'VK_LANG' => self.lang
30
+ }
31
+
32
+ if service == 1001
33
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_STAMP', 'VK_AMOUNT', 'VK_CURR', 'VK_ACC', 'VK_NAME', 'VK_REF', 'VK_MSG']
34
+ else
35
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_STAMP', 'VK_AMOUNT', 'VK_CURR', 'VK_REF', 'VK_MSG']
36
+ end
37
+
38
+ req.sign(self.key, self.key_secret, param_order)
39
+ req
40
+ end
41
+
42
+ def payment_response(params)
43
+ response = Ipizza::PaymentResponse.new(params, Ipizza::Util::SAMPO)
44
+ response.verify(cert)
45
+
46
+ return response
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,81 @@
1
+ module Ipizza::Provider
2
+ class Seb
3
+
4
+ class << self
5
+ attr_accessor :service_url, :return_url, :cancel_url, :file_key, :key_secret, :file_cert, :snd_id, :encoding, :rec_acc, :rec_name
6
+ end
7
+
8
+ def payment_request(payment, service = 1002)
9
+ req = Ipizza::PaymentRequest.new
10
+ req.service_url = self.class.service_url
11
+ req.sign_params = {
12
+ 'VK_SERVICE' => service.to_s,
13
+ 'VK_VERSION' => '008',
14
+ 'VK_SND_ID' => self.class.snd_id,
15
+ 'VK_STAMP' => payment.stamp,
16
+ 'VK_AMOUNT' => sprintf('%.2f', payment.amount),
17
+ 'VK_CURR' => payment.currency,
18
+ 'VK_REF' => Ipizza::Util.sign_731(payment.refnum),
19
+ 'VK_MSG' => payment.message
20
+ }
21
+
22
+ # TODO: add 1001 support
23
+ # if service == 1001
24
+ # req.sign_params['VK_ACC'] = self.rec_acc
25
+ # req.sign_params['VK_NAME'] = self.rec_name
26
+ # end
27
+
28
+ req.extra_params = {
29
+ 'VK_CHARSET' => self.class.encoding,
30
+ 'VK_RETURN' => self.class.return_url,
31
+ 'VK_CANCEL' => self.class.cancel_url
32
+ }
33
+
34
+ if service == 1001
35
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_STAMP', 'VK_AMOUNT', 'VK_CURR', 'VK_ACC', 'VK_NAME', 'VK_REF', 'VK_MSG']
36
+ else
37
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_STAMP', 'VK_AMOUNT', 'VK_CURR', 'VK_REF', 'VK_MSG']
38
+ end
39
+
40
+ req.sign(self.class.file_key, self.class.key_secret, param_order)
41
+ req
42
+ end
43
+
44
+ def payment_response(params)
45
+ response = Ipizza::PaymentResponse.new(params, Ipizza::Util::SEB)
46
+ response.verify(cert)
47
+
48
+ return response
49
+ end
50
+
51
+ def authentication_request(service_no = 4001)
52
+ req = Ipizza::AuthenticationRequest.new
53
+ req.service_url = self.class.service_url
54
+ req.sign_params = {
55
+ 'VK_SERVICE' => service_no,
56
+ 'VK_VERSION' => '008',
57
+ 'VK_SND_ID' => self.class.snd_id,
58
+ 'VK_REPLY' => '3002',
59
+ 'VK_RETURN' => self.class.return_url,
60
+ 'VK_DATE' => Date.today.strftime('%d.%m.%Y'),
61
+ 'VK_TIME' => Time.now.strftime('%H:%M:%S')
62
+ }
63
+
64
+ req.extra_params = {
65
+ 'VK_CHARSET' => self.class.encoding
66
+ }
67
+
68
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_REPLY', 'VK_RETURN', 'VK_DATE', 'VK_TIME']
69
+
70
+ req.sign(self.class.file_key, self.class.key_secret, param_order)
71
+ req
72
+ end
73
+
74
+ def authentication_response(params)
75
+ response = Ipizza::AuthenticationResponse.new(params)
76
+ response.verify(self.class.file_cert, self.class.encoding)
77
+ return response
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,69 @@
1
+ module Ipizza::Provider
2
+ class Swedbank
3
+
4
+ class << self
5
+ attr_accessor :service_url, :return_url, :cancel_url, :file_key, :key_secret, :file_cert, :snd_id, :encoding
6
+ end
7
+
8
+ def payment_request(payment, service = 1002)
9
+ req = Ipizza::PaymentRequest.new
10
+ req.service_url = self.class.service_url
11
+ req.sign_params = {
12
+ 'VK_SERVICE' => '1002',
13
+ 'VK_VERSION' => '008',
14
+ 'VK_SND_ID' => self.class.snd_id,
15
+ 'VK_STAMP' => payment.stamp,
16
+ 'VK_AMOUNT' => sprintf('%.2f', payment.amount),
17
+ 'VK_CURR' => payment.currency,
18
+ 'VK_REF' => Ipizza::Util.sign_731(payment.refnum),
19
+ 'VK_MSG' => payment.message
20
+ }
21
+
22
+ req.extra_params = {
23
+ 'VK_CHARSET' => self.class.encoding,
24
+ 'VK_RETURN' => self.class.return_url,
25
+ 'VK_CANCEL' => self.class.cancel_url
26
+ }
27
+
28
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_STAMP', 'VK_AMOUNT', 'VK_CURR', 'VK_REF', 'VK_MSG']
29
+
30
+ req.sign(self.class.file_key, self.class.key_secret, param_order)
31
+ req
32
+ end
33
+
34
+ def payment_response(params)
35
+ response = Ipizza::PaymentResponse.new(params)
36
+ response.verify(self.class.file_cert, self.class.encoding)
37
+ return response
38
+ end
39
+
40
+ def authentication_request(service_no = 4001)
41
+ req = Ipizza::AuthenticationRequest.new
42
+ req.service_url = self.class.service_url
43
+ req.sign_params = {
44
+ 'VK_SERVICE' => service_no,
45
+ 'VK_VERSION' => '008',
46
+ 'VK_SND_ID' => self.class.snd_id,
47
+ 'VK_REPLY' => '3002',
48
+ 'VK_RETURN' => self.class.return_url,
49
+ 'VK_DATE' => Date.today.strftime('%d.%m.%Y'),
50
+ 'VK_TIME' => Time.now.strftime('%H:%M:%S')
51
+ }
52
+
53
+ req.extra_params = {
54
+ 'VK_ENCODING' => self.class.encoding
55
+ }
56
+
57
+ param_order = ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_REPLY', 'VK_RETURN', 'VK_DATE', 'VK_TIME']
58
+
59
+ req.sign(self.class.file_key, self.class.key_secret, param_order)
60
+ req
61
+ end
62
+
63
+ def authentication_response(params)
64
+ response = Ipizza::AuthenticationResponse.new(params)
65
+ response.verify(self.class.file_cert, self.class.encoding)
66
+ return response
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ module Ipizza
2
+ class Request
3
+
4
+ attr_accessor :extra_params
5
+ attr_accessor :sign_params
6
+ attr_accessor :service_url
7
+
8
+ def sign(privkey_path, privkey_secret, order, mac_param = 'VK_MAC')
9
+ signature = Ipizza::Util.sign(privkey_path, privkey_secret, Ipizza::Util.mac_data_string(sign_params, order))
10
+ self.sign_params[mac_param] = signature
11
+ end
12
+
13
+ def request_params
14
+ sign_params.merge(extra_params)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ class Ipizza::Response
2
+
3
+ attr_accessor :verify_params
4
+ attr_accessor :verify_params_order
5
+
6
+ @@response_param_order = {
7
+ '1101' => ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_REC_ID', 'VK_STAMP', 'VK_T_NO', 'VK_AMOUNT', 'VK_CURR', 'VK_REC_ACC', 'VK_REC_NAME', 'VK_SND_ACC', 'VK_SND_NAME', 'VK_REF', 'VK_MSG', 'VK_T_DATE'],
8
+ '3002' => ['VK_SERVICE', 'VK_VERSION', 'VK_USER', 'VK_DATE', 'VK_TIME', 'VK_SND_ID', 'VK_INFO'],
9
+ '1901' => ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_REC_ID', 'VK_STAMP', 'VK_REF', 'VK_MSG'],
10
+ '1902' => ['VK_SERVICE', 'VK_VERSION', 'VK_SND_ID', 'VK_REC_ID', 'VK_STAMP', 'VK_REF', 'VK_MSG', 'VK_ERROR_CODE']
11
+ }
12
+
13
+ def initialize(params)
14
+ @params = params
15
+ end
16
+
17
+ def verify(certificate_path, charset = 'UTF-8')
18
+ param_order = @@response_param_order[@params['VK_SERVICE']]
19
+ verify_params = param_order.inject(Hash.new) { |h, p| h[p] = @params[p]; h }
20
+ mac_string = Ipizza::Util.mac_data_string(verify_params, param_order, 'UTF-8', charset)
21
+
22
+ @valid = Ipizza::Util.verify_signature(certificate_path, @params['VK_MAC'], mac_string)
23
+ end
24
+ end
@@ -0,0 +1,74 @@
1
+ require 'base64'
2
+ require 'iconv'
3
+ require 'openssl'
4
+
5
+ module Ipizza
6
+ class Util
7
+
8
+ SWEDBANK = 'swedbank'
9
+ SEB = 'seb'
10
+ SAMPO = 'sampo'
11
+ NORDEA = 'nordea'
12
+
13
+ class << self
14
+
15
+ def verify_signature(certificate_path, signature, data)
16
+ certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path).gsub(/ /, '')).public_key
17
+ @valid = certificate.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), data)
18
+ end
19
+
20
+ def sign(privkey_path, privkey_secret, data)
21
+ privkey = File.open(privkey_path, 'r') { |f| f.read }
22
+ privkey = OpenSSL::PKey::RSA.new(privkey.gsub(/ /, ''), privkey_secret)
23
+
24
+ signature = privkey.sign(OpenSSL::Digest::SHA1.new, data)
25
+ signature = Base64.encode64(signature).gsub(/\n/, '')
26
+ end
27
+
28
+ # Calculates and adds control number using 7-3-1 algoritm for Estonian banking account and reference numbers.
29
+ def sign_731(ref_num)
30
+ arr = ref_num.to_s.reverse!.split('')
31
+ m = 0
32
+ r = 0
33
+
34
+ arr.each do |e|
35
+ m = case m
36
+ when 7:
37
+ 3
38
+ when 3:
39
+ 1
40
+ else
41
+ 7
42
+ end
43
+ r = r + (e.to_i * m)
44
+ end
45
+ c = ((r + 9) / 10).to_f.truncate * 10 - r
46
+ arr.reverse! << c
47
+ arr.join
48
+ end
49
+
50
+ # Produces string to be signed out of service message parameters.
51
+ #
52
+ # p(x1)||x1||p(x2)||x2||...||p(xn)||xn
53
+ #
54
+ # Where || is string concatenation, p(x) is length of the field x represented by three digits.
55
+ #
56
+ # Parameters val1, val2, value3 would be turned into "003val1003val2006value3".
57
+ def mac_data_string(params, sign_param_order, to_charset = 'UTF-8', from_charset = 'UTF-8')
58
+ sign_param_order.inject('') do |memo, param|
59
+ val = params[param].to_s
60
+ val = Iconv.conv(to_charset, from_charset, val)
61
+ memo << func_p(val) << val
62
+ memo
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # p(x) is length of the field x represented by three digits
69
+ def func_p(val)
70
+ sprintf("%03i", val.size)
71
+ end
72
+ end
73
+ end
74
+ end