Skip to content

Commit

Permalink
feat: add support for Luminor
Browse files Browse the repository at this point in the history
  • Loading branch information
priithaamer committed Sep 15, 2021
1 parent 7511ab4 commit 29c5b90
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 93 deletions.
2 changes: 1 addition & 1 deletion ipizza.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Gem::Specification.new do |s|
s.homepage = 'https://github.com/Voog/ipizza'
s.summary = 'Implements iPizza protocol to communicate with Estonian Banks'
s.description = 'Simplifies generating payment requests and parsing responses from banks when using iPizza protocol.'

s.add_development_dependency 'rspec', '~> 2.9.0'
s.add_development_dependency 'guard-rspec'
s.add_development_dependency 'rb-fsevent'
Expand Down
1 change: 1 addition & 0 deletions lib/ipizza.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
require 'ipizza/provider/sampo'
require 'ipizza/provider/krediidipank'
require 'ipizza/provider/nordea'
require 'ipizza/provider/luminor'
22 changes: 11 additions & 11 deletions lib/ipizza/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ class << self

def load_from_file(yaml_path)
@certs_root = File.dirname(yaml_path)

load_from_hash(YAML::load_file(yaml_path))
end

def load_from_hash(config)
config.each do |bank, params|
params.each do |param, value|
Expand All @@ -21,43 +21,43 @@ def load_from_hash(config)
end
end
end

def configure
yield self
end

def method_missing(m, *args)
if /^(lhv|swedbank|seb|sampo|krediidipank|nordea)_(.*)=$/ =~ m.to_s
if /^(lhv|swedbank|seb|sampo|krediidipank|nordea|luminor)_(.*)=$/ =~ m.to_s
clz = Ipizza::Provider.const_get($1.capitalize)
key = $2
value = args.first

value = load_certificate(value) if /^file_(cert|key)/ =~ key

if clz.respond_to?(:"#{key}=")
return clz.send(:"#{key}=", *[value])
end
end

super
end

private

def load_certificate(file_path)
if File.exist?(file_path)
file_path
else
file_path = File.expand_path(File.join(certs_root, file_path))
end

if File.exist?(file_path)
file_path
else
raise "Could not load certificate from file '#{file_path}'"
end
end

end
end
end
4 changes: 2 additions & 2 deletions lib/ipizza/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def get(provider_name)
Ipizza::Provider::Sampo.new
when 'krep', 'krediidipank'
Ipizza::Provider::Krediidipank.new
when 'nordea'
Ipizza::Provider::Nordea.new
when 'luminor', 'testluminor'
Ipizza::Provider::Luminor.new
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/ipizza/provider/luminor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Ipizza::Provider
class Luminor < Ipizza::Provider::Base
end
end
75 changes: 1 addition & 74 deletions lib/ipizza/provider/nordea.rb
Original file line number Diff line number Diff line change
@@ -1,77 +1,4 @@
module Ipizza::Provider

# TODO: configure whether use sha-1 or md5 for signing and verification
class Nordea

require 'ipizza/provider/nordea/payment_request'
require 'ipizza/provider/nordea/payment_response'
require 'ipizza/provider/nordea/authentication_request'
require 'ipizza/provider/nordea/authentication_response'

class << self
attr_accessor :payments_service_url, :payments_return_url, :payments_reject_url, :payments_cancel_url
attr_accessor :payments_rcv_id, :payments_language
attr_accessor :auth_service_url, :auth_return_url, :auth_reject_url, :auth_cancel_url, :auth_language
attr_accessor :auth_rcv_id
attr_accessor :file_key, :rcv_account, :rcv_name, :confirm, :keyvers
end

def payment_request(payment, service = 1002)
req = Ipizza::Provider::Nordea::PaymentRequest.new
req.service_url = self.class.payments_service_url
req.params = {
'VERSION' => '0003',
'STAMP' => payment.stamp,
'RCV_ID' => self.class.payments_rcv_id,
# 'RCV_ACCOUNT' => self.rcv_account,
# 'RCV_NAME' => self.rcv_name,
'LANGUAGE' => self.class.payments_language,
'AMOUNT' => sprintf('%.2f', payment.amount),
'REF' => Ipizza::Util.sign_731(payment.refnum),
'DATE' => 'EXPRESS',
'MSG' => payment.message,
'CONFIRM' => self.class.confirm,
'CUR' => payment.currency,
'KEYVERS' => self.class.keyvers,
'REJECT' => self.class.payments_reject_url,
'RETURN' => self.class.payments_return_url,
'CANCEL' => self.class.payments_cancel_url
}

