@kreiseck/kasseneck-api 0.9.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +41 -0
  3. package/dist/cjs/payments/hobex-hps/index.d.ts +2 -2
  4. package/dist/cjs/payments/hobex-hps/index.js +36 -1
  5. package/dist/cjs/payments/hobex-hps/outcome.d.ts +20 -1
  6. package/dist/cjs/payments/hobex-hps/outcome.js +11 -0
  7. package/dist/cjs/payments/hobex-hps/payments.d.ts +19 -0
  8. package/dist/cjs/payments/hobex-hps/payments.js +160 -33
  9. package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +180 -31
  10. package/dist/cjs/payments/hobex-hps/transaction-response.js +308 -88
  11. package/dist/cjs/payments/index.d.ts +1 -1
  12. package/dist/cjs/payments/index.js +8 -1
  13. package/dist/cjs/printing/escpos.d.ts +85 -1
  14. package/dist/cjs/printing/escpos.js +140 -4
  15. package/dist/cjs/printing/index.d.ts +2 -1
  16. package/dist/cjs/printing/index.js +13 -1
  17. package/dist/cjs/printing/qr-groesse.d.ts +122 -0
  18. package/dist/cjs/printing/qr-groesse.js +126 -0
  19. package/dist/cjs/receipt/epos.d.ts +42 -1
  20. package/dist/cjs/receipt/epos.js +66 -5
  21. package/dist/cjs/receipt/index.d.ts +2 -2
  22. package/dist/cjs/receipt/index.js +3 -1
  23. package/dist/cjs/receipt/layout-escpos.d.ts +50 -2
  24. package/dist/cjs/receipt/layout-escpos.js +59 -2
  25. package/dist/esm/payments/hobex-hps/index.d.ts +2 -2
  26. package/dist/esm/payments/hobex-hps/index.js +2 -2
  27. package/dist/esm/payments/hobex-hps/outcome.d.ts +20 -1
  28. package/dist/esm/payments/hobex-hps/outcome.js +10 -0
  29. package/dist/esm/payments/hobex-hps/payments.d.ts +19 -0
  30. package/dist/esm/payments/hobex-hps/payments.js +161 -34
  31. package/dist/esm/payments/hobex-hps/transaction-response.d.ts +180 -31
  32. package/dist/esm/payments/hobex-hps/transaction-response.js +302 -87
  33. package/dist/esm/payments/index.d.ts +1 -1
  34. package/dist/esm/payments/index.js +1 -1
  35. package/dist/esm/printing/escpos.d.ts +85 -1
  36. package/dist/esm/printing/escpos.js +138 -4
  37. package/dist/esm/printing/index.d.ts +2 -1
  38. package/dist/esm/printing/index.js +2 -1
  39. package/dist/esm/printing/qr-groesse.d.ts +122 -0
  40. package/dist/esm/printing/qr-groesse.js +120 -0
  41. package/dist/esm/receipt/epos.d.ts +42 -1
  42. package/dist/esm/receipt/epos.js +65 -5
  43. package/dist/esm/receipt/index.d.ts +2 -2
  44. package/dist/esm/receipt/index.js +2 -2
  45. package/dist/esm/receipt/layout-escpos.d.ts +50 -2
  46. package/dist/esm/receipt/layout-escpos.js +59 -3
  47. package/fixtures/hobex-hps-codes.json +365 -15
  48. package/fixtures/kasse-texte.json +1 -1
  49. package/fixtures/oberflaeche.json +1 -1
  50. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,124 @@ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
4
4
  es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
5
5
  nur der Grund überlebt den nächsten Umbau.
6
6
 
