]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Convert get_sepa_accounts() and get_balance() to new API. Move segments around. Imple...
authorHenryk Plötz <henryk@ploetzli.ch>
Sun, 19 Aug 2018 20:39:05 +0000 (22:39 +0200)
committerRaphael Michel <mail@raphaelmichel.de>
Mon, 3 Dec 2018 18:34:29 +0000 (19:34 +0100)
Still missing: Dummy encryption, PIN authorization.

16 files changed:
fints/client.py
fints/connection.py
fints/dialog.py
fints/fields.py
fints/formals.py
fints/message.py
fints/segments/__init__.py
fints/segments/accounts.py
fints/segments/auth.py
fints/segments/dialog.py
fints/segments/message.py
fints/segments/saldo.py
fints/types.py
tests/test_formals.py
tests/test_message_serializer.py
tests/test_models.py

index 33920a42a3f47de67e28d98914b78c05943f4d59..7bd9df0cc1fe4ccfe56ad50536e88e5d3666cfdf 100644 (file)
@@ -2,69 +2,97 @@ import datetime
 import logging
 from decimal import Decimal
 
-from fints.segments import HISPA1
 from fints.segments.debit import HKDME, HKDSE
 from mt940.models import Balance
 from sepaxml import SepaTransfer
 
 from .connection import FinTSHTTPSConnection
-from .dialog import FinTSDialog
+from .dialog import FinTSDialogOLD, FinTSDialog
 from .formals import TwoStepParametersCommon
-from .message import FinTSMessage
+from .message import FinTSMessageOLD
 from .models import (
     SEPAAccount, TANChallenge, TANChallenge3,
     TANChallenge4, TANChallenge5, TANChallenge6,
 )
-from .segments.accounts import HKSPA
+from .segments import HIUPA4, HIBPA3
+from .segments.accounts import HKSPA, HKSPA1, HISPA1
 from .segments.auth import HKTAB, HKTAN
+from .segments.dialog import HKSYN3, HISYN4
 from .segments.depot import HKWPD
-from .segments.saldo import HKSAL
+from .segments.saldo import HKSAL6, HKSAL7, HISAL6, HISAL7
 from .segments.statement import HKKAZ
 from .segments.transfer import HKCCM, HKCCS
 from .utils import MT535_Miniparser, Password, mt940_to_array
+from .formals import Account3, KTI1, BankIdentifier, SynchronisationMode
 
 logger = logging.getLogger(__name__)
 
+SYSTEM_ID_UNASSIGNED = '0'
 
 class FinTS3Client:
     version = 300
 
-    def __init__(self):
+    def __init__(self, bank_identifier, user_id, customer_id=None):
         self.accounts = []
+        if isinstance(bank_identifier, BankIdentifier):
+            self.bank_identifier = bank_identifier
+        elif isinstance(bank_identifier, str):
+            self.bank_identifier = BankIdentifier('280', bank_identifier)
+        else:
+            raise TypeError("bank_identifier must be BankIdentifier or str (BLZ)")
+        self.system_id = SYSTEM_ID_UNASSIGNED
+        self.user_id = user_id
+        self.customer_id = customer_id or user_id
+        self.bpd_version = 0
+        self.bpa = None
+        self.bpd = []
+        self.upd_version = 0
+        self.product_name = 'pyfints'
+        self.product_version = '0.2'
+
+    def _new_dialog(self, lazy_init=False):
+        raise NotImplemented()
 
-    def _new_dialog(self):
+    def _new_message(self, dialog: FinTSDialogOLD, segments, tan=None):
         raise NotImplemented()
 
-    def _new_message(self, dialog: FinTSDialog, segments, tan=None):
+    def _ensure_system_id(self):
         raise NotImplemented()
 
+    def process_institute_response(self, message):
+        bpa = message.find_segment_first(HIBPA3)
+        if bpa:
+            self.bpa = bpa
+            self.bpd_version = bpa.bpd_version
+            self.bpd = list(
+                message.find_segments(
+                    callback = lambda m: len(m.header.type) == 6 and m.header.type[1] == 'I' and m.header.type[5] == 'S'
+                )
+            )
+
+        for seg in message.find_segments(HIUPA4):
+            self.upd_version = seg.upd_version
+
+    def find_bpd(self, type):
+        for seg in self.bpd:
+            if seg.header.type == type:
+                yield seg
+
     def get_sepa_accounts(self):
         """
         Returns a list of SEPA accounts
 
         :return: List of SEPAAccount objects.
         """
-        dialog = self._new_dialog()
-        dialog.sync()
-        dialog.init()
-
-        def _get_msg():
-            return self._new_message(dialog, [
-                HKSPA(3, None, None, None)
-            ])
-
-        with self.pin.protect():
-            logger.debug('Sending HKSPA: {}'.format(_get_msg()))
-
-        resp = dialog.send(_get_msg())
-        logger.debug('Got HKSPA response: {}'.format(resp))
-        dialog.end()
 
+        with self._new_dialog() as dialog:
+            response = dialog.send(HKSPA1())
+            
         self.accounts = []
-        for seg in resp.find_segments(HISPA1):
+        for seg in response.find_segments(HISPA1):
             self.accounts.extend(seg.accounts)
 
-        return self.accounts
+        return [a for a in [acc.as_sepa_account() for acc in self.accounts] if a]
 
     def get_statement(self, account: SEPAAccount, start_date: datetime.datetime, end_date: datetime.date):
         """
@@ -121,7 +149,7 @@ class FinTS3Client:
         dialog.end()
         return statement
 
-    def _create_statement_message(self, dialog: FinTSDialog, account: SEPAAccount, start_date, end_date, touchdown):
+    def _create_statement_message(self, dialog: FinTSDialogOLD, account: SEPAAccount, start_date, end_date, touchdown):
         hversion = dialog.hkkazversion
 
         if hversion in (4, 5, 6):
@@ -153,35 +181,35 @@ class FinTS3Client:
         :param account: SEPA account to fetch the balance
         :return: A mt940.models.Balance object
         """
