import type { HpsConnectClient, HpsConnectTarget } from './connect-client.js'; import type { HpsPaymentObserver } from './events.js'; import type { HpsPaymentResult } from './outcome.js'; /** * Kartenzahlung ueber **Kasseneck Connect**, deren Ausgang IMMER bekannt ist. * * Zwilling von `HpsPayments` (`kasseneck_api/lib/src/hobex_hps/hps_payments.dart`) * — Logik und Begruendungen sind von dort uebernommen, nicht neu erfunden. * Was dort an Flutter/Dart-Spezifischem haengt, gehoert hier nicht hin; siehe * `connect-client.ts` fuer den wesentlichen Unterschied (Connect statt * direktem Terminal-Kontakt). * * [pay], [refund] und [cancel] -- seit `kasseneck-connect` Commit `1c8a003` * traegt Connect auch die beiden letzteren, siehe `connect-client.ts`. * * Regel, von der nicht abgewichen wird: `outcome: 'declined'` entsteht * ausschliesslich aus einer POSITIVEN Aussage — einem GEMESSENEN Ergebniscode * des Terminals ungleich `'0'` ([isConclusive]), einem nachweislich * gelungenen Abbruch, oder dem gemessenen "Terminal beschaeftigt"-Fall * (HTTP 409 auf die ERZEUGENDE Anfrage, siehe `errors.ts`, * `HpsConnectTerminalError.isTerminalBusy`). Ein Transportfehler, ein * Zeitablauf oder eine Wissensluecke fuehren NIE dorthin: keines davon ist * eine Aussage darueber, dass nichts belastet wurde. Genau diese * Verwechslung hat am 24.08.2026 eine echte Belastung als unbelastet * ausgewiesen und den Kunden ein zweites Mal belastet. * * ## Der Klaerweg, wie er am 26./27.08.2026 am hobex-HPS gemessen wurde * * Bleibt die Antwort auf eine Zahlung aus: * * 1. [HpsConnectClient.abort] einmalig versuchen. * 2. `responseCode === '0'` -> der Vorgang war noch abbrechbar, also nicht * abgeschlossen -> `declined`, beweisbar. * 3. Jeder andere Code (gemessen `100010`) -> der Vorgang ist ueber den * abbrechbaren Punkt hinaus -> JETZT die Statusabfrage pollen, sie liefert * nun eine echte Aussage. * 4. Abbruch scheitert am Transport -> pollen wie in 3. * 5. Beim Pollen ist `9027` KEIN Ergebnis, sondern ein Grund weiterzumachen. * Budget erschoepft -> `unresolved`. * * Der Abbruch VOR dem Pollen ist wesentlich: die Statusabfrage meldet * "laeuft noch" nie (sie antwortet auf jeden nicht genehmigten Vorgang mit * `9027`). Der Abbruchversuch ist auf einen Bruchteil des Klaerbudgets * gedeckelt ([ABORT_BUDGET_DIVISOR]), sonst friesst ein haengender Abbruch * die ganze Klaerung. * * Die Kennung ist in JEDEM Ergebnis gesetzt, auch bei `'unresolved'`. * * ## [refund] bekommt EXAKT denselben Klaerweg wie [pay] * * Abbruch eingeschlossen -- und das ist GEMESSEN, nicht analog geschlossen. * Am 26.08.2026 nachgemessen: `abort` auf eine LAUFENDE Gutschrift antwortet * ebenfalls mit `responseCode '0'`, und die Gutschrift endet daraufhin mit * `100002` "Aborted". Der Abbruch ist dort also derselbe Diskriminator wie bei * einer Zahlung. Die Kennung ist bei [refund] die des NEUEN Vorgangs (der * Gutschrift selbst), eine Statusabfrage darauf liefert also genau deren * Ausgang -- deshalb reicht dieselbe [resolve]-Funktion unveraendert. Ohne den * Abbruch haette die Klaerung einer Gutschrift gar keinen Diskriminator mehr * und endete fast immer bei `unresolved`, weil die Statusabfrage auch hier * `9027` antwortet. * * ## [cancel] ist die Ausnahme, in beiden Richtungen * * Die uebergebene Kennung ist die der URSPRUENGLICHEN Zahlung, nicht die eines * neuen Vorgangs -- und `'0'` bedeutet dort NICHT "genehmigt". Am 26. und * 28.08.2026 gemessen (Statusabfrage auf die Original-Kennung, NACHDEM eine * genehmigte Zahlung per Void aufgehoben wurde): * * | Antwort der Statusabfrage auf die Originalkennung | Bedeutung | * |---|---| * | `9011` "Transaction Canceled" | die Aufhebung hat GEWIRKT -> `approved` | * | `'0'` | die Originalzahlung steht UNVERAENDERT -> die Aufhebung hat NICHT gegriffen -> `declined` | * | `9027` und alles andere | weiter klaeren, am Ende `unresolved` | * * Zwei Sicherungen dagegen, `'0'` faelschlich fuer "nicht gegriffen" zu halten * und damit den Kunden nach dem Tagesabschluss ueber eine Rueckerstattung ein * zweites Mal zu bezahlen (siehe [fromCancelStatus]): * * 1. **`'0'` entscheidet erst ab der ZWEITEN beantworteten Statusabfrage.** * Die erste laeuft unmittelbar nachdem der Aufhebungs-Aufruf abgerissen * ist -- genau das Fenster, in dem die Aufhebung noch unterwegs sein kann. * Reicht das Budget nur fuer eine Abfrage, endet die Klaerung bei * `unresolved`. * 2. **`9011` auf dem DIREKTEN Antwortweg von `cancel`** wird NICHT als * `declined` gelesen -- was es dort genau heisst, ist ungemessen (siehe * [fromCancelResponse]). Der Zustand der Originalzahlung wird abgefragt * statt geraten. * * Kein [tryAbort]-Versuch bei [cancel]: die Originalzahlung ist laengst * abgeschlossen und antwortet gemessen mit `100010` -- ein Abbruch darauf * waere sinnlos. * * ## Stoerung beim Host: die Klaerung darf nichts schliessen (11.09.2026) * * Die Antwortcodeliste von hobex benennt Codes, bei denen der Host beteiligt * war und das Terminal NICHT selbst storniert (`effect: 'hostUncertain'`, * etwa `100007`). Fuer sie gilt zweierlei: * * 1. Die Zwei-9027-Regel ([ausGeschlossenerAntwort]) greift nicht. Sie * schliesst aus "das Terminal hat geantwortet und nichts gespeichert" auf * "nichts belastet" -- das stimmt fuer einen Vorgang, der am Terminal * endete, aber nicht fuer einen, dessen Ausgang beim Host liegt. Dasselbe * gilt sinngemaess fuer [cancel]: ein unveraendertes `'0'` auf die * Originalzahlung beweist dann nicht, dass die Aufhebung beim Host nicht * ankam. * 2. Die Klaerung wartet trotzdem nicht das ganze Budget ab. Sagt die * Statusabfrage zweimal in Folge nichts Neues (`9027` oder erneut ein * solcher Code), endet sie sofort als `unresolved` -- das Terminal wird es * auch in 90 Sekunden nicht wissen. Meldet sie dagegen `'0'`, ist die * Zahlung genehmigt, ganz normal. * * ## Storno nach ausbleibender Host-Antwort (16.09.2026) * * Fuer einen Teil dieser Codes -- der Host hat nicht oder nicht brauchbar * geantwortet, etwa `9908` -- gibt hobex vor, ein Storno nachzuschicken * (`sendReversal`). [pay] tut das genau einmal, mit Kennung und Betrag der * Zahlung, bevor die Statusabfrage laeuft: quittiert mit `'0'` -> `declined` * mit `'voidedAfterHostFault'`; sonst weiter wie oben, und ein `9011` in der * Statusabfrage heisst dann ebenfalls `declined`. Am Geraet ungemessen, siehe * Dart-Zwilling. [refund] schickt kein Storno nach (TECS: `9031`), [cancel] * ist selbst eines. */ export interface HpsPaymentsOptions { /** Wie lange insgesamt geklaert wird, bevor der Ausgang offen bleibt. Vorgabe 90 s. */ resolveBudgetMs?: number; /** Obergrenze fuer den Abstand zwischen zwei Statusabfragen. Vorgabe 10 s. */ maxBackoffMs?: number; /** Nach so vielen Statusabfragen in Folge, die am Transport scheitern, wird abgebrochen. Vorgabe 3. */ maxTransportFailures?: number; /** Pausenquelle — Naht fuer Tests. Vorgabe `setTimeout`. */ sleep?: (ms: number) => Promise; /** Uhr fuer das Budget in ms — Naht fuer Tests. Vorgabe `Date.now`. */ now?: () => number; observer?: HpsPaymentObserver; } export interface HpsPaymentOptions { /** Zu belastender Betrag in **Cent** (ohne Trinkgeld). */ amountCents: number; tipCents?: number; reference?: string; currency?: string; language?: string; /** * Kennung; wird ohne Angabe erzeugt und im Ergebnis zurueckgegeben. Vorgeben, * um einen abgebrochenen Vorgang gezielt weiterzuverfolgen. */ transactionId?: string; } export interface HpsRefundOptions { /** Zu erstattender Betrag in **Cent**. */ amountCents: number; /** Kennung der erstatteten Zahlung -- Connect verlangt sie zwingend. */ originalTransactionId: string; reference?: string; currency?: string; language?: string; /** * Kennung der Gutschrift SELBST; wird ohne Angabe erzeugt und im Ergebnis * zurueckgegeben. Vorgeben, um eine abgebrochene Gutschrift gezielt * weiterzuverfolgen -- wie bei [HpsPaymentOptions.transactionId]. */ transactionId?: string; } export interface HpsCancelOptions { /** Kennung der URSPRUENGLICHEN Zahlung -- keine neue, MUSS feststehen. */ transactionId: string; /** Pflicht: ein Void ohne Betrag weist das Terminal mit `400 Missing amount` ab. */ amountCents: number; currency?: string; language?: string; } export interface HpsPayments { pay(options: HpsPaymentOptions): Promise; /** Gutschrift, geklaert wie [pay] -- siehe Klassendoku oben. */ refund(options: HpsRefundOptions): Promise; /** Aufhebung (Storno/Void) einer bestehenden Zahlung -- eigener Klaerweg, siehe Klassendoku oben. */ cancel(options: HpsCancelOptions): Promise; } export declare function createHpsPayments(client: HpsConnectClient, target: HpsConnectTarget, options?: HpsPaymentsOptions): HpsPayments;