7
+ ## 0.11.0
8
+
9
+ ### Die QR-Modulgröße wird gerechnet, nicht gesetzt — samt Notausgang und Modell 1
10
+
11
+ **Anlass:** Am echten Beleg fehlte der QR-Code, die Probe im Drucker-Wizard
12
+ druckte ihn. Der native QR-Befehl bekommt eine Modulgröße in Druckpunkten mit
13
+ und rechnet nicht nach, ob das Symbol samt Ruhezone auf die Rolle geht. Ein
14
+ Beleg-QR mit realer RKSV-Nutzlast hat 57 Module, mit Ruhezone also 65; bei
15
+ sechs Punkten je Modul sind das 390 Druckpunkte, und ein 58-mm-Kopf hat 384.
16
+ Zu breit heißt bei den meisten Geräten nicht „abgeschnitten", sondern **gar
17
+ kein QR**. Der Befund stammt aus dem Flutter-Zwilling `kasseneck_api` (dort
18
+ 6.9.0), der fest mit sechs Punkten druckte; dieses Paket druckt seit jeher mit
19
+ vier und war davon nicht betroffen. Die Regel ist trotzdem dieselbe — sie
20
+ deckelt auch eine bewusst gewählte größere Modulgröße gegen den Papierrand ab.
21
+
22
+ - **Neu `…/printing`: die Rechenregel, rein und ohne Drucker.**
23
+ `qrModulAnzahl(nutzlast)` gibt die Modulanzahl bei Fehlerkorrektur **M**
24
+ (konservativ: der native Befehl druckt mit L, das braucht nie mehr Module);
25
+ `qrGroesseBerechnen({ papierbreitePunkte, moduleAnzahl, groesse })` und
26
+ `qrGroesseFuer({ nutzlast, papierbreitePunkte, groesse })` geben ein
27
+ `QrGroesse` mit `punkte` (`null` = passt nicht), `module`, `breitePunkte`,
28
+ `unterMindestmass` und `passt`. Dazu `QrModulGroesse`, `QR_MODUL_DECKEL`,
29
+ `QR_RUHEZONE_MODULE` (4), `QR_MINDEST_PUNKTE` (4), `QR_AUSNAHME_PUNKTE` (3),
30
+ `QR_HOECHST_PUNKTE` (8) und `QR_DRUCK_PUNKTE` (58 mm = 384, 80 mm = 576 —
31
+ die echte Kopfbreite, nicht die Spaltenbreite 372/558).
32
+ - **`escPosQrCode` rechnet, wenn keine feste `size` mitkommt.** `groesse` ist
33
+ ein **Deckel**, keine Vorgabe: gedruckt wird die größte Größe, die noch
34
+ passt, höchstens aber der Deckel. `auto` deckelt beim Bestandswert dieses
35
+ Pakets (4), `klein` 4, `mittel` 6, `gross` 8. **Ohne ausdrückliche Wahl
36
+ ändert sich kein Byte** — nur dort, wo heute gar nichts herauskommt, rechnet
37
+ die Regel herunter. (Im Flutter-Zwilling deckelt `auto` bei 6, weil dort 6
38
+ der Bestandswert ist; die Regel ist dieselbe, der Bestand nicht.)
39
+ - **`EscPosDocument.qrFehler` / `.qrAusweich`.** `qrFehler` heißt „Beleg ohne
40
+ QR" — das Symbol passt auch mit der Ausnahmegröße nicht, und ein Befehl, von
41
+ dem man weiß, dass er nichts druckt, täuscht nur einen Ausdruck vor.
42
+ `qrAusweich` heißt „gedruckt, aber nicht auf dem eingestellten Weg" — unter
43
+ der Mindestgröße oder als Bild. Das eine gehört dem Kunden gesagt, das
44
+ andere dem Chef. `escPosReset` räumt beide weg.
45
+ - **Neu `…/receipt`: `escPosLayoutErgebnis`** gibt `{ bytes, qrFehler,
46
+ qrAusweich }`; `escPosLayoutBytes` bleibt unverändert und gibt weiter nur
47
+ die Bytes. Neue Optionen `qrGroesse`, `qrModus` (`QrPrintMode`) und
48
+ `qrMatrix`.
49
+ - **Der Notausgang.** Passt das Symbol nativ nicht aufs Papier und ist ein
50
+ `qrMatrix` mitgegeben, geht der QR als Rasterbild hinaus (`GS v 0`) statt
51
+ gar nicht — ein Pflichtbeleg ohne QR ist der schlechteste aller Ausgänge.
52
+ Das Raster kommt vom Aufrufer: dieses Paket rechnet keine QR-Codes und
53
+ verarbeitet keine Bilder. Dafür neu `escPosQrRaster`, `qrRasterPunkte` und
54
+ der Typ `QrMatrix`.
55
+ - **Modell 1.** `qrCodeBytes(..., { modell1: true })` bzw. `qrModus:
56
+ 'nativeModel1'` stellt den Wahlbefehl `GS ( k 04 00 31 41 31 00` voran. Für
57
+ günstige Drucker, die nur diesen älteren Symboltyp beherrschen; belegt ist
58
+ eines, das bei Modell 2 unter dem Code eine „0" ausgibt — das Parameterbyte
59
+ `0x30` des Druckbefehls, das es nicht als Befehl erkennt. Ohne ausdrückliche
60
+ Wahl geht **gar kein** Modellbefehl hinaus, wie bisher.
61
+ - **Derselbe Fehler im ePOS-Weg — und dort ist er der akute.** `eposPrintXml`
62
+ setzte `<symbol … width="6">` fest, unabhängig von der Papierbreite: 57
63
+ Module plus Ruhezone sind bei sechs Punkten 390 Druckpunkte, ein 58-mm-Kopf
64
+ hat 384, und der Epson lässt ein zu breites Symbol weg. **Genau daran fehlte
65
+ am echten Beleg der QR.** Jetzt gilt dieselbe Regel — Untergrenze 4,
66
+ Ausnahme 3 mit Meldung, darunter kein `<symbol>`. Der Bestandswert dieses
67
+ Wegs ist 6, deshalb ist die Vorgabe hier der Deckel `mittel`; ohne
68
+ ausdrückliche Wahl ändert sich nur dort etwas, wo heute gar nichts
69
+ herauskommt. Neu `eposPrintXmlErgebnis` (`{ xml, qrFehler, qrAusweich }`) und
70
+ die Optionen `qrGroesse` an `eposPrintXml` und `eposDirectPrint`; `qrBreite`
71
+ bleibt, ist jetzt aber ausdrücklich die **feste** Größe und schaltet die
72
+ Rechnung ab.
73
+ - **Bestandsschutz als Golden-Test.** Zwei feste SHA-256 über den gesamten
74
+ Bytestrom eines Belegs (58 und 80 mm) und zwei weitere über das vollständige
75
+ ePOS-XML halten fest, dass sich ohne Wahl kein Byte ändert. Dazu prüft `test/paket-inhalt.test.ts`, dass die Dateiliste des
76
+ Pakets eine Positivliste bleibt und im mitgelieferten `fixtures/` nichts
77
+ Örtliches liegt.
78
+
79
+ ## 0.10.0
80
+
81
+ ### Die Antwortcodeliste von hobex — jeder Code eingeordnet, jeder Ausgang mit Grund
82
+
83
+ **Anlass:** hobex hat am 11.09.2026 die Antwortcodeliste der HPS-Anwendung
84
+ geschickt. Drei der Codes kamen seit dem 28.08.2026 im Betrieb vor (`100004`,
85
+ `100005`, `100015`) und waren bis jetzt ungedeutet. Jede Zahlung damit lief in
86
+ die Klaerung und endete erst ueber die Zwei-9027-Regel. Zwilling:
87
+ `kasseneck_api` 6.10.0 (6.9.0 war inzwischen vergeben), Begruendungen dort in
88
+ `doc/kartenzahlung.md`.
89
+
90
+ - **`HPS_CODES`** fuehrt alle 31 Codes der Liste zusammen mit den gemessenen:
91
+ Code, hobex-Titel, Bedeutung, Wirkung (`effect`), Grund (`reason`) und
92
+ Quelle (`source`). `isConclusive` liest seine Positivliste daraus.
93
+ `HPS_MEASURED_CODES` bleibt als abgekuendigter Name fuer dieselbe Tabelle.
94
+ - **Was vor dem Host scheitert, ist eine Ablehnung** (Kartenlesen, EMV-Kernel,
95
+ Eingaben am Geraet, Geraetezustand, fehlerhafte Anfrage) -- `declined`, ohne
96
+ Abbruch und ohne Statusabfrage. Dazu `100029`: das Terminal storniert laut
97
+ hobex selbst.
98
+ - **Neu: `effect: 'hostUncertain'`** (`isHostUncertain`) fuer `100006`,
99
+ `100007`, `100023`, `100024`, `100026`, `100027`, `100999`. Der Host war
100
+ beteiligt, das Terminal storniert nicht selbst. Die Zwei-9027-Regel greift
101
+ hier **nicht**; die Klaerung endet nach zwei Abfragen ohne Neues als
102
+ `unresolved`, nur ein `'0'` entscheidet danach noch, und ein Abbruchversuch
103
+ entfaellt. Bei einer Aufhebung entscheidet ein unveraendertes `'0'` auf die
104
+ Originalzahlung dann ebenfalls nichts.
105
+ - **`rejectsRequest` / `isConclusiveAsStatus`:** zehn Codes weisen die Anfrage
106
+ selbst ab (`9002`, `100001`, `100008`, `100108`, `100009`, `100010`,
107
+ `100013`, `100018`, `100022`, `100998`). Auf eine Zahlung sind sie deren
108
+ Ablehnung, auf eine Statusabfrage sagen sie nichts ueber den gesuchten
109
+ Vorgang (gemessen fuer `100108`). Die Klaerung liest den Status deshalb
110
+ ueber `isConclusiveAsStatus` -- sonst haette ein gesperrtes Terminal eine
111
+ verlorene Zahlung als "nicht belastet" ausgewiesen.
112
+ - **`HpsPaymentResult.reason`** traegt fuer jeden Ausgang den Grund, den Satz
113
+ fuer den Bediener gibt `HPS_REASON_HINTS`. Gesetzt, wo entschieden wurde:
114
+ bestaetigter Abbruch `aborted`, HTTP 409 `terminalBusy`, eine ueber die
115
+ Zwei-9027-Regel geklaerte Zahlung mit dem Grund ihres eigenen Codes.
116
+ `isHostUncertainResult` sagt einer spaeteren Nachfrage, dass ein `9027` dort
117
+ nicht "nicht belastet" heisst.
118
+ - `hpsCodeInfo`, `hpsCodeReason` und die Namen aller neuen Codes
119
+ (`CARD_READ_FAILED_CODE` usw.) sind exportiert.
120
+ - Der Nachweis nennt bei einer Ablehnung den hobex-Titel mit
121
+ (`Terminal: abgelehnt (100015 "Card declined")`).
122
+ - `fixtures/hobex-hps-codes.json` fuehrt je Code zusaetzlich `title`,
123
+ `effect`, `reason`, `source` und die Saetze je Grund (`gruende`).
124
+
7
125
  ## 0.9.4