-        # init dialog
-        dialog = self._new_dialog()
-        dialog.sync()
-        dialog.init()
-
-        # execute job
-        def _get_msg():
-            return self._create_balance_message(dialog, account)
-
-        with self.pin.protect():
-            logger.debug('Sending HKSAL: {}'.format(_get_msg()))
-
-        resp = dialog.send(_get_msg())
-        logger.debug('Got HKSAL response: {}'.format(resp))
 
-        # end dialog
-        dialog.end()
+        max_hksal_version = max(
+            (seg.header.version for seg in self.find_bpd('HISALS')),
+            default=6
+        )
 
-        # find segment and split up to balance part
-        seg = resp._find_segment('HISAL')
-        arr = seg[4]
+        if max_hksal_version in (1, 2, 3, 4, 5, 6):
+            seg = HKSAL6(
+                Account3.from_sepa_account(account),
+                False
+            )
+        elif max_hksal_version == 7:
+            seg = HKSAL7(
+                KTI1.from_sepa_account(account),
+                False
+            )
+        else:
+            raise ValueError('Unsupported HKSAL version {}'.format(max_hksal_version))
 
-        # get balance date
-        date = datetime.datetime.strptime(arr[3], "%Y%m%d").date()
 
-        # return balance
-        return Balance(arr[0], arr[1], date, currency=arr[2])
+        with self._new_dialog() as dialog:
+            response = dialog.send(seg)
+        
+        # find segment
+        seg = response.find_segment_first((HISAL6, HISAL7))
+        if seg:
+            return seg.balance_booked.as_mt940_Balance()
 
-    def _create_balance_message(self, dialog: FinTSDialog, account: SEPAAccount):
+    def _create_balance_message(self, dialog: FinTSDialogOLD, account: SEPAAccount):
         hversion = dialog.hksalversion
 
         if hversion in (1, 2, 3, 4, 5, 6):
@@ -242,7 +270,7 @@ class FinTS3Client:
             logger.debug('No HIWPD response segment found - maybe account has no holdings?')
             return []
 
-    def _create_get_holdings_message(self, dialog: FinTSDialog, account: SEPAAccount):
+    def _create_get_holdings_message(self, dialog: FinTSDialogOLD, account: SEPAAccount):
         hversion = dialog.hksalversion
 
         if hversion in (1, 2, 3, 4, 5, 6):
@@ -264,7 +292,7 @@ class FinTS3Client:
             )
         ])
 
-    def _create_send_tan_message(self, dialog: FinTSDialog, challenge: TANChallenge, tan):
+    def _create_send_tan_message(self, dialog: FinTSDialogOLD, challenge: TANChallenge, tan):
         return self._new_message(dialog, [
             HKTAN(3, '2', challenge.reference, '', challenge.version)
         ], tan)
@@ -454,7 +482,7 @@ class FinTS3Client:
         dialog.end()
         return dialog.tan_mechs
 
-    def _create_get_tan_description_message(self, dialog: FinTSDialog):
+    def _create_get_tan_description_message(self, dialog: FinTSDialogOLD):
         return self._new_message(dialog, [
             HKTAB(3)
         ])
@@ -483,18 +511,35 @@ class FinTS3Client:
 
 class FinTS3PinTanClient(FinTS3Client):
 
-    def __init__(self, blz, username, pin, server):
-        self.username = username
-        self.blz = blz
+    def __init__(self, bank_identifier, user_id, pin, server, customer_id=None):
         self.pin = Password(pin)
         self.connection = FinTSHTTPSConnection(server)
-        self.systemid = 0
-        super().__init__()
+        super().__init__(bank_identifier=bank_identifier, user_id=user_id, customer_id=customer_id)
 
-    def _new_dialog(self):
-        dialog = FinTSDialog(self.blz, self.username, self.pin, self.systemid, self.connection)
-        return dialog
+    def _new_dialog(self, lazy_init=False):
+        if not lazy_init:
+            self._ensure_system_id()
 
