Environment:
python-fints 5.0.0
Bank: comdirect (BLZ 20041133), FinTS 3.0/PSD2
FinTS3PinTanClient, standard with client: usage, no system_id passed in (fresh client each time)
Symptom:
Every dialog open + get_transactions() call fails with:
FinTSClientPINError: Error during dialog initialization, PIN wrong?
The bank-side responses (captured via the fints.client logger) show:
9050 - Die Nachricht enthaelt Fehler.
9800 - Dialog abgebrochen
9340 - Ungueltige Auftragsnachricht: Ungueltige Signatur.
Notably, the mobile TAN is delivered to the phone before the failure — the connection gets quite far before failing.
Root cause analysis:
Since system_id is unassigned, FinTS3Client.enter() → _get_dialog() calls _ensure_system_id(), which opens its own preliminary lazy dialog (HKSYN3).
The response to that preliminary dialog appears to already include response code 3920, which _process_response() (client.py, _process_response) handles by auto-selecting a real two-step selected_security_function (e.g. "901") — this succeeds safely since self._standing_dialog is not yet set at that point.
Back in _get_dialog(), self._new_dialog(lazy_init=False) is then called to build the real dialog. Since selected_security_function is no longer None/'999' at this point, _new_dialog() (client.py ~line 1270) now builds the dialog's outer envelope with PinTanTwoStepAuthenticationMechanism instead of the one-step mechanism — meaning the dialog-init (HKIDN) itself gets two-step signed.
comdirect appears to reject a two-step-signed dialog-init outright (only accepting two-step signing for the actual TAN-requiring business command, not for opening the dialog), resulting in 9340.
Attempted workaround and its own failure mode:
Explicitly persisting/passing a known system_id into the client constructor skips step 1/2 above (no preliminary dialog, so selected_security_function stays None going into _new_dialog(), keeping the dialog-init one-step as desired). However, FinTSDialog.init() (dialog.py ~line 74) also unconditionally embeds an HKTAN segment into the INIT message whenever self.client.get_tan_mechanisms() is non-empty — and once the real dialog's own INIT response later triggers response code 3920 for the first time, _process_response()'s auto-select tries to call set_tan_mechanism() while self._standing_dialog is already set, raising:
Exception: Cannot change TAN mechanism with a standing dialog
So there appears to be no way, via the public API, to get both a one-step-signed dialog-init and a correctly negotiated TAN mechanism for the subsequent command against this bank — one approach two-step-signs the INIT (rejected by comdirect), the other crashes on the standing-dialog guard.
Question: Is this a known interaction? Is there a supported way to suppress the two-step auto-selection specifically for dialog-init while still allowing it for the actual TAN-requiring command afterward?
Environment:
python-fints 5.0.0
Bank: comdirect (BLZ 20041133), FinTS 3.0/PSD2
FinTS3PinTanClient, standard with client: usage, no system_id passed in (fresh client each time)
Symptom:
Every dialog open + get_transactions() call fails with:
FinTSClientPINError: Error during dialog initialization, PIN wrong?
The bank-side responses (captured via the fints.client logger) show:
9050 - Die Nachricht enthaelt Fehler.
9800 - Dialog abgebrochen
9340 - Ungueltige Auftragsnachricht: Ungueltige Signatur.
Notably, the mobile TAN is delivered to the phone before the failure — the connection gets quite far before failing.
Root cause analysis:
Since system_id is unassigned, FinTS3Client.enter() → _get_dialog() calls _ensure_system_id(), which opens its own preliminary lazy dialog (HKSYN3).
The response to that preliminary dialog appears to already include response code 3920, which _process_response() (client.py, _process_response) handles by auto-selecting a real two-step selected_security_function (e.g. "901") — this succeeds safely since self._standing_dialog is not yet set at that point.
Back in _get_dialog(), self._new_dialog(lazy_init=False) is then called to build the real dialog. Since selected_security_function is no longer None/'999' at this point, _new_dialog() (client.py ~line 1270) now builds the dialog's outer envelope with PinTanTwoStepAuthenticationMechanism instead of the one-step mechanism — meaning the dialog-init (HKIDN) itself gets two-step signed.
comdirect appears to reject a two-step-signed dialog-init outright (only accepting two-step signing for the actual TAN-requiring business command, not for opening the dialog), resulting in 9340.
Attempted workaround and its own failure mode:
Explicitly persisting/passing a known system_id into the client constructor skips step 1/2 above (no preliminary dialog, so selected_security_function stays None going into _new_dialog(), keeping the dialog-init one-step as desired). However, FinTSDialog.init() (dialog.py ~line 74) also unconditionally embeds an HKTAN segment into the INIT message whenever self.client.get_tan_mechanisms() is non-empty — and once the real dialog's own INIT response later triggers response code 3920 for the first time, _process_response()'s auto-select tries to call set_tan_mechanism() while self._standing_dialog is already set, raising:
Exception: Cannot change TAN mechanism with a standing dialog
So there appears to be no way, via the public API, to get both a one-step-signed dialog-init and a correctly negotiated TAN mechanism for the subsequent command against this bank — one approach two-step-signs the INIT (rejected by comdirect), the other crashes on the standing-dialog guard.
Question: Is this a known interaction? Is there a supported way to suppress the two-step auto-selection specifically for dialog-init while still allowing it for the actual TAN-requiring command afterward?