Skip to content

Commit 4f4d0d8

Browse files
henrykraphaelm
authored andcommitted
Convert get_sepa_accounts() and get_balance() to new API. Move segments around. Implement new types.
Still missing: Dummy encryption, PIN authorization.
1 parent c3e7b14 commit 4f4d0d8

16 files changed

+540
-115
lines changed

fints/client.py

Lines changed: 108 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,97 @@
22
import logging
33
from decimal import Decimal
44

5-
from fints.segments import HISPA1
65
from fints.segments.debit import HKDME, HKDSE
76
from mt940.models import Balance
87
from sepaxml import SepaTransfer
98

109
from .connection import FinTSHTTPSConnection
11-
from .dialog import FinTSDialog
10+
from .dialog import FinTSDialogOLD, FinTSDialog
1211
from .formals import TwoStepParametersCommon
13-
from .message import FinTSMessage
12+
from .message import FinTSMessageOLD
1413
from .models import (
1514
SEPAAccount, TANChallenge, TANChallenge3,
1615
TANChallenge4, TANChallenge5, TANChallenge6,
1716
)
18-
from .segments.accounts import HKSPA
17+
from .segments import HIUPA4, HIBPA3
18+
from .segments.accounts import HKSPA, HKSPA1, HISPA1
1919
from .segments.auth import HKTAB, HKTAN
20+
from .segments.dialog import HKSYN3, HISYN4
2021
from .segments.depot import HKWPD
21-
from .segments.saldo import HKSAL
22+
from .segments.saldo import HKSAL6, HKSAL7, HISAL6, HISAL7
2223
from .segments.statement import HKKAZ
2324
from .segments.transfer import HKCCM, HKCCS
2425
from .utils import MT535_Miniparser, Password, mt940_to_array
26+
from .formals import Account3, KTI1, BankIdentifier, SynchronisationMode
2527

2628
logger = logging.getLogger(__name__)
2729

30+
SYSTEM_ID_UNASSIGNED = '0'
2831

2932
class FinTS3Client:
3033
version = 300
3134

32-
def __init__(self):
35+
def __init__(self, bank_identifier, user_id, customer_id=None):
3336
self.accounts = []
37+
if isinstance(bank_identifier, BankIdentifier):
38+
self.bank_identifier = bank_identifier
39+
elif isinstance(bank_identifier, str):
40+
self.bank_identifier = BankIdentifier('280', bank_identifier)
41+
else:
42+
raise TypeError("bank_identifier must be BankIdentifier or str (BLZ)")
43+
self.system_id = SYSTEM_ID_UNASSIGNED
44+
self.user_id = user_id
45+
self.customer_id = customer_id or user_id
46+
self.bpd_version = 0
47+
self.bpa = None
48+
self.bpd = []
49+
self.upd_version = 0
50+
self.product_name = 'pyfints'
51+
self.product_version = '0.2'
52+
53+
def _new_dialog(self, lazy_init=False):
54+
raise NotImplemented()
3455

35-
def _new_dialog(self):
56+
def _new_message(self, dialog: FinTSDialogOLD, segments, tan=None):
3657
raise NotImplemented()
3758

38-
def _new_message(self, dialog: FinTSDialog, segments, tan=None):
59+
def _ensure_system_id(self):
3960
raise NotImplemented()
4061

62+
def process_institute_response(self, message):
63+
bpa = message.find_segment_first(HIBPA3)
64+
if bpa:
65+
self.bpa = bpa
66+
self.bpd_version = bpa.bpd_version
67+
self.bpd = list(
68+
message.find_segments(
69+
callback = lambda m: len(m.header.type) == 6 and m.header.type[1] == 'I' and m.header.type[5] == 'S'
70+
)
71+
)
72+
73+
for seg in message.find_segments(HIUPA4):
74+
self.upd_version = seg.upd_version
75+
76+
def find_bpd(self, type):
77+
for seg in self.bpd:
78+
if seg.header.type == type:
79+
yield seg
80+
4181
def get_sepa_accounts(self):
4282
"""
4383
Returns a list of SEPA accounts
4484
4585
:return: List of SEPAAccount objects.
4686
"""
47-
dialog = self._new_dialog()
48-
dialog.sync()
49-
dialog.init()
50-
51-
def _get_msg():
52-
return self._new_message(dialog, [
53-
HKSPA(3, None, None, None)
54-
])
55-
56-
with self.pin.protect():
57-
logger.debug('Sending HKSPA: {}'.format(_get_msg()))
58-
59-
resp = dialog.send(_get_msg())
60-
logger.debug('Got HKSPA response: {}'.format(resp))
61-
dialog.end()
6287

