### Install python-fints with pip3 Source: https://python-fints.readthedocs.io/en/latest/_sources/quickstart.rst.txt Installs the python-fints library using pip3. Run this in a terminal to begin using the FinTS client. ```console $ pip3 install fints ``` -------------------------------- ### Full example: get transactions with TAN handling Source: https://python-fints.readthedocs.io/en/latest/_sources/tans.rst.txt Full example for getting transactions when a TAN is required. If a TAN is required, result is a NeedTANResponse; otherwise it holds transactions directly. Uses minimal_interactive_cli_bootstrap and terminal_flicker_unix for TAN entry. ```python import datetime from fints.utils import minimal_interactive_cli_bootstrap from fints.client import FinTS3PinTanClient, NeedTANResponse from fints.hhd.flicker import terminal_flicker_unix from credentials import blz, username, password, hbci_backend product_id = "CHANGE_ME" def ask_for_tan(f, response): print("A TAN is required") print(response.challenge) if getattr(response, 'challenge_hhduc', None): try: terminal_flicker_unix(response.challenge_hhduc) except KeyboardInterrupt: pass if response.decoupled: tan = input('Please press enter after confirming the transaction in your app:') else: tan = input('Please enter TAN:') return f.send_tan(response, tan) client = FinTS3PinTanClient(blz, username, password, hbci_backend, product_id=product_id) minimal_interactive_cli_bootstrap(client) with client: while isinstance(client.init_tan_response, NeedTANResponse): client.init_tan_response = ask_for_tan(client, client.init_tan_response) accounts = client.get_sepa_accounts() for account in accounts: print(f"Doing {account.iban}") result = client.get_transactions(account, start_date=datetime.datetime.now() - datetime.timedelta(days=100), end_date=datetime.datetime.now()) if isinstance(result, NeedTANResponse): result = ask_for_tan(client, result) else: print("No TAN is required") print(f"Got {len(result)} transactions for {account.iban}") ``` -------------------------------- ### Initialize FinTS3PinTanClient Source: https://python-fints.readthedocs.io/en/latest/quickstart.html Initializes a FinTS3PinTanClient with the bank's BLZ, login name, PIN, and HBCI endpoint. Logging in with a signature file or chip card is not supported. Requires the fints library to be installed. ```python import logging from datetime import date import getpass from fints.client import FinTS3PinTanClient logging.basicConfig(level=logging.DEBUG) f = FinTS3PinTanClient( '123456789', # Your bank's BLZ 'myusername', # Your login name getpass.getpass('PIN:'), # Your banking PIN 'https://hbci-pintan.gad.de/cgi-bin/hbciservlet', # Your bank's HBCI endpoint ) ``` -------------------------------- ### Choose TAN method with minimal_interactive_cli_bootstrap Source: https://python-fints.readthedocs.io/en/latest/_sources/quickstart.rst.txt Launches a minimal interactive command-line utility to help choose a TAN method after client initialization. Useful for quick starts to handle PSD2 TAN challenges. ```python from fints.utils import minimal_interactive_cli_bootstrap minimal_interactive_cli_bootstrap(f) ``` -------------------------------- ### Full example: interactive SEPA transfer with TAN and VOP handling Source: https://python-fints.readthedocs.io/en/latest/transfers.html Shows a complete interactive SEPA transfer flow with TAN and VOP handling. It initializes the client, handles the initial TAN challenge, performs a simple transfer, and then loops through NeedTANResponse and NeedVOPResponse responses, prompting for TAN or confirming payee verification before printing the final status and responses. ```python client = FinTS3PinTanClient(...) minimal_interactive_cli_bootstrap(client) with client: if client.init_tan_response: print("A TAN is required", client.init_tan_response.challenge) if getattr(client.init_tan_response, 'challenge_hhduc', None): try: terminal_flicker_unix(client.init_tan_response.challenge_hhduc) except KeyboardInterrupt: pass tan = input('Please enter TAN:') client.send_tan(client.init_tan_response, tan) res = client.simple_sepa_transfer( account=accounts[0], iban='DE12345', bic='BIC12345', amount=Decimal('7.00'), recipient_name='Foo', account_name='Test', reason='Birthday gift', endtoend_id='NOTPROVIDED', ) while isinstance(res, NeedTANResponse | NeedVOPResponse): if isinstance(res, NeedTANResponse): print("A TAN is required", res.challenge) if getattr(res, 'challenge_hhduc', None): try: terminal_flicker_unix(res.challenge_hhduc) except KeyboardInterrupt: pass if result.decoupled: tan = input('Please press enter after confirming the transaction in your app:') else: tan = input('Please enter TAN:') res = client.send_tan(res, tan) elif isinstance(res, NeedVOPResponse): if res.vop_result.vop_single_result.result == "RCVC": print("Payee name is an exact match") if res.vop_result.vop_single_result.other_identification: print("Other info retrieved by bank:", res.vop_result.vop_single_result.other_identification) elif res.vop_result.vop_single_result.result == "RVMC": print("Payee name is a close match") print("Name retrieved by bank:", res.vop_result.vop_single_result.close_match_name) if res.vop_result.vop_single_result.other_identification: print("Other info retrieved by bank:", res.vop_result.vop_single_result.other_identification) elif res.vop_result.vop_single_result.result == "RVNM": print("Payee name does not match match") elif res.vop_result.vop_single_result.result == "RVNA": print("Payee name could not be verified") print("Reason:", res.vop_result.vop_single_result.na_reason) elif res.vop_result.vop_single_result.result == "PDNG": print("Payee name could not be verified (pending state, can't be handled by this library)") print("Do you want to continue? Your bank will not be liable if the money ends up in the wrong place.") input('Please press enter to confirm or Ctrl+C to cancel') res = client.approve_vop_response(res) print(res.status) print(res.responses) ``` -------------------------------- ### Get TAN media with Postbank workaround Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Retrieves TAN media information. For Postbank (URL https://hbci.postbank.de/banking/hbci.do) it uses a lazy-init dialog and the init method; otherwise it uses a regular dialog and the send method. ```python def get_tan_media(self, media_type = TANMediaType2.ALL, media_class = TANMediaClass4.ALL): """Get information about TAN lists/generators. Returns tuple of fints.formals.TANUsageOption and a list of fints.formals.TANMedia4 or fints.formals.TANMedia5 objects.""" if self.connection.url == 'https://hbci.postbank.de/banking/hbci.do': # see https://github.com/raphaelm/python-fints/issues/101#issuecomment-572486099 context = self._new_dialog(lazy_init=True) method = lambda dialog: dialog.init else: context = self._get_dialog() method = lambda dialog: dialog.send ``` -------------------------------- ### Diagnostic script for FinTS3PinTanClient with TAN and VOP handling Source: https://python-fints.readthedocs.io/en/latest/_sources/trouble.rst.txt Run this script to test the affected feature of the library. Replace the placeholder arguments (BLZ, USER, PIN, ENDPOINT, product_id) with your bank's parameters and credentials. Do not modify the rest of the script. If it fails, report an issue with the bank name and failing operations; if it works, include full working example code in your issue. The script handles TAN challenges and VOP (Verification of Payee) responses. ```python import datetime import getpass import logging import sys from decimal import Decimal from fints.client import FinTS3PinTanClient, NeedTANResponse, FinTSUnsupportedOperation from fints.hhd.flicker import terminal_flicker_unix from fints.utils import minimal_interactive_cli_bootstrap logging.basicConfig(level=logging.DEBUG) client_args = ( 'REPLACEME', # BLZ 'REPLACEME', # USER getpass.getpass('PIN: '), 'REPLACEME' # ENDPOINT ) product_id = 'REPLACEME' f = FinTS3PinTanClient(*client_args, product_id=product_id) minimal_interactive_cli_bootstrap(f) def ask_for_tan(response): print("A TAN is required") print(response.challenge) if getattr(response, 'challenge_hhduc', None): try: terminal_flicker_unix(response.challenge_hhduc) except KeyboardInterrupt: pass if response.decoupled: tan = input('Please press enter after confirming the transaction in your app:') else: tan = input('Please enter TAN:') return f.send_tan(response, tan) def ask_for_vop(response: NeedVOPResponse): if response.vop_result.vop_single_result.result == "RVMC": print("Payee name is a close match") print("Name retrieved by bank:", response.vop_result.vop_single_result.close_match_name) if response.vop_result.vop_single_result.other_identification: print("Other info retrieved by bank:", response.vop_result.vop_single_result.other_identification) elif response.vop_result.vop_single_result.result == "RVNM": print("Payee name does not match match") elif response.vop_result.vop_single_result.result == "RVNA": print("Payee name could not be verified") print("Reason:", response.vop_result.vop_single_result.na_reason) elif response.vop_result.vop_single_result.result == "PDNG": print("Payee name could not be verified (pending state, can't be handled by this library)") print("Do you want to continue? Your bank will not be liable if the money ends up in the wrong place.") input('Please press enter to confirm or Ctrl+C to cancel') return f.approve_vop_response(response) # Open the actual dialog with f: # Since PSD2, a TAN might be needed for dialog initialization. Let's check if there is one required while isinstance(f.init_tan_response, NeedTANResponse): f.init_tan_response = ask_for_tan(f.init_tan_response) ``` -------------------------------- ### FinTS3Segment subclass example output Source: https://python-fints.readthedocs.io/en/latest/_sources/developer/segments/index.rst.txt Shows the expected representation of a parsed segment as a nested list, as produced by the FinTS3Segment class. The example is a Python list literal representing a segment header and its fields. ```python ['Segment', 'HNHBK', 3, 0, 1] ``` -------------------------------- ### Send SEPA transfer with TAN and VOP handling Source: https://python-fints.readthedocs.io/en/latest/_sources/transfers.rst.txt Complete example of sending a SEPA transfer with the FinTS3PinTanClient. It handles an initial TAN challenge, calls simple_sepa_transfer, and loops through NeedTANResponse and NeedVOPResponse responses, prompting for TANs and approving VOP responses as needed. The code uses helper functions minimal_interactive_cli_bootstrap and terminal_flicker_unix that are not defined in this snippet. ```python client = FinTS3PinTanClient(...) minimal_interactive_cli_bootstrap(client) with client: if client.init_tan_response: print("A TAN is required", client.init_tan_response.challenge) if getattr(client.init_tan_response, 'challenge_hhduc', None): try: terminal_flicker_unix(client.init_tan_response.challenge_hhduc) except KeyboardInterrupt: pass tan = input('Please enter TAN:') client.send_tan(client.init_tan_response, tan) res = client.simple_sepa_transfer( account=accounts[0], iban='DE12345', bic='BIC12345', amount=Decimal('7.00'), recipient_name='Foo', account_name='Test', reason='Birthday gift', endtoend_id='NOTPROVIDED', ) while isinstance(res, NeedTANResponse | NeedVOPResponse): if isinstance(res, NeedTANResponse): print("A TAN is required", res.challenge) if getattr(res, 'challenge_hhduc', None): try: terminal_flicker_unix(res.challenge_hhduc) except KeyboardInterrupt: pass if result.decoupled: tan = input('Please press enter after confirming the transaction in your app:') else: tan = input('Please enter TAN:') res = client.send_tan(res, tan) elif isinstance(res, NeedVOPResponse): if res.vop_result.vop_single_result.result == "RCVC": print("Payee name is an exact match") if res.vop_result.vop_single_result.other_identification: print("Other info retrieved by bank:", res.vop_result.vop_single_result.other_identification) elif res.vop_result.vop_single_result.result == "RVMC": print("Payee name is a close match") print("Name retrieved by bank:", res.vop_result.vop_single_result.close_match_name) if res.vop_result.vop_single_result.other_identification: print("Other info retrieved by bank:", res.vop_result.vop_single_result.other_identification) elif res.vop_result.vop_single_result.result == "RVNM": print("Payee name does not match match") elif res.vop_result.vop_single_result.result == "RVNA": print("Payee name could not be verified") print("Reason:", res.vop_result.vop_single_result.na_reason) elif res.vop_result.vop_single_result.result == "PDNG": print("Payee name could not be verified (pending state, can't be handled by this library)") print("Do you want to continue? Your bank will not be liable if the money ends up in the wrong place.") input('Please press enter to confirm or Ctrl+C to cancel') res = client.approve_vop_response(res) print(res.status) print(res.responses) ``` -------------------------------- ### Startcode class for HHD start codes Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/hhd/flicker.html Startcode class for parsing and rendering HHD start codes. It reads the length and control bytes, supports HHD versions 13 and 14, and adjusts the rendered length when control bytes are present. ```python class Startcode(DE): def __init__(self): super().__init__(LDE_LENGTH_DEFAULT) self.control_bytes = [] def parse(self, data): self.lde = int(data[:2], 16) data = data[2:] self.length = bit_sum(self.lde, 5) self.version = HHD_VERSION_13 if self.lde & (1 << BIT_CONTROLBYTE) != 0: self.version = HHD_VERSION_14 for i in range(10): cbyte = int(data[:2], 16) self.control_bytes.append(cbyte) data = data[2:] if cbyte & (1 << BIT_CONTROLBYTE) == 0: break self.data = data[:self.length] return data[self.length:] def render_length(self): s = super().render_length() if self.version == HHD_VERSION_13 or not self.control_bytes: return s l = int(s, 16) + (1 << BIT_CONTROLBYTE) return h(l, 2) ``` -------------------------------- ### Initialize FinTS3PinTanClient with credentials and product ID Source: https://python-fints.readthedocs.io/en/latest/_sources/quickstart.rst.txt Initializes a FinTS3PinTanClient with your bank's BLZ, username, PIN, and HBCI endpoint. Requires a registered product ID (see DK registration). Placeholder values are shown; replace them with your actual credentials and endpoint. Logging is set to DEBUG for verbosity. ```python import logging from datetime import date import getpass from fints.client import FinTS3PinTanClient logging.basicConfig(level=logging.DEBUG) f = FinTS3PinTanClient( '123456789', # Your bank's BLZ 'myusername', # Your login name getpass.getpass('PIN:'), # Your banking PIN 'https://hbci-pintan.gad.de/cgi-bin/hbciservlet', product_id='Your product ID' # see above ) ``` -------------------------------- ### Defining a custom segment class Source: https://python-fints.readthedocs.io/en/latest/_sources/developer/segments/index.rst.txt Example of defining a custom segment class using DataElementField. ```APIDOC ## [CLASS] Custom Segment Class Definition ### Description Shows how to define a custom FinTS segment class by subclassing FinTS3Segment and specifying fields with DataElementField. ### Example ```python from fints.segments.base import FinTS3Segment from fints.fields import DataElementField class HNHBS1(FinTS3Segment): message_number = DataElementField(type='num', max_length=4) ``` ### Notes - The class name determines TYPE and VERSION automatically if it consists of uppercase letters followed by digits. - All type conversion and construction are handled automatically. ``` -------------------------------- ### Get current TAN mechanism Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Returns the currently selected TAN mechanism (security function). ```python def get_current_tan_mechanism(self): return self.selected_security_function ``` -------------------------------- ### print_nested output for HNHBS1 Source: https://python-fints.readthedocs.io/en/latest/developer/segments/index.html Example output of print_nested on an instance of the HNHBS1 segment class. The output is eval()able to reconstruct the object. ```python fints.segments.HNHBS1( header = fints.formals.SegmentHeader('HNHBS', 4, 1), message_number = 1, ) ``` -------------------------------- ### Open dialog with bank and fetch SEPA accounts Source: https://python-fints.readthedocs.io/en/latest/_sources/quickstart.rst.txt Opens a communication dialog with the bank using a context manager. Checks if a TAN is required for initialization本体 and prompts for it, then fetches SEPA accounts. Requires the client from the previous initialization step. ```python with f: # Since PSD2, a TAN might be needed for dialog initialization. Let's check if there is one required if f.init_tan_response: print("A TAN is required", f.init_tan_response.challenge) tan = input('Please enter TAN:') f.send_tan(f.init_tan_response, tan) # Fetch accounts accounts = f.get_sepa_accounts() ``` -------------------------------- ### Get available TAN mechanisms Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Returns a dictionary mapping security_function to TwoStepParameters for available TAN mechanisms. Only considers HITANS versions listed in IMPLEMENTED_HKTAN_VERSIONS and filters by allowed_security_functions. ```python def get_tan_mechanisms(self): """ Get the available TAN mechanisms. Note: Only checks for HITANS versions listed in IMPLEMENTED_HKTAN_VERSIONS. :return: Dictionary of security_function: TwoStepParameters objects. """ retval = OrderedDict() for version in sorted(IMPLEMENTED_HKTAN_VERSIONS.keys()): for seg in self.bpd.find_segments('HITANS', version): for parameter in seg.parameter.twostep_parameters: if parameter.security_function in self.allowed_security_functions: retval[parameter.security_function] = parameter return retval ``` -------------------------------- ### Diagnostic script for FinTS3PinTanClient Source: https://python-fints.readthedocs.io/en/latest/trouble.html Run this script to test the affected feature of the library. Replace the placeholders (BLZ, USER, ENDPOINT, product_id) with your bank's parameters and credentials. Do not modify the script otherwise; pasting it into a Jupyter notebook is considered a modification. If the script fails, the issue is likely a compatibility problem with your bank; if it works, the issue is likely in your usage. Ensure no sensitive data (username, PIN, IBAN) is included when reporting. ```python import datetime import getpass import logging import sys from decimal import Decimal from fints.client import FinTS3PinTanClient, NeedTANResponse, FinTSUnsupportedOperation from fints.hhd.flicker import terminal_flicker_unix from fints.utils import minimal_interactive_cli_bootstrap logging.basicConfig(level=logging.DEBUG) client_args = ( 'REPLACEME', # BLZ 'REPLACEME', # USER getpass.getpass('PIN: '), 'REPLACEME' # ENDPOINT ) product_id = 'REPLACEME' f = FinTS3PinTanClient(*client_args, product_id=product_id) minimal_interactive_cli_bootstrap(f) def ask_for_tan(response): print("A TAN is required") print(response.challenge) if getattr(response, 'challenge_hhduc', None): try: terminal_flicker_unix(response.challenge_hhduc) except KeyboardInterrupt: pass if response.decoupled: tan = input('Please press enter after confirming the transaction in your app:') else: tan = input('Please enter TAN:') return f.send_tan(response, tan) def ask_for_vop(response: NeedVOPResponse): if response.vop_result.vop_single_result.result == "RVMC": print("Payee name is a close match") print("Name retrieved by bank:", response.vop_result.vop_single_result.close_match_name) if response.vop_result.vop_single_result.other_identification: print("Other info retrieved by bank:", response.vop_result.vop_single_result.other_identification) elif response.vop_result.vop_single_result.result == "RVNM": print("Payee name does not match match") elif response.vop_result.vop_single_result.result == "RVNA": print("Payee name could not be verified") print("Reason:", response.vop_result.vop_single_result.na_reason) elif response.vop_result.vop_single_result.result == "PDNG": print("Payee name could not be verified (pending state, can't be handled by this library)") print("Do you want to continue? Your bank will not be liable if the money ends up in the wrong place.") input('Please press enter to confirm or Ctrl+C to cancel') return f.approve_vop_response(response) # Open the actual dialog with f: # Since PSD2, a TAN might be needed for dialog initialization. Let's check if there is one required while isinstance(f.init_tan_response, NeedTANResponse): f.init_tan_response = ask_for_tan(f.init_tan_response) # Fetch accounts accounts = f.get_sepa_accounts() while isinstance(accounts, NeedTANResponse): accounts = ask_for_tan(accounts) if len(accounts) == 1: account = accounts[0] else: print("Multiple accounts available, choose one") for i, mm in enumerate(accounts): print(i, mm.iban) choice = input("Choice: ").strip() account = accounts[int(choice)] # Test pausing and resuming the dialog dialog_data = f.pause_dialog() client_data = f.deconstruct(including_private=True) ``` -------------------------------- ### Get transactions: get_transactions method Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Fetches transactions for a bank account, preferring MT940-based files and falling back to XML parsing. Handles pending transactions and returns a list of Transaction objects. ```python def get_transactions(self, account: SEPAAccount, start_date: datetime.date = None, end_date: datetime.date = None, include_pending = False): """ Fetches the list of transactions of a bank account in a certain timeframe. This prefers using the mt940-based files from the bank for historical reasons. However, if they are not available, it falls back to use get_transactions_xml and parses the data in a similar way. :param account: SEPA :param start_date: First day to fetch :param end_date: Last day to fetch :param include_pending: Include pending transactions (might lack some data like booking day) :return: A list of mt940.models.Transaction or fints.models.Transaction objects """ with self._get_dialog() as dialog: try: hkkaz = self._find_highest_supported_command(HKKAZ5, HKKAZ6, HKKAZ7) return self._get_transactions_mt940(dialog, hkkaz, account, start_date, end_date, include_pending) except FinTSUnsupportedOperation: hkcaz = self._find_highest_supported_command(HKCAZ1) response = self._get_transactions_xml(dialog, hkcaz, account, start_date, end_date) # If a TAN is required, exit early if isinstance(response, NeedTANResponse): return response booked_streams, pending_streams = response transactions = [] for s in booked_streams: transactions += [Transaction(t) for t in camt053_to_dict(s)] if include_pending: for s in pending_streams: transactions += [Transaction(t) for t in camt053_to_dict(s)] return transactions ``` -------------------------------- ### Use FinTS3PinTanClient with context manager and state persistence Source: https://python-fints.readthedocs.io/en/latest/_sources/client.rst.txt Shows how to use the client as a context manager to keep a standing dialog open for multiple operations, and how to store/restore client state via deconstruct/set_data. The datablob is retrieved from backend storage or set to None, and after the with block, deconstruct() is called to store the updated state. ```python datablob = ... # get from backend storage, or set to None client = FinTS3PinTanClient(..., from_data=datablob) with client: accounts = client.get_sepa_accounts() balance = client.get_balance(accounts[0]) transactions = client.get_transactions(accounts[0]) datablob = client.deconstruct() # Store datablob to backend storage ``` -------------------------------- ### Handle TAN/VOP responses and download statements Source: https://python-fints.readthedocs.io/en/latest/_sources/trouble.rst.txt This snippet shows a loop that handles NeedTANResponse and NeedVOPResponse by calling ask_for_tan or ask_for_vop. It also demonstrates selecting a statement and downloading it as PDF, with error handling for unsupported operations. ```python while isinstance(res, NeedTANResponse | NeedVOPResponse): if isinstance(res, NeedTANResponse): res = ask_for_tan(res) elif isinstance(res, NeedVOPResponse): res = ask_for_vop(res) ``` ```python elif choice == 11: print("Select statement") statements = f.get_statements(account) for i, statement in enumerate(statements): print(i, f"Statement {statement.statement_number}/{statement.year}") choice = int(input("Choice: ").strip()) statement = statements[choice] output_pdf = 'statement.pdf' res = f.get_statement(account, statement.statement_number, statement.year, StatementFormat.PDF) while isinstance(res, NeedTANResponse): res = ask_for_tan(res) with open(output_pdf, 'wb') as file: file.write(res.data) print("Written to", output_pdf) ``` -------------------------------- ### Get supported TAN mechanisms Source: https://python-fints.readthedocs.io/en/latest/_sources/tans.rst.txt Retrieves a dictionary mapping TAN mechanism identifiers (usually three-digit numerals) to TwoStepParametersCommon subclass instances. Use this before any TAN-based operation to see which mechanisms the bank supports. ```python mechanisms = client.get_tan_mechanisms() ``` -------------------------------- ### Handle KeyboardInterrupt when displaying flicker code Source: https://python-fints.readthedocs.io/en/latest/tans.html Wrap terminal_flicker_unix in a try/except block to catch KeyboardInterrupts, allowing the user to abort the flicker code display and continue with the TAN. The example passes result.challenge_hhduc as the code argument. ```python try: terminal_flicker_unix(result.challenge_hhduc) except KeyboardInterrupt: pass ``` -------------------------------- ### set_product(product_name, product_version) Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Sets the product name and version transmitted during identification with the bank. ```APIDOC ## set_product(product_name, product_version) ### Description Sets the product name and version that is transmitted as part of the client identification, as required by the FinTS specification. ### Parameters - **product_name** (string) - Required - The name of the end-user product. - **product_version** (string) - Required - The version of the end-user product. ### Returns None ### Example ```python client.set_product("MyBankingApp", "1.0.0") ``` ``` -------------------------------- ### Constructor with Positional Arguments Source: https://python-fints.readthedocs.io/en/latest/developer/working.html Demonstrates how to pass non-keyword arguments to the constructor, with fields assigned in order, except for header which must be a keyword argument. ```APIDOC ## Constructor with Positional Arguments ### Description When calling the constructor with non-keyword arguments, fields are assigned in order. The `header` field can only be given as a keyword argument. If no `header` is provided, a default `SegmentHeader` is automatically constructed. ### Method Constructor ### Endpoint `fints.segments.HNHBS1(*args, **kwargs)` ### Parameters #### Constructor Arguments - **args** (any) - Optional - Positional arguments assigned to fields in order, excluding `header`. - **header** (SegmentHeader) - Optional - Keyword-only argument for the segment header. ### Request Example ```python >>> HNHBS1(42) fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=42) >>> HNHBS1(42, header=SegmentHeader('FOO')) fints.segments.HNHBS1(header=fints.formals.SegmentHeader('FOO', None, None), message_number=42) ``` ### Response #### Success Response Returns a new segment object with the specified field values. #### Response Example ```python >>> HNHBS1(42) fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=42) ``` ``` -------------------------------- ### Parse and serialize a FinTS 3.0 message Source: https://python-fints.readthedocs.io/en/latest/developer/parsing.html Shows round-trip usage of FinTS3Parser.parse_message and FinTS3Serializer.serialize_message on a FinTS/HBCI 3.0 message. The example is shown as an interactive Python session (>>> prompt) and demonstrates parsing a byte array into a SegmentSequence and serializing it back to bytes. ```python >>> message = (b'HNHBK:1:3+000000000428+300+430711670077=043999659571CN9D=+2+430711670077=043' ... b"999659571CN9D=:2'HNVSK:998:3+PIN:1+998+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+" ... b"2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0'HNVSD:999:1+@195@HNSHK:2:" ... b'4+PIN:1+999+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+' ... b'2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0\' ``` -------------------------------- ### FinTS3Client as context manager Source: https://python-fints.readthedocs.io/en/latest/_sources/client.rst.txt Using the client as a context manager keeps a standing dialog open for multiple operations, reducing roundtrips. This is required for two-step TAN transactions. ```APIDOC ## FinTS3Client as context manager ### Description Using the client as a context manager keeps a standing dialog open for multiple operations, reducing roundtrips. This is required for two-step TAN transactions. ### Usage python with client: accounts = client.get_sepa_accounts() balance = client.get_balance(accounts[0]) transactions = client.get_transactions(accounts[0]) ### Notes - The client automatically opens and closes dialogs for simple reading operations, but using a context manager keeps the dialog open. - For TAN transactions, a standing dialog is mandatory because both steps must be in the same dialog. ``` -------------------------------- ### Define a FinTS segment class (HNHBS1) Source: https://python-fints.readthedocs.io/en/latest/developer/segments/index.html Example segment class definition. Subclasses of FinTS3Segment specify fields as class attributes using DataElementField. The type and version are determined from the class name if it consists of uppercase characters followed by decimal digits. ```python class HNHBS1(FinTS3Segment): message_number = DataElementField(type='num', max_length=4) ``` -------------------------------- ### FinTS3Client.__enter__ / __exit__ (context manager) Source: https://python-fints.readthedocs.io/en/latest/client.html Use FinTS3Client as a context manager to keep a standing dialog open for multiple operations. The dialog is automatically opened on entry and closed on exit, unless paused. ```APIDOC ## FinTS3Client.__enter__ / __exit__ (context manager) ### Description Use FinTS3Client as a context manager to keep a standing dialog open for multiple operations. The dialog is automatically opened on entry and closed on exit, unless paused. ### Method Context manager (with statement) ### Usage ```python with client: accounts = client.get_sepa_accounts() balance = client.get_balance(accounts[0]) transactions = client.get_transactions(accounts[0]) ``` ### Notes - Each opening and closing of a dialog takes one FinTS roundtrip. - For TAN transactions that require both steps in the same dialog, a standing dialog is mandatory. ``` -------------------------------- ### resume_dialog method Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Creates a dialog based on data from a previous dialog. The dialog_data must not come from an untrusted source. ```python @contextmanager def resume_dialog(self, dialog_data): """ Create a dialog based on the data of a previous dialog. **Warning:** `dialog_data` **MUST NOT** be from an untrusted source such as user-controlled or client-side state or you will have a major security issue. """ if self._standing_dialog: raise Exception("Cannot resume dialog, existing standing dialog") self._standing_dialog = FinTSDialog.create_resume(self, dialog_data) with self._standing_dialog: yield self self._standing_dialog = None ``` -------------------------------- ### set_product method Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Sets the product name and version transmitted as part of identification. According to the FinTS specification, this should contain information about the end-user product, not the FinTS library. ```python def set_product(self, product_name, product_version): """Set the product name and version that is transmitted as part of our identification According to 'FinTS Financial Transaction Services, Schnittstellenspezifikation, Formals', version 3.0, section C.3.1.3, you should fill this with useful information about the end-user product, *NOT* the FinTS library.""" self.product_name = product_name self.product_version = product_version ``` -------------------------------- ### Creating a Segment Object Source: https://python-fints.readthedocs.io/en/latest/developer/working.html Demonstrates how to create a FinTS3Segment subclass instance using its constructor, with optional keyword arguments for fields. ```APIDOC ## Creating a Segment Object ### Description Create a new instance of a FinTS3Segment subclass by calling its constructor. The constructor accepts optional keyword arguments for all fields of the class. ### Method Constructor ### Endpoint `fints.segments.HNHBS1()` ### Parameters #### Constructor Arguments - **header** (SegmentHeader) - Optional - The segment header. Can only be given as a keyword argument. If omitted, a default SegmentHeader is constructed. - **message_number** (int) - Optional - The message number. ### Request Example ```python >>> from fints.segments import HNHBS1 >>> s = HNHBS1() >>> s fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=None) ``` ### Response #### Success Response Returns a new segment object with the specified field values. #### Response Example ```python >>> s.header.number = 3 >>> s.header fints.formals.SegmentHeader('HNHBS', 3, 1) ``` ``` -------------------------------- ### FinTS Client Imports and Constants Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Initial imports and constants for the FinTS client, including bank identifiers and data blob magic bytes. ```python import datetime import logging from abc import ABCMeta, abstractmethod from base64 import b64decode from collections import OrderedDict from contextlib import contextmanager from decimal import Decimal from enum import Enum import bleach from sepaxml import SepaTransfer from . import version from .camt_parser import camt053_to_dict from .connection import FinTSHTTPSConnection from .dialog import FinTSDialog from .exceptions import * from .formals import ( CUSTOMER_ID_ANONYMOUS, KTI1, BankIdentifier, DescriptionRequired, SynchronizationMode, TANMediaClass4, TANMediaType2, SupportedMessageTypes, StatementFormat, TANUsageOption ) from .message import FinTSInstituteMessage from .models import SEPAAccount, Transaction from .parser import FinTS3Serializer from .security import ( PinTanDummyEncryptionMechanism, PinTanOneStepAuthenticationMechanism, PinTanTwoStepAuthenticationMechanism, ) from .segments.accounts import HISPA1, HKSPA1 from .segments.auth import HIPINS1, HKTAB4, HKTAB5, HKTAN2, HKTAN3, HKTAN5, HKTAN6, HKTAN7, HIVPPS1, HIVPP1, PSRD1, HKVPA1 from .segments.bank import HIBPA3, HIUPA4, HKKOM4 from .segments.debit import ( HKDBS1, HKDBS2, HKDMB1, HKDMC1, HKDME1, HKDME2, HKDSC1, HKDSE1, HKDSE2, DebitResponseBase, ) from .segments.depot import HKWPD5, HKWPD6 from .segments.dialog import HIRMG2, HIRMS2, HISYN4, HKSYN3 from .segments.journal import HKPRO3, HKPRO4 from .segments.saldo import HKSAL5, HKSAL6, HKSAL7 from .segments.statement import DKKKU2, HKKAZ5, HKKAZ6, HKKAZ7, HKCAZ1, HKKAU2, HKKAU1, HKEKA3, HKEKA4, HKEKA5 from .segments.transfer import HKCCM1, HKCCS1, HKIPZ1, HKIPM1 from .types import SegmentSequence from .utils import ( MT535_Miniparser, Password, SubclassesMixin, compress_datablob, decompress_datablob, mt940_to_array, ) logger = logging.getLogger(__name__) SYSTEM_ID_UNASSIGNED = '0' DATA_BLOB_MAGIC = b'python-fints_DATABLOB' DATA_BLOB_MAGIC_RETRY = b'python-fints_RETRY_DATABLOB' # workaround for ING not offering PSD2 conform two step authentication # ING only accepts one step authentication and only allows reading operations ING_BANK_IDENTIFIER = BankIdentifier(country_identifier='280', bank_code='50010517') ``` -------------------------------- ### Find highest supported command: _find_highest_supported_command method Source: https://python-fints.readthedocs.io/en/latest/_modules/fints/client.html Searches the BPD for the highest supported version of a segment class. Raises FinTSUnsupportedOperation if no version is found. Optionally returns the parameter segment. ```python def _find_highest_supported_command(self, *segment_classes, **kwargs): """Search the BPD for the highest supported version of a segment.""" return_parameter_segment = kwargs.get("return_parameter_segment", False) parameter_segment_name = "{}I{}S".format(segment_classes[0].TYPE[0], segment_classes[0].TYPE[2:]) version_map = dict((clazz.VERSION, clazz) for clazz in segment_classes) max_version = self.bpd.find_segment_highest_version(parameter_segment_name, version_map.keys()) if not max_version: raise FinTSUnsupportedOperation('No supported {} version found. I support {}, bank supports {}.'.format( parameter_segment_name, tuple(version_map.keys()), tuple(v.header.version for v in self.bpd.find_segments(parameter_segment_name)) )) if return_parameter_segment: return max_version, version_map.get(max_version.header.version) else: return version_map.get(max_version.header.version) ``` -------------------------------- ### Fetch SEPA accounts and handle TAN responses Source: https://python-fints.readthedocs.io/en/latest/_sources/trouble.rst.txt Retrieves SEPA accounts, prompting for TAN when needed. If multiple accounts exist, the user selects one interactively; otherwise the single account is used. ```python # Fetch accounts accounts = f.get_sepa_accounts() while isinstance(accounts, NeedTANResponse): accounts = ask_for_tan(accounts) if len(accounts) == 1: account = accounts[0] else: print("Multiple accounts available, choose one") for i, mm in enumerate(accounts): print(i, mm.iban) choice = input("Choice: ").strip() account = accounts[int(choice)] ``` ```python # Test pausing and resuming the dialog dialog_data = f.pause_dialog() client_data = f.deconstruct(including_private=True) f = FinTS3PinTanClient(*client_args, product_id=product_id, from_data=client_data) with f.resume_dialog(dialog_data): while True: operations = [ "End dialog", "Fetch transactions of the last 30 days", "Fetch transactions of the last 120 days", "Fetch transactions XML of the last 30 days", "Fetch transactions XML of the last 120 days", "Fetch information", "Fetch balance", "Fetch holdings", "Fetch scheduled debits", "Fetch status protocol", ``` -------------------------------- ### Create and inspect a FinTS3Segment (HNHBS1) Source: https://python-fints.readthedocs.io/en/latest/_sources/developer/working.rst.txt Shows how to instantiate a segment object and access its fields. Setting ``header.number`` updates the segment header, and the object prints with its current field values. ```python >>> from fints.segments import HNHBS1 >>> s = HNHBS1() >>> s fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=None) >>> s.header.number = 3 >>> s.header fints.formals.SegmentHeader('HNHBS', 3, 1) ``` -------------------------------- ### Handle TAN request with NeedRetryResponse Source: https://python-fints.readthedocs.io/en/latest/_sources/tans.rst.txt Three-step workflow for handling a TAN request: pause the dialog and save client state, prompt for the TAN, then resume the dialog and send the TAN. Use with client and dialog state restoration facilities. ```python client = FinTS3PinTanClient(...) # Optionally: choose a tan mechanism with # client.set_tan_mechanism(…) with client: response = client.sepa_transfer(...) dialog_data = client.pause_dialog() client_data = client.deconstruct() tan_data = response.get_data() ``` ```python tan_request = NeedRetryResponse.from_data(tan_data) print("TAN request: {}".format(tan_request.challenge)) tan = input('Enter TAN: ') ``` ```python tan_request = NeedRetryResponse.from_data(tan_data) client = FinTS3PinTanClient(..., from_data=client_data) with client.resume_dialog(dialog_data): response = client.send_tan(tan_request, tan) print(response.status) print(response.responses) ```