ipizza 0.4.0

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.
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