88+
with self._new_dialog() as dialog:
89+
response = dialog.send(HKSPA1())
90+
6391
self.accounts = []
64-
for seg in resp.find_segments(HISPA1):
92+
for seg in response.find_segments(HISPA1):
6593
self.accounts.extend(seg.accounts)
6694

67-
return self.accounts
95+
return [a for a in [acc.as_sepa_account() for acc in self.accounts] if a]
6896

6997
def get_statement(self, account: SEPAAccount, start_date: datetime.datetime, end_date: datetime.date):
7098
"""
@@ -121,7 +149,7 @@ def _get_msg():
121149
dialog.end()
122150
return statement
123151

124-
def _create_statement_message(self, dialog: FinTSDialog, account: SEPAAccount, start_date, end_date, touchdown):
152+
def _create_statement_message(self, dialog: FinTSDialogOLD, account: SEPAAccount, start_date, end_date, touchdown):
125153
hversion = dialog.hkkazversion
126154

127155
if hversion in (4, 5, 6):
@@ -153,35 +181,35 @@ def get_balance(self, account: SEPAAccount):
153181
:param account: SEPA account to fetch the balance
154182
:return: A mt940.models.Balance object
155183
"""
156-
# init dialog
157-
dialog = self._new_dialog()
158-
dialog.sync()
159-
dialog.init()
160-
161-
# execute job
162-
def _get_msg():
163-
return self._create_balance_message(dialog, account)
164-
165-
with self.pin.protect():
166-
logger.debug('Sending HKSAL: {}'.format(_get_msg()))
167-
168-
resp = dialog.send(_get_msg())
169-
logger.debug('Got HKSAL response: {}'.format(resp))
170184

171-
# end dialog
172-
dialog.end()
185+
max_hksal_version = max(
186+
(seg.header.version for seg in self.find_bpd('HISALS')),
187+
default=6
188+
)
173189

174-
# find segment and split up to balance part
175-
seg = resp._find_segment('HISAL')
176-
arr = seg[4]
190+
if max_hksal_version in (1, 2, 3, 4, 5, 6):
191+
seg = HKSAL6(
192+
Account3.from_sepa_account(account),
193+
False
194+
)
195+
elif max_hksal_version == 7:
196+
seg = HKSAL7(
197+
KTI1.from_sepa_account(account),
198+
False
199+
)
200+
else:
201+
raise ValueError('Unsupported HKSAL version {}'.format(max_hksal_version))
177202

178-
# get balance date
179-
date = datetime.datetime.strptime(arr[3], "%Y%m%d").date()
180203

181-
# return balance
182-
return Balance(arr[0], arr[1], date, currency=arr[2])
204+
with self._new_dialog() as dialog:
205+
response = dialog.send(seg)
206+
207+
# find segment
208+
seg = response.find_segment_first((HISAL6, HISAL7))
209+
if seg:
210+
return seg.balance_booked.as_mt940_Balance()
183211

184-
def _create_balance_message(self, dialog: FinTSDialog, account: SEPAAccount):
212+
def _create_balance_message(self, dialog: FinTSDialogOLD, account: SEPAAccount):
185213
hversion = dialog.hksalversion
186214

187215
if hversion in (1, 2, 3, 4, 5, 6):
@@ -242,7 +270,7 @@ def _get_msg():
242270
logger.debug('No HIWPD response segment found - maybe account has no holdings?')
243271
return []
244272

245-
def _create_get_holdings_message(self, dialog: FinTSDialog, account: SEPAAccount):
273+
def _create_get_holdings_message(self, dialog: FinTSDialogOLD, account: SEPAAccount):
246274
hversion = dialog.hksalversion
247275

248276
if hversion in (1, 2, 3, 4, 5, 6):
@@ -264,7 +292,7 @@ def _create_get_holdings_message(self, dialog: FinTSDialog, account: SEPAAccount
264292
)
265293
])
266294

