Still missing: Dummy encryption, PIN authorization.
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):
"""
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):
: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):
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):
)
])
- 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)
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)
])
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
from fints.parser import FinTS3Parser
from fints.utils import Password
-from .message import FinTSMessage, FinTSResponse
+from .message import FinTSMessage, FinTSInstituteMessage
class FinTSConnectionError(Exception):
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():
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
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
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)
])
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'
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 """
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
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")
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
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)
import re
from fints.formals import (
- AccountInformation, AccountInternational, AccountLimit,
+ AccountInformation, KTZ1, AccountLimit,
AllowedTransaction, BankIdentifier, Certificate, Container, ContainerMeta,
DataElementField, DataElementGroupField, EncryptionAlgorithm,
HashAlgorithm, KeyName, ParameterPinTan, ParameterTwostepTAN1,
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"
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")
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
-from . import FinTS3SegmentOLD
+from . import FinTS3SegmentOLD, FinTS3Segment
+from ..fields import DataElementGroupField
+from ..formals import KTZ1, Account3
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")
+
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):
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):
"""
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")
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):
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
-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):
'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")
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.
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'])
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'])
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']
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():
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)