8
126
 
9
127
  ### `qrModus` faengt bei `auto` an — sonst stellte die Vorgabe den Altbestand um
package/README.md CHANGED
@@ -344,6 +344,47 @@ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) =>
344
344
 
345
345
  So zieht sich niemand den React-Adapter in ein Node-Programm.
346
346
 
347
+ ## Der QR-Code passt aufs Papier
348
+
349
+ Der native QR-Befehl bekommt eine Modulgröße in Druckpunkten mit und rechnet
350
+ selbst nicht nach, ob das Symbol samt Ruhezone auf die Rolle geht. Zu breit
351
+ heißt bei den meisten Bondruckern nicht „abgeschnitten", sondern **gar kein
352
+ QR** — auf einem Pflichtbeleg der schlechteste aller Ausgänge. Deshalb rechnet
353
+ dieses Paket die Größe, statt sie zu setzen:
354
+
355
+ ```ts
356
+ import { qrGroesseFuer, QR_DRUCK_PUNKTE } from '@kreiseck/kasseneck-api/printing';
357
+
358
+ const mass = qrGroesseFuer({ nutzlast: beleg.qr, papierbreitePunkte: QR_DRUCK_PUNKTE.mm58 });
359
+ // mass.punkte: Punkte je Modul, null = passt auch mit der Ausnahmegröße nicht
360
+ // mass.unterMindestmass: gedruckt, aber unter 4 Punkten je Modul
361
+ ```
362
+
363
+ Am Belegweg passiert das von selbst. `qrGroesse` ist ein **Deckel**, keine
364
+ Vorgabe: gedruckt wird die größte Größe, die noch passt, höchstens aber der
365
+ Deckel. `auto` (Vorgabe) deckelt beim Bestandswert 4 — ohne ausdrückliche Wahl
366
+ ändert sich also kein Byte.
367
+
368
+ ```ts
369
+ import { escPosLayoutErgebnis } from '@kreiseck/kasseneck-api/receipt';
370
+
371
+ const { bytes, qrFehler, qrAusweich } = escPosLayoutErgebnis(layout, {
372
+ qrGroesse: 'gross', // 'auto' | 'klein' | 'mittel' | 'gross'
373
+ qrModus: 'nativeModel1', // ältere Drucker, die nur Modell 1 können
374
+ qrMatrix: rasterFuer, // Notausgang: der QR als Bild statt gar nicht
375
+ });
376
+ ```
377
+
378
+ Der Epson-Weg (`eposPrintXml` / `eposDirectPrint`) rechnet genauso;
379
+ `eposPrintXmlErgebnis` gibt dort `{ xml, qrFehler, qrAusweich }`. Sein
380
+ Bestandswert ist 6, deshalb ist die Vorgabe dort der Deckel `mittel`.
381
+
382
+ `qrFehler` heißt „Beleg ohne QR" — das gehört dem Kunden gesagt. `qrAusweich`
383
+ heißt „gedruckt, aber der eingestellte Weg taugt für dieses Gerät nicht" — das
384
+ gehört dem Chef gesagt. Den Bildweg fährt das Paket nur mit einem `qrMatrix`,
385
+ das die Nutzlast in ein fertiges Raster übersetzt: hier wird bewusst weder ein
386
+ QR gerechnet noch ein Bild verarbeitet.
387
+
347
388
  ## Hobex HPS über Kasseneck Connect
