feat: HKEKA — electronic account statements (Elektronischer Kontoauszug) - #32
Merged
robocode13 merged 2 commits intoAug 1, 2026
Merged
Conversation
…auszug) Fetches the statement document the bank files in the customer's electronic mailbox (a PDF for most banks) instead of a list of transactions. For accounts where the bank has withdrawn DKKKU this is the only remaining automated way to get at credit card statements. HKEKA v5 request (SEG "Kontoauszug5") HIEKA v5 response (SEG "KontoauszugRes5") HIEKAS v5 parameters (SEG "KontoauszugPar5") Element order follows the FinTS 3.0 specification. It is worth spelling out because it is easy to get wrong: in HIEKA v5 `booked` sits SIXTH, after format/TimeRange/date/year/number — whereas in HIEKP v2 the same field comes first. Ordering it wrongly makes the segment silently unparseable. The request element is deliberately named `offset` and not `continuationMark`: for HKEKA the bank does not split one oversized response across messages, it announces with code 3040 that a *further document* is waiting. Reusing the `continuationMark` name would enlist the generic parted-message handling, which splices two complete HIEKA segments into one corrupt segment. Callers page through the mailbox with `nextOffset` instead. Verified against a live bank (Berliner Volksbank, HKEKA v5, tanRequired=false): six credit card statements and two current account documents retrieved, every PDF intact from %PDF header to %%EOF, paging terminating on the bank's last document. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects found by adversarial review of the HKEKA feature, both of the silent kind: no exception, success: true, wrong data. Version gating. The definitions described version 5 only, while getMaxSupportedTransactionVersion happily negotiates whatever the bank announces below that. Per the FinTS 3.0 segment catalogue, versions 1-3 of the request carry the national account connection (KTV3) rather than the international one, `year` does not exist before version 3, only version 5 of the response carries date/year/number, and version 1 has no iban/bic/name. A version 4 response decoded with the version 5 layout shifts every field by three positions and hands out the advertisement text as the statement document. The elements are now gated the way HKSAL and HKKAZ do it. Binary length. Binary.decode returned everything after the second '@' instead of the declared number of characters. The length is the only thing that tells payload apart from the separators and escape characters a PDF is full of. Fields without a length prefix keep their previous behaviour. That second fix also settled an open question: the fetched statements carry one 0x00 after their %%EOF, and it survives length-exact decoding — so the byte is inside the length the bank declares. It is the bank's, not ours. Tests cover encoding version 3 vs version 5, decoding version 1 and version 4, a payload containing +, ', @ and ?, a padded binary field, and the interaction itself: mapping, nextOffset, several statements per response, and the base64 unwrapping in both directions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
|
Nice contribution, thanks. I could test it successfully with one of my banks, the others don't support it unfortunately. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
HKEKAfetches the electronic account statement (Elektronischer Kontoauszug) — the document the bank files in the customer's electronic mailbox, usually a PDF. Not a transaction list: the bank's own periodic statement, the same one a customer would otherwise download by hand.It follows the existing shape:
can*capability check,getX/getXWithTanpair, README table rows, segments registered inregistry.ts.Why it is worth having: it is an independent source. Transactions come from the bank's transaction endpoint; the statement comes from the bank's document archive. Comparing the two catches the failure mode where a sync reports success and silently returns nothing — which is exactly what led me here.
Files
HKEKA/HIEKA/HIEKASelectronicStatementInteraction.tselectronicStatement.tsclient.ts,index.ts,README.md,registry.tsHKEKA.test.ts129 tests pass (112 before).
Two details worth your attention
Version gates. Only version 5 carries
date/year/number; up to version 4 the document follows the time range directly, and version 1 has noiban/bic/name. Decoding an older response with the v5 layout does not fail — it shifts every field by three positions and hands out the advertisement text as the document. The same applies to the request:KTV3up to v3,KTVIntfrom v4, statement year only from v3. Both are gated byminVersion.Also note
bookedsits after date/year/number inHIEKA, whileHIEKPv2 orders the same fields the other way round. The order is per-segment and cannot be carried over.Binary.decodenow honours the declared length. This touches a shared primitive, so it is the change I would scrutinise first.A binary value arrives as
@<length>@<data>. The old implementation returned everything after the second@. For a PDF that is wrong: the payload is full of bytes that look like separators (+,:,') and escape characters, and the declared length is the only thing that tells data from what follows it. Without this, statement documents come out with trailing garbage.There is a guard for values that are not length-prefixed — they are returned unchanged, which is what the existing tests feed in.
Deliberately not implemented: acknowledgement (
HKQTG)Banks that set
receiptRequiredin theirHIEKASparameters keep offering a statement until it is acknowledged with its receipt. The receipt is parsed and exposed on the response, but nothing is sent back.That is intentional and I would rather it be a conscious gap than a silent one: acknowledging is destructive from the customer's point of view — the statement stops being offered, and depending on the bank leaves the mailbox. A library should not do that as a side effect of a read call. If you want it, I would suggest an explicit separate method rather than a flag on the fetch.
Consequence for the current state: against a bank that requires acknowledgement, repeated calls will keep returning the same statement. My bank does not set the flag, so this path is untested against a real bank.
Scope of verification — please read
Verified against one bank: Berliner Volksbank (GAD backend),
HIEKAv5. 55 statement documents across 8 accounts, fetched without a TAN, PDFs opened and checked against the accounts they belong to.Untested against a real bank: versions 1–4,
receiptRequired, formats other than PDF (format1= MT940,2= ISO,3= PDF). Those follow the specification and are covered by unit tests only.Relation to #31
Independent in code — no shared files, either can merge first. In practice they belong together: statement documents are large, so a bank will routinely spread them over several messages, and without #31 the response arrives empty. That is how I found #31 in the first place.
Written with AI assistance (noted in the commit trailers); the measurements and the review are my own. Happy to adjust anything to your preference — naming, comment density, or splitting the
Binarychange out into its own PR if you would rather look at it separately.