req.sign(self.class.file_key)
req
end

def payment_response(params)
response = Ipizza::Provider::Nordea::PaymentResponse.new(params)
response.verify(self.class.file_key)
return response
end

def authentication_request
req = Ipizza::Provider::Nordea::AuthenticationRequest.new
req.service_url = self.class.auth_service_url
req.params = {
'ACTION_ID' => '701',
'VERS' => '0002',
'RCVID' => self.class.auth_rcv_id,
'LANGCODE' => self.class.auth_language,
'STAMP' => Time.now.strftime('%Y%m%d%H%M%S'),
'IDTYPE' => '02',
'KEYVERS' => self.class.keyvers,
'RETLINK' => self.class.auth_return_url,
'CANLINK' => self.class.auth_cancel_url,
'REJLINK' => self.class.auth_reject_url,
'ALG' => '01'
}
req.sign(self.class.file_key)
req
end

def authentication_response(params)
response = Ipizza::Provider::Nordea::AuthenticationResponse.new(params)
response.verify(self.class.file_key)
return response
end
class Nordea < Ipizza::Provider::Base
end
end
2 changes: 1 addition & 1 deletion lib/ipizza/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Ipizza
VERSION = '2.0.1'
VERSION = '2.1.0'
end
18 changes: 18 additions & 0 deletions spec/certificates/pangalink_luminor_bank.cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC+jCCAeICCQDuCELf8TcV7DANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJF
RTEOMAwGA1UECAwFVGFydHUxDjAMBgNVBAcMBVRhcnR1MRAwDgYDVQQKDAdNaWxl
ZWRpMB4XDTIwMTIwMzA5NDkxMloXDTI1MTIwMTA5NDkxMlowPzELMAkGA1UEBhMC
RUUxDjAMBgNVBAgMBVRhcnR1MQ4wDAYDVQQHDAVUYXJ0dTEQMA4GA1UECgwHTWls
ZWVkaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALmozE2DIZyCddrW
AVoVkNWYRYDytAi5lefa5+PEAQLGFzzfTBRnClla/Qerfe4NBGjyiwu8W6VeLGQ9
+GGDw5uEQumII2t0f1vpKWiauyfM+MlSOB6cobf9bwlgs9gBss/PEAa8E4MhpS60
p7KNfR1qcq3AWgxpjE9ns7KheARW1q0vNZFzPkONyleZ4HJIC8rlta38/VMvbPnp
PLbMnVOvrOsxeDMCgGpD9hupxVsFnhsv/Y4F14W7f4XHl36FhDjP8dkrgrkQE4E1
dRnxx6GC4Lv71rQDu+gmgyKtZWmsym1b1Tzw3aCF27J1EReHvCxtkDfMH1VzQSad
FGF4yXsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAQfs7p7MLZqtm9QxBkGcYALDw
q/f+5b+n1uPdFcXu8K26hG6RiVZTXUJrIqR71kGLG0mBJ3V6+vAM9gjsUJs20uiZ
qvdV2xQglTpJlNTG4Je3Sq4Lbyz4w+8e9VVrE3atfARHxA5FIewCviJNdXS+xeuh
GqKaGqLSf4sJlbe3hE69hzlDXmzUE+/OhoaSPS3zFAzcp9m50o7InhYy80eKDRFS
hBIDbLnyzb+97If9BMOhqQ/nfCrsX6TiTiqhIFB+qNSIZt733fx9AD3AwSxM51RC
V232swdtXx9d39S/DskhMkXkOiqpr+0P87Qyy8Y0/FoWO57i/jXb0uCccPatdw==
-----END CERTIFICATE-----
17 changes: 17 additions & 0 deletions spec/certificates/pangalink_luminor_bank_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICnzCCAYegAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDDAhCYW5r
bGluazAeFw0yMDEyMDMxMDUwNDJaFw0yMjEyMDMxMDUwNDJaMBMxETAPBgNVBAMM
CEJhbmtsaW5rMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgI7m13+Z
2B3aBtXE4/2YmsG9koPpD7RHj5B7uEozBCNM1MhfYoE7R7vP1tPm7lD/oJxKk0G3
gpODtGDqo+kMfI+TlJ/w2KzwS5YPEop7aliEQ1bbwjjjY2tXYwsBYa0uRvkbwizH
lp3PHmr4DBl+JxyETIWGX623nZxZcHAvwcM6VpTtd9+KEpgToQ2gPHHj4/svQfwm
mzhdEwFZjwjCaOEHssaUcLsyvKVoJ5OJqBWS1e0mVqWsuJJJnkeG7NVXY2LWDDfP
iGogVz9hqqxfNKhCTLGMCG8k8VDQdLTaR+vvmXSUBLh+4TbFA7ZPLSvgbRu3995F
CCY/wADRophRVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAcTtcH4m//FyGrNC2h
rcry6wt843xclEkLQSSNGz9wVCQW/D8I0VNjFPk4PgoUh4bTr+mKrZ6UrrIwBuO6
oFj2Nh+Y1wgkpd3LMKae5DvWLE4taZ4ylBGSUXOpQ062+pOtUldZQxBiczaUJWm4
4Ns+27Zi5oGyTNnlZXfguEnt54KA+63S4aiHWUSaaXkRN62LeNuyrX0ZdITEfTaz
0ZvVcn6FbL6OnsqoZn2MF9+xZfMqlu/K5HfGl928v0fzY8/jQygxUoGFIBfxhn46
NniPM9xtnPSu11ut1n0PQPP8K94+MUJz7GDw9o9z3X0zgy8SOFSpY8HNp0MEjN7l
MT8n
-----END CERTIFICATE-----
11 changes: 11 additions & 0 deletions spec/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ seb:
encoding: UTF-8
snd_id: sender

