hbci 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +26 -0
- data/.rubocop_todo.yml +26 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +107 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/examples/credentials.rb +62 -0
- data/examples/get_accounts.rb +11 -0
- data/examples/get_balance.rb +8 -0
- data/examples/get_system_id.rb +6 -0
- data/examples/get_transactions.rb +15 -0
- data/hbci.gemspec +36 -0
- data/lib/development_profiler.rb +26 -0
- data/lib/hbci.rb +66 -0
- data/lib/hbci/connector.rb +33 -0
- data/lib/hbci/dialog.rb +77 -0
- data/lib/hbci/element_group.rb +93 -0
- data/lib/hbci/element_groups/generated_element_groups.rb +969 -0
- data/lib/hbci/element_groups/segment_head.rb +12 -0
- data/lib/hbci/element_groups/unknown.rb +11 -0
- data/lib/hbci/element_unparser.rb +21 -0
- data/lib/hbci/message.rb +47 -0
- data/lib/hbci/message_factory.rb +17 -0
- data/lib/hbci/parser.rb +114 -0
- data/lib/hbci/request.rb +32 -0
- data/lib/hbci/response.rb +25 -0
- data/lib/hbci/segment.rb +118 -0
- data/lib/hbci/segment_factory.rb +15 -0
- data/lib/hbci/segments/hikaz.rb +29 -0
- data/lib/hbci/segments/hikazs.rb +31 -0
- data/lib/hbci/segments/hirmg.rb +10 -0
- data/lib/hbci/segments/hirms.rb +17 -0
- data/lib/hbci/segments/hisal.rb +100 -0
- data/lib/hbci/segments/hisals.rb +19 -0
- data/lib/hbci/segments/hisyn.rb +11 -0
- data/lib/hbci/segments/hiupa.rb +12 -0
- data/lib/hbci/segments/hiupd.rb +37 -0
- data/lib/hbci/segments/hkend.rb +14 -0
- data/lib/hbci/segments/hkidn.rb +22 -0
- data/lib/hbci/segments/hkkaz.rb +25 -0
- data/lib/hbci/segments/hksal.rb +31 -0
- data/lib/hbci/segments/hksyn.rb +12 -0
- data/lib/hbci/segments/hkvvb.rb +18 -0
- data/lib/hbci/segments/hnhbk.rb +27 -0
- data/lib/hbci/segments/hnhbs.rb +14 -0
- data/lib/hbci/segments/hnsha.rb +31 -0
- data/lib/hbci/segments/hnshk.rb +79 -0
- data/lib/hbci/segments/hnvsd.rb +50 -0
- data/lib/hbci/segments/hnvsk.rb +60 -0
- data/lib/hbci/segments/segment_head.rb +11 -0
- data/lib/hbci/segments/unknown.rb +21 -0
- data/lib/hbci/services/accounts_receiver.rb +19 -0
- data/lib/hbci/services/balance_receiver.rb +69 -0
- data/lib/hbci/services/base_receiver.rb +43 -0
- data/lib/hbci/services/system_id_receiver.rb +35 -0
- data/lib/hbci/services/transactions_receiver.rb +75 -0
- data/lib/hbci/version.rb +5 -0
- metadata +315 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Segments
|
5
|
+
# Signature Head Segment
|
6
|
+
#
|
7
|
+
# Top of the signature and counterpart of HNSHA
|
8
|
+
# https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf#page=63
|
9
|
+
class HNSHKv4 < Hbci::Segment
|
10
|
+
element_group :head, type: ElementGroups::SegmentHead do
|
11
|
+
# element :position, default: 2
|
12
|
+
element :position
|
13
|
+
end
|
14
|
+
|
15
|
+
element_group :security_profile do
|
16
|
+
element :security_method, default: 'PIN'
|
17
|
+
element :version, default: 1
|
18
|
+
end
|
19
|
+
|
20
|
+
element :tan_mechanism, default: 999
|
21
|
+
|
22
|
+
# Random security control reference. MUST be the same
|
23
|
+
# as in the signature footer
|
24
|
+
element :security_reference
|
25
|
+
|
26
|
+
element :area_of_security_application, default: 1
|
27
|
+
element :role_of_security_supplier, default: 1
|
28
|
+
element_group :security_identification_details do
|
29
|
+
element :type, default: 1
|
30
|
+
element :cid
|
31
|
+
element :party_identification # system_id
|
32
|
+
end
|
33
|
+
|
34
|
+
element :security_reference_number, default: 1
|
35
|
+
|
36
|
+
element_group :secured_at do
|
37
|
+
element :identifier, default: 1
|
38
|
+
element :date, default: ->(_eg) { Time.now.strftime('%Y%m%d') }
|
39
|
+
element :time, default: ->(_eg) { Time.now.strftime('%H%m%S') }
|
40
|
+
end
|
41
|
+
|
42
|
+
element_group :hash_alg do
|
43
|
+
element :usage, default: 1
|
44
|
+
element :code, default: 999
|
45
|
+
element :param_code, default: 1
|
46
|
+
element :param_value
|
47
|
+
end
|
48
|
+
|
49
|
+
element_group :sig_alg do
|
50
|
+
element :usage, default: 6
|
51
|
+
element :code, default: 10
|
52
|
+
element :operation_mode, default: 16
|
53
|
+
end
|
54
|
+
|
55
|
+
element_group :key do
|
56
|
+
element :bank_country_code, default: 280
|
57
|
+
element :bank_code # blz
|
58
|
+
element :user_id # login id # "Benutzerkennung"
|
59
|
+
element :type, default: 'S'
|
60
|
+
element :number, default: 0
|
61
|
+
element :version, default: 0
|
62
|
+
end
|
63
|
+
|
64
|
+
def compile
|
65
|
+
self.security_reference = request_message.sec_ref
|
66
|
+
self.security_identification_details.party_identification = request_message.dialog ? request_message.dialog.system_id : 0
|
67
|
+
self.tan_mechanism = request_message.dialog.tan_mechanism if request_message.dialog && request_message.dialog.tan_mechanism
|
68
|
+
set_credentials
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def set_credentials
|
74
|
+
key.bank_code = Connector.instance.credentials.bank_code
|
75
|
+
key.user_id = Connector.instance.credentials.user_id
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Segments
|
5
|
+
class HNVSDv1 < Segment
|
6
|
+
attr_reader :segments
|
7
|
+
|
8
|
+
element_group :head, type: ElementGroups::SegmentHead
|
9
|
+
element :content, type: :binary
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super()
|
13
|
+
@segments = []
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(segment_type)
|
18
|
+
segments = find_all(segment_type)
|
19
|
+
warn "more then one #{segment_type} segment available" if segments.size > 1
|
20
|
+
segments.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_all(segment_type)
|
24
|
+
@raw_segments = Parser.parse(content)
|
25
|
+
@raw_segments.select { |sd| sd[0][0] == segment_type }.map{ |sd| Hbci::SegmentFactory.build(sd) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_segment(segment)
|
29
|
+
segment.build(self)
|
30
|
+
@segments.push(segment)
|
31
|
+
end
|
32
|
+
|
33
|
+
def compile
|
34
|
+
head.position = 999
|
35
|
+
@segments.each_with_index do |segment, _index|
|
36
|
+
segment.request_message = request_message
|
37
|
+
segment.compile
|
38
|
+
unless segment.head.position
|
39
|
+
segment.head.position = request_message.next_position
|
40
|
+
request_message.next_position += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@segments.each do |segment|
|
44
|
+
segment.after_compile if segment.respond_to?(:after_compile)
|
45
|
+
end
|
46
|
+
self.content = @segments.join('')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Segments
|
5
|
+
class HNVSKv3 < Hbci::Segment
|
6
|
+
element_group :head, type: ElementGroups::SegmentHead do
|
7
|
+
element :position, default: 998
|
8
|
+
end
|
9
|
+
|
10
|
+
element_group :security_profile do
|
11
|
+
element :method, default: 'PIN'
|
12
|
+
element :version, default: 1
|
13
|
+
end
|
14
|
+
|
15
|
+
element :security_function_code, default: 998
|
16
|
+
|
17
|
+
element :role_of_security_supplier, default: 1
|
18
|
+
|
19
|
+
element_group :security_identification_details do
|
20
|
+
element :type, default: 1
|
21
|
+
element :cid
|
22
|
+
element :party_identification # system_id
|
23
|
+
end
|
24
|
+
|
25
|
+
element_group :secured_at do
|
26
|
+
element :identifier, default: 1
|
27
|
+
element :date, default: ->(_eg) { Time.now.strftime('%Y%m%d') }
|
28
|
+
element :time, default: ->(_eg) { Time.now.strftime('%H%m%S') }
|
29
|
+
end
|
30
|
+
|
31
|
+
element_group :enc_alg do
|
32
|
+
element :usage, default: 2
|
33
|
+
element :operation_mode, default: 2
|
34
|
+
element :code, default: 13
|
35
|
+
element :key, default: '@5@NOKEY'
|
36
|
+
element :type, default: 6
|
37
|
+
element :additional_name, default: 1
|
38
|
+
element :additional_value
|
39
|
+
end
|
40
|
+
|
41
|
+
element_group :key do
|
42
|
+
element :bank_country_code, default: 280
|
43
|
+
element :bank_code # blz
|
44
|
+
element :user_id # login id
|
45
|
+
element :type, default: 'V'
|
46
|
+
element :number, default: 1
|
47
|
+
element :version, default: 1
|
48
|
+
end
|
49
|
+
|
50
|
+
element :compression_method, default: 0
|
51
|
+
|
52
|
+
def compile
|
53
|
+
head.position = 998
|
54
|
+
security_identification_details.party_identification = request_message.dialog ? request_message.dialog.system_id : 0
|
55
|
+
key.bank_code = Connector.instance.credentials.bank_code
|
56
|
+
key.user_id = Connector.instance.credentials.user_id
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Segments
|
5
|
+
class Unknown < Segment
|
6
|
+
element_group :head, type: ElementGroups::SegmentHead
|
7
|
+
|
8
|
+
10.times do |i|
|
9
|
+
element_group "element_group_#{i}".to_sym, type: ElementGroups::Unknown
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.type
|
13
|
+
'Unknown'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.version
|
17
|
+
'0'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Services
|
5
|
+
class AccountsReceiver
|
6
|
+
attr_reader :dialog
|
7
|
+
|
8
|
+
def initialize(dialog)
|
9
|
+
@dialog = dialog
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
dialog.response.find('HNVSD').find_all('HIUPD').map do |hiupd|
|
14
|
+
{ account_number: hiupd.ktv.number, bank_code: hiupd.ktv.kik_blz }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Services
|
5
|
+
class BalanceReceiver < BaseReceiver
|
6
|
+
def perform
|
7
|
+
request_message = MessageFactory.build(dialog) do |hnvsd|
|
8
|
+
hnvsd.add_segment(build_hksal)
|
9
|
+
end
|
10
|
+
request_message.compile
|
11
|
+
|
12
|
+
@response = Response.new(Connector.instance.post(request_message))
|
13
|
+
|
14
|
+
raise @response.to_s unless request_successful?
|
15
|
+
|
16
|
+
@response.find('HNVSD').find('HISAL').booked_amount
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def request_successful?
|
22
|
+
hirmg = @response.find('HIRMG')
|
23
|
+
return false if hirmg && hirmg.ret_val_1.code[0].to_i == 9
|
24
|
+
|
25
|
+
hnvsd = @response.find('HNVSD')
|
26
|
+
hirmg = hnvsd.find('HIRMG')
|
27
|
+
return false if hirmg && hirmg.ret_val_1.code[0].to_i == 9
|
28
|
+
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_hksal
|
33
|
+
case version
|
34
|
+
when 4 then build_hksal_v4
|
35
|
+
when 6 then build_hksal_v6
|
36
|
+
when 7 then build_hksal_v7
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_hksal_v4
|
41
|
+
hksal = Segments::HKSALv4.new
|
42
|
+
hksal.account.code = iban.extended_data.bank_code
|
43
|
+
hksal.account.number = iban.extended_data.account_number
|
44
|
+
hksal
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_hksal_v6
|
48
|
+
hksal = Segments::HKSALv6.new
|
49
|
+
hksal.account.number = iban.extended_data.account_number
|
50
|
+
hksal.account.kik_blz = iban.extended_data.bank_code
|
51
|
+
hksal
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_hksal_v7
|
55
|
+
hksal = Segments::HKSALv7.new
|
56
|
+
hksal.account.iban = iban.to_s
|
57
|
+
hksal.account.bic = iban.extended_data.bic
|
58
|
+
hksal.account.kik_blz = iban.extended_data.bank_code
|
59
|
+
hksal.account.kik_country = 280
|
60
|
+
hksal.account.number = iban.extended_data.account_number
|
61
|
+
hksal
|
62
|
+
end
|
63
|
+
|
64
|
+
def supported_versions
|
65
|
+
dialog.response.find('HNVSD').find_all('HISALS').map { |x| x.head.version.to_i }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Services
|
5
|
+
class BaseReceiver
|
6
|
+
attr_reader :dialog
|
7
|
+
attr_reader :iban
|
8
|
+
|
9
|
+
def initialize(dialog, iban, version = nil)
|
10
|
+
@dialog = dialog
|
11
|
+
@iban = Ibanizator.iban_from_string(iban)
|
12
|
+
@version = version
|
13
|
+
|
14
|
+
# raise "The version #{@version} is not supported" if version && !supported_versions.include?(@version)
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
raise NotImplementedError, "#{self.class.name}#perform is an abstract method."
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def request_successful?
|
24
|
+
hirmg = @response.find('HIRMG')
|
25
|
+
return false if hirmg && hirmg.ret_val_1.code[0].to_i == 9
|
26
|
+
|
27
|
+
hnvsd = @response.find('HNVSD')
|
28
|
+
hirmg = hnvsd.find('HIRMG')
|
29
|
+
return false if hirmg && hirmg.ret_val_1.code[0].to_i == 9
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def version
|
35
|
+
@version ||= supported_versions.max
|
36
|
+
end
|
37
|
+
|
38
|
+
def supported_versions
|
39
|
+
raise NotImplementedError, "#{self.class.name}#supported_versions is an abstract method."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Services
|
5
|
+
class SystemIdReceiver
|
6
|
+
def perform
|
7
|
+
request_message = MessageFactory.build(nil) do |hnvsd|
|
8
|
+
hnvsd.add_segment(Segments::HKIDNv2.new)
|
9
|
+
hnvsd.add_segment(Segments::HKVVBv3.new)
|
10
|
+
hnvsd.add_segment(Segments::HKSYNv3.new)
|
11
|
+
end
|
12
|
+
request_message.compile
|
13
|
+
|
14
|
+
@response = Response.new(Connector.instance.post(request_message))
|
15
|
+
|
16
|
+
raise @response.to_s unless request_successful?
|
17
|
+
|
18
|
+
@response.find('HNVSD').find('HISYN').system_id
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def request_successful?
|
24
|
+
hirmg = @response.find('HIRMG')
|
25
|
+
return false if hirmg && hirmg.ret_val_1.code[0].to_i == 9
|
26
|
+
|
27
|
+
hnvsd = @response.find('HNVSD')
|
28
|
+
hirmg = hnvsd.find('HIRMG')
|
29
|
+
return false if hirmg && hirmg.ret_val_1.code[0].to_i == 9
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Services
|
5
|
+
class TransactionsReceiver < BaseReceiver
|
6
|
+
attr_reader :next_attach
|
7
|
+
|
8
|
+
def perform(start_date, end_date)
|
9
|
+
@start_date = start_date
|
10
|
+
@end_date = end_date
|
11
|
+
|
12
|
+
transactions = []
|
13
|
+
loop do
|
14
|
+
request_message = MessageFactory.build(dialog) do |hnvsd|
|
15
|
+
hnvsd.add_segment(build_hkkaz)
|
16
|
+
end
|
17
|
+
request_message.compile
|
18
|
+
|
19
|
+
@response = Response.new(Connector.instance.post(request_message))
|
20
|
+
|
21
|
+
raise @response.to_s unless request_successful?
|
22
|
+
|
23
|
+
hikaz = @response.find('HNVSD').find('HIKAZ')
|
24
|
+
transactions.concat(parse_transactions(hikaz.booked)) if hikaz
|
25
|
+
|
26
|
+
break if @response.find('HNVSD').find('HIRMS').ret_val_1.code != '3040'
|
27
|
+
|
28
|
+
@next_attach = @response.find('HNVSD').find('HIRMS').ret_val_1.parm
|
29
|
+
end
|
30
|
+
transactions
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def build_hkkaz
|
36
|
+
case version
|
37
|
+
when 6 then build_hkkaz_v6
|
38
|
+
when 7 then build_hkkaz_v7
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_hkkaz_v6
|
43
|
+
hkkaz = Segments::HKKAZv6.new
|
44
|
+
hkkaz.account.number = iban.extended_data.account_number
|
45
|
+
hkkaz.account.kik_blz = iban.extended_data.bank_code
|
46
|
+
hkkaz.account.kik_country = 280
|
47
|
+
hkkaz.from = @start_date.strftime('%Y%m%d')
|
48
|
+
hkkaz.to = @end_date.strftime('%Y%m%d')
|
49
|
+
hkkaz.attach = next_attach
|
50
|
+
hkkaz
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_hkkaz_v7
|
54
|
+
hkkaz = Segments::HKKAZv7.new
|
55
|
+
hkkaz.account.iban = iban.to_s
|
56
|
+
hkkaz.account.bic = iban.extended_data.bic
|
57
|
+
hkkaz.account.number = iban.extended_data.account_number
|
58
|
+
hkkaz.account.kik_blz = iban.extended_data.bank_code
|
59
|
+
hkkaz.account.kik_country = 280
|
60
|
+
hkkaz.from = @start_date.strftime('%Y%m%d')
|
61
|
+
hkkaz.to = @end_date.strftime('%Y%m%d')
|
62
|
+
hkkaz.attach = next_attach
|
63
|
+
hkkaz
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_transactions(mt940)
|
67
|
+
Cmxl.parse(mt940.force_encoding('ISO-8859-1').encode('UTF-8')).flat_map(&:transactions).map(&:to_h)
|
68
|
+
end
|
69
|
+
|
70
|
+
def supported_versions
|
71
|
+
dialog.response.find('HNVSD').find_all('HIKAZS').map { |x| x.head.version.to_i }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|