348
389
 
349
390
  Ein Browser hat weiterhin keine rohen TCP-Sockets — ein **direkter**
@@ -20,8 +20,8 @@
20
20
  export { type HpsConnectCancelOptions, type HpsConnectClient, type HpsConnectClientOptions, type HpsConnectFetch, type HpsConnectFetchResponse, type HpsConnectPaymentOptions, type HpsConnectRefundOptions, type HpsConnectTarget, type HpsConnectTransactionOptions, createHpsConnectClient, } from './connect-client.js';
21
21
  export { HpsClarifyTimeoutError, HpsConnectException, HpsConnectTerminalError, HpsConnectTransportError, HpsPreflightError, HpsTransactionIdError, PREFLIGHT_CONNECT_CODES, } from './errors.js';
22
22
  export { type HpsPaymentEvent, type HpsPaymentEventKind, type HpsPaymentObserver, } from './events.js';
23
- export { type CardPaymentOutcome, type HpsPaymentResult, mayRetrySafely, } from './outcome.js';
23
+ export { type CardPaymentOutcome, type HpsPaymentResult, isHostUncertainResult, mayRetrySafely, } from './outcome.js';
24
24
  export { type HpsCancelOptions, type HpsPaymentOptions, type HpsPayments, type HpsPaymentsOptions, type HpsRefundOptions, createHpsPayments, } from './payments.js';
25
25
  export { MAX_TRANSACTION_ID_LENGTH, createHpsTransactionIdGenerator, isValidHpsTransactionId, newHpsTransactionId, type HpsTransactionIdGeneratorOptions, } from './transaction-id.js';
