Skip to content

Fix Consorsbank compatibility (4 issues: TAN version, account details, forced TAN, QR-TAN routing)#209

Open
ArlindNocaj wants to merge 2 commits intoraphaelm:masterfrom
ArlindNocaj:fix/consorsbank-compatibility
Open

Fix Consorsbank compatibility (4 issues: TAN version, account details, forced TAN, QR-TAN routing)#209
ArlindNocaj wants to merge 2 commits intoraphaelm:masterfrom
ArlindNocaj:fix/consorsbank-compatibility

Conversation

@ArlindNocaj
Copy link

@ArlindNocaj ArlindNocaj commented Feb 28, 2026

Summary

Fixes four protocol-level issues preventing Consorsbank (BLZ 76030080) from working with python-fints. Discovered by comparing mitmdump traces of the working hbci4j Java library against python-fints.

After all four fixes, transactions and SEPA transfers with photoTAN (QR code) work correctly.

Fix 1: security_method_version=2 for two-step TAN auth (fints/security.py)

Per the ZKA FinTS spec (page 58), two-step TAN methods (security_function != '999') require security_method_version=2 in the SecurityProfile of the HNSHK signature header. The previous hardcoded value of 1 caused Consorsbank to reject the request.

Before (wire): HNSHK:...:PIN:1 → bank rejects
After (wire): HNSHK:...:PIN:2 → bank accepts

Closes #99

Fix 2: Full account details in KTI1.from_sepa_account (fints/formals.py)

KTI1.from_sepa_account only populated iban and bic. Consorsbank requires the full account details (account_number, subaccount_number, bank_identifier). Other classes (KTZ1, Account2, Account3) already include these fields — KTI1 was the only inconsistent one.

Before (wire): HKKAZ:...:DE12345:CSDBDE71 → error 9010 (missing required fields)
After (wire): HKKAZ:...:DE12345:CSDBDE71:1234567890::280:76030080 → accepted

Fix 3: force_twostep_tan parameter (fints/client.py)

Some banks (Consorsbank) report HKKAZ:N in HIPINS (TAN not required) yet reject requests without HKTAN (error 9075). Added an opt-in force_twostep_tan parameter to FinTS3PinTanClient.__init__ — a set of segment types that should always include HKTAN regardless of HIPINS.

Usage:

client = FinTS3PinTanClient(
    ...,
    force_twostep_tan={'HKKAZ', 'HKSAL'},
)

Defaults to empty set, so existing behavior is completely unchanged.

Fix 4: Handle TAN response attached to command segment (fints/client.py)

Some banks (Consorsbank) attach the 0030 (TAN required) response code to the original command segment (e.g. HKCCS for transfers) rather than to the HKTAN segment. This caused _send_pay_with_possible_retry() to miss the TAN challenge entirely and return a plain TransactionResponse(SUCCESS) instead of NeedTANResponse — the transfer appeared to succeed but was never authorized.

Root cause: The code only checked response.responses(tan_seg) which filters by the HKTAN segment reference number. When the bank attaches 0030 to the HKCCS segment reference instead, the loop finds no matching responses.

Fix: Added a fallback that also checks response.responses(command_seg) for 0030/3955 codes. This correctly surfaces the NeedTANResponse with the photoTAN QR code image.

Verified with a real SEPA transfer using photoTAN (security function 900).

Additional changes

  • docs/transfers.rst: Added photoTAN/QR code handling to the full example; fixed typo (result.decoupledres.decoupled)
  • docs/tested.rst: Added Consorsbank (Transactions + Transfer) and security function 900
  • sample_consorsbank.py: Complete example showing Consorsbank setup with photoTAN transfers

Methodology

  1. Captured working hbci4j traffic with mitmproxy (mitmdump -s dump.py)
  2. Captured failing python-fints traffic with the same setup
  3. Compared request/response pairs byte-by-byte to identify the differences
  4. Validated each fix individually and all four together
  5. Successfully completed a real SEPA transfer with photoTAN QR code authentication

Backwards compatibility

  • Fix 1: Only affects two-step TAN mode (already requires security_function != '999'). One-step mode still uses version 1.
  • Fix 2: Adds optional fields that were already defined in the KTI1 DEG. Banks that don't need them will ignore them per the FinTS spec.
  • Fix 3: Opt-in parameter, defaults to empty set. No behavior change unless explicitly configured.
  • Fix 4: Only adds a fallback check after the existing loop. If the existing loop finds the response (current behavior for most banks), the fallback is never reached.
  • All 68 existing tests pass with no modifications.

These three issues were discovered by comparing mitmdump traces of the
working hbci4j Java library against python-fints when connecting to
Consorsbank (BLZ 76030080). After applying all three fixes, transactions
are fetched successfully, matching the Java output exactly.

1. security.py: Use security_method_version=2 for two-step TAN auth
   Per the ZKA FinTS spec (page 58), two-step TAN methods
   (security_function != '999') require version 2 in the
   SecurityProfile of the HNSHK signature header. The previous
   hardcoded value of 1 caused Consorsbank to reject the request.
   Ref: raphaelm#99

2. formals.py: Include full account details in KTI1.from_sepa_account
   KTI1.from_sepa_account only populated iban and bic, but Consorsbank
   requires the full account details (account_number, subaccount_number,
   bank_identifier). Other classes like KTZ1 already include these
   fields — KTI1 was the only one missing them.

3. client.py: Add force_twostep_tan parameter for banks that require
   HKTAN despite HIPINS saying otherwise
   Some banks (Consorsbank) report HKKAZ:N in HIPINS yet reject
   requests without HKTAN (error 9075). The new opt-in
   force_twostep_tan parameter (set of segment types) allows users
   to override HIPINS for specific segments. Defaults to empty set,
   so existing behavior is unchanged.

All three fixes are backwards-compatible and all existing tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Some banks (e.g. Consorsbank) attach the 0030/3955 response code to
the original command segment (HKCCS) rather than to the HKTAN segment.
This caused _send_pay_with_possible_retry() to miss the TAN challenge
and return a plain TransactionResponse instead of NeedTANResponse.

Added fallback: after checking tan_seg responses, also check
command_seg responses for 0030/3955 codes.

Also:
- Add photoTAN QR code handling to transfers.rst full example
- Fix typo (result.decoupled → res.decoupled) in transfers.rst
- Add Consorsbank to tested.rst (Transactions + Transfer)
- Add security function 900 (photoTAN / SecurePlus)
- Add sample_consorsbank.py showing photoTAN transfer flow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ArlindNocaj ArlindNocaj changed the title Fix Consorsbank compatibility (3 issues found via hbci4j comparison) Fix Consorsbank compatibility (4 issues: TAN version, account details, forced TAN, QR-TAN routing) Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consorsbank: Error during dialog initialization, could not fetch BPD.

1 participant