luminor:
service_url: https://banklink.luminor.ee/test
return_url: http://test.local/seb
cancel_url: http://test.local/seb
login: dealer
file_cert: ../certificates/pangalink_seb_bank_cert.pem
file_key: ../certificates/pangalink_seb_user_key.pem
key_secret: foobar
encoding: UTF-8
snd_id: sender

nordea:
payments_service_url: https://netbank.nordea.com/pnbepaytest/epayn.jsp
payments_return_url: http://test.local/nordea
Expand Down
89 changes: 89 additions & 0 deletions spec/ipizza/provider/luminor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

describe Ipizza::Provider::Luminor do
let(:response_time) { Ipizza::Util.time_to_iso8601(Time.now) }
let(:bank_key) { File.expand_path('../../../certificates/pangalink_luminor_bank_key.pem', __FILE__) }

describe '#payment_request' do
let(:payment) { Ipizza::Payment.new(stamp: 1, amount: '123.34', refnum: 1, message: 'Payment message', currency: 'EUR') }

before(:each) do
req_time = Time.now
Time.stub!(:now).and_return(req_time)
end

it 'should sign the request' do
req = Ipizza::Provider::Luminor.new.payment_request(payment)
params = {
'VK_SERVICE' => '1012',
'VK_VERSION' => '008',
'VK_SND_ID' => Ipizza::Provider::Luminor.snd_id,
'VK_STAMP' => payment.stamp,
'VK_AMOUNT' => sprintf('%.2f', payment.amount),
'VK_CURR' => payment.currency,
'VK_REF' => Ipizza::Util.sign_731(payment.refnum),
'VK_MSG' => payment.message,
'VK_RETURN' => Ipizza::Provider::Luminor.return_url,
'VK_CANCEL' => Ipizza::Provider::Luminor.cancel_url,
'VK_DATETIME' => Ipizza::Util.time_to_iso8601(Time.now)
}
signature = Ipizza::Util.sign(Ipizza::Provider::Luminor.file_key, Ipizza::Provider::Luminor.key_secret, Ipizza::Util.mac_data_string(params, Ipizza::Request::PARAM_ORDER['1012']))
req.sign_params['VK_MAC'].should == signature
end
end

describe '#payment_response' do
let(:params) {
{
'VK_SERVICE' => '1111', 'VK_VERSION' => '008', 'VK_SND_ID' => 'LUMINOR', 'VK_REC_ID' => 'sender',
'VK_STAMP' => '20150111000004', 'VK_T_NO' => '1143', 'VK_AMOUNT' => '.17', 'VK_CURR' => 'EUR',
'VK_REC_ACC' => 'EE411010002050618003', 'VK_REC_NAME' => 'ÕILIS OÜ',
'VK_SND_ACC' => 'EE541010010046155012', 'VK_SND_NAME' => 'TÕÄGER Leõpäöld¸´¨¦',
'VK_REF' => '201501110000048', 'VK_MSG' => 'Invoice #20150111000004', 'VK_T_DATETIME' => response_time,
'VK_ENCODING' => 'UTF-8', 'VK_LANG' => 'EST', 'VK_AUTO' => 'N'
}
}

