ruby_fints 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 334a1e0c17c756b2380b9fb739cfeeb7198030e5
4
+ data.tar.gz: 565548ae22bb8510b7da5e4db147c28076a9ca48
5
+ SHA512:
6
+ metadata.gz: 42513b7488dbd861747ad06d08ad5948811bd81d508cb352f50a75a6132c405d633a2346063fde5b7b88ddd55d834c4a14a700e3bfe3013d0471da0ad23acfc3
7
+ data.tar.gz: 6bfd3b8107a41506a60d0ab56d230e078ac50d1fc060e164e2192e5f35bf5b0089c9b237c454dd29f983f3f4ac0d4212e6bac6563668efacbb38ea03293f1612
data/.gitignore ADDED
@@ -0,0 +1,58 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # rvm
15
+ .ruby-version
16
+
17
+ # jeweler generated
18
+ #pkg # upload gem file to server
19
+
20
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
21
+ #
22
+ # * Create a file at ~/.gitignore
23
+ # * Include files you want ignored
24
+ # * Run: git config --global core.excludesfile ~/.gitignore
25
+ #
26
+ # After doing this, these files will be ignored in all your git projects,
27
+ # saving you from having to 'pollute' every project you touch with them
28
+ #
29
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
30
+ #
31
+ # For MacOS:
32
+ #
33
+ .DS_Store
34
+
35
+ # For TextMate
36
+ #*.tmproj
37
+ #tmtags
38
+
39
+ # For emacs:
40
+ #*~
41
+ #\#*
42
+ #.\#*
43
+
44
+ # For VS Code
45
+ .vscode/
46
+
47
+ # For vim:
48
+ *.swp
49
+
50
+ # For redcar:
51
+ #.redcar
52
+
53
+ # For rubinius:
54
+ #*.rbc
55
+
56
+ pkg/
57
+
58
+ /Gemfile.lock
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ruby_fints
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.7
5
+ - 2.3.4
6
+ - 2.4.1
7
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 PlaytestCloud GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ ruby_fints
2
+ ==========
3
+
4
+ This is a pure-Ruby implementation of FinTS (formerly known as HBCI), a
5
+ online-banking protocol commonly supported by German banks.
6
+
7
+ Limitations
8
+ -----------
9
+
10
+ * Only FinTS 3.0 is supported
11
+ * Only PIN/TAN authentication is supported, no signature cards
12
+ * Only a number of reading operations are currently supported
13
+ * Supports Ruby 2.2+
14
+
15
+ Banks tested:
16
+
17
+ * Sparkasse
18
+
19
+ Usage
20
+ -----
21
+
22
+ ```ruby
23
+ require 'ruby_fints'
24
+ require 'pp'
25
+
26
+ FinTS::Client.logger.level = Logger::DEBUG
27
+ f = FinTS::PinTanClient.new(
28
+ '123456789', # Your bank's BLZ
29
+ 'myusername',
30
+ 'mypin',
31
+ 'https://mybank.com/...' # endpoint, e.g.: https://hbci-pintan.gad.de/cgi-bin/hbciservlet
32
+ )
33
+
34
+ accounts = f.get_sepa_accounts
35
+ pp accounts
36
+ # [{iban: 'DE12345678901234567890', bic: 'ABCDEFGH1DEF', accountnumber: '123456790', subaccount: '', blz: '123456789'}]
37
+
38
+ statement = f.get_statement(accounts[0], Date.new(2017, 4, 3), Date.new(2017, 4, 4))
39
+ pp statement.map(&:data)
40
+
41
+ # [#<Cmxl::Fields::Transaction:0x007fab6b457ec8
42
+ # @data=
43
+ # {"date"=>"170404",
44
+ # "entry_date"=>"0404",
45
+ # "funds_code"=>"C",
46
+ # "currency_letter"=>"R",
47
+ # "amount"=>"96,38",
48
+ # "swift_code"=>"N062",
49
+ # "reference"=>"NONREF",
50
+ # "bank_reference"=>""},
51
+ # @details=
52
+ # #<Cmxl::Fields::StatementDetails:0x007fab6b457838
53
+ # @data=
54
+ # {"transaction_code"=>"166",
55
+ # "details"=>
56
+ # "?00GUTSCHRIFT?109251?20EREF+010F209270562741?21SVWZ+STRIPEX4J1J3?22AWV-MELDEPFLICHT BEACHTEN?23HOTLINE BUNDESBANK.?24(0800) 1234-111?30SXPYDKKK?35DK6689000000010241?32Stripe Payments UK Ltd?34888",
57
+ # "seperator"=>"?"}
58
+ # ]
59
+
60
+ # for retrieving the holdings of an account (This has not been tested for this implementation yet so it might not work)
61
+ holdings = f.get_holdings(accounts[0])
62
+ ```
63
+
64
+ Credits
65
+ -------
66
+
67
+ This is a close port of [python-fints](https://github.com/raphaelm/python-fints) library by Raphael Michel
68
+ which in turn is a port of the [fints-hbci-php](https://github.com/mschindler83/fints-hbci-php)
69
+ implementation that was released by Markus Schindler under the MIT license.
70
+
71
+ Thanks for your work!
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,144 @@
1
+ module FinTS
2
+ class Client
3
+ class << self
4
+ attr_writer :logger
5
+
6
+ def logger
7
+ @logger ||= Logger.new($stdout).tap do |log|
8
+ log.progname = self.name
9
+ end
10
+ end
11
+ end
12
+
13
+ def initialize
14
+ @accounts = []
15
+ end
16
+
17
+ def get_sepa_accounts
18
+ dialog = new_dialog
19
+ dialog.sync
20
+ dialog.init
21
+
22
+ msg_spa = new_message(dialog, [Segment::HKSPA.new(3, nil, nil, nil)])
23
+ FinTS::Client.logger.debug("Sending HKSPA: #{msg_spa}")
24
+ resp = dialog.send_msg(msg_spa)
25
+ FinTS::Client.logger.debug("Got HKSPA response: #{resp}")
26
+ dialog.send_end
27
+
28
+ accounts = resp.find_segment('HISPA')
29
+ raise SegmentNotFoundError, 'Could not find HISPA segment' if accounts.nil?
30
+ accountlist = accounts.split('+').drop(1)
31
+ @accounts = accountlist.map do |acc|
32
+ arr = acc.split(':')
33
+ {
34
+ iban: arr[1],
35
+ bic: arr[2],
36
+ accountnumber: arr[3],
37
+ subaccount: arr[4],
38
+ blz: arr[6]
39
+ }
40
+ end
41
+ end
42
+
43
+ def get_statement(account, start_date, end_date)
44
+ FinTS::Client.logger.info("Start fetching from #{start_date} to #{end_date}")
45
+
46
+ dialog = new_dialog
47
+ dialog.sync
48
+ dialog.init
49
+
50
+ msg = create_statement_message(dialog, account, start_date, end_date, nil)
51
+ FinTS::Client.logger.debug("Send message: #{msg}")
52
+ resp = dialog.send_msg(msg)
53
+ touchdowns = resp.get_touchdowns(msg)
54
+ responses = [resp]
55
+ touchdown_counter = 1
56
+
57
+ while touchdowns.include?(Segment::HKKAZ)
58
+ FinTS::Client.logger.info("Fetching more results (#{touchdown_counter})...")
59
+ msg = create_statement_message(dialog, account, start_date, end_date, touchdowns[Segment::HKKAZ])
60
+ FinTS::Client.logger.debug("Send message: #{msg}")
61
+
62
+ resp = dialog.send_msg(msg)
63
+ responses << resp
64
+ touchdowns = resp.get_touchdowns(msg)
65
+
66
+ touchdown_counter += 1
67
+ end
68
+
69
+ FinTS::Client.logger.info('Fetching done.')
70
+ re_data = /^[^@]*@([0-9]+?)@(.+)/m
71
+ statement_response = ''
72
+ responses.each do |r|
73
+ seg = r.find_segment('HIKAZ')
74
+ next unless seg
75
+ match = re_data.match(seg)
76
+ next unless match
77
+ statement_response += match[2]
78
+ end
79
+ statement = Helper.mt940_to_array(statement_response)
80
+
81
+ FinTS::Client.logger.debug("Statement: #{statement}")
82
+ dialog.send_end
83
+ statement
84
+ end
85
+
86
+ def create_statement_message(dialog, account, start_date, end_date, touchdown)
87
+ hversion = dialog.hkkazversion
88
+
89
+ acc = if [4, 5, 6].include?(hversion)
90
+ [account[:accountnumber], account[:subaccount], '280', account[:blz]].join(':')
91
+ elsif hversion == 7
92
+ [account[:iban], account[:bic], account[:accountnumber], account[:subaccount], '280', account[:blz]].join(':')
93
+ else
94
+ raise ArgumentError, "Unsupported HKKAZ version #{hversion}"
95
+ end
96
+
97
+ segment = Segment::HKKAZ.new(3, hversion, acc, start_date, end_date, touchdown)
98
+ new_message(dialog, [segment])
99
+ end
100
+
101
+ def get_holdings(account)
102
+ # init dialog
103
+ dialog = self.new_dialog()
104
+ dialog.sync
105
+ dialog.init
106
+
107
+ # execute job
108
+ msg = create_get_holdings_message(dialog, account)
109
+ FinTS::Client.logger.debug("Sending HKWPD: #{msg}")
110
+ resp = dialog.send_msg(msg)
111
+ FinTS::Client.logger.debug("Got HIWPD response: #{resp}")
112
+
113
+ # end dialog
114
+ dialog.send_end
115
+
116
+ # find segment and split up to balance part
117
+ seg = resp.find_segment('HIWPD')
118
+ if seg
119
+ mt535_lines = seg.lines
120
+ # The first line contains a FinTS HIWPD header - drop it.
121
+ mt535_lines.delete_at(0)
122
+ mt535 = MT535Miniparser.new
123
+ mt535.parse(mt535_lines)
124
+ else
125
+ FinTS::Client.logger.warn('No HIWPD response segment found - maybe account has no holdings?')
126
+ []
127
+ end
128
+ end
129
+
130
+ def create_get_holdings_message(dialog, account)
131
+ hversion = dialog.hksalversion
132
+
133
+ acc = if [1, 2, 3, 4, 5, 6].include?(hversion)
134
+ [account[:accountnumber], account[:subaccount], '280', account[:blz]].join(':')
135
+ elsif hversion == 7
136
+ [account[:iban], account[:bic], account[:accountnumber], account[:subaccount], '280', account[:blz]].join(':')
137
+ else
138
+ raise ArgumentError, "Unsupported HKSAL version #{hversion}"
139
+ end
140
+
141
+ new_message(dialog, [Segment::HKWPD.new(3, hversion, acc)])
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,3 @@
1
+ module FinTS
2
+ class ConnectionError < StandardError; end
3
+ end
@@ -0,0 +1,104 @@
1
+ module FinTS
2
+ class DialogError < StandardError; end
3
+
4
+ class Dialog
5
+ attr_accessor :system_id
6
+ attr_accessor :dialog_id
7
+ attr_accessor :msg_no
8
+ attr_accessor :tan_mechs
9
+ attr_accessor :hkkazversion
10
+ attr_accessor :hksalversion
11
+
12
+ def initialize(blz, username, pin, system_id, connection)
13
+ @blz = blz
14
+ @username = username
15
+ @pin = pin
16
+ @system_id = system_id
17
+ @connection = connection
18
+ @msg_no = 1
19
+ @dialog_id = 0
20
+ @hksalversion = 6
21
+ @hkkazversion = 6
22
+ @tan_mechs = []
23
+ end
24
+
25
+ def sync
26
+ FinTS::Client.logger.info('Initialize SYNC')
27
+
28
+ seg_identification = Segment::HKIDN.new(3, @blz, @username, 0)
29
+ seg_prepare = Segment::HKVVB.new(4)
30
+ seg_sync = Segment::HKSYN.new(5)
31
+
32
+ msg_sync = Message.new(@blz, @username, @pin, @system_id, @dialog_id, @msg_no, [
33
+ seg_identification,
34
+ seg_prepare,
35
+ seg_sync
36
+ ])
37
+
38
+ FinTS::Client.logger.debug("Sending SYNC: #{msg_sync}")
39
+ resp = send_msg(msg_sync)
40
+ FinTS::Client.logger.debug("Got SYNC response: #{resp}")
41
+ @system_id = resp.get_system_id
42
+ @dialog_id = resp.get_dialog_id
43
+ @bankname = resp.get_bank_name
44
+ @hksalversion = resp.get_hksal_max_version
45
+ @hkkazversion = resp.get_hkkaz_max_version
46
+ @tan_mechs = resp.get_supported_tan_mechanisms
47
+
48
+ FinTS::Client.logger.debug("Bank name: #{@bankname}")
49
+ FinTS::Client.logger.debug("System ID: #{@system_id}")
50
+ FinTS::Client.logger.debug("Dialog ID: #{@dialog_id}")
51
+ FinTS::Client.logger.debug("HKKAZ max version: #{@hkkazversion}")
52
+ FinTS::Client.logger.debug("HKSAL max version: #{@hksalversion}")
53
+ FinTS::Client.logger.debug("TAN mechanisms: #{@tan_mechs}")
54
+ send_end
55
+ end
56
+
57
+ def init
58
+ FinTS::Client.logger.info('Initialize Dialog')
59
+ seg_identification = Segment::HKIDN.new(3, @blz, @username, @system_id)
60
+ seg_prepare = Segment::HKVVB.new(4)
61
+
62
+ msg_init = Message.new(@blz, @username, @pin, @system_id, @dialog_id, @msg_no, [
63
+ seg_identification,
64
+ seg_prepare,
65
+ ], @tan_mechs)
66
+ FinTS::Client.logger.debug("Sending INIT: #{msg_init}")
67
+ resp = send_msg(msg_init)
68
+ FinTS::Client.logger.debug("Got INIT response: #{resp}")
69
+
70
+ @dialog_id = resp.get_dialog_id
71
+ FinTS::Client.logger.info("Received dialog ID: #{@dialog_id}")
72
+
73
+ @dialog_id
74
+ end
75
+
76
+ def send_msg(msg)
77
+ FinTS::Client.logger.info('Sending Message')
78
+ msg.msg_no = @msg_no
79
+ msg.dialog_id = @dialog_id
80
+
81
+ resp = Response.new(@connection.send_msg(msg))
82
+ if !resp.successful?
83
+ raise DialogError, resp.get_summary_by_segment('HIRMG')
84
+ end
85
+ @msg_no += 1
86
+ resp
87
+ end
88
+
89
+ def send_end
90
+ FinTS::Client.logger.info('Initialize END')
91
+
92
+ msg_end = Message.new(@blz, @username, @pin, @system_id, @dialog_id, @msg_no, [
93
+ Segment::HKEND.new(3, @dialog_id)
94
+ ])
95
+ FinTS::Client.logger.debug("Sending END: #{msg_end}")
96
+ resp = send_msg(msg_end)
97
+ FinTS::Client.logger.debug("Got END response: #{resp}")
98
+ FinTS::Client.logger.info('Resetting dialog ID and message number count')
99
+ @dialog_id = 0
100
+ @msg_no = 1
101
+ resp
102
+ end
103
+ end
104
+ end