hbci 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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