/** * Antwort des hobex-HPS-Terminals auf eine Zahlung oder eine Statusabfrage, * durchgereicht von **Kasseneck Connect** (`POST /v1/terminal/payment`, * `/status`, `/abort` — Feld `hps` im Erfolgsrumpf). Connect ordnet nichts * ein, es reicht den Terminal-Rumpf roh durch — die Einordnung passiert hier. * * **Zwilling:** `kasseneck_api/lib/src/hobex_hps/transaction_response.dart`. * Beide Seiten pinnen dieselbe Codetabelle, siehe `HPS_CODES` unten * und `fixtures/hobex-hps-codes.json`. * * **`responseCode !== '0'` ist NICHT die Pruefung auf eine Ablehnung.** Genau * diese Lesart hat am 24.08.2026 zur doppelten Belastung gefuehrt: * [NO_STATEMENT_CODE] (`9027`) ist ein Code ungleich `'0'` und sagt trotzdem * nichts aus — er steht am gemessenen Terminal gleichermassen fuer "laeuft * gerade", "abgebrochen" und "nie gesehen". Wer ihn als Ablehnung liest, * meldet fuer einen LAUFENDEN Vorgang "nichts belastet, Wiederholung * gefahrlos". * * [isConclusive] ist die einzige Stelle, an der ein Code zu einem Ausgang * wird — und sie ist eine ECHTE Positivliste: nur ein Code, dessen Bedeutung * feststeht und in [HPS_CODES] benannt ist, zaehlt. Jeder andere — * auch ein neuer, heute noch unbekannter Code — ist eine Wissensluecke, siehe * [isUnknownCode]. Am 27.08.2026 hat der Dart-Zwilling gemessen, warum die * Gegenrichtung ("jeder Code ausser 9027 ist schluessig") gefaehrlich ist: ein * bis dahin unbenannter Code ([TECHNICAL_ERROR_CODE], `9900`) war darueber * schluessig und haette eine Zahlung, unter der tatsaechlich Geld geflossen * sein kann, als `declined` gemeldet. * * Seit 16.09.2026 kommt die TECS-Liste dazu (`tecs-codes.ts`): das HPS * reicht die Codes der TECS-Plattform durch, auf der hobex autorisiert. Neu * sind dabei [HpsCode.sendReversal] (Storno nachschicken, siehe * `payments.ts`) und die Schreibweise: TECS fuehrt `0055`, das Terminal * sendet `55` -- [normalizeHpsCode] macht beides zu einem Code. * * **Einen neuen Code aufnehmen:** Eintrag hier oder in `tecs-codes.ts`, * derselbe im Dart-Zwilling, `npm run fixtures:hobex-hps-codes`; ein neuer * Grund braucht in jeder Kasse eine Uebersetzung. */ /** Wie ein Ergebniscode den Ausgang eines Vorgangs bestimmt. */ export type HpsCodeEffect = /** Schreibt den Ausgang fest: `'0'` genehmigt, jeder andere abgelehnt. */ 'conclusive' /** Gemessen oder dokumentiert, aber KEINE Aussage (9027, 100011, fremde TECS-Produkte). */ | 'noStatement' /** * Der Host war beteiligt, das Terminal storniert nicht selbst -- ob belastet * wurde, weiss das Terminal nicht. Weiterklaeren, aber ohne dass ein * spaeteres `9027` daraus "nichts belastet" machen darf (siehe `payments.ts`). */ | 'hostUncertain'; /** Woher die Bedeutung eines Codes stammt. */ export type HpsCodeSource = /** Am Geraet gemessen (`doc/kartenzahlung.md` im Dart-Zwilling). */ 'measured' /** * Von hobex dokumentiert: HPS-Liste (11.09.2026) oder TECS-Liste * (16.09.2026, dann ist `tecsTitle` gesetzt). */ | 'documented' /** Beides: gemessen und von hobex bestaetigt. */ | 'measuredAndDocumented'; /** * Worauf eine Kasse reagiert -- der Grund hinter einem Ergebniscode. Mehrere * Codes teilen sich einen Grund, wenn am Tresen dasselbe zu tun ist * (`100004`, `100005`, `100012`: "Karte nicht gelesen, noch einmal"). Der Satz * fuer den Bediener steht in [HPS_REASON_HINTS]; eine Kasse mit eigener * Uebersetzung schluesselt ueber den Grund selbst. */ export type HpsCodeReason = 'approved' | 'approvedWithCondition' | 'aborted' | 'noCard' | 'cardReadFailed' | 'cardDeclined' | 'issuerDeclined' | 'cardBlocked' | 'cardExpired' | 'insufficientFunds' | 'wrongPin' | 'pinTriesExceeded' | 'pinRequired' | 'amountInvalid' | 'tipNotSelected' | 'terminalBusy' | 'terminalBlocked' | 'terminalSetup' | 'acquirerSetup' | 'terminalFault' | 'requestRejected' | 'hostRejected' | 'hostUnavailable' | 'invalidTransaction' | 'refundPassword' | 'refundDisabled' | 'refundRejected' | 'hostTimeoutReversed' | 'reversedByHost' | 'voidedAfterHostFault' | 'hostFault' | 'hostTimeout' | 'internalError' | 'canceled' | 'cancelDenied' | 'originalDeclined' | 'notAbortable' | 'noStatement' | 'technicalError' | 'unknown'; /** Der Satz fuer den Bediener je Grund, deutsch. */ export declare const HPS_REASON_HINTS: Readonly>; /** * Die Gruende hinter einem Code mit `effect: 'hostUncertain'`: ob (und wie * viel) Geld geflossen ist, weiss das Terminal nicht. */ export declare function isHostUncertainReason(reason: HpsCodeReason | undefined): boolean; /** Ein Ergebniscode, dessen Bedeutung feststeht (gemessen oder dokumentiert). */ export interface HpsMeasuredCode { /** Der Ergebniscode, wie ihn das Terminal im Feld `responseCode` sendet. */ readonly code: string; /** Bedeutung — deutsch, ohne Umlaute (siehe Vorbild). */ readonly meaning: string; /** * `true`: der Code schreibt einen Ausgang fest (Teil der Positivliste, * siehe [isConclusive]). `false`: benannt, aber KEINE Aussage ueber den * Vorgang -- oder ein ungewisser Host-Ausgang, siehe [HpsCode.effect]. */ readonly conclusive: boolean; } /** Ein Eintrag der vollstaendigen Codetabelle [HPS_CODES]. */ export interface HpsCode extends HpsMeasuredCode { /** Titel, wie hobex ihn fuehrt bzw. das Terminal als `responseText` sendet. */ readonly title: string; readonly effect: HpsCodeEffect; readonly reason: HpsCodeReason; readonly source: HpsCodeSource; /** * Der Code weist die ANFRAGE selbst ab (TID, Form, Geraetezustand). Auf eine * Zahlung ist das deren Ablehnung; auf eine STATUSABFRAGE heisst es nur, dass * diese Abfrage nicht bedient wurde -- ueber den gesuchten Vorgang sagt es * nichts (gemessen fuer `100108`). Siehe [isConclusiveAsStatus]. */ readonly rejectsRequest: boolean; /** * Bei diesem Code wird ein Storno nachgeschickt: der Host hat nicht oder * nicht brauchbar geantwortet, und das Terminal nimmt den Vorgang nicht * selbst zurueck. Nur bei `effect: 'hostUncertain'`. Vorgabe von hobex * (16.09.2026) zu `9908`: "ein Timeout wie jeder andere. Richtigerweise * sollte in dem Fall ein Storno nachgeschickt werden." */ readonly sendReversal: boolean; /** * Titel in der TECS-Liste (16.09.2026), wenn der Code dort steht -- sonst * `null`. Bei gemessenen Codes weicht er vom [title] ab (`55`: "PIN * falsch" gegen "Incorrect PIN"). */ readonly tecsTitle: string | null; } /** * Die vollstaendige Codetabelle — Vertrag mit dem Dart-Zwilling * (`HpsCodes.all` in `lib/src/hobex_hps/response_codes.dart`), ausgegeben als * `fixtures/hobex-hps-codes.json`. * * Zwei Quellen: GEMESSEN an hobex-HPS-Geraeten (TID 3600335, HPS 1.10.0, * Firmware 7.3.6, 26.–28.08.2026; TID 3556988 im Betrieb) und die * Antwortcodeliste von hobex (erhalten 11.09.2026). Die Regel bleibt: nur ein * Code mit feststehender Bedeutung schreibt einen Ausgang fest -- die Liste des * Herstellers ist eine solche Feststellung, kein Raten aus der Codefamilie. * * Eingeordnet wird danach, WO im Ablauf ein dokumentierter Code entsteht: * - vor dem Host (Anfrage, Karte, EMV-Kernel, Eingaben, Geraetezustand) -> * `conclusive`, also `declined`; * - `100029`, Zeitueberschreitung zum Host MIT auto-reversal -> ebenfalls * `declined`, das Terminal storniert laut hobex selbst; * - beim oder nach dem Host OHNE auto-reversal, dazu der Sammelcode `100999` * -> `hostUncertain`. * * Reihenfolge: zuerst die gemessenen wie im Messprotokoll, dann die * dokumentierten aufsteigend -- identisch mit dem Dart-Zwilling. */ export declare const HPS_CODES: readonly HpsCode[]; /** * @deprecated Seit 0.10.0 [HPS_CODES] -- die Tabelle fuehrt nicht mehr nur * gemessene Codes. Gleicher Inhalt, bleibt fuer bestehende Aufrufer. */ export declare const HPS_MEASURED_CODES: readonly HpsMeasuredCode[]; /** `responseCode` einer genehmigten Zahlung. */ export declare const APPROVED_CODE = "0"; /** Siehe [HPS_CODES]: ungueltiger Vorgang, nichts passiert. */ export declare const INVALID_TRANSACTION_CODE = "9002"; /** Siehe [HPS_CODES]: aufgehoben. */ export declare const TRANSACTION_CANCELED_CODE = "9011"; /** Siehe [HPS_CODES]: keine Aussage. */ export declare const NO_STATEMENT_CODE = "9027"; /** Siehe [HPS_CODES]: Kennung nicht numerisch, keine Aussage. */ export declare const TECHNICAL_ERROR_CODE = "9900"; /** Siehe [HPS_CODES]: abgebrochen. */ export declare const ABORTED_CODE = "100002"; /** Siehe [HPS_CODES]: Karte nicht aufgelegt. */ export declare const CARD_NOT_PRESENT_CODE = "100003"; /** Siehe [HPS_CODES]: nicht mehr abbrechbar. */ export declare const NOT_ABORTABLE_CODE = "100010"; /** Siehe [HPS_CODES]: Betrag abgewiesen, vor dem Kartenfluss. */ export declare const INVALID_AMOUNT_CODE = "9003"; /** Siehe [HPS_CODES]: Betrag ausserhalb des zulaessigen Bereichs. */ export declare const AMOUNT_OUT_OF_RANGE_CODE = "100019"; /** Siehe [HPS_CODES]: Terminal-Kennung unbekannt. */ export declare const INVALID_TID_CODE = "100108"; /** * Siehe [HPS_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet. * * Zweistellig, weil ein Antwortcode des HOSTS (ISO 8583, 55 = "Incorrect * PIN"), kein `9xxx`-Terminalcode und kein `100xxx`-Code der HPS-Anwendung. * Daraus folgt KEINE Regel fuer andere zweistellige Codes: in derselben * Familie stehen Genehmigungen (`08`, `10`, `11`, `85`). Jeder andere * Host-Code bleibt eine Wissensluecke, bis er gemessen ist -- und die * Zwei-9027-Regel in `payments.ts` faengt ihn nicht, weil die Statusabfrage * dann nicht 9027 antwortet, sondern mit dem Code selbst. */ export declare const WRONG_PIN_CODE = "55"; /** `100001` "Bad Request" -- nichts belastet. */ export declare const BAD_REQUEST_CODE = "100001"; /** `100004` "Card read failed" -- nichts belastet. Im Betrieb am 28.08.2026 gesehen. */ export declare const CARD_READ_FAILED_CODE = "100004"; /** `100005` "App select failed" -- nichts belastet. Im Betrieb am 28.08.2026 gesehen. */ export declare const APP_SELECT_FAILED_CODE = "100005"; /** `100006` "Communication with TecsXml failed" (No auto-reversal) -- ungewiss. */ export declare const HOST_COMMUNICATION_FAILED_CODE = "100006"; /** `100007` "Processing of TecsXml step failed" (No auto-reversal) -- ungewiss. */ export declare const HOST_STEP_FAILED_CODE = "100007"; /** `100008` "Invalid TID" laut hobex; gemessen wurde [INVALID_TID_CODE]. */ export declare const INVALID_TID_DOCUMENTED_CODE = "100008"; /** `100009` "Invalid Tx Type" -- nichts belastet. */ export declare const INVALID_TX_TYPE_CODE = "100009"; /** `100011` "Not Found" -- keine Aussage, aber ohne die Zwei-9027-Regel. */ export declare const NOT_FOUND_CODE = "100011"; /** `100012` "Max retries exceeded" -- nichts belastet. */ export declare const MAX_RETRIES_EXCEEDED_CODE = "100012"; /** `100013` "Diagnosis failed" -- nichts belastet. */ export declare const DIAGNOSIS_FAILED_CODE = "100013"; /** `100014` "Card information wasn't entered" (MOTO) -- nichts belastet. */ export declare const CARD_INFO_NOT_ENTERED_CODE = "100014"; /** `100015` "Card declined" (EMV-Kernel) -- nichts belastet. Im Betrieb am 28. und 31.08.2026 gesehen. */ export declare const CARD_DECLINED_CODE = "100015"; /** `100017` "Card Not Supported" -- nichts belastet. */ export declare const CARD_NOT_SUPPORTED_CODE = "100017"; /** `100018` "Scep enrollment failed" -- nichts belastet. */ export declare const SCEP_ENROLLMENT_FAILED_CODE = "100018"; /** `100020` "Refund password is invalid" -- nichts ausgezahlt. */ export declare const REFUND_PASSWORD_INVALID_CODE = "100020"; /** `100021` "Failed to enter the password" -- nichts ausgezahlt. */ export declare const PASSWORD_NOT_ENTERED_CODE = "100021"; /** `100022` "Terminal is blocked" -- nichts belastet. */ export declare const TERMINAL_BLOCKED_CODE = "100022"; /** `100023` "Invalid message type" -- ungewiss. */ export declare const INVALID_MESSAGE_TYPE_CODE = "100023"; /** `100024` "Transaction completion has failed" -- ungewiss. */ export declare const COMPLETION_FAILED_CODE = "100024"; /** `100025` "Refund transactions are disabled" -- nichts ausgezahlt. */ export declare const REFUND_DISABLED_CODE = "100025"; /** `100026` "Transaction was declined." (Chip-Daten fuer eine Karte ohne Chip) -- ungewiss. */ export declare const CHIP_DATA_MISMATCH_CODE = "100026"; /** `100027` "Unsupported UserData in TecsXml Response" -- ungewiss. */ export declare const UNSUPPORTED_USER_DATA_CODE = "100027"; /** `100028` "Tip selection process has failed." -- nichts belastet. */ export declare const TIP_SELECTION_FAILED_CODE = "100028"; /** `100029` "Communication with TecsXml timeout" (auto-reversal) -- nichts belastet. */ export declare const HOST_TIMEOUT_REVERSED_CODE = "100029"; /** `100998` "Terminal is busy" -- nichts belastet; gemessen als HTTP [TERMINAL_BUSY_HTTP_STATUS]. */ export declare const TERMINAL_BUSY_CODE = "100998"; /** `100999` "Internal Error" -- Sammelcode, ungewiss. */ export declare const INTERNAL_ERROR_CODE = "100999"; /** * HTTP `409` ("Terminal is busy"): das Terminal serialisiert und weist eine * zweite Anfrage ab, waehrend eine erste noch laeuft. Am 27.08.2026 gemessen: * kommt nach 87 Millisekunden, der abgewiesene Vorgang hinterlaesst KEINE * Spur (die Statusabfrage auf seine Kennung liefert weiterhin * [NO_STATEMENT_CODE]). * * Bewusst KEIN Eintrag in [HPS_CODES]: es ist ein HTTP-Status, kein * `responseCode` — er entsteht, bevor ueberhaupt ein Antwortrumpf gelesen * wird. Siehe `errors.ts` (`HpsConnectTerminalError.isTerminalBusy`) fuer die * getrennte Auswertung. */ export declare const TERMINAL_BUSY_HTTP_STATUS = 409; /** * HTTP `404` beim Terminal-Kontakt. **Lesart ungemessen:** am Produktivgeraet * (TID 3556988, Firmware 2.3.9) antwortet `POST /api/transaction/abort/...` * damit, am Testgeraet (3600335) nie. Es kann "diesen Vorgang kenne ich nicht" * heissen oder "diesen Endpunkt gibt es hier nicht" -- beides sagt nichts ueber * die Zahlung. Bei hobex angefragt; bis dahin benennt `payments.ts` beim * Abbruch beide Lesarten, statt eine zu behaupten. * * Wie [TERMINAL_BUSY_HTTP_STATUS] bewusst KEIN Eintrag in [HPS_CODES]: ein * HTTP-Status ist kein `responseCode`. */ export declare const NOT_FOUND_HTTP_STATUS = 404; /** * Die Schreibweise, unter der [code] in [HPS_CODES] steht: ein rein * numerischer Code ohne fuehrende Nullen (`0055` -> `55`, `0000` -> `0`), * jeder andere unveraendert. Ohne diese Angleichung waere ein `0000` kein * `0` -- und damit eine ABLEHNUNG einer genehmigten Zahlung. * [parseHpsTransactionResponse] gleicht deshalb schon beim Einlesen an. */ export declare function normalizeHpsCode(code: string): string; /** * Der Eintrag zu [code] in [HPS_CODES], oder `undefined`, wenn seine * Bedeutung nicht feststeht. Findet beide Schreibweisen * ([normalizeHpsCode]) und die Familien mit Platzhalter (`8105` -> `81xx`). */ export declare function hpsCodeInfo(code: string | undefined): HpsCode | undefined; /** * Der Grund zu [code] -- `'unknown'` fuer einen Code ausserhalb der Tabelle, * `undefined` ohne Code. */ export declare function hpsCodeReason(code: string | undefined): HpsCodeReason | undefined; /** Antwort des Terminals — Zahlung, Statusabfrage oder Abbruch. */ export interface HpsTransactionResponse { /** Kennung dieser Transaktion, wie vom Terminal bestaetigt bzw. echoed. */ readonly transactionId: string | undefined; /** Kennung der Original-Transaktion (Gutschrift, Aufhebung). */ readonly originalTransactionId: string | undefined; readonly tid: string | undefined; readonly receipt: string | undefined; readonly approvalCode: string | undefined; readonly reference: string | undefined; readonly transactionDate: string | undefined; readonly cardNumber: string | undefined; readonly cardExpiry: string | undefined; readonly brand: string | undefined; readonly cardIssuer: string | undefined; readonly transactionType: string | undefined; readonly currency: string | undefined; readonly amount: number | undefined; readonly tip: number | undefined; /** * Ergebniscode. `undefined` (nur bei einer Statusabfrage moeglich) heisst * "laeuft noch" — siehe [isInProgress]. Auf dieser Firmware ungemessen, * bleibt aber ebenso eine Nicht-Aussage wie [NO_STATEMENT_CODE]. * * Ein leerer String aus dem Rumpf wird beim Einlesen zu `undefined` * normalisiert (siehe [parseHpsTransactionResponse]): er traegt keine * Aussage, und `!== '0'` wuerde ihn sonst faelschlich als Ablehnung lesen. */ readonly responseCode: string | undefined; readonly responseText: string | undefined; /** Nur Statusabfrage (v2). Auf der gemessenen Firmware durchgehend `undefined`. */ readonly state: string | undefined; /** Der roh decodierte Rumpf, fuer Felder ohne eigenes Modell. */ readonly raw: Record; } /** * Liest eine Terminal-Antwort aus dem `hps`-Feld der Connect-Huelle. * * Wirft, wenn [raw] keine brauchbare Form hat (kein Objekt) — das ist ein * Formfehler der Antwort, kein Ausgang der Zahlung, und wird vom Aufrufer * (`payments.ts`) genauso behandelt wie jeder andere unerwartete Fehler: die * Klaerung laeuft konservativ weiter, statt ihn stillschweigend als * "laeuft noch" zu deuten. */ export declare function parseHpsTransactionResponse(raw: unknown): HpsTransactionResponse; /** `true`, wenn der Vorgang genehmigt ist. */ export declare function isApproved(res: Pick): boolean; /** `true`, wenn eine Statusabfrage meldet, der Vorgang laeuft noch (kein Code). */ export declare function isInProgress(res: Pick): boolean; /** `true`, wenn ein Abbruch daran scheiterte, dass der Vorgang nicht mehr abbrechbar war. */ export declare function isNotAbortable(res: Pick): boolean; /** * `true`, wenn das Terminal zu dieser Kennung keine Auskunft gibt (9027). * * Bewusst NUR `9027`, nicht auch [NOT_FOUND_CODE] (`100011`): auf `9027` ruht * die Zwei-9027-Regel in `payments.ts`, und die ist fuer genau diesen Code * gemessen. `100011` ist dokumentiert, aber nie gesehen. */ export declare function isNoStatement(res: Pick): boolean; /** * `true`, wenn das Terminal einen technischen Fehler meldet (9900). Seit der * TECS-Liste zugleich [isHostUncertain]: ob belastet wurde, ist offen. */ export declare function isTechnicalError(res: Pick): boolean; /** * `true`, wenn der Vorgang unter dieser Kennung aufgehoben wurde (9011). * * Vorsicht bei der Verwendung: fuer die DIREKTE Antwort auf eine Aufhebung * bedeutet dieser Code etwas anderes als bei einer Statusabfrage auf die * Originalzahlung -- siehe `payments.ts`, `fromCancelResponse` vs. * `fromCancelStatus`. */ export declare function isCanceled(res: Pick): boolean; /** * `true`, wenn diese Antwort ueberhaupt eine Aussage ueber den Ausgang * traegt — ein Ergebniscode, der in [HPS_CODES] als `conclusive` gefuehrt * wird. Die einzige Stelle, an der ein Code zu einem Ausgang wird. */ export declare function isConclusive(res: Pick): boolean; /** * Wie [isConclusive], aber fuer die Antwort auf eine STATUSABFRAGE: ein Code, * der die Anfrage selbst abweist (`rejectsRequest`, etwa `100022` "Terminal is * blocked" oder `100108` "Invalid TID"), sagt dort nichts ueber den gesuchten * Vorgang. Als `declined` gelesen, hiesse ein gesperrtes Terminal "die Zahlung * ist nicht belastet". */ export declare function isConclusiveAsStatus(res: Pick): boolean; /** * `true`, wenn der Code einen Ausgang meldet, den das Terminal selbst nicht * kennt: der hobex-Host war beteiligt, und das Terminal storniert nicht von * sich aus (`effect: 'hostUncertain'`). Ein spaeteres `9027` auf die * Statusabfrage heisst dann NICHT "nichts belastet" -- es spiegelt nur den * Speicher des Terminals, nicht den des Hosts. */ export declare function isHostUncertain(res: Pick): boolean; /** * `true`, wenn zu diesem Code ein Storno nachzuschicken ist * ([HpsCode.sendReversal]) -- der Host hat nicht oder nicht brauchbar * geantwortet, etwa `9908`. */ export declare function needsReversal(res: Pick): boolean; /** * `true`, wenn ein Ergebniscode VORHANDEN ist, dessen Bedeutung aber nicht * feststeht -- er fehlt in [HPS_CODES]. Ein Code, den dieses Modell schlicht * nicht kennt; anders als [isNoStatement], [isTechnicalError] und * [isHostUncertain], die eine Wissensluecke ueber den VORGANG benennen. */ export declare function isUnknownCode(res: Pick): boolean;