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