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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +26 -0
  4. data/.rubocop_todo.yml +26 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +1 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +107 -0
  10. data/Rakefile +8 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +7 -0
  13. data/examples/credentials.rb +62 -0
  14. data/examples/get_accounts.rb +11 -0
  15. data/examples/get_balance.rb +8 -0
  16. data/examples/get_system_id.rb +6 -0
  17. data/examples/get_transactions.rb +15 -0
  18. data/hbci.gemspec +36 -0
  19. data/lib/development_profiler.rb +26 -0
  20. data/lib/hbci.rb +66 -0
  21. data/lib/hbci/connector.rb +33 -0
  22. data/lib/hbci/dialog.rb +77 -0
  23. data/lib/hbci/element_group.rb +93 -0
  24. data/lib/hbci/element_groups/generated_element_groups.rb +969 -0
  25. data/lib/hbci/element_groups/segment_head.rb +12 -0
  26. data/lib/hbci/element_groups/unknown.rb +11 -0
  27. data/lib/hbci/element_unparser.rb +21 -0
  28. data/lib/hbci/message.rb +47 -0
  29. data/lib/hbci/message_factory.rb +17 -0
  30. data/lib/hbci/parser.rb +114 -0
  31. data/lib/hbci/request.rb +32 -0
  32. data/lib/hbci/response.rb +25 -0
  33. data/lib/hbci/segment.rb +118 -0
  34. data/lib/hbci/segment_factory.rb +15 -0
  35. data/lib/hbci/segments/hikaz.rb +29 -0
  36. data/lib/hbci/segments/hikazs.rb +31 -0
  37. data/lib/hbci/segments/hirmg.rb +10 -0
  38. data/lib/hbci/segments/hirms.rb +17 -0
  39. data/lib/hbci/segments/hisal.rb +100 -0
  40. data/lib/hbci/segments/hisals.rb +19 -0
  41. data/lib/hbci/segments/hisyn.rb +11 -0
  42. data/lib/hbci/segments/hiupa.rb +12 -0
  43. data/lib/hbci/segments/hiupd.rb +37 -0
  44. data/lib/hbci/segments/hkend.rb +14 -0
  45. data/lib/hbci/segments/hkidn.rb +22 -0
  46. data/lib/hbci/segments/hkkaz.rb +25 -0
  47. data/lib/hbci/segments/hksal.rb +31 -0
  48. data/lib/hbci/segments/hksyn.rb +12 -0
  49. data/lib/hbci/segments/hkvvb.rb +18 -0
  50. data/lib/hbci/segments/hnhbk.rb +27 -0
  51. data/lib/hbci/segments/hnhbs.rb +14 -0
  52. data/lib/hbci/segments/hnsha.rb +31 -0
  53. data/lib/hbci/segments/hnshk.rb +79 -0
  54. data/lib/hbci/segments/hnvsd.rb +50 -0
  55. data/lib/hbci/segments/hnvsk.rb +60 -0
  56. data/lib/hbci/segments/segment_head.rb +11 -0
  57. data/lib/hbci/segments/unknown.rb +21 -0
  58. data/lib/hbci/services/accounts_receiver.rb +19 -0
  59. data/lib/hbci/services/balance_receiver.rb +69 -0
  60. data/lib/hbci/services/base_receiver.rb +43 -0
  61. data/lib/hbci/services/system_id_receiver.rb +35 -0
  62. data/lib/hbci/services/transactions_receiver.rb +75 -0
  63. data/lib/hbci/version.rb +5 -0
  64. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hbci
4
+ module Segments
5
+ module SegmentHead
6
+ def self.included(klass)
7
+ klass.element_group :head, type: ElementGroups::SegmentHead
8
+ end
9
+ end
10
+ end
11
+ 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