26
- export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
26
+ export { ABORTED_CODE, APP_SELECT_FAILED_CODE, APPROVED_CODE, BAD_REQUEST_CODE, CARD_DECLINED_CODE, CARD_INFO_NOT_ENTERED_CODE, CARD_NOT_PRESENT_CODE, CARD_NOT_SUPPORTED_CODE, CARD_READ_FAILED_CODE, CHIP_DATA_MISMATCH_CODE, COMPLETION_FAILED_CODE, DIAGNOSIS_FAILED_CODE, HOST_COMMUNICATION_FAILED_CODE, HOST_STEP_FAILED_CODE, HOST_TIMEOUT_REVERSED_CODE, HPS_CODES, HPS_MEASURED_CODES, HPS_REASON_HINTS, INTERNAL_ERROR_CODE, INVALID_MESSAGE_TYPE_CODE, INVALID_TID_DOCUMENTED_CODE, INVALID_TX_TYPE_CODE, MAX_RETRIES_EXCEEDED_CODE, NOT_FOUND_CODE, PASSWORD_NOT_ENTERED_CODE, REFUND_DISABLED_CODE, REFUND_PASSWORD_INVALID_CODE, SCEP_ENROLLMENT_FAILED_CODE, TERMINAL_BLOCKED_CODE, TERMINAL_BUSY_CODE, TIP_SELECTION_FAILED_CODE, UNSUPPORTED_USER_DATA_CODE, hpsCodeInfo, hpsCodeReason, isHostUncertain, isHostUncertainReason, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsCode, type HpsCodeEffect, type HpsCodeReason, type HpsCodeSource, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
27
27
  export { hobexReceiptFromHps } from './receipt.js';
@@ -19,7 +19,8 @@
19
19
  * des Aufrufers.
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.hobexReceiptFromHps = exports.parseHpsTransactionResponse = exports.isUnknownCode = exports.isTechnicalError = exports.isNotAbortable = exports.isNoStatement = exports.isInProgress = exports.isConclusive = exports.isCanceled = exports.isApproved = exports.TRANSACTION_CANCELED_CODE = exports.TERMINAL_BUSY_HTTP_STATUS = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.WRONG_PIN_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.INVALID_TRANSACTION_CODE = exports.HPS_MEASURED_CODES = exports.CARD_NOT_PRESENT_CODE = exports.APPROVED_CODE = exports.ABORTED_CODE = exports.newHpsTransactionId = exports.isValidHpsTransactionId = exports.createHpsTransactionIdGenerator = exports.MAX_TRANSACTION_ID_LENGTH = exports.createHpsPayments = exports.mayRetrySafely = exports.PREFLIGHT_CONNECT_CODES = exports.HpsTransactionIdError = exports.HpsPreflightError = exports.HpsConnectTransportError = exports.HpsConnectTerminalError = exports.HpsConnectException = exports.HpsClarifyTimeoutError = exports.createHpsConnectClient = void 0;
22
+ exports.isHostUncertain = exports.hpsCodeReason = exports.hpsCodeInfo = exports.UNSUPPORTED_USER_DATA_CODE = exports.TIP_SELECTION_FAILED_CODE = exports.TERMINAL_BUSY_CODE = exports.TERMINAL_BLOCKED_CODE = exports.SCEP_ENROLLMENT_FAILED_CODE = exports.REFUND_PASSWORD_INVALID_CODE = exports.REFUND_DISABLED_CODE = exports.PASSWORD_NOT_ENTERED_CODE = exports.NOT_FOUND_CODE = exports.MAX_RETRIES_EXCEEDED_CODE = exports.INVALID_TX_TYPE_CODE = exports.INVALID_TID_DOCUMENTED_CODE = exports.INVALID_MESSAGE_TYPE_CODE = exports.INTERNAL_ERROR_CODE = exports.HPS_REASON_HINTS = exports.HPS_MEASURED_CODES = exports.HPS_CODES = exports.HOST_TIMEOUT_REVERSED_CODE = exports.HOST_STEP_FAILED_CODE = exports.HOST_COMMUNICATION_FAILED_CODE = exports.DIAGNOSIS_FAILED_CODE = exports.COMPLETION_FAILED_CODE = exports.CHIP_DATA_MISMATCH_CODE = exports.CARD_READ_FAILED_CODE = exports.CARD_NOT_SUPPORTED_CODE = exports.CARD_NOT_PRESENT_CODE = exports.CARD_INFO_NOT_ENTERED_CODE = exports.CARD_DECLINED_CODE = exports.BAD_REQUEST_CODE = exports.APPROVED_CODE = exports.APP_SELECT_FAILED_CODE = exports.ABORTED_CODE = exports.newHpsTransactionId = exports.isValidHpsTransactionId = exports.createHpsTransactionIdGenerator = exports.MAX_TRANSACTION_ID_LENGTH = exports.createHpsPayments = exports.mayRetrySafely = exports.isHostUncertainResult = exports.PREFLIGHT_CONNECT_CODES = exports.HpsTransactionIdError = exports.HpsPreflightError = exports.HpsConnectTransportError = exports.HpsConnectTerminalError = exports.HpsConnectException = exports.HpsClarifyTimeoutError = exports.createHpsConnectClient = void 0;
23
+ exports.hobexReceiptFromHps = exports.parseHpsTransactionResponse = exports.isUnknownCode = exports.isTechnicalError = exports.isNotAbortable = exports.isNoStatement = exports.isInProgress = exports.isConclusiveAsStatus = exports.isConclusive = exports.isCanceled = exports.isApproved = exports.TRANSACTION_CANCELED_CODE = exports.TERMINAL_BUSY_HTTP_STATUS = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.WRONG_PIN_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.INVALID_TRANSACTION_CODE = exports.isHostUncertainReason = void 0;
23
24
  var connect_client_js_1 = require("./connect-client.js");
24
25
  Object.defineProperty(exports, "createHpsConnectClient", { enumerable: true, get: function () { return connect_client_js_1.createHpsConnectClient; } });
25
26
  var errors_js_1 = require("./errors.js");
@@ -31,6 +32,7 @@ Object.defineProperty(exports, "HpsPreflightError", { enumerable: true, get: fun
31
32
  Object.defineProperty(exports, "HpsTransactionIdError", { enumerable: true, get: function () { return errors_js_1.HpsTransactionIdError; } });
32
33
  Object.defineProperty(exports, "PREFLIGHT_CONNECT_CODES", { enumerable: true, get: function () { return errors_js_1.PREFLIGHT_CONNECT_CODES; } });
33
34
  var outcome_js_1 = require("./outcome.js");
35
+ Object.defineProperty(exports, "isHostUncertainResult", { enumerable: true, get: function () { return outcome_js_1.isHostUncertainResult; } });
34
36
  Object.defineProperty(exports, "mayRetrySafely", { enumerable: true, get: function () { return outcome_js_1.mayRetrySafely; } });
35
37
  var payments_js_1 = require("./payments.js");
36
38
  Object.defineProperty(exports, "createHpsPayments", { enumerable: true, get: function () { return payments_js_1.createHpsPayments; } });
@@ -41,9 +43,41 @@ Object.defineProperty(exports, "isValidHpsTransactionId", { enumerable: true, ge
41
43
  Object.defineProperty(exports, "newHpsTransactionId", { enumerable: true, get: function () { return transaction_id_js_1.newHpsTransactionId; } });
42
44
  var transaction_response_js_1 = require("./transaction-response.js");
43
45
  Object.defineProperty(exports, "ABORTED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.ABORTED_CODE; } });
46
+ Object.defineProperty(exports, "APP_SELECT_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.APP_SELECT_FAILED_CODE; } });
44
47
  Object.defineProperty(exports, "APPROVED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.APPROVED_CODE; } });
48
+ Object.defineProperty(exports, "BAD_REQUEST_CODE", { enumerable: true, get: function () { return transaction_response_js_1.BAD_REQUEST_CODE; } });
49
+ Object.defineProperty(exports, "CARD_DECLINED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.CARD_DECLINED_CODE; } });
50
+ Object.defineProperty(exports, "CARD_INFO_NOT_ENTERED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.CARD_INFO_NOT_ENTERED_CODE; } });
45
51
  Object.defineProperty(exports, "CARD_NOT_PRESENT_CODE", { enumerable: true, get: function () { return transaction_response_js_1.CARD_NOT_PRESENT_CODE; } });
52
+ Object.defineProperty(exports, "CARD_NOT_SUPPORTED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.CARD_NOT_SUPPORTED_CODE; } });
53
+ Object.defineProperty(exports, "CARD_READ_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.CARD_READ_FAILED_CODE; } });
54
+ Object.defineProperty(exports, "CHIP_DATA_MISMATCH_CODE", { enumerable: true, get: function () { return transaction_response_js_1.CHIP_DATA_MISMATCH_CODE; } });
55
+ Object.defineProperty(exports, "COMPLETION_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.COMPLETION_FAILED_CODE; } });
56
+ Object.defineProperty(exports, "DIAGNOSIS_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.DIAGNOSIS_FAILED_CODE; } });
57
+ Object.defineProperty(exports, "HOST_COMMUNICATION_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.HOST_COMMUNICATION_FAILED_CODE; } });
58
+ Object.defineProperty(exports, "HOST_STEP_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.HOST_STEP_FAILED_CODE; } });
59
+ Object.defineProperty(exports, "HOST_TIMEOUT_REVERSED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.HOST_TIMEOUT_REVERSED_CODE; } });
60
+ Object.defineProperty(exports, "HPS_CODES", { enumerable: true, get: function () { return transaction_response_js_1.HPS_CODES; } });
46
61
  Object.defineProperty(exports, "HPS_MEASURED_CODES", { enumerable: true, get: function () { return transaction_response_js_1.HPS_MEASURED_CODES; } });
62
+ Object.defineProperty(exports, "HPS_REASON_HINTS", { enumerable: true, get: function () { return transaction_response_js_1.HPS_REASON_HINTS; } });
63
+ Object.defineProperty(exports, "INTERNAL_ERROR_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INTERNAL_ERROR_CODE; } });
64
+ Object.defineProperty(exports, "INVALID_MESSAGE_TYPE_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_MESSAGE_TYPE_CODE; } });
65
+ Object.defineProperty(exports, "INVALID_TID_DOCUMENTED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_TID_DOCUMENTED_CODE; } });
66
+ Object.defineProperty(exports, "INVALID_TX_TYPE_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_TX_TYPE_CODE; } });
67
+ Object.defineProperty(exports, "MAX_RETRIES_EXCEEDED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.MAX_RETRIES_EXCEEDED_CODE; } });
68
+ Object.defineProperty(exports, "NOT_FOUND_CODE", { enumerable: true, get: function () { return transaction_response_js_1.NOT_FOUND_CODE; } });
69
+ Object.defineProperty(exports, "PASSWORD_NOT_ENTERED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.PASSWORD_NOT_ENTERED_CODE; } });
70
+ Object.defineProperty(exports, "REFUND_DISABLED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.REFUND_DISABLED_CODE; } });
71
+ Object.defineProperty(exports, "REFUND_PASSWORD_INVALID_CODE", { enumerable: true, get: function () { return transaction_response_js_1.REFUND_PASSWORD_INVALID_CODE; } });
72
+ Object.defineProperty(exports, "SCEP_ENROLLMENT_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.SCEP_ENROLLMENT_FAILED_CODE; } });
73
+ Object.defineProperty(exports, "TERMINAL_BLOCKED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.TERMINAL_BLOCKED_CODE; } });
74
+ Object.defineProperty(exports, "TERMINAL_BUSY_CODE", { enumerable: true, get: function () { return transaction_response_js_1.TERMINAL_BUSY_CODE; } });
75
+ Object.defineProperty(exports, "TIP_SELECTION_FAILED_CODE", { enumerable: true, get: function () { return transaction_response_js_1.TIP_SELECTION_FAILED_CODE; } });
76
+ Object.defineProperty(exports, "UNSUPPORTED_USER_DATA_CODE", { enumerable: true, get: function () { return transaction_response_js_1.UNSUPPORTED_USER_DATA_CODE; } });
77
+ Object.defineProperty(exports, "hpsCodeInfo", { enumerable: true, get: function () { return transaction_response_js_1.hpsCodeInfo; } });
78
+ Object.defineProperty(exports, "hpsCodeReason", { enumerable: true, get: function () { return transaction_response_js_1.hpsCodeReason; } });
79
+ Object.defineProperty(exports, "isHostUncertain", { enumerable: true, get: function () { return transaction_response_js_1.isHostUncertain; } });
80
+ Object.defineProperty(exports, "isHostUncertainReason", { enumerable: true, get: function () { return transaction_response_js_1.isHostUncertainReason; } });
47
81
  Object.defineProperty(exports, "INVALID_TRANSACTION_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_TRANSACTION_CODE; } });
48
82
  Object.defineProperty(exports, "NOT_ABORTABLE_CODE", { enumerable: true, get: function () { return transaction_response_js_1.NOT_ABORTABLE_CODE; } });
49
83
  Object.defineProperty(exports, "INVALID_AMOUNT_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_AMOUNT_CODE; } });
@@ -57,6 +91,7 @@ Object.defineProperty(exports, "TRANSACTION_CANCELED_CODE", { enumerable: true,
57
91
  Object.defineProperty(exports, "isApproved", { enumerable: true, get: function () { return transaction_response_js_1.isApproved; } });
58
92
  Object.defineProperty(exports, "isCanceled", { enumerable: true, get: function () { return transaction_response_js_1.isCanceled; } });
59
93
  Object.defineProperty(exports, "isConclusive", { enumerable: true, get: function () { return transaction_response_js_1.isConclusive; } });
94
+ Object.defineProperty(exports, "isConclusiveAsStatus", { enumerable: true, get: function () { return transaction_response_js_1.isConclusiveAsStatus; } });
60
95
  Object.defineProperty(exports, "isInProgress", { enumerable: true, get: function () { return transaction_response_js_1.isInProgress; } });
61
96
  Object.defineProperty(exports, "isNoStatement", { enumerable: true, get: function () { return transaction_response_js_1.isNoStatement; } });
62
97
  Object.defineProperty(exports, "isNotAbortable", { enumerable: true, get: function () { return transaction_response_js_1.isNotAbortable; } });
@@ -1,4 +1,4 @@
1
- import type { HpsTransactionResponse } from './transaction-response.js';
1
+ import { type HpsCodeReason, type HpsTransactionResponse } from './transaction-response.js';
2
2
  /**
3
3
  * Ausgang eines Kartenzahlvorgangs — die einzige Frage, die ein Aufrufer
4
4
  * wirklich hat: darf ich es nochmal versuchen?
@@ -35,6 +35,18 @@ export interface HpsPaymentResult {
35
35
  * Entscheidung getragen. Bei schluessigem Ausgang nicht gesetzt.
36
36
  */
37
37
  readonly lastResponse?: HpsTransactionResponse;
38
+ /**
39
+ * Worauf die Kasse reagiert -- der Grund hinter dem Ausgang, mit dem Satz
40
+ * fuer den Bediener in `HPS_REASON_HINTS`.
41
+ *
42
+ * Gesetzt an der Stelle, die den Ausgang entschieden hat, nicht aus
43
+ * [response] abgeleitet: ein bestaetigter Abbruch antwortet `'0'` und ist
44
+ * trotzdem `'aborted'`, und wenn die Zwei-9027-Regel eine Zahlung als
45
+ * abgelehnt klaert, zaehlt der Code der ZAHLUNG, nicht das `9027` danach.
46
+ * Fehlt, wenn kein Code etwas erklaert (die Leitung riss ab, bevor das
47
+ * Terminal etwas sagte) und bei einer Aufhebung, die nicht gegriffen hat.
48
+ */
49
+ readonly reason?: HpsCodeReason;
38
50
  /**
39
51
  * Verlauf der Klaerung, in Reihenfolge — der Nachweis, der im
40
52
  * Belastungsstreit gelesen wird. Behauptet nie eine Ursache, die nicht
@@ -44,3 +56,10 @@ export interface HpsPaymentResult {
44
56
  }
45
57
  /** Nur bei `'declined'` steht fest, dass nichts belastet wurde. */
46
58
  export declare function mayRetrySafely(result: Pick<HpsPaymentResult, 'outcome'>): boolean;
59
+ /**
60
+ * `true`, wenn der Ausgang offen ist, weil das Terminal eine Stoerung beim
61
+ * oder nach dem hobex-Host meldete (`effect: 'hostUncertain'`). Wichtig fuer
62
+ * jede SPAETERE Nachfrage: antwortet die Statusabfrage dann `9027`, heisst das
63
+ * hier NICHT "nichts belastet".
64
+ */
65
+ export declare function isHostUncertainResult(result: Pick<HpsPaymentResult, 'outcome' | 'reason'>): boolean;
@@ -1,7 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mayRetrySafely = mayRetrySafely;
4
+ exports.isHostUncertainResult = isHostUncertainResult;
5
+ const transaction_response_js_1 = require("./transaction-response.js");
4
6
  /** Nur bei `'declined'` steht fest, dass nichts belastet wurde. */
5
7
  function mayRetrySafely(result) {
6
8
  return result.outcome === 'declined';
7
9
  }
10
+ /**
11
+ * `true`, wenn der Ausgang offen ist, weil das Terminal eine Stoerung beim
12
+ * oder nach dem hobex-Host meldete (`effect: 'hostUncertain'`). Wichtig fuer
13
+ * jede SPAETERE Nachfrage: antwortet die Statusabfrage dann `9027`, heisst das
14
+ * hier NICHT "nichts belastet".
15
+ */
16
+ function isHostUncertainResult(result) {
17
+ return result.outcome === 'unresolved' && (0, transaction_response_js_1.isHostUncertainReason)(result.reason);
18
+ }
@@ -89,6 +89,25 @@ import type { HpsPaymentResult } from './outcome.js';
89
89
  * Kein [tryAbort]-Versuch bei [cancel]: die Originalzahlung ist laengst
90
90
  * abgeschlossen und antwortet gemessen mit `100010` -- ein Abbruch darauf
91
91
  * waere sinnlos.
92
+ *
93
+ * ## Stoerung beim Host: die Klaerung darf nichts schliessen (11.09.2026)
94
+ *
95
+ * Die Antwortcodeliste von hobex benennt Codes, bei denen der Host beteiligt
96
+ * war und das Terminal NICHT selbst storniert (`effect: 'hostUncertain'`,
97
+ * etwa `100007`). Fuer sie gilt zweierlei:
98
+ *
99
+ * 1. Die Zwei-9027-Regel ([ausGeschlossenerAntwort]) greift nicht. Sie
100
+ * schliesst aus "das Terminal hat geantwortet und nichts gespeichert" auf
101
+ * "nichts belastet" -- das stimmt fuer einen Vorgang, der am Terminal
102
+ * endete, aber nicht fuer einen, dessen Ausgang beim Host liegt. Dasselbe
103
+ * gilt sinngemaess fuer [cancel]: ein unveraendertes `'0'` auf die
104
+ * Originalzahlung beweist dann nicht, dass die Aufhebung beim Host nicht
105
+ * ankam.
106
+ * 2. Die Klaerung wartet trotzdem nicht das ganze Budget ab. Sagt die
107
+ * Statusabfrage zweimal in Folge nichts Neues (`9027` oder erneut ein
108
+ * solcher Code), endet sie sofort als `unresolved` -- das Terminal wird es
109
+ * auch in 90 Sekunden nicht wissen. Meldet sie dagegen `'0'`, ist die
110
+ * Zahlung genehmigt, ganz normal.
92
111
  */
93
112
  export interface HpsPaymentsOptions {
94
113
  /** Wie lange insgesamt geklaert wird, bevor der Ausgang offen bleibt. Vorgabe 90 s. */