-    def _new_message(self, dialog: FinTSDialog, segments, tan=None):
-        return FinTSMessage(self.blz, self.username, self.pin, dialog.systemid, dialog.dialogid, dialog.msgno,
+        return FinTSDialog(self, lazy_init=lazy_init)
+
+        # FIXME
+        # dialog = FinTSDialogOLD(self.blz, self.username, self.pin, self.systemid, self.connection)
+        # return dialog
+
+    def _new_message(self, dialog: FinTSDialogOLD, segments, tan=None):
+        return FinTSMessageOLD(self.blz, self.username, self.pin, dialog.systemid, dialog.dialogid, dialog.msgno,
                             segments, dialog.tan_mechs, tan)
+
+    def _ensure_system_id(self):
+        if self.system_id != SYSTEM_ID_UNASSIGNED:
+            return
+
+        with self._new_dialog(lazy_init=True) as dialog:
+            response = dialog.init(
+                HKSYN3(SynchronisationMode.NEW_SYSTEM_ID),
+            )
+            
+        seg = response.find_segment_first(HISYN4)
+        if not seg:
+            raise ValueError('Could not find system_id')
+        self.system_id = seg.system_id
index 281a7f9978bc57f020038c751f47316ebf962941..69b3e6d20bc7b667b4731e3b0472e5ab170351da 100644 (file)
@@ -4,7 +4,7 @@ import requests
 from fints.parser import FinTS3Parser
 from fints.utils import Password
 
-from .message import FinTSMessage, FinTSResponse
+from .message import FinTSMessage, FinTSInstituteMessage
 
 
 class FinTSConnectionError(Exception):
@@ -18,15 +18,15 @@ class FinTSHTTPSConnection:
     def send(self, msg: FinTSMessage):
         print("Sending >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
         with Password.protect():
-            FinTS3Parser().parse_message(str(msg).encode('iso-8859-1')).print_nested()
+            msg.print_nested()
         print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
         r = requests.post(
-            self.url, data=base64.b64encode(str(msg).encode('iso-8859-1')),
+            self.url, data=base64.b64encode(msg.render_bytes()),
         )
         if r.status_code < 200 or r.status_code > 299:
             raise FinTSConnectionError('Bad status code {}'.format(r.status_code))
         response = base64.b64decode(r.content.decode('iso-8859-1'))
-        retval = FinTSResponse(response)
+        retval = FinTSInstituteMessage(segments=response)
         #import pprint; pprint.pprint(response)  FIXME Remove
         print("Received <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
         with Password.protect():
index 89d874aaf32e28c41948ca00da925e3567f1e342..f6f358d65d9bcb02cd893575b7c9004f2a513afa 100644 (file)
 import logging
 
-from .message import FinTSMessage
+from .message import FinTSMessageOLD, MessageDirection, FinTSMessage, FinTSCustomerMessage
 from .segments.auth import HKIDN, HKSYN, HKVVB
-from .segments.dialog import HKEND
+from .segments.dialog import HKEND, HKEND1
+from .segments.auth import HKIDN2, HKVVB3
+from .segments.message import HNHBK3, HNHBS1
+from .formals import BankIdentifier, SystemIDStatus, Language2, SynchronisationMode
 
 logger = logging.getLogger(__name__)
 
+DIALOGUE_ID_UNASSIGNED = '0'
 
 class FinTSDialogError(Exception):
     pass
 
 
 class FinTSDialog:
+    def __init__(self, client=None, lazy_init=False):
+        self.client = client
+        self.next_message_number = dict((v, 1) for v in  MessageDirection)
+        self.messages = dict((v, {}) for v in MessageDirection)
+        self.auth_mechanisms = []
+        self.enc_mechanism = None
+        self.open = False
+        self.need_init = True
+        self.lazy_init = lazy_init
+        self.dialogue_id = DIALOGUE_ID_UNASSIGNED
+
+    def __enter__(self):
+        if not self.lazy_init:
+            self.init()
+        return self
+    
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.end()
+
+    def init(self, *extra_segments):
+        if self.need_init and not self.open:
+            segments = [
+                HKIDN2(
+                    self.client.bank_identifier,
+                    self.client.customer_id,
+                    self.client.system_id,
+                    SystemIDStatus.ID_NECESSARY
+                ),
+                HKVVB3(
+                    self.client.bpd_version,
+                    self.client.upd_version,
+                    Language2.DE,
+                    self.client.product_name,
+                    self.client.product_version
+                )
+            ]
+            for s in extra_segments:
+                segments.append(s)
+
+            try:
+                self.open = True
+                retval = self.send(*segments)
+                self.need_init = False
+                return retval
+            except:
+                self.open = False
+                raise
+            finally:
+                self.lazy_init = False
+
+    def end(self):
+        if self.open:
+            self.send(HKEND1(self.dialogue_id))
+            self.open = False
+
+    def send(self, *segments):
+        if not self.open:
+            if self.lazy_init and self.need_init:
+                self.init()
+
+        if not self.open:
+            raise Exception("Cannot send on dialog that is not open")
+
+        message = self.new_customer_message()
+        for s in segments:
+            message += s
+        self.finish_message(message)
+
+        assert message.segments[0].message_number == self.next_message_number[message.DIRECTION]
+        self.messages[message.segments[0].message_number] = message
+        self.next_message_number[message.DIRECTION] += 1
+
+        response = self.client.connection.send(message)
+
+        ##assert response.segments[0].message_number == self.next_message_number[response.DIRECTION]
+        # FIXME Better handling
+        self.messages[response.segments[0].message_number] = message
+        self.next_message_number[response.DIRECTION] += 1
+
+        if self.enc_mechanism:
+            self.enc_mechanism.decrypt(message)
+
+        for auth_mech in self.auth_mechanisms:
+            auth_mech.verify(message)
+
+        if self.dialogue_id == DIALOGUE_ID_UNASSIGNED:
+            seg = response.find_segment_first(HNHBK3)
+            if not seg:
+                raise ValueError('Could not find dialogue_id')
+            self.dialogue_id = seg.dialogue_id
+
+        self.client.process_institute_response(response)
+
+        return response
+
+    def new_customer_message(self):
+        message = FinTSCustomerMessage(self)
+        message += HNHBK3(0, 300, self.dialogue_id, self.next_message_number[message.DIRECTION])
+        
+        for auth_mech in self.auth_mechanisms:
+            auth_mech.sign_prepare(message)
+        
+        return message
+
+    def finish_message(self, message):
+        message += HNHBS1(message.segments[0].message_number)
+
+        # Create signature(s) in reverse order: from inner to outer
+        for auth_mech in reversed(self.auth_mechanisms):
+            auth_mech.sign_commit(message)
+
+        if self.enc_mechanism:
+            self.enc_mechanism.encrypt(message)
+
+        message.segments[0].message_size = len(message.render_bytes())
+
+
+class FinTSDialogOLD:
     def __init__(self, blz, username, pin, systemid, connection):
         self.blz = blz
         self.username = username
@@ -29,7 +151,7 @@ class FinTSDialog:
         seg_prepare = HKVVB(4)
         seg_sync = HKSYN(5)
 
-        return FinTSMessage(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
+        return FinTSMessageOLD(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
             seg_identification,
             seg_prepare,
             seg_sync
@@ -39,13 +161,13 @@ class FinTSDialog:
         seg_identification = HKIDN(3, self.blz, self.username, self.systemid)
         seg_prepare = HKVVB(4)
 
-        return FinTSMessage(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
+        return FinTSMessageOLD(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
             seg_identification,
             seg_prepare,
         ], self.tan_mechs)
 
     def _get_msg_end(self):
-        return FinTSMessage(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
+        return FinTSMessageOLD(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
             HKEND(3, self.dialogid)
         ])
 
index b6cec5f8b59852d874527a39830bada3a586dce7..39e64001daa25252c8cd3d178d19fc3aa9757fb0 100644 (file)
@@ -217,7 +217,28 @@ class DigitsField(FieldRenderFormatStringMixin, DataElementField):
 
 class FloatField(FieldRenderFormatStringMixin, DataElementField):
     type = 'float'
-    ## FIXME: Not implemented, no one uses this?
+    _DOC_TYPE = float
+    _FORMAT_STRING = "{:.12f}"  # Warning: Python's float is not exact!
+    ## FIXME: Needs test
+
+    def _parse_value(self, value):
+        if isinstance(value, float):
+            return value
+
+        _value = str(value)
+        if not re.match(r'^(?:0|[1-9]\d*),(?:\d*[1-9]|)$', _value):
+            raise TypeError("Only digits and ',' allowed for value of type 'float', no superfluous leading or trailing zeroes allowed: {!r}".format(value))
+
+        return float(_value.replace(",", "."))
+
+    def _render_value(self, value):
+        retval = super()._render_value(value)
+        return retval.replace('.', ',').rstrip('0')
+
+class AmountField(FixedLengthMixin, FloatField):
+    type = 'wrt'
+    _FIXED_LENGTH = [None, None, 15]
+    # FIXME Needs test
 
 class BinaryField(DataElementField):
     type = 'bin'
index 99acc4fadee9fe3144930b29c6a72ee75a323e52..6043e4cda9f5e12c5e4e1b5fd1d4c653e38f0faf 100644 (file)
@@ -300,7 +300,7 @@ class SupportedLanguages2(DataElementGroup):
 class SupportedHBCIVersions2(DataElementGroup):
     versions = DataElementField(type='code', max_length=3, min_count=1, max_count=9)
 
-class AccountInternational(DataElementGroup):
+class KTZ1(DataElementGroup):
     """Kontoverbindung ZV international, version 1
 
     Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
@@ -311,6 +311,71 @@ class AccountInternational(DataElementGroup):
     subaccount_number = DataElementField(type='id', _d="Unterkontomerkmal")
     bank_identifier = DataElementGroupField(type=BankIdentifier, _d="Kreditinstitutskennung")
 
+    def as_sepa_account(self):
+        from fints.models import SEPAAccount
+        if not self.is_sepa:
+            return None
+        if not self.bank_identifier.country_identifier == '280':
+            # FIXME Limitation of SEPAAccount object
+            return None
+        return SEPAAccount(self.iban, self.bic, self.account_number, self.subaccount_number, self.bank_identifier.bank_code)
+
+    @classmethod
+    def from_sepa_account(cls, acc):
+        return cls(
+            is_sepa=True,
+            iban=acc.iban,
+            bic=acc.bic,
+            account_number=acc.accountnumber,
+            subaccount_number=acc.subaccount,
+            bank_identifier=BankIdentifier(
+                country_identifier='280',
+                bank_code=acc.blz
+            )
+        )
+
+class KTI1(DataElementGroup):
+    """Kontoverbindung international, version 1
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    iban = DataElementField(type='an', max_length=34, required=False, _d="IBAN")
+    bic = DataElementField(type='an', max_length=11, required=False, _d="BIC")
+    account_number = DataElementField(type='id', required=False, _d="Konto-/Depotnummer")
+    subaccount_number = DataElementField(type='id', required=False, _d="Unterkontomerkmal")
+    bank_identifier = DataElementGroupField(type=BankIdentifier, required=False, _d="Kreditinstitutskennung")
+
+    @classmethod
+    def from_sepa_account(cls, acc):
+        return cls(
+            iban=acc.iban,
+            bic=acc.bic,
+            account_number=acc.accountnumber,
+            subaccount_number=acc.subaccount,
+            bank_identifier=BankIdentifier(
+                country_identifier='280',
+                bank_code=acc.blz
+            )
+        )
+
+class Account3(DataElementGroup):
+    """Kontoverbindung, version 3
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    account_number = DataElementField(type='id', _d="Konto-/Depotnummer")
+    subaccount_number = DataElementField(type='id', _d="Unterkontomerkmal")
+    bank_identifier = DataElementGroupField(type=BankIdentifier, _d="Kreditinstitutskennung")
+
+    @classmethod
+    def from_sepa_account(cls, acc):
+        return cls(
+            account_number=acc.accountnumber,
+            subaccount_number=acc.subaccount,
+            bank_identifier=BankIdentifier(
+                country_identifier='280',
+                bank_code=acc.blz
+            )
+        )
+
 class SecurityRole(RepresentableEnum):
     """Rolle des Sicherheitslieferanten, kodiert, version 2
 
@@ -372,3 +437,50 @@ class SystemIDStatus(RepresentableEnum):
     Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Formals"""
     ID_UNNECESSARY = '0' #: Kundensystem-ID wird nicht benötigt
     ID_NECESSARY = '1' #: Kundensystem-ID wird benötigt
+
+class SynchronisationMode(RepresentableEnum):
+    """Synchronisierungsmodus, version 2
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Formals"""
+    NEW_SYSTEM_ID = '0' #: Neue Kundensystem-ID zurückmelden
+    LAST_MESSAGE = '1' #: Letzte verarbeitete Nachrichtennummer zurückmelden
+    SIGNATURE_ID = '2' #: Signatur-ID zurückmelden
+
+class Amount1(DataElementGroup):
+    """Betrag
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    amount = DataElementField(type='wrt', _d="Wert")
+    currency = DataElementField(type='cur', _d="Währung")
+
+class CreditDebit2(RepresentableEnum):
+    """Soll-Haben-Kennzeichen, version 2
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    CREDIT = 'C' #: Haben
+    DEBIT = 'D' #: Soll
+
+class Balance2(DataElementGroup):
+    """Saldo, version 2
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    credit_debit = CodeField(enum=CreditDebit2, length=1, _d="Soll-Haben-Kennzeichen")
+    amount = DataElementGroupField(type=Amount1, _d="Betrag")
+    date = DataElementField(type='dat', _d="Datum")
+    time = DataElementField(type='tim', required=False, _d="Uhrzeit")
+
+    def as_mt940_Balance(self):
+        from mt940.models import Balance
+        return Balance(
+            self.credit_debit.value,
+            "{:.12f}".format(self.amount.amount).rstrip('0'),
+            self.date,
+            currency=self.amount.currency
+        )
+
+class Timestamp1(DataElementGroup):
+    """Zeitstempel
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    date = DataElementField(type='dat', _d="Datum")
+    time = DataElementField(type='tim', required=False, _d="Uhrzeit")
index f60fe438a04dd81276d6683175144835719a90af..7287c4348bbf48f7504dd980db2a8209100bb44c 100644 (file)
@@ -1,11 +1,52 @@
 import random
+from enum import Enum
 
 from .formals import SegmentSequence
-from .segments import HIBPA3, HIRMG2, HIRMS2, HISYN4, HNHBK3, HITANSBase
-from .segments.message import HNHBK, HNHBS, HNSHA, HNSHK, HNVSD, HNVSK
+from .segments import FinTS3Segment, HIBPA3, HIRMG2, HIRMS2, HITANSBase
+from .segments.message import HNHBK3, HNHBS1, HNSHA, HNSHK, HNVSD, HNVSK
 
+class MessageDirection(Enum):
+    FROM_CUSTOMER = 1
+    FROM_INSTITUTE = 2
 
-class FinTSMessage:
+class FinTSMessage(SegmentSequence):
+    DIRECTION = None
+    # Auto-Numbering, dialog relation, security base
+
+    def __init__(self, dialog=None, *args, **kwargs):
+        self.dialog = dialog
+        self.next_segment_number = 1
+        super().__init__(*args, **kwargs)
+
+    def __iadd__(self, segment: FinTS3Segment):
+        if not isinstance(segment, FinTS3Segment):
+            raise TypeError("Can only append FinTS3Segment instances, not {!r}".format(segment))
+        segment.header.number = self.next_segment_number
+        self.next_segment_number += 1
+        self.segments.append(segment)
+        return self
+
+    def sign_prepare(self, auth_mech):
+        pass
+
+    def sign_commit(self, auth_mech):
+        pass
+
+    def encrypt(self, enc_mech):
+        pass
+
+    def decrypt(self, enc_mech):
+        pass
+
+
+class FinTSCustomerMessage(FinTSMessage):
+    DIRECTION = MessageDirection.FROM_CUSTOMER
+    # Identification, authentication
+
+class FinTSInstituteMessage(FinTSMessage):
+    DIRECTION = MessageDirection.FROM_INSTITUTE
+
+class FinTSMessageOLD:
     def __init__(self, blz, username, pin, systemid, dialogid, msgno, encrypted_segments, tan_mechs=None, tan=None):
         self.blz = blz
         self.username = username
@@ -112,7 +153,7 @@ class FinTSResponse(SegmentSequence):
             if seg.header.reference == int(str(ref.segmentno)):
                 return seg
 
-    def get_touchdowns(self, msg: FinTSMessage):
+    def get_touchdowns(self, msg: FinTSMessageOLD):
         touchdown = {}
         for msgseg in msg.encrypted_segments:
             seg = self._find_segment_for_reference(HIRMS2, msgseg)
index 11e1d98957b8df3d51d36435bae462a847fcfed1..9d66c602c37424e166cbaf56cba41e98f70d6450 100644 (file)
@@ -1,7 +1,7 @@
 import re
 
 from fints.formals import (
-    AccountInformation, AccountInternational, AccountLimit,
+    AccountInformation, KTZ1, AccountLimit,
     AllowedTransaction, BankIdentifier, Certificate, Container, ContainerMeta,
     DataElementField, DataElementGroupField, EncryptionAlgorithm,
     HashAlgorithm, KeyName, ParameterPinTan, ParameterTwostepTAN1,
@@ -81,18 +81,6 @@ class FinTS3Segment(Container, SubclassesMixin, metaclass=FinTS3SegmentMeta):
 
         return target_cls
 
-class HNHBK3(FinTS3Segment):
-    "Nachrichtenkopf"
-    message_size = DataElementField(type='dig', length=12, _d="Größe der Nachricht (nach Verschlüsselung und Komprimierung)")
-    hbci_version = DataElementField(type='num', max_length=3, _d="HBCI-Version")
-    dialogue_id = DataElementField(type='id', _d="Dialog-ID")
-    message_number = DataElementField(type='num', max_length=4, _d="Nachrichtennummer")
-    reference_message = DataElementGroupField(type=ReferenceMessage, required=False, _d="Bezugsnachricht")
-
-class HNHBS1(FinTS3Segment):
-    "Nachrichtenabschluss"
-    message_number = DataElementField(type='num', max_length=4, _d="Nachrichtennummer")
-
 
 class HNVSD1(FinTS3Segment):
     "Verschlüsselte Daten"
@@ -161,13 +149,6 @@ class HIUPD6(FinTS3Segment):
     allowed_transactions = DataElementGroupField(type=AllowedTransaction, max_count=999, required=False, _d="Erlaubte Geschäftsvorfälle")
     extension = DataElementField(type='an', max_length=2048, required=False, _d="Erweiterung, kontobezogen")
 
-class HISYN4(FinTS3Segment):
-    "Synchronisierungsantwort"
-    customer_system_id = DataElementField(type='id', _d="Kundensystem-ID")
-    message_number = DataElementField(type='num', max_length=4, required=False, _d="Nachrichtennummer")
-    security_reference_signature_key = DataElementField(type='num', max_length=16, required=False, _d="Sicherheitsreferenznummer für Signierschlüssel")
-    security_reference_digital_signature = DataElementField(type='num', max_length=16, required=False, _d="Sicherheitsreferenznummer für Digitale Signatur")
-
 class ParameterSegment(FinTS3Segment):
     max_number_tasks = DataElementField(type='num', max_length=3, _d="Maximale Anzahl Aufträge")
     min_number_signatures = DataElementField(type='num', length=1, _d="Anzahl Signaturen mindestens")
@@ -216,9 +197,4 @@ class HIBPA3(FinTS3Segment):
     min_timeout = DataElementField(type='num', max_length=4, required=False, _d="Minimaler Timeout-Wert")
     max_timeout = DataElementField(type='num', max_length=4, required=False, _d="Maximaler Timeout-Wert")
 
-class HISPA1(FinTS3Segment):
-    """SEPA-Kontoverbindung rückmelden, version 1
-
-    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle 
-    """
-    accounts = DataElementGroupField(type=AccountInternational, max_count=999, required=False, _d="SEPA-Kontoverbindung")
+from . import accounts, auth, debit, depot, dialog, message, saldo, statement, transfer
index 6357af034ef15437281865cb4bbacc9722c310f5..aa668aaff18202876eda3cda4a70afbd68282dc7 100644 (file)
@@ -1,5 +1,7 @@
-from . import FinTS3SegmentOLD
+from . import FinTS3SegmentOLD, FinTS3Segment
 
+from ..fields import DataElementGroupField
+from ..formals import KTZ1, Account3
 
 class HKSPA(FinTS3SegmentOLD):
     """
@@ -17,3 +19,18 @@ class HKSPA(FinTS3SegmentOLD):
             ]) if accno is not None else ''
         ]
         super().__init__(segno, data)
+
+class HKSPA1(FinTS3Segment):
+    """SEPA-Kontoverbindung anfordern, version 1
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle 
+    """
+    accounts = DataElementGroupField(type=Account3, max_count=999, required=False, _d="Kontoverbindung")
+
+class HISPA1(FinTS3Segment):
+    """SEPA-Kontoverbindung rückmelden, version 1
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle 
+    """
+    accounts = DataElementGroupField(type=KTZ1, max_count=999, required=False, _d="SEPA-Kontoverbindung")
+
index f01bb6a85186d621d70bb24b2d5d2a731035ae98..de77e6a05846d3f0017e8584598c88146fe12ef9 100644 (file)
@@ -1,7 +1,7 @@
 from fints.utils import fints_escape
 
 from . import FinTS3SegmentOLD, FinTS3Segment
-from fints.formals import BankIdentifier, SystemIDStatus, Language2
+from fints.formals import BankIdentifier, SystemIDStatus, Language2, SynchronisationMode
 from fints.fields import DataElementGroupField, DataElementField, CodeField
 
 class HKIDN(FinTS3SegmentOLD):
index 4d74679a348eaf8d0c4aceb846749592bbb48eb5..97b9913cbbf8511debbba4013c1665b74546efad 100644 (file)
@@ -1,5 +1,21 @@
 from . import FinTS3SegmentOLD
 
+from . import FinTS3Segment
+from ..fields import CodeField, DataElementField
+from ..formals import SynchronisationMode
+
+class HKSYN3(FinTS3Segment):
+    """Synchronisierung, version 3
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Formals"""
+    synchronisation_mode = CodeField(enum=SynchronisationMode, length=1)    
+
+class HISYN4(FinTS3Segment):
+    "Synchronisierungsantwort"
+    system_id = DataElementField(type='id', _d="Kundensystem-ID")
+    message_number = DataElementField(type='num', max_length=4, required=False, _d="Nachrichtennummer")
+    security_reference_signature_key = DataElementField(type='num', max_length=16, required=False, _d="Sicherheitsreferenznummer für Signierschlüssel")
+    security_reference_digital_signature = DataElementField(type='num', max_length=16, required=False, _d="Sicherheitsreferenznummer für Digitale Signatur")
 
 class HKEND(FinTS3SegmentOLD):
     """
@@ -14,3 +30,9 @@ class HKEND(FinTS3SegmentOLD):
             dialogid,
         ]
         super().__init__(segno, data)
+
+class HKEND1(FinTS3Segment):
+    """Dialogende, version 1
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Formals"""
+    dialogue_id = DataElementField(type='id', _d="Dialog-ID")
index ff2440b656687e746711269242219590af07f8f1..81801847700cb6829a437101cbf37afff587cac4 100644 (file)
@@ -2,7 +2,21 @@ import time
 
 from fints.utils import fints_escape
 
-from . import FinTS3SegmentOLD
+from . import FinTS3SegmentOLD, FinTS3Segment
+from fints.formals import ReferenceMessage
+from fints.fields import DataElementGroupField, DataElementField, ZeroPaddedNumericField
+
+class HNHBK3(FinTS3Segment):
+    "Nachrichtenkopf"
+    message_size = ZeroPaddedNumericField(length=12, _d="Größe der Nachricht (nach Verschlüsselung und Komprimierung)")
+    hbci_version = DataElementField(type='num', max_length=3, _d="HBCI-Version")
+    dialogue_id = DataElementField(type='id', _d="Dialog-ID")
+    message_number = DataElementField(type='num', max_length=4, _d="Nachrichtennummer")
+    reference_message = DataElementGroupField(type=ReferenceMessage, required=False, _d="Bezugsnachricht")
+
+class HNHBS1(FinTS3Segment):
+    "Nachrichtenabschluss"
+    message_number = DataElementField(type='num', max_length=4, _d="Nachrichtennummer")
 
 
 class HNHBK(FinTS3SegmentOLD):
@@ -48,7 +62,7 @@ class HNSHK(FinTS3SegmentOLD):
             secref,
             self.SECURITY_BOUNDARY,
             self.SECURITY_SUPPLIER_ROLE,
-            ':'.join(['1', '', str(systemid)]),
+            ':'.join(['1', '', fints_escape(str(systemid))]),
             1,
             ':'.join(['1', time.strftime('%Y%m%d'), time.strftime('%H%M%S')]),
             ':'.join(['1', '999', '1']),  # Negotiate hash algorithm
index a53333d6cf1ffcbbfa1d9e894b1106108e2cd35d..19091a2470e2f12f45434550d73663493526e767 100644 (file)
@@ -1,4 +1,7 @@
-from . import FinTS3SegmentOLD
+from . import FinTS3SegmentOLD, FinTS3Segment
+
+from fints.formals import Account3, KTI1, Balance2, Amount1, Timestamp1
+from fints.fields import DataElementGroupField, DataElementField
 
 
 class HKSAL(FinTS3SegmentOLD):
@@ -15,3 +18,54 @@ class HKSAL(FinTS3SegmentOLD):
             'N'
         ]
         super().__init__(segno, data)
+
+class HKSAL6(FinTS3Segment):
+    """Saldenabfrage, version 6
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    account = DataElementGroupField(type=Account3, _d="Kontoverbindung Auftraggeber")
+    all_accounts = DataElementField(type='jn', _d="Alle Konten")
+    max_number_responses = DataElementField(type='num', max_length=4, required=False, _d="Maximale Anzahl Einträge")
+    touchdown_point = DataElementField(type='an', max_length=35, required=False, _d="Aufsetzpunkt")
+
+class HISAL6(FinTS3Segment):
+    """Saldenrückmeldung, version 6
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    account = DataElementGroupField(type=Account3, _d="Kontoverbindung Auftraggeber")
+    account_product = DataElementField(type='an', max_length=30, _d="Kontoproduktbezeichnung")
+    currency = DataElementField(type='cur', _d="Kontowährung")
+    balance_booked = DataElementGroupField(type=Balance2, _d="Gebuchter Saldo")
+    balance_pending = DataElementGroupField(type=Balance2, required=False, _d="Saldo der vorgemerkten Umsätze")
+    line_of_credit = DataElementGroupField(type=Amount1, required=False, _d="Kreditlinie")
+    available_amount = DataElementGroupField(type=Amount1, required=False, _d="Verfügbarer Betrag")
+    used_amount = DataElementGroupField(type=Amount1, required=False, _d="Bereits verfügter Betrag")
+    overdraft = DataElementGroupField(type=Amount1, required=False, _d="Überziehung")
+    booking_time = DataElementGroupField(type=Timestamp1, required=False, _d="Buchungszeitpunkt")
+    date_due = DataElementField(type='dat', required=False, _d="Fälligkeit")
+
+class HKSAL7(FinTS3Segment):
+    """Saldenabfrage, version 7
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    account = DataElementGroupField(type=KTI1, _d="Kontoverbindung international")
+    all_accounts = DataElementField(type='jn', _d="Alle Konten")
+    max_number_responses = DataElementField(type='num', max_length=4, required=False, _d="Maximale Anzahl Einträge")
+    touchdown_point = DataElementField(type='an', max_length=35, required=False, _d="Aufsetzpunkt")
+
+
+class HISAL7(FinTS3Segment):
+    """Saldenrückmeldung, version 7
+
+    Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """
+    account = DataElementGroupField(type=KTI1, _d="Kontoverbindung international")
+    account_product = DataElementField(type='an', max_length=30, _d="Kontoproduktbezeichnung")
+    currency = DataElementField(type='cur', _d="Kontowährung")
+    balance_booked = DataElementGroupField(type=Balance2, _d="Gebuchter Saldo")
+    balance_pending = DataElementGroupField(type=Balance2, required=False, _d="Saldo der vorgemerkten Umsätze")
+    line_of_credit = DataElementGroupField(type=Amount1, required=False, _d="Kreditlinie")
+    available_amount = DataElementGroupField(type=Amount1, required=False, _d="Verfügbarer Betrag")
+    used_amount = DataElementGroupField(type=Amount1, required=False, _d="Bereits verfügter Betrag")
+    overdraft = DataElementGroupField(type=Amount1, required=False, _d="Überziehung")
+    booking_time = DataElementGroupField(type=Timestamp1, required=False, _d="Buchungszeitpunkt")
+    date_due = DataElementField(type='dat', required=False, _d="Fälligkeit")
index 997bcef5fd7ed9e9869a3c9bcb1dce0c68c22572..e5876fa537059db0a411202e9a3ebd557f7b930b 100644 (file)
@@ -129,7 +129,7 @@ class SegmentSequence:
     def find_segments(self, query=None, version=None, callback=None, recurse=True):
         """Yields an iterable of all matching segments.
 
-        :param query: Either a str or class specifying a segment type (such as 'HNHBK', or :class:`~fints.segments.HNHBK3`), or a list or tuple of strings or classes.
+        :param query: Either a str or class specifying a segment type (such as 'HNHBK', or :class:`~fints.segments.message.HNHBK3`), or a list or tuple of strings or classes.
                      If a list/tuple is specified, segments returning any matching type will be returned.
         :param version: Either an int specifying a segment version, or a list or tuple of ints.
                         If a list/tuple is specified, segments returning any matching version will be returned.
index d5292828f2ba2e89d2731798766376547f239b5b..0b8808e2e082dfe2d06c40af732b6a71604d371a 100644 (file)
@@ -342,7 +342,7 @@ def test_container_generic():
 def test_find_1():
     from conftest import TEST_MESSAGES
     from fints.parser import FinTS3Parser
-    from fints.segments import HNHBS1, HNHBK3
+    from fints.segments.message import HNHBS1, HNHBK3
 
     m = FinTS3Parser().parse_message(TEST_MESSAGES['basic_complicated'])
 
@@ -365,7 +365,7 @@ def test_find_1():
 def test_find_by_class():
     from conftest import TEST_MESSAGES
     from fints.parser import FinTS3Parser
-    from fints.segments import HNHBS1
+    from fints.segments.message import HNHBS1
 
     m = FinTS3Parser().parse_message(TEST_MESSAGES['basic_complicated'])
 
index 14db934ec68ecec7e75dd4c931b5698b8a61d333..661fedbe2631016a6128d95a27e018bccff3a11e 100644 (file)
@@ -63,6 +63,6 @@ def test_escape():
 def test_serialize_2():
     from fints.formals import SegmentSequence
     import fints.formals, fints.segments
-    s = SegmentSequence([fints.segments.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(date_time_type='1'), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0'), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(date_time_type='1'), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1'), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0)), fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', 3, 2), responses=[fints.formals.Response(code='0010', reference_element=None, text='Nachricht entgegengenommen.'), fints.formals.Response(code='0100', reference_element=None, text='Dialog beendet.')]), fints.segments.HNSHA2(header=fints.formals.SegmentHeader('HNSHA', 4, 2), security_reference='9166926')])), fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1), message_number=2)])
+    s = SegmentSequence([fints.segments.message.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(date_time_type='1'), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0'), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(date_time_type='1'), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1'), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0)), fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', 3, 2), responses=[fints.formals.Response(code='0010', reference_element=None, text='Nachricht entgegengenommen.'), fints.formals.Response(code='0100', reference_element=None, text='Dialog beendet.')]), fints.segments.HNSHA2(header=fints.formals.SegmentHeader('HNSHA', 4, 2), security_reference='9166926')])), fints.segments.message.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1), message_number=2)])
     
     assert FinTS3Serializer().serialize_message(s) == TEST_MESSAGES['basic_simple']
index 68ca208191ff2f1d42af2531f73a38d43c3f086a..10fa2f639f6603603e0b672ddb780904201f9875 100644 (file)
@@ -5,7 +5,8 @@ from fints.formals import (
     AlphanumericField, DataElementField, GenericField,
     NumericField, SegmentHeader, SegmentSequence,
 )
-from fints.segments import HNHBK3, HNHBS1, FinTS3Segment
+from fints.segments import FinTS3Segment
+from fints.segments.message import HNHBK3, HNHBS1 
 
 
 def test_metaclass_foo():
@@ -119,7 +120,7 @@ def test_find_subclass():
 def test_nested_output_evalable():
     import fints.segments, fints.formals
 
-    a = SegmentSequence([fints.segments.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3, None), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3, None), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(date_time_type='1', date=None, time=None), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1', algorithm_parameter_iv_value=None), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0', certificate=fints.formals.Certificate(certificate_type=None, certificate_content=None)), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1, None), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4, None), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(date_time_type='1', date=None, time=None), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1', algorithm_parameter_value=None), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), certificate=fints.formals.Certificate(certificate_type=None, certificate_content=None)), fints.segments.FinTS3Segment(header=fints.formals.SegmentHeader('HIRMG', 3, 2, None), _additional_data=[['0010', None, 'Nachricht entgegengenommen.'], ['0100', None, 'Dialog beendet.']]), fints.segments.FinTS3Segment(header=fints.formals.SegmentHeader('HNSHA', 4, 2, None), _additional_data=['9166926'])])), fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1, None), message_number=2)])
+    a = SegmentSequence([fints.segments.message.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3, None), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3, None), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(date_time_type='1', date=None, time=None), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1', algorithm_parameter_iv_value=None), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0', certificate=fints.formals.Certificate(certificate_type=None, certificate_content=None)), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1, None), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4, None), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(identified_role='2', cid=None, identifier='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(date_time_type='1', date=None, time=None), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1', algorithm_parameter_value=None), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), certificate=fints.formals.Certificate(certificate_type=None, certificate_content=None)), fints.segments.FinTS3Segment(header=fints.formals.SegmentHeader('HIRMG', 3, 2, None), _additional_data=[['0010', None, 'Nachricht entgegengenommen.'], ['0100', None, 'Dialog beendet.']]), fints.segments.FinTS3Segment(header=fints.formals.SegmentHeader('HNSHA', 4, 2, None), _additional_data=['9166926'])])), fints.segments.message.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1, None), message_number=2)])
 
     output = StringIO()
     a.print_nested(stream=output)