267-
def _create_send_tan_message(self, dialog: FinTSDialog, challenge: TANChallenge, tan):
295+
def _create_send_tan_message(self, dialog: FinTSDialogOLD, challenge: TANChallenge, tan):
268296
return self._new_message(dialog, [
269297
HKTAN(3, '2', challenge.reference, '', challenge.version)
270298
], tan)
@@ -454,7 +482,7 @@ def get_tan_methods(self):
454482
dialog.end()
455483
return dialog.tan_mechs
456484

457-
def _create_get_tan_description_message(self, dialog: FinTSDialog):
485+
def _create_get_tan_description_message(self, dialog: FinTSDialogOLD):
458486
return self._new_message(dialog, [
459487
HKTAB(3)
460488
])
@@ -483,18 +511,35 @@ def get_tan_description(self):
483511

484512
class FinTS3PinTanClient(FinTS3Client):
485513

486-
def __init__(self, blz, username, pin, server):
487-
self.username = username
488-
self.blz = blz
514+
def __init__(self, bank_identifier, user_id, pin, server, customer_id=None):
489515
self.pin = Password(pin)
490516
self.connection = FinTSHTTPSConnection(server)
491-
self.systemid = 0
492-
super().__init__()
517+
super().__init__(bank_identifier=bank_identifier, user_id=user_id, customer_id=customer_id)
493518

494-
def _new_dialog(self):
495-
dialog = FinTSDialog(self.blz, self.username, self.pin, self.systemid, self.connection)
496-
return dialog
519+
def _new_dialog(self, lazy_init=False):
520+
if not lazy_init:
521+
self._ensure_system_id()
497522

498-
def _new_message(self, dialog: FinTSDialog, segments, tan=None):
499-
return FinTSMessage(self.blz, self.username, self.pin, dialog.systemid, dialog.dialogid, dialog.msgno,
523+
return FinTSDialog(self, lazy_init=lazy_init)
524+
525+
# FIXME
526+
# dialog = FinTSDialogOLD(self.blz, self.username, self.pin, self.systemid, self.connection)
527+
# return dialog
528+
529+
def _new_message(self, dialog: FinTSDialogOLD, segments, tan=None):
530+
return FinTSMessageOLD(self.blz, self.username, self.pin, dialog.systemid, dialog.dialogid, dialog.msgno,
500531
segments, dialog.tan_mechs, tan)
532+
533+
def _ensure_system_id(self):
534+
if self.system_id != SYSTEM_ID_UNASSIGNED:
535+
return
536+
537+
with self._new_dialog(lazy_init=True) as dialog:
538+
response = dialog.init(
539+
HKSYN3(SynchronisationMode.NEW_SYSTEM_ID),
540+
)
541+
542+
seg = response.find_segment_first(HISYN4)
543+
if not seg:
544+
raise ValueError('Could not find system_id')
545+
self.system_id = seg.system_id

fints/connection.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fints.parser import FinTS3Parser
55
from fints.utils import Password
66

7-
from .message import FinTSMessage, FinTSResponse
7+
from .message import FinTSMessage, FinTSInstituteMessage
88

99

1010
class FinTSConnectionError(Exception):
@@ -18,15 +18,15 @@ def __init__(self, url):
1818
def send(self, msg: FinTSMessage):
1919
print("Sending >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
2020
with Password.protect():
21-
FinTS3Parser().parse_message(str(msg).encode('iso-8859-1')).print_nested()
21+
msg.print_nested()
2222
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
2323
r = requests.post(
24-
self.url, data=base64.b64encode(str(msg).encode('iso-8859-1')),
24+
self.url, data=base64.b64encode(msg.render_bytes()),
2525
)
2626
if r.status_code < 200 or r.status_code > 299:
2727
raise FinTSConnectionError('Bad status code {}'.format(r.status_code))
2828
response = base64.b64decode(r.content.decode('iso-8859-1'))
29-
retval = FinTSResponse(response)
29+
retval = FinTSInstituteMessage(segments=response)
3030
#import pprint; pprint.pprint(response) FIXME Remove
3131
print("Received <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
3232
with Password.protect():

0 commit comments

Comments
 (0)