/** * Die fuenf Fehlerarten des Kasseneck-Clients — bewusst getrennt, weil ein * Aufrufer auf jede anders reagieren muss: * * - `KasseneckValidationError` — die **Form** stimmt nicht: entweder ging * mangels brauchbarer Eingabe gar keine Anfrage raus, oder die Antwort war * nicht verwertbar. `scope` trennt die beiden Richtungen. `'request'`: die * Eingabe des Aufrufers verletzt eine Regel, die dieses Paket schon vor dem * Senden kennt (Beleg ohne Positionen, unmoegliche Gutscheinkombination) — * das ist ein Programmierfehler im Aufrufer, kein Betriebszustand. * `'response'`: das Backend meldete Erfolg, die Nutzlast enthielt aber * nicht, was der Aufruf zusagt. * * - `KasseneckApiError`: **fachlicher** Fehler mit `code`. Das Backend legt * Erfolg/Misserfolg in den Rumpf (`{status:'success'|'error', message, * code, data}`, siehe `successResponse`/`errorResponse` im Backend), meist * unter HTTP 200, unter `/v3` bei unbekanntem Endpunkt auch unter HTTP 404 * (`not_found`). Dazu kommen drei Codes des Pakets selbst: * `route_missing` (HTTP 200 mit HTML: die Auffangregel der * Single-Page-App hat geantwortet, der Aufruf kam nie an), * `dialect_mismatch` (Antwort ohne Kennzeichen `Kasseneck-Api-Version: v3`: * ein Rand ohne `/v3` hat geantwortet, **Ausgang unklar**) und * `response_unreadable` (ein Aufruf mit Wirkung meldete Erfolg, die * Antwort traegt aber nicht, was er zusagt: **Ausgang unklar**). * **Entscheidend ist `outcome`:** `'rejected'` heisst abgelehnt, nichts * geschehen, Wiederholen hilft nicht. `'unknown'` heisst: der Vorgang kann * ausgefuehrt sein (bei `createReceipt` ein signierter Beleg). Dann **nie * blind wiederholen**: Belege, Stornos und Zahlungen nachlesen * (Belegliste, `getReceipt`, Terminal-Beleg); ein Aufruf mit * `idempotencyKey` (Lager schreiben, Rechnung, Partner-Betrieb mit * Schluessel) darf mit **demselben** Schluessel erneut gesendet werden und * wirkt genau einmal, nie mit einem neuen. `createInvoiceItem` erkennt die * Wiederholung an `source` + `reference` (dieselben Werte erneut senden); * Posten aendern oder zurueckziehen und Mandate haben keinen Schluessel: * erst nachlesen. * - `KasseneckHttpError` — die Antwort war **keine** verwertbare Huelle: * HTTP 500/404 ohne Huelle, leerer Rumpf oder Text statt JSON. Beim * Bericht-Download gelten dieselben Gruende fuer alles, was kein PDF ist. * `reason` trennt die Faelle maschinenlesbar. Auf einem Aufruf mit * Wirkung (`UNKNOWN_OUTCOME_CALLS` in aufrufe.ts) hat HTTP 5xx * `outcome: 'unknown'`, ebenso HTTP 200 mit Kennzeichen, aber leerem oder * unlesbarem Rumpf. * - `KasseneckNetworkError` — die Antwort kam gar nicht: Netz weg, DNS, * abgebrochene Verbindung oder Zeitueberschreitung (`timedOut`). Auch hier * gilt `outcome`: war die Anfrage schon unterwegs und ist der Aufruf einer * mit Wirkung (signieren, Geld bewegen, buchen, ausstellen, anlegen, * aendern, loeschen, senden; die Liste `UNKNOWN_OUTCOME_CALLS`), ist er * `'unknown'`. Lesen und Probelauf bleiben `'rejected'`. * - `KasseneckAuthError` — es kam nicht einmal zur Anfrage, weil die Anmeldung * scheiterte (fehlende Zugangsdaten, oder der Token-/Sitzungsgeber warf). * In der Browser-Kasse mit ihrer 90-Sekunden-Sitzung ist das Alltag, kein * Sonderfall. * * [isOutcomeUnknown] fasst das fuer jede Fehlerart zusammen. * * **Geheimnisse gehoeren in keinen dieser Fehler.** Fehlermeldungen landen in * Protokollen und Fehlerdiensten; weder `api_key`, Kassen-Token, ID- * ID-Token noch Sitzungsbezeichner duerfen dorthin. Deshalb tragen die Fehler * ausschliesslich Funktionsname, HTTP-Status/Inhaltstyp, den vom Paket * formulierten Grund und die vom Backend formulierte Meldung — nie Kopfzeilen, * nie den gesendeten Rumpf und auch nicht den empfangenen Rumpf (der koennte * bei einem fremden Proxy alles Moegliche zurueckspiegeln). * * Aus demselben Grund haengt **keine fremde Ursache** als `cause` an diesen * Fehlern: `console.error(err)` und `util.inspect` drucken die Ursachenkette * mit, und fremde HTTP-Bibliotheken haengen ihre Anfrage an ihre Fehler (axios * `config.headers`, got `options.headers`) — mit dem Bearer-Schluessel darin. * Statt der Ursache selbst traegt `KasseneckNetworkError` ihre **verdichtete** * Form: `causeName`/`causeCode`, beide nur, wenn sie wie ein Bezeichner * aussehen **und** mit keinem der gesendeten Geheimnisse ueberlappen (siehe * `causeDigest`). * * Der Formfilter allein traegt diese Zusage naemlich nicht: der * Sitzungsbezeichner der Browser-Kasse ist selbst bezeichner-foermig und kaeme * durch. Verdichtet werden darf nur, wo die gesendeten Geheimnisse bekannt * sind — also nach dem Bauen der Anfrage. `KasseneckAuthError` traegt darum * **gar keine** Ursachen-Verdichtung: dort ist noch nichts gesendet, es gibt * keine Liste zum Abgleichen, und Diagnose ist an dieser Stelle weniger wert * als die Zusage. */ /** Verdichtete, geheimnisfreie Form einer fremden Fehlerursache. */ export interface CauseDigest { causeName?: string | undefined; causeCode?: string | undefined; } /** * Reduziert eine fremde Ursache auf Name und Code — und verwirft beides, wenn * es kein Bezeichner ist oder mit einem der uebergebenen Geheimnisse * ueberlappt (ein Sitzungsbezeichner sieht durchaus bezeichner-artig aus). * * `geheimnisse` hat bewusst **keinen** Vorgabewert: eine Verdichtung ohne * Abgleichliste waere nur ein Formfilter und damit keine Zusage. Wo die * gesendeten Werte nicht bekannt sind, wird gar nicht verdichtet. */ export declare function causeDigest(ursache: unknown, geheimnisse: readonly string[]): CauseDigest; /** * Siebt das `data` einer Fehlerantwort zu einer Form, die man gefahrlos an * einem Fehler mitfuehren kann. * * **Warum ueberhaupt:** die Partner-API legt ihre Entscheidung nicht in den * Text, sondern in `data.code` (`vertrag_offen`, `signature_not_ready`, * `activation_failed` samt `data.schritt`). Ohne diese Felder muesste ein * Aufrufer die deutsche `message` nach Zeichenketten durchsuchen — genau die * Kopplung, die beim naechsten Formulierungsschliff still bricht. * * **Warum gesiebt und nicht durchgereicht:** siehe Modulkommentar. Der Rumpf * kommt ueber fremde Proxys, und ein Fehler landet in Protokollen. Deshalb * ueberlebt nur, was flach, klein und bezeichner-foermig benannt ist — und * kein Wert, der mit einem der gesendeten Geheimnisse ueberlappt. Damit gilt * hier dieselbe Zusage wie fuer [causeDigest], und `geheimnisse` hat aus * demselben Grund **keinen** Vorgabewert. */ export declare function fehlerDetails(daten: unknown, geheimnisse: readonly string[]): Record; /** * Ausgang eines gescheiterten Aufrufs. `'rejected'`: nichts geschehen. * `'unknown'`: der Vorgang kann ausgefuehrt sein; nie blind wiederholen. * Belege, Stornos und Zahlungen nachlesen; ein Aufruf mit `idempotencyKey` * nur mit **demselben** Schluessel erneut senden (wirkt genau einmal); ohne * Schluessel erst nachlesen. */ export type ErrorOutcome = 'unknown' | 'rejected'; /** * Codes, die auf einem Geldweg `outcome: 'rejected'` ergeben; jeder andere * Code und eine Huelle ohne Code sind dort `'unknown'`. Aufgenommen ist nur, * was entsteht, bevor das Backend den Zahlungsanbieter anspricht (Vertrag * v3, `errorCodes`; dieselbe Liste wie `paymentCallRejectedCodes` im * Dart-Zwilling): * * - `errorCodes.auth` ohne die sieben des Partner-Zugangs (19 Codes): * Anmeldung und Pruefung in `checkRequest` laufen vor jeder Zeile des * Handlers; `validation` heisst dort Pflichtfeld fehlt oder falscher Typ. * Der Partner-Zugang trifft diese `api_key`-Aufrufe mit Kassen-Token nie. * - aus `errorCodes.edge`: `not_found` (unbekannter Endpunkt, HTTP 404) und * `internal_translation_error` (die Anfrage liess sich nicht uebersetzen, * es wurde nichts ausgefuehrt); `validation` des Rands (unbekannte Felder, * Rumpf ohne Objekt) steht schon oben. * - `module_inactive` und `not_permitted`: das Modul- bzw. Rechte-Tor steht * ebenfalls vor dem Anbieter. * - `route_missing` (vergibt das Paket): HTML ohne `/v3`-Kennzeichen, keine * Function hat den Aufruf gesehen. * * Nicht darin: `dialect_mismatch` (das Paket vergibt ihn auch, wenn ein Rand * ohne `/v3` geantwortet hat, dessen Handler gelaufen sein kann) und * `response_translation_failed` (der Handler lief; auch `handled: false` * heisst nur, dass seine Antwort ein Fehler war, und der kann hinter dem * Anbieteraufruf entstanden sein). Der Sammelfang der Handler ("Error hobex * details", "Fehler beim Capturing") antwortet ohne Code. */ export declare const PAYMENT_CALL_REJECTED_CODES: readonly string[]; /** * Codes, die das Paket selbst vergibt, nicht der Server: `route_missing` * (HTML statt Backend, der Aufruf kam nie an) und `response_unreadable` * (ein Aufruf mit Wirkung meldete Erfolg, die Antwort ist aber unlesbar; * Ausgang unklar). `dialect_mismatch` vergibt das Paket ebenfalls, der Code * gehoert aber schon zum Rand des Servers (`errorCodes.edge`). */ export declare const CLIENT_ERROR_CODES: readonly ["route_missing", "response_unreadable"]; export type ClientErrorCode = (typeof CLIENT_ERROR_CODES)[number]; export declare class KasseneckApiError extends Error { readonly name = "KasseneckApiError"; /** Aufgerufene Backend-Funktion, z. B. `createReceipt`. */ readonly functionName: string; /** Meldung des Backends, unveraendert (`message` aus der Huelle). */ readonly serverMessage: string; /** * Stabiler, maschinenlesbarer Fehlercode des Backends, wenn der Endpunkt * einen legt. Zwei Ablageorte kommen vor und beide zaehlen: das Feld `code` * neben `message` (cancelReceipt, siehe CANCELLATION_ERROR_CODES) und * `data.code` (Partner-API: `vertrag_offen`, `rate_limited`, …). Nur * Bezeichner gelten — ein Freitext waere kein Code. Die aelteren Endpunkte * antworten ohne Code — dann `undefined`. **Daran entscheiden, nie an * [serverMessage]:** der Text darf sich aendern, der Code nicht. */ readonly code: string | undefined; /** * Die uebrige Fehler-Nutzlast, gesiebt (siehe [fehlerDetails]): `schritt`, * `rc`, `retryAfterSec`, `errors[]` und was der jeweilige Endpunkt sonst * beilegt. Immer ein Objekt, notfalls ein leeres. */ readonly details: Record; /** * `'unknown'` bei `dialect_mismatch`, `receipt_outcome_unknown`, * `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet, * Antwort eines Aufrufs mit Wirkung aber unlesbar) und * `response_translation_failed` (ausser mit `details.handled === false`); * sonst `'rejected'`. Auf den Geldwegen (`hobexPayApi`, `hobexRefundApi`, * `stripeCaptureIntent`) umgekehrt: `'rejected'` nur mit einem Code aus * [PAYMENT_CALL_REJECTED_CODES], ohne Code und mit jedem anderen Code * `'unknown'`. Bei `'unknown'` nie wiederholen, sondern nachlesen. */ readonly outcome: ErrorOutcome; constructor(functionName: string, serverMessage: string, details?: Record, code?: string); } /** Warum die Antwort keine verwertbare Huelle war. */ export type HttpFailureReason = 'server-error' | 'empty-body' | 'not-json' | 'missing-status'; /** Antwort ohne verwertbare Huelle (HTTP-Fehler ohne Huelle, leerer Rumpf, Text statt JSON). */ export declare class KasseneckHttpError extends Error { readonly name = "KasseneckHttpError"; readonly functionName: string; /** HTTP-Statuscode der Antwort (bei Text statt JSON durchaus 200). */ readonly statusCode: number; /** Inhaltstyp der Antwort, falls die Gegenstelle einen gesetzt hat. */ readonly contentType: string | undefined; /** Maschinenlesbarer Grund — trennt den Rewrite-Fall vom 500er ohne Textparsen. */ readonly reason: HttpFailureReason; /** * `'unknown'` auf einem Aufruf mit Wirkung (`UNKNOWN_OUTCOME_CALLS`: * signieren, Geld bewegen, buchen, ausstellen, anlegen, aendern, loeschen, * senden) bei HTTP 5xx und bei HTTP 200 mit Kennzeichen, aber unlesbarem * Rumpf (`empty-body`, `not-json` auch bei `text/html`, `missing-status`): * der Handler kann gelaufen sein, nie blind wiederholen (siehe * [ErrorOutcome]). Sonst `'rejected'` (auch 4xx, Lesen und Probelauf). */ readonly outcome: ErrorOutcome; constructor(functionName: string, statusCode: number, contentType: string | undefined, reason: HttpFailureReason, outcome?: ErrorOutcome); } /** Es kam keine Antwort: Netzfehler oder Zeitueberschreitung. */ export declare class KasseneckNetworkError extends Error { readonly name = "KasseneckNetworkError"; readonly functionName: string; /** true = das Zeitlimit lief ab und die Anfrage wurde abgebrochen. */ readonly timedOut: boolean; /** Das geltende Zeitlimit in Millisekunden. */ readonly timeoutMs: number; /** Name der zugrunde liegenden Ursache, sofern unbedenklich (s. `causeDigest`). */ readonly causeName: string | undefined; /** Code der zugrunde liegenden Ursache, sofern unbedenklich (z. B. `ECONNREFUSED`). */ readonly causeCode: string | undefined; /** * `'unknown'`, wenn die Anfrage schon unterwegs war und der Aufruf Wirkung * hat (`UNKNOWN_OUTCOME_CALLS`): dann nie blind wiederholen (siehe * [ErrorOutcome]). Sonst `'rejected'`, auch bei Lesen und Probelauf und * wenn schon die Anmeldung das Zeitlimit verbrauchte. */ readonly outcome: ErrorOutcome; constructor(functionName: string, timedOut: boolean, timeoutMs: number, cause?: CauseDigest, outcome?: ErrorOutcome); } /** * Die Anmeldung scheiterte — es ging keine Anfrage raus. * * Dieser Fehler traegt **nichts** aus der fremden Ursache: weder ihre Meldung * (ein Token-Geber fuehrt gern den Token mit, den er gerade nicht erneuern * konnte) noch eine Verdichtung ihres Namens/Codes. Zum Zeitpunkt des * Anmeldefehlers ist noch nichts gesendet, es gibt also keine Liste der * Geheimnisse, gegen die verdichtete Werte geprueft werden koennten — und ein * reiner Formfilter laesst genau die Form durch, die ein Sitzungsbezeichner * hat. */ export declare class KasseneckAuthError extends Error { readonly name = "KasseneckAuthError"; /** Betroffene Backend-Funktion, falls der Fehler bei einem Aufruf entstand. */ readonly functionName: string | undefined; /** Vom Paket formulierter Grund — geheimnisfrei, anders als fremde Meldungen. */ readonly reason: string; constructor(reason: string, options?: { functionName?: string; }); } /** Woran die Form haengt: an der Eingabe des Aufrufers oder an der Antwort. */ export type ValidationScope = 'request' | 'response'; /** * Die Form stimmt nicht — bei `scope: 'request'` ging deshalb keine Anfrage * raus, bei `scope: 'response'` kam eine unbrauchbare zurueck. * * Es gilt dieselbe Zusage wie fuer die uebrigen Fehlerarten: `reason` ist vom * Paket formuliert und geheimnisfrei. Bei `'response'` wird insbesondere * **nichts aus der Antwort** uebernommen — ein fremder Proxy koennte dort * alles Moegliche zurueckspiegeln. */ export declare class KasseneckValidationError extends Error { readonly name = "KasseneckValidationError"; /** * Betroffener Vorgang — meist die Backend-Funktion, bei Pruefungen ohne * Aufruf der Name der Paketfunktion (z. B. `buildReceiptLayout`). */ readonly functionName: string; /** Vom Paket formulierter Grund. */ readonly reason: string; readonly scope: ValidationScope; constructor(functionName: string, reason: string, scope: ValidationScope); } /** Alle Fehler, die dieses Paket wirft. */ export type KasseneckError = KasseneckApiError | KasseneckHttpError | KasseneckNetworkError | KasseneckAuthError | KasseneckValidationError; /** * Ist der Ausgang dieses Fehlers unklar (`outcome === 'unknown'`)? Dann den * Aufruf **nicht blind wiederholen**: Belege, Stornos und Zahlungen * nachlesen; einen Aufruf mit `idempotencyKey` nur mit **demselben** * Schluessel erneut senden (wirkt genau einmal, ein neuer buchte doppelt); * ohne Schluessel erst nachlesen. Gilt fuer jede Fehlerart; nur * [KasseneckApiError], [KasseneckHttpError] und [KasseneckNetworkError] * koennen `'unknown'` sein. */ export declare function isOutcomeUnknown(error: unknown): boolean; export declare function isKasseneckApiError(error: unknown): error is KasseneckApiError; export declare function isKasseneckHttpError(error: unknown): error is KasseneckHttpError; export declare function isKasseneckNetworkError(error: unknown): error is KasseneckNetworkError; export declare function isKasseneckAuthError(error: unknown): error is KasseneckAuthError; export declare function isKasseneckValidationError(error: unknown): error is KasseneckValidationError; /** * Liest die Erfolgsantwort eines Aufrufs **mit Wirkung** (signierend oder * geldbewegend). Scheitert das Lesen (fehlender Beleg, fehlender Bezug, * unbrauchbares Feld oder ein Laufzeitfehler beim Umwandeln), hat der Server * trotzdem Erfolg gemeldet: der Beleg ist signiert und im DEP, die Karte * belastet bzw. der Einzug gelaufen. Das darf nie als gewoehnlicher Fehler * enden, sonst kassiert die Kasse ein zweites Mal. Darum wird daraus * `KasseneckApiError` mit Code `response_unreadable` und `outcome: 'unknown'`. * Der Grund stammt vom Paket; aus der Antwort selbst wird nichts uebernommen. * * Paketintern (receipts.ts, payments/); nicht Teil der Paketoberflaeche. */ export declare function signiertGelesen(functionName: string, lesen: () => T): T;