it 'should parse and verify the payment response from bank' do
signature = Ipizza::Util.sign(bank_key, nil, Ipizza::Util.mac_data_string(params, Ipizza::Response::PARAM_ORDER['1111']))
Ipizza::Provider::Luminor.new.payment_response(params.merge('VK_MAC' => signature)).should be_valid
end
end

describe '#authentication_request' do
before(:each) do
req_time = Time.now
Time.stub!(:now).and_return(req_time)
end

it 'should sign the request' do
req = Ipizza::Provider::Luminor.new.authentication_request
params = {
'VK_SERVICE' => '4011',
'VK_VERSION' => '008',
'VK_SND_ID' => Ipizza::Provider::Luminor.snd_id,
'VK_RETURN' => Ipizza::Provider::Luminor.return_url,
'VK_DATETIME' => Ipizza::Util.time_to_iso8601(Time.now),
'VK_RID' => '',
'VK_REPLY' => '3012'
}
signature = Ipizza::Util.sign(Ipizza::Provider::Luminor.file_key, Ipizza::Provider::Luminor.key_secret, Ipizza::Util.mac_data_string(params, Ipizza::Request::PARAM_ORDER['4011']))
req.sign_params['VK_MAC'].should == signature
end
end

describe '#authentication_response' do
let(:params) {
{
'VK_SERVICE' => '3012', 'VK_VERSION' => '008', 'VK_USER' => 'dealer', 'VK_DATETIME' => response_time,
'VK_SND_ID' => 'LUMINOR', 'VK_REC_ID' => 'sender', 'VK_USER_NAME' => 'TÕÄGER Leõpäöld¸´¨¦', 'VK_USER_ID' => '35511280268',
'VK_COUNTRY' => 'EE', 'VK_OTHER' => '', 'VK_TOKEN' => '7', 'VK_RID' => ''
}
}

it 'should parse and verify the authentication response from bank' do
signature = Ipizza::Util.sign(bank_key, nil, Ipizza::Util.mac_data_string(params, Ipizza::Response::PARAM_ORDER['3012']))
Ipizza::Provider::Luminor.new.authentication_response(params.merge('VK_MAC' => signature)).should be_valid
end
end
end
16 changes: 12 additions & 4 deletions spec/ipizza/provider_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
it 'returns swedbank provider for "swedbank" attribute' do
Ipizza::Provider.get('swedbank').should be_a(Ipizza::Provider::Swedbank)
end

it 'returns swedbank provider for "hp" attribute' do
Ipizza::Provider.get('hp').should be_a(Ipizza::Provider::Swedbank)
end

it 'returns seb provider for "eyp" attribute' do
Ipizza::Provider.get('eyp').should be_a(Ipizza::Provider::Seb)
end
Expand All @@ -22,6 +22,14 @@
Ipizza::Provider.get('seb').should be_a(Ipizza::Provider::Seb)
end

it 'returns luminor provider for "luminor" attribute' do
Ipizza::Provider.get('luminor').should be_a(Ipizza::Provider::Luminor)
end

it 'returns luminor provider for "testluminor" attribute' do
Ipizza::Provider.get('luminor').should be_a(Ipizza::Provider::Luminor)
end

it 'returns sampo provider for "sampo" attribute' do
Ipizza::Provider.get('sampo').should be_a(Ipizza::Provider::Sampo)
end
Expand All @@ -33,11 +41,11 @@
it 'returns krediidipank provider for "krep" attribute' do
Ipizza::Provider.get('krep').should be_a(Ipizza::Provider::Krediidipank)
end

it 'returns krediidipank provider for "krediidipank" attribute' do
Ipizza::Provider.get('krediidipank').should be_a(Ipizza::Provider::Krediidipank)
end

it 'returns nordea provider for "nordea" attribute' do
Ipizza::Provider.get('nordea').should be_a(Ipizza::Provider::Nordea)
end
Expand Down

0 comments on commit 29c5b90

Please sign in to comment.