@kreiseck/kasseneck-api 0.6.45 → 0.6.49

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 (110) hide show
  1. package/README.md +95 -15
  2. package/dist/cjs/client/aufrufe.d.ts +1 -1
  3. package/dist/cjs/client/aufrufe.js +2 -0
  4. package/dist/cjs/client/errors.d.ts +7 -1
  5. package/dist/cjs/client/errors.js +8 -1
  6. package/dist/cjs/client/receipts.d.ts +16 -0
  7. package/dist/cjs/client/receipts.js +10 -0
  8. package/dist/cjs/client/transport.js +5 -4
  9. package/dist/cjs/index.d.ts +1 -1
  10. package/dist/cjs/index.js +4 -2
  11. package/dist/cjs/kasse/index.d.ts +1 -0
  12. package/dist/cjs/kasse/index.js +3 -0
  13. package/dist/cjs/kasse/trinkgeld.d.ts +10 -0
  14. package/dist/cjs/kasse/trinkgeld.js +27 -0
  15. package/dist/cjs/models/cancellation.d.ts +22 -0
  16. package/dist/cjs/models/cancellation.js +31 -1
  17. package/dist/cjs/models/hobex-receipt.js +9 -2
  18. package/dist/cjs/models/index.d.ts +1 -1
  19. package/dist/cjs/models/index.js +3 -1
  20. package/dist/cjs/models/receipt.js +3 -0
  21. package/dist/cjs/models/voucher.js +18 -2
  22. package/dist/cjs/payments/hobex-hps/connect-client.d.ts +104 -0
  23. package/dist/cjs/payments/hobex-hps/connect-client.js +159 -0
  24. package/dist/cjs/payments/hobex-hps/errors.d.ts +122 -0
  25. package/dist/cjs/payments/hobex-hps/errors.js +158 -0
  26. package/dist/cjs/payments/hobex-hps/events.d.ts +22 -0
  27. package/dist/cjs/payments/hobex-hps/events.js +2 -0
  28. package/dist/cjs/payments/hobex-hps/index.d.ts +27 -0
  29. package/dist/cjs/payments/hobex-hps/index.js +66 -0
  30. package/dist/cjs/payments/hobex-hps/outcome.d.ts +31 -0
  31. package/dist/cjs/payments/hobex-hps/outcome.js +7 -0
  32. package/dist/cjs/payments/hobex-hps/payments.d.ts +149 -0
  33. package/dist/cjs/payments/hobex-hps/payments.js +550 -0
  34. package/dist/cjs/payments/hobex-hps/receipt.d.ts +28 -0
  35. package/dist/cjs/payments/hobex-hps/receipt.js +60 -0
  36. package/dist/cjs/payments/hobex-hps/transaction-id.d.ts +65 -0
  37. package/dist/cjs/payments/hobex-hps/transaction-id.js +91 -0
  38. package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +163 -0
  39. package/dist/cjs/payments/hobex-hps/transaction-response.js +245 -0
  40. package/dist/cjs/payments/hobex.js +9 -6
  41. package/dist/cjs/payments/index.d.ts +26 -14
  42. package/dist/cjs/payments/index.js +59 -15
  43. package/dist/cjs/printing/index.d.ts +1 -1
  44. package/dist/cjs/printing/index.js +2 -1
  45. package/dist/cjs/printing/webusb.d.ts +37 -7
  46. package/dist/cjs/printing/webusb.js +76 -18
  47. package/dist/cjs/receipt/epos.d.ts +18 -0
  48. package/dist/cjs/receipt/epos.js +78 -6
  49. package/dist/cjs/receipt/index.d.ts +1 -1
  50. package/dist/cjs/receipt/index.js +2 -1
  51. package/dist/cjs/register/index.d.ts +1 -1
  52. package/dist/cjs/register/index.js +2 -1
  53. package/dist/cjs/register/pairing.d.ts +67 -1
  54. package/dist/cjs/register/pairing.js +59 -3
  55. package/dist/esm/client/aufrufe.d.ts +1 -1
  56. package/dist/esm/client/aufrufe.js +2 -0
  57. package/dist/esm/client/errors.d.ts +7 -1
  58. package/dist/esm/client/errors.js +8 -1
  59. package/dist/esm/client/receipts.d.ts +16 -0
  60. package/dist/esm/client/receipts.js +10 -0
  61. package/dist/esm/client/transport.js +5 -4
  62. package/dist/esm/index.d.ts +1 -1
  63. package/dist/esm/index.js +1 -1
  64. package/dist/esm/kasse/index.d.ts +1 -0
  65. package/dist/esm/kasse/index.js +1 -0
  66. package/dist/esm/kasse/trinkgeld.d.ts +10 -0
  67. package/dist/esm/kasse/trinkgeld.js +24 -0
  68. package/dist/esm/models/cancellation.d.ts +22 -0
  69. package/dist/esm/models/cancellation.js +29 -0
  70. package/dist/esm/models/hobex-receipt.js +10 -3
  71. package/dist/esm/models/index.d.ts +1 -1
  72. package/dist/esm/models/index.js +1 -1
  73. package/dist/esm/models/receipt.js +3 -0
  74. package/dist/esm/models/voucher.js +18 -2
  75. package/dist/esm/payments/hobex-hps/connect-client.d.ts +104 -0
  76. package/dist/esm/payments/hobex-hps/connect-client.js +156 -0
  77. package/dist/esm/payments/hobex-hps/errors.d.ts +122 -0
  78. package/dist/esm/payments/hobex-hps/errors.js +149 -0
  79. package/dist/esm/payments/hobex-hps/events.d.ts +22 -0
  80. package/dist/esm/payments/hobex-hps/events.js +1 -0
  81. package/dist/esm/payments/hobex-hps/index.d.ts +27 -0
  82. package/dist/esm/payments/hobex-hps/index.js +26 -0
  83. package/dist/esm/payments/hobex-hps/outcome.d.ts +31 -0
  84. package/dist/esm/payments/hobex-hps/outcome.js +4 -0
  85. package/dist/esm/payments/hobex-hps/payments.d.ts +149 -0
  86. package/dist/esm/payments/hobex-hps/payments.js +547 -0
  87. package/dist/esm/payments/hobex-hps/receipt.d.ts +28 -0
  88. package/dist/esm/payments/hobex-hps/receipt.js +57 -0
  89. package/dist/esm/payments/hobex-hps/transaction-id.d.ts +65 -0
  90. package/dist/esm/payments/hobex-hps/transaction-id.js +86 -0
  91. package/dist/esm/payments/hobex-hps/transaction-response.d.ts +163 -0
  92. package/dist/esm/payments/hobex-hps/transaction-response.js +233 -0
  93. package/dist/esm/payments/hobex.js +9 -6
  94. package/dist/esm/payments/index.d.ts +26 -14
  95. package/dist/esm/payments/index.js +26 -14
  96. package/dist/esm/printing/index.d.ts +1 -1
  97. package/dist/esm/printing/index.js +1 -1
  98. package/dist/esm/printing/webusb.d.ts +37 -7
  99. package/dist/esm/printing/webusb.js +74 -17
  100. package/dist/esm/receipt/epos.d.ts +18 -0
  101. package/dist/esm/receipt/epos.js +76 -6
  102. package/dist/esm/receipt/index.d.ts +1 -1
  103. package/dist/esm/receipt/index.js +1 -1
  104. package/dist/esm/register/index.d.ts +1 -1
  105. package/dist/esm/register/index.js +1 -1
  106. package/dist/esm/register/pairing.d.ts +67 -1
  107. package/dist/esm/register/pairing.js +58 -3
  108. package/fixtures/hobex-hps-codes.json +67 -0
  109. package/fixtures/oberflaeche.json +3 -1
  110. package/package.json +4 -2
@@ -5,6 +5,7 @@ exports.cancelScopeOf = cancelScopeOf;
5
5
  exports.receiptsScopeOf = receiptsScopeOf;
6
6
  exports.pairRegisterDevice = pairRegisterDevice;
7
7
  exports.unpairRegisterDevice = unpairRegisterDevice;
8
+ exports.listRegisterSessionsForDevice = listRegisterSessionsForDevice;
8
9
  exports.listRegisterUsersForDevice = listRegisterUsersForDevice;
9
10
  exports.registerUserLogin = registerUserLogin;
10
11
  exports.registerPinLogin = registerPinLogin;
@@ -48,7 +49,14 @@ const settings_js_1 = require("../kasse/settings.js");
48
49
  * es auch halten.
49
50
  */
50
51
  const ohneAnmeldung = () => ({ headers: {}, params: {} });
51
- /** Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen dagegen. */
52
+ /**
53
+ * Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen
54
+ * dagegen, und ueber `fixtures/oberflaeche.json` steht die Liste im Vertrag.
55
+ *
56
+ * `satisfies` schliesst die eine Richtung: ein Name, den [RegisterUserPerms]
57
+ * nicht fuehrt (Tippfehler, umbenanntes Recht), kommt hier nicht durch.
58
+ * Die Gegenrichtung schliesst [_RechteVollstaendig] direkt darunter.
59
+ */
52
60
  exports.REGISTER_PERMS = [
53
61
  'sell', 'cancel', 'articles', 'layout', 'reports', 'takeover',
54
62
  'cancelScope', 'receiptsScope', 'drawer', 'discount', 'tipAssign',
@@ -112,6 +120,48 @@ async function unpairRegisterDevice(options) {
112
120
  pflicht(name, 'deviceSecret', deviceSecret);
113
121
  await transportFuer(verbindung)(name, { ownerUid, deviceId, deviceSecret }, undefined, [deviceSecret]);
114
122
  }
123
+ /**
124
+ * Welche Sitzungen haelt diese Kasse gerade?
125
+ *
126
+ * Fuer den Anmeldebildschirm: Sind alle Lizenzplaetze belegt, muss eine
127
+ * weichen -- und der Kassier soll sehen, WELCHE, statt dass das Backend still
128
+ * die aelteste nimmt. Die Wahl geht dann als
129
+ * [RegisterUserLoginOptions.takeoverSessionId] mit.
130
+ *
131
+ * Ausgewiesen wird sich ueber das Geraete-Geheimnis, wie bei
132
+ * [listRegisterUsersForDevice]: An diesem Punkt gibt es weder Sitzung noch
133
+ * Token. Die Kasse bestimmt das Backend aus dem Geraet, nicht aus dem Aufruf.
134
+ */
135
+ async function listRegisterSessionsForDevice(options) {
136
+ const { ownerUid, deviceId, deviceSecret, ...verbindung } = options;
137
+ const name = 'listRegisterSessionsForDevice';
138
+ pflicht(name, 'ownerUid', ownerUid);
139
+ pflicht(name, 'deviceId', deviceId);
140
+ pflicht(name, 'deviceSecret', deviceSecret);
141
+ const daten = await transportFuer(verbindung)(name, { ownerUid, deviceId, deviceSecret }, undefined, [deviceSecret]);
142
+ const liste = daten?.sessions;
143
+ if (!Array.isArray(liste)) {
144
+ throw antwortfehler(name, 'Antwort enthaelt keine Sitzungsliste (data.sessions fehlt)');
145
+ }
146
+ const sessions = liste.map((eintrag) => {
147
+ const roh = (typeof eintrag === 'object' && eintrag !== null ? eintrag : {});
148
+ return {
149
+ // Ohne Kennung waere die Zeile nicht waehlbar -- eine Schaltflaeche, die
150
+ // nichts tun kann, ist schlimmer als keine.
151
+ id: pflichtfeld(name, roh, 'id'),
152
+ deviceId: typeof roh['deviceId'] === 'string' && roh['deviceId'] ? roh['deviceId'] : null,
153
+ deviceLabel: text(roh['deviceLabel']) || 'Kasse',
154
+ startedAt: typeof roh['startedAt'] === 'number' ? roh['startedAt'] : null,
155
+ expiresAt: typeof roh['expiresAt'] === 'number' ? roh['expiresAt'] : null,
156
+ selbst: roh['selbst'] === true,
157
+ // Nur uebernehmen, wenn wirklich einer kam: ein leerer String stuende
158
+ // in der Oberflaeche als namenlose Zeile, statt die Spalte wegzulassen.
159
+ ...(typeof roh['userName'] === 'string' && roh['userName'] ? { userName: roh['userName'] } : {}),
160
+ };
161
+ });
162
+ const licenses = typeof daten?.licenses === 'number' && daten.licenses > 0 ? daten.licenses : 1;
163
+ return { licenses, sessions };
164
+ }
115
165
  async function listRegisterUsersForDevice(options) {
116
166
  const { ownerUid, deviceId, deviceSecret, ...verbindung } = options;
117
167
  const name = 'listRegisterUsersForDevice';
@@ -185,7 +235,7 @@ function regel(wert) {
185
235
  * sperren gestaffelt (ab dem fuenften 30 Sekunden, ab dem neunten 15 Minuten).
186
236
  */
187
237
  async function registerUserLogin(options) {
188
- const { ownerUid, deviceId, deviceSecret, userId, pin, cashregisterId, takeover, client, geo, ...verbindung } = options;
238
+ const { ownerUid, deviceId, deviceSecret, userId, pin, cashregisterId, takeover, takeoverSessionId, client, geo, ...verbindung } = options;
189
239
  const name = 'registerUserLogin';
190
240
  pflicht(name, 'ownerUid', ownerUid);
191
241
  pflicht(name, 'deviceId', deviceId);
@@ -203,6 +253,9 @@ async function registerUserLogin(options) {
203
253
  // Nur die ausdrueckliche Uebernahme geht mit: das Backend prueft auf
204
254
  // `=== true`, und ein mitgesendetes `false` waere nur Rauschen.
205
255
  takeover: takeover === true ? true : undefined,
256
+ // Leerer String heisst „nichts gewaehlt" und gehoert nicht in die
257
+ // Nutzlast -- das Backend wiese ihn sonst als unbekannte Sitzung ab.
258
+ takeoverSessionId: takeoverSessionId ? takeoverSessionId : undefined,
206
259
  client, geo: geo ?? undefined,
207
260
  }, undefined, [pin, deviceSecret]);
208
261
  return sitzungAusAntwort(name, daten);
@@ -214,7 +267,7 @@ async function registerUserLogin(options) {
214
267
  * zaehlen und sperren dort am **Geraet**, nicht an einem Benutzer.
215
268
  */
216
269
  async function registerPinLogin(options) {
217
- const { ownerUid, deviceId, deviceSecret, pin, cashregisterId, takeover, client, geo, ...verbindung } = options;
270
+ const { ownerUid, deviceId, deviceSecret, pin, cashregisterId, takeover, takeoverSessionId, client, geo, ...verbindung } = options;
218
271
  const name = 'registerPinLogin';
219
272
  pflicht(name, 'ownerUid', ownerUid);
220
273
  pflicht(name, 'deviceId', deviceId);
@@ -229,6 +282,9 @@ async function registerPinLogin(options) {
229
282
  cashregisterId,
230
283
  // Nur die ausdrueckliche Uebernahme geht mit — wie bei registerUserLogin.
231
284
  takeover: takeover === true ? true : undefined,
285
+ // Leerer String heisst „nichts gewaehlt" und gehoert nicht in die
286
+ // Nutzlast -- das Backend wiese ihn sonst als unbekannte Sitzung ab.
287
+ takeoverSessionId: takeoverSessionId ? takeoverSessionId : undefined,
232
288
  client, geo: geo ?? undefined,
233
289
  }, undefined, [pin, deviceSecret]);
234
290
  return sitzungAusAntwort(name, daten);
@@ -13,7 +13,7 @@
13
13
  * umhuellt, muss ihn weiterhin absetzen koennen.
14
14
  */
15
15
  import type { TransportBodyFields } from './transport.js';
16
- export declare const AUFRUFE: readonly ["cancelReceipt", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getFirstReceiptDate", "getKasseSettings", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listRegisterUsersForDevice", "pairRegisterDevice", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice"];
16
+ export declare const AUFRUFE: readonly ["cancelReceipt", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getFirstReceiptDate", "getKasseSettings", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyTipRecipients", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice"];
17
17
  export type Aufruf = typeof AUFRUFE[number];
18
18
  /** Wie [KasseneckTransport], nur mit bekanntem Aufrufnamen. Nicht exportiert nach aussen. */
19
19
  export type InternerTransport = <T = unknown>(functionName: Aufruf, params?: Record<string, unknown>, extraBodyFields?: TransportBodyFields, secretParams?: readonly string[]) => Promise<T>;
@@ -19,7 +19,9 @@ export const AUFRUFE = [
19
19
  'listMyCashregisters',
20
20
  'listMyPrinters',
21
21
  'listMyReceipts',
22
+ 'listMyTipRecipients',
22
23
  'listRegisterUsersForDevice',
24
+ 'listRegisterSessionsForDevice',
23
25
  'pairRegisterDevice',
24
26
  'registerPinLogin',
25
27
  'registerUserLogin',
@@ -76,7 +76,13 @@ export declare class KasseneckApiError extends Error {
76
76
  readonly functionName: string;
77
77
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
78
78
  readonly serverMessage: string;
79
- constructor(functionName: string, serverMessage: string);
79
+ /**
80
+ * Stabiler Fehlercode des Backends (`code` aus der Huelle), wenn der
81
+ * Endpunkt einen legt — heute `cancelReceipt` (siehe CANCELLATION_ERROR_CODES).
82
+ * Daran entscheiden, nie an [serverMessage]: der Text darf sich aendern.
83
+ */
84
+ readonly code: string | undefined;
85
+ constructor(functionName: string, serverMessage: string, code?: string);
80
86
  }
81
87
  /** Warum die Antwort keine verwertbare Huelle war. */
82
88
  export type HttpFailureReason = 'server-error' | 'empty-body' | 'not-json' | 'missing-status';
@@ -92,10 +92,17 @@ export class KasseneckApiError extends Error {
92
92
  functionName;
93
93
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
94
94
  serverMessage;
95
- constructor(functionName, serverMessage) {
95
+ /**
96
+ * Stabiler Fehlercode des Backends (`code` aus der Huelle), wenn der
97
+ * Endpunkt einen legt — heute `cancelReceipt` (siehe CANCELLATION_ERROR_CODES).
98
+ * Daran entscheiden, nie an [serverMessage]: der Text darf sich aendern.
99
+ */
100
+ code;
101
+ constructor(functionName, serverMessage, code) {
96
102
  super(`${functionName} fehlgeschlagen: ${serverMessage}`);
97
103
  this.functionName = functionName;
98
104
  this.serverMessage = serverMessage;
105
+ this.code = code;
99
106
  }
100
107
  }
101
108
  const GRUND_TEXT = {
@@ -54,6 +54,22 @@ export interface TipOptions {
54
54
  paymentMethod?: KeckPaymentMethod | KeckPaymentMethodKey;
55
55
  /** Empfaenger; ohne Angabe der angemeldete Kassen-Benutzer. Summe der cents = cents. */
56
56
  recipients?: TipRecipientShare[];
57
+ /**
58
+ * Hat der Empfaenger das Geld schon? `true` = ja (Bargeld mitgenommen,
59
+ * Kartentrinkgeld sofort aus der Lade ausgezahlt), `false` = der Betrieb
60
+ * behaelt es und schuldet es.
61
+ *
62
+ * Entscheidend ist NICHT die Zahlart, sondern der Besitz -- § 2j Abs 2 AVRAG
63
+ * kennt beide Faelle. Ohne Angabe gilt die Voreinstellung des Betriebs (bar:
64
+ * schon erhalten, bargeldlos: einbehalten); diese Angabe braucht es nur fuer
65
+ * die Ausnahme.
66
+ *
67
+ * NUR mit dem Recht `tipAssign` bei persoenlicher Anmeldung: das Merkmal
68
+ * entscheidet, ob der Betrieb Geld schuldet, und gewoehnliches Personal soll
69
+ * das nicht am Geraet umstellen koennen. Ueber einen Geraete-API-Schluessel
70
+ * (ohne angemeldeten Kassen-Benutzer) gilt die Einschraenkung nicht.
71
+ */
72
+ sofortErhalten?: boolean;
57
73
  }
58
74
  export interface TipRecipientShare {
59
75
  registerUserId: string;
@@ -385,6 +385,16 @@ function gepruefterTip(tip) {
385
385
  const nutzlast = { cents: tip.cents };
386
386
  if (tip.paymentMethod != null)
387
387
  nutzlast['paymentMethod'] = gepruefteZahlungsart(tip.paymentMethod);
388
+ // Nur mitschicken, wenn gesetzt: fehlt das Feld, entscheidet die
389
+ // Voreinstellung des Betriebs. Ein `false` waere dort eine Aussage, kein
390
+ // Weglassen. Der Wortlaut des Fehlers ist der des Backends
391
+ // (tip-core.normalizeTip) -- wer ihn hier sieht, sieht denselben Satz.
392
+ if (tip.sofortErhalten != null) {
393
+ if (typeof tip.sofortErhalten !== 'boolean') {
394
+ throw eingabefehler('Trinkgeld: sofortErhalten muss true oder false sein.');
395
+ }
396
+ nutzlast['sofortErhalten'] = tip.sofortErhalten;
397
+ }
388
398
  if (tip.recipients != null) {
389
399
  if (!Array.isArray(tip.recipients) || tip.recipients.length === 0) {
390
400
  throw eingabefehler('Trinkgeld: recipients darf nicht leer sein.');
@@ -176,7 +176,7 @@ function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
176
176
  }
177
177
  // Alles, was nicht ausdruecklich Erfolg ist, gilt als fachlicher Fehler —
178
178
  // ein unbekannter Statuswert darf nie stillschweigend als Erfolg durchgehen.
179
- throw fachfehler(functionName, huelle.message);
179
+ throw fachfehler(functionName, huelle.message, huelle.code);
180
180
  }
181
181
  /**
182
182
  * Auswertung des Binaerwegs. Der Kern der Zusage: **ein Aufrufer bekommt nie
@@ -222,7 +222,7 @@ function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp) {
222
222
  }
223
223
  // Derselbe fachliche Fehler wie auf dem JSON-Weg — fuer den Aufrufer macht
224
224
  // es keinen Unterschied, ob er ein PDF oder eine Nutzlast erwartet hat.
225
- throw fachfehler(functionName, huelle.message);
225
+ throw fachfehler(functionName, huelle.message, huelle.code);
226
226
  }
227
227
  /** `%PDF` am Anfang — die Kennung jeder PDF-Datei. */
228
228
  function istPdf(bytes) {
@@ -235,9 +235,10 @@ function alsHuelle(wert) {
235
235
  }
236
236
  return wert;
237
237
  }
238
- function fachfehler(functionName, message) {
238
+ function fachfehler(functionName, message, code) {
239
239
  const meldung = typeof message === 'string' && message.trim() ? message : 'Unbekannter Fehler';
240
- return new KasseneckApiError(functionName, meldung);
240
+ // Nur ein Text zaehlt als Code -- alles andere waere ein geratener Vertrag.
241
+ return new KasseneckApiError(functionName, meldung, typeof code === 'string' && code ? code : undefined);
241
242
  }
242
243
  /**
243
244
  * Nutzlast aus Auth- und Aufruferparametern. Auth-Parameter bilden die
@@ -1,5 +1,5 @@
1
1
  export { ReceiptType, type ReceiptTypeKey, VatRate, type VatRateKey, KeckPaymentMethod, type KeckPaymentMethodKey, CreditCardProvider, type CreditCardProviderKey, VoucherType, type VoucherTypeKey, VoucherAction, type VoucherActionKey, StripeLinkMode, type StripeLinkModeKey, } from './enums/index.js';
2
- export { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead, toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, type TipRecipient, isTipItem, type Voucher, type VoucherPayload, toVoucherPayload, fromVoucherPayload, voucherIsValid, type Receipt, type ReceiptPayload, type ReceiptPayloadRead, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, CANCELLATION_REASONS, CANCELLATION_RESERVATION_MS, isCancellationReason, remainingQuantities, type ReceiptCompany, type ReceiptCompanyPayload, fromReceiptCompanyPayload, receiptCompanyTaxInfo, type Cashregister, type CashregisterOnboarding, type CashregisterPayload, type CashregisterOnboardingPayload, fromCashregisterPayload, type ReceiptSummary, type ReceiptSummaryPayload, fromReceiptSummaryPayload, type ZeroKind, ZERO_KINDS, istZeroKind, type ReportMonth, reportMonthFromDate, previousReportMonth, nextReportMonth, reportMonthKey, reportMonthReadable, type StripeUrlSession, type StripeUrlSessionPayload, toStripeUrlSessionPayload, fromStripeUrlSessionPayload, type HobexReceipt, type HobexReceiptPayload, toHobexReceiptPayload, fromHobexReceiptPayload, hobexReceiptToCardPaymentData, hobexReceiptNeedsSignature, } from './models/index.js';
2
+ export { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead, toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, type TipRecipient, isTipItem, type Voucher, type VoucherPayload, toVoucherPayload, fromVoucherPayload, voucherIsValid, type Receipt, type ReceiptPayload, type ReceiptPayloadRead, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, type CancellationErrorCode, CANCELLATION_REASONS, CANCELLATION_ERROR_CODES, CANCELLATION_RESERVATION_MS, isCancellationReason, isCancellationErrorCode, remainingQuantities, type ReceiptCompany, type ReceiptCompanyPayload, fromReceiptCompanyPayload, receiptCompanyTaxInfo, type Cashregister, type CashregisterOnboarding, type CashregisterPayload, type CashregisterOnboardingPayload, fromCashregisterPayload, type ReceiptSummary, type ReceiptSummaryPayload, fromReceiptSummaryPayload, type ZeroKind, ZERO_KINDS, istZeroKind, type ReportMonth, reportMonthFromDate, previousReportMonth, nextReportMonth, reportMonthKey, reportMonthReadable, type StripeUrlSession, type StripeUrlSessionPayload, toStripeUrlSessionPayload, fromStripeUrlSessionPayload, type HobexReceipt, type HobexReceiptPayload, toHobexReceiptPayload, fromHobexReceiptPayload, hobexReceiptToCardPaymentData, hobexReceiptNeedsSignature, } from './models/index.js';
3
3
  export { type AuthCredentials, type KasseneckAuth, type ApiKeyAuthOptions, type RegisterUserAuthOptions, apiKeyAuth, registerUserAuth, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, type HttpResponseLike, type HttpRequestInit, type FetchLike, type TransportOptions, type TransportBodyFields, type KasseneckTransport, type KasseneckBinaryTransport, AUFRUFE, type Aufruf, createTransport, createBinaryTransport, KasseneckApiError, KasseneckAuthError, KasseneckHttpError, KasseneckNetworkError, KasseneckValidationError, type ValidationScope, type KasseneckError, type HttpFailureReason, type CauseDigest, isKasseneckApiError, isKasseneckAuthError, isKasseneckHttpError, isKasseneckNetworkError, isKasseneckValidationError, type ReceiptCommonOptions, type SellReceiptOptions, type TipOptions, type TipRecipientShare, type CancelReceiptOptions, type CancelReceiptResult, type CreateCancelReceiptOptions, type ReceiptWithCompany, sellReceipt, sellReceiptWithCompany, cancelReceipt, createCancelReceipt, zeroReceipt, getReceipt, getReceiptWithCompany, generateFullReceiptId, getFirstReceiptDate, listMyReceipts, type ListMyReceiptsOptions, type ReceiptList, type ReceiptListStats, listMyCashregisters, checkVoucherCombinationError, downloadDailyReport, downloadMonthlyReport, type CashboxStatus, type SignatureStatus, getCashboxStatus, getSignatureStatus, type KasseneckApi, createKasseneckApi, } from './client/index.js';
4
4
  export { parseServerTimeStamp, toViennaWallClock, type ViennaWallClock } from './vienna-time.js';
5
5
  export { type KasseSettingsBetrieb, type KasseSettingsGeraet, type KasseSettings, type ArticleGroup, type KasseArtikel, KASSE_BETRIEB_STANDARD, KASSE_GERAET_STANDARD, mergeKasseSettings, fromArticleGroupPayload, fromKasseArtikelPayload, listMyArticleGroups, listMyArticles, getKasseSettings, setMyKasseSettings, setMyRegisterDeviceSettings, verteileRabatt, cancelScopeOf, receiptsScopeOf, } from './kasse/index.js';
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ReceiptType, VatRate, KeckPaymentMethod, CreditCardProvider, VoucherType, VoucherAction, StripeLinkMode, } from './enums/index.js';
2
- export { toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, isTipItem, toVoucherPayload, fromVoucherPayload, voucherIsValid, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, CANCELLATION_REASONS, CANCELLATION_RESERVATION_MS, isCancellationReason, remainingQuantities, fromReceiptCompanyPayload, receiptCompanyTaxInfo, fromCashregisterPayload, fromReceiptSummaryPayload, ZERO_KINDS, istZeroKind, reportMonthFromDate, previousReportMonth, nextReportMonth, reportMonthKey, reportMonthReadable, toStripeUrlSessionPayload, fromStripeUrlSessionPayload, toHobexReceiptPayload, fromHobexReceiptPayload, hobexReceiptToCardPaymentData, hobexReceiptNeedsSignature, } from './models/index.js';
2
+ export { toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, isTipItem, toVoucherPayload, fromVoucherPayload, voucherIsValid, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, CANCELLATION_REASONS, CANCELLATION_ERROR_CODES, CANCELLATION_RESERVATION_MS, isCancellationReason, isCancellationErrorCode, remainingQuantities, fromReceiptCompanyPayload, receiptCompanyTaxInfo, fromCashregisterPayload, fromReceiptSummaryPayload, ZERO_KINDS, istZeroKind, reportMonthFromDate, previousReportMonth, nextReportMonth, reportMonthKey, reportMonthReadable, toStripeUrlSessionPayload, fromStripeUrlSessionPayload, toHobexReceiptPayload, fromHobexReceiptPayload, hobexReceiptToCardPaymentData, hobexReceiptNeedsSignature, } from './models/index.js';
3
3
  export { apiKeyAuth, registerUserAuth, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, AUFRUFE, createTransport, createBinaryTransport, KasseneckApiError, KasseneckAuthError, KasseneckHttpError, KasseneckNetworkError, KasseneckValidationError, isKasseneckApiError, isKasseneckAuthError, isKasseneckHttpError, isKasseneckNetworkError, isKasseneckValidationError, sellReceipt, sellReceiptWithCompany, cancelReceipt, createCancelReceipt, zeroReceipt, getReceipt, getReceiptWithCompany, generateFullReceiptId, getFirstReceiptDate, listMyReceipts, listMyCashregisters, checkVoucherCombinationError, downloadDailyReport, downloadMonthlyReport, getCashboxStatus, getSignatureStatus, createKasseneckApi, } from './client/index.js';
4
4
  export { parseServerTimeStamp, toViennaWallClock } from './vienna-time.js';
5
5
  export { KASSE_BETRIEB_STANDARD, KASSE_GERAET_STANDARD, mergeKasseSettings, fromArticleGroupPayload, fromKasseArtikelPayload, listMyArticleGroups, listMyArticles, getKasseSettings, setMyKasseSettings, setMyRegisterDeviceSettings, verteileRabatt, cancelScopeOf, receiptsScopeOf, } from './kasse/index.js';
@@ -9,3 +9,4 @@ export { getKasseSettings, setMyKasseSettings, setMyRegisterDeviceSettings } fro
9
9
  export { verteileRabatt } from '../receipt/discount.js';
10
10
  export { cancelScopeOf, receiptsScopeOf, type RegisterScope, type RegisterUserPerms } from '../register/pairing.js';
11
11
  export { type NetzDrucker, type DruckJob, type DruckJobStatus, type CreatePrintJobOptions, listMyPrinters, createPrintJob, getPrintJob } from './drucker.js';
12
+ export { listMyTipRecipients } from './trinkgeld.js';
@@ -13,3 +13,4 @@ export { verteileRabatt } from '../receipt/discount.js';
13
13
  // Register-Unterpfad: dessen Exportnamen sind 1:1 Function-Namen (Rewrites).
14
14
  export { cancelScopeOf, receiptsScopeOf } from '../register/pairing.js';
15
15
  export { listMyPrinters, createPrintJob, getPrintJob } from './drucker.js';
16
+ export { listMyTipRecipients } from './trinkgeld.js';
@@ -0,0 +1,10 @@
1
+ import type { InternerTransport } from '../client/aufrufe.js';
2
+ import type { TipRecipient } from '../models/index.js';
3
+ /**
4
+ * Die Personen, denen dieser Aufrufer Trinkgeld zuweisen kann.
5
+ *
6
+ * Es ist **dieselbe Menge**, die `createReceipt` akzeptiert — wer hier steht,
7
+ * wird beim Verkauf nicht zurueckgewiesen. Ist ein Kassen-Benutzer ohne das
8
+ * Recht `tipAssign` angemeldet, steht nur er selbst darin.
9
+ */
10
+ export declare function listMyTipRecipients(rufen: InternerTransport): Promise<TipRecipient[]>;
@@ -0,0 +1,24 @@
1
+ import { KasseneckValidationError } from '../client/errors.js';
2
+ /**
3
+ * Die Personen, denen dieser Aufrufer Trinkgeld zuweisen kann.
4
+ *
5
+ * Es ist **dieselbe Menge**, die `createReceipt` akzeptiert — wer hier steht,
6
+ * wird beim Verkauf nicht zurueckgewiesen. Ist ein Kassen-Benutzer ohne das
7
+ * Recht `tipAssign` angemeldet, steht nur er selbst darin.
8
+ */
9
+ export async function listMyTipRecipients(rufen) {
10
+ const daten = await rufen('listMyTipRecipients');
11
+ const roh = daten?.recipients;
12
+ if (!Array.isArray(roh)) {
13
+ // Keine Liste ist etwas anderes als eine leere Liste: „noch niemand
14
+ // angelegt" darf nicht aussehen wie „Antwort kaputt".
15
+ throw new KasseneckValidationError('listMyTipRecipients', 'Antwort enthaelt keine Liste (data.recipients fehlt)', 'response');
16
+ }
17
+ return roh.map((e) => {
18
+ const d = (e ?? {});
19
+ // Dieselbe Gestalt wie der Empfaenger am Beleg-Item — `TipRecipient` aus
20
+ // src/models/receipt-item.ts. Kein neuer Typ: „wer kann Trinkgeld
21
+ // bekommen" und „wer hat es bekommen" sind dieselbe Sache.
22
+ return { registerUserId: String(d.registerUserId ?? ''), name: String(d.name ?? ''), owner: d.owner === true };
23
+ });
24
+ }
@@ -18,6 +18,19 @@ export declare const CANCELLATION_REASONS: Readonly<{
18
18
  }>;
19
19
  export type CancellationReason = keyof typeof CANCELLATION_REASONS;
20
20
  export declare function isCancellationReason(value: unknown): value is CancellationReason;
21
+ /**
22
+ * Stabile Fehlercodes von `cancelReceipt` — Zwilling von
23
+ * `functions/storno-core.js` STORNO_FEHLERCODES. Das Backend legt sie bei
24
+ * jedem fachlichen Fehler als `code` neben die Meldung; das Paket reicht sie
25
+ * als [KasseneckApiError.code] durch. **Entscheide am Code, nie am Text** —
26
+ * die deutsche Meldung darf sich aendern, der Code nicht.
27
+ *
28
+ * Nur Auth-/Parameterfehler (Sitzung abgelaufen, Pflichtfeld fehlt) kommen
29
+ * ohne Code; dort bleibt `code` undefined.
30
+ */
31
+ export declare const CANCELLATION_ERROR_CODES: readonly ["beleg_nicht_gefunden", "belegart_nicht_stornierbar", "trainingsbeleg", "bereits_storniert", "position_ungueltig", "menge_ueber_rest", "grund_unbekannt", "anmerkung_zu_lang", "items_ungueltig", "kasse_nicht_zugewiesen", "keine_berechtigung", "nur_eigene_belege", "kasse_unvollstaendig", "storno_fehlgeschlagen"];
32
+ export type CancellationErrorCode = (typeof CANCELLATION_ERROR_CODES)[number];
33
+ export declare function isCancellationErrorCode(value: unknown): value is CancellationErrorCode;
21
34
  /** Bezug eines Storno-Belegs auf sein Original. */
22
35
  export interface CancellationOf {
23
36
  receiptId: string;
@@ -40,6 +53,15 @@ export interface Cancellation {
40
53
  by: string | null;
41
54
  note: string | null;
42
55
  items: CancellationItem[];
56
+ /**
57
+ * Rabattgutschein-Ausgleich, den DIESER Storno gewaehrt hat — Cent je
58
+ * Steuertopf des Backends (`amountRateStandard`, `amountRateReduced1`, …).
59
+ * Ein Rabattgutschein ist am Original schon in den Umsatz eingerechnet;
60
+ * jeder Storno nimmt ihn anteilig der stornierten Menge zurueck (bei 3 Stueck
61
+ * a 10 EUR mit 6 EUR Rabatt: 2,00 je Stueck). Fehlt das Feld, hat der Eintrag
62
+ * nichts gewaehrt (Altbestand vor dieser Regel) — der naechste Storno holt nach.
63
+ */
64
+ promoAdjustmentCents?: Record<string, number>;
43
65
  }
44
66
  /** Ab wann eine liegengebliebene Reservierung nicht mehr zaehlt (wie im Backend). */
45
67
  export declare const CANCELLATION_RESERVATION_MS = 120000;
@@ -18,6 +18,35 @@ export const CANCELLATION_REASONS = Object.freeze({
18
18
  export function isCancellationReason(value) {
19
19
  return typeof value === 'string' && Object.prototype.hasOwnProperty.call(CANCELLATION_REASONS, value);
20
20
  }
21
+ /**
22
+ * Stabile Fehlercodes von `cancelReceipt` — Zwilling von
23
+ * `functions/storno-core.js` STORNO_FEHLERCODES. Das Backend legt sie bei
24
+ * jedem fachlichen Fehler als `code` neben die Meldung; das Paket reicht sie
25
+ * als [KasseneckApiError.code] durch. **Entscheide am Code, nie am Text** —
26
+ * die deutsche Meldung darf sich aendern, der Code nicht.
27
+ *
28
+ * Nur Auth-/Parameterfehler (Sitzung abgelaufen, Pflichtfeld fehlt) kommen
29
+ * ohne Code; dort bleibt `code` undefined.
30
+ */
31
+ export const CANCELLATION_ERROR_CODES = [
32
+ 'beleg_nicht_gefunden', // Original fehlt oder gehoert nicht zu dieser Kasse
33
+ 'belegart_nicht_stornierbar', // Original ist selbst Storno-, Null- oder Startbeleg
34
+ 'trainingsbeleg', // Trainingsbelege werden nicht storniert
35
+ 'bereits_storniert', // keine Restmenge mehr (auch beim positionslosen Beleg)
36
+ 'position_ungueltig', // Index unbekannt oder doppelt
37
+ 'menge_ueber_rest', // Menge nicht ganzzahlig >= 1 oder groesser als der Rest
38
+ 'grund_unbekannt', // reason fehlt oder nicht im Katalog
39
+ 'anmerkung_zu_lang', // note laenger als 200 Zeichen
40
+ 'items_ungueltig', // items ist keine Liste
41
+ 'kasse_nicht_zugewiesen', // Register-Benutzer darf diese Kasse nicht
42
+ 'keine_berechtigung', // Register-Benutzer ohne Storno-Recht
43
+ 'nur_eigene_belege', // Recht "eigene", fremder Beleg
44
+ 'kasse_unvollstaendig', // api_key/token fehlen am Konto bzw. an der Kasse
45
+ 'storno_fehlgeschlagen', // der Storno-Beleg selbst wurde abgelehnt (z. B. Signatur)
46
+ ];
47
+ export function isCancellationErrorCode(value) {
48
+ return typeof value === 'string' && CANCELLATION_ERROR_CODES.includes(value);
49
+ }
21
50
  /** Ab wann eine liegengebliebene Reservierung nicht mehr zaehlt (wie im Backend). */
22
51
  export const CANCELLATION_RESERVATION_MS = 120_000;
23
52
  /**
@@ -1,5 +1,5 @@
1
1
  import { CreditCardProvider } from '../enums/index.js';
2
- import { centsToEuro } from '../money.js';
2
+ import { centsToEuro, euroToCents } from '../money.js';
3
3
  export function toHobexReceiptPayload(beleg) {
4
4
  return {
5
5
  transactionId: beleg.transactionId,
@@ -38,8 +38,15 @@ export function fromHobexReceiptPayload(payload) {
38
38
  responseCode: payload.responseCode,
39
39
  transactionType: payload.transactionType,
40
40
  currency: payload.currency,
41
- amountCents: Math.round((payload.amount ?? 0) * 100),
42
- tipCents: Math.round((payload.tip ?? 0) * 100),
41
+ // Euro->Cent ueber die eine gehaertete Stelle im Paket ([euroToCents],
42
+ // money.ts) statt einer eigenen `Math.round(x * 100)` -- die Hobex-Antwort
43
+ // ist eine fremde Antwort und wird hier nur auf transactionId/
44
+ // transactionDate geprueft (siehe payments/hobex.ts, belegAusNutzlast);
45
+ // amount/tip bleiben ungeprueft. Ein nicht-numerischer oder
46
+ // nicht-endlicher Wert ergab bis zu dieser Fassung `NaN` -- lautlos, ohne
47
+ // dass irgendwo geworfen wurde. [euroToCents] liefert dafuer `0`.
48
+ amountCents: euroToCents(payload.amount),
49
+ tipCents: euroToCents(payload.tip),
43
50
  cvm: String(payload.cvm),
44
51
  // Die Cloud-API-Nutzlast traegt keinen eigenen Provider-Schluessel — der
45
52
  // Provider ergibt sich aus dem Transportweg (Cloud vs. HPS), nicht aus
@@ -1,7 +1,7 @@
1
1
  export { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead, toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, type TipRecipient, isTipItem, isDiscountItem, } from './receipt-item.js';
2
2
  export { type Voucher, type VoucherPayload, toVoucherPayload, fromVoucherPayload, voucherIsValid } from './voucher.js';
3
3
  export { type Receipt, type ReceiptPayload, type ReceiptPayloadRead, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, } from './receipt.js';
4
- export { type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, CANCELLATION_REASONS, CANCELLATION_RESERVATION_MS, isCancellationReason, remainingQuantities, } from './cancellation.js';
4
+ export { type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, type CancellationErrorCode, CANCELLATION_REASONS, CANCELLATION_ERROR_CODES, CANCELLATION_RESERVATION_MS, isCancellationReason, isCancellationErrorCode, remainingQuantities, } from './cancellation.js';
5
5
  export { type ReceiptCompany, type ReceiptCompanyPayload, fromReceiptCompanyPayload, receiptCompanyTaxInfo, } from './receipt-company.js';
6
6
  export { type Cashregister, type CashregisterOnboarding, type CashregisterPayload, type CashregisterOnboardingPayload, fromCashregisterPayload, } from './cashregister.js';
7
7
  export { type ReceiptSummary, type ReceiptSummaryPayload, fromReceiptSummaryPayload, type ZeroKind, ZERO_KINDS, istZeroKind } from './receipt-summary.js';
@@ -1,7 +1,7 @@
1
1
  export { toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, isTipItem, isDiscountItem, } from './receipt-item.js';
2
2
  export { toVoucherPayload, fromVoucherPayload, voucherIsValid } from './voucher.js';
3
3
  export { toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, } from './receipt.js';
4
- export { CANCELLATION_REASONS, CANCELLATION_RESERVATION_MS, isCancellationReason, remainingQuantities, } from './cancellation.js';
4
+ export { CANCELLATION_REASONS, CANCELLATION_ERROR_CODES, CANCELLATION_RESERVATION_MS, isCancellationReason, isCancellationErrorCode, remainingQuantities, } from './cancellation.js';
5
5
  export { fromReceiptCompanyPayload, receiptCompanyTaxInfo, } from './receipt-company.js';
6
6
  export { fromCashregisterPayload, } from './cashregister.js';
7
7
  export { fromReceiptSummaryPayload, ZERO_KINDS, istZeroKind } from './receipt-summary.js';
@@ -84,6 +84,9 @@ function leseStorno(eintrag) {
84
84
  by: eintrag.by ?? null,
85
85
  note: eintrag.note ?? null,
86
86
  items: (eintrag.items ?? []).map((p) => ({ index: Number(p.index), quantity: Number(p.quantity) })),
87
+ ...(eintrag.promoAdjustmentCents && typeof eintrag.promoAdjustmentCents === 'object'
88
+ ? { promoAdjustmentCents: Object.fromEntries(Object.entries(eintrag.promoAdjustmentCents).map(([k, v]) => [k, Number(v)])) }
89
+ : {}),
87
90
  };
88
91
  }
89
92
  /**
@@ -1,4 +1,5 @@
1
1
  import { VoucherAction, VoucherType } from '../enums/index.js';
2
+ import { euroToCents } from '../money.js';
2
3
  import { readEnumKey, requireEnumKey } from './enum-payload.js';
3
4
  export function toVoucherPayload(voucher) {
4
5
  return {
@@ -30,16 +31,31 @@ export function voucherIsValid(voucher) {
30
31
  if (voucher.type === VoucherType.promo && voucher.valueCents == null) {
31
32
  return false;
32
33
  }
33
- if (voucher.valueCents != null && voucher.valueCents <= 0) {
34
+ // `!Number.isFinite` VOR den Vergleich, nicht dahinter: `NaN <= 0` und
35
+ // `Infinity <= 0` sind beide `false`, ein Vergleich mit einem
36
+ // nicht-endlichen Wert ist also IMMER falsch und liesse ihn hier
37
+ // durchrutschen. Gemessen (28.08.2026): ein Gutschein mit `valueCents: NaN`
38
+ // (aus einem kaputten `value` in der Nutzlast, siehe [fromVoucherPayload])
39
+ // oder `valueCents: Infinity` galt bis zu dieser Fassung als GUELTIG und
40
+ // waere still in die Belegsummen gewandert. Diese Pruefung greift
41
+ // unabhaengig davon, ob `valueCents` ueber [fromVoucherPayload] oder direkt
42
+ // gesetzt wurde -- die gehaertete Umrechnung dort allein reicht nicht.
43
+ if (voucher.valueCents != null && (!Number.isFinite(voucher.valueCents) || voucher.valueCents <= 0)) {
34
44
  return false;
35
45
  }
36
46
  return true;
37
47
  }
38
48
  export function fromVoucherPayload(payload) {
49
+ // Euro->Cent ueber die eine gehaertete Stelle im Paket ([euroToCents],
50
+ // money.ts) statt einer eigenen `Math.round(payload.value * 100)` --
51
+ // [euroToCents] prueft `Number.isFinite` und liefert fuer einen
52
+ // nicht-numerischen oder nicht-endlichen Wert `0` statt eines lautlosen
53
+ // `NaN`. Siehe auch [voucherIsValid]: `0` faellt dort ueber die
54
+ // "<= 0"-Pruefung, ein `NaN` waere durchgerutscht.
39
55
  const valueCents = payload.valueCents != null
40
56
  ? payload.valueCents
41
57
  : payload.value != null
42
- ? Math.round(payload.value * 100)
58
+ ? euroToCents(payload.value)
43
59
  : undefined;
44
60
  return {
45
61
  name: payload.name ?? undefined,
@@ -0,0 +1,104 @@
1
+ import { type HpsTransactionResponse } from './transaction-response.js';
2
+ /**
3
+ * Client fuer die Terminal-Endpunkte von **Kasseneck Connect** — dem lokalen
4
+ * Geraete-Agenten, der ueber HTTP erreichbar ist und mit dem hobex-HPS
5
+ * spricht (`kasseneck-connect/lib/src/api/routes_terminal.dart`). Connect
6
+ * reicht die Antwort des Terminals ROH durch und ordnet nichts ein; die
7
+ * Einordnung passiert in `transaction-response.ts` und `payments.ts`.
8
+ *
9
+ * **Kein direkter Terminal-Kontakt.** Ein Browser hat keine rohen
10
+ * TCP-Sockets — das ist der Unterschied zum Dart-Zwilling `HpsClient`, der
11
+ * das HPS-Terminal direkt anspricht. Dieser Client spricht ausschliesslich
12
+ * mit Connect, niemals mit dem Terminal selbst.
13
+ *
14
+ * **`payment`/`status`/`abort`/`refund`/`cancel`.** Seit
15
+ * `kasseneck-connect` Commit `1c8a003` traegt Connect auch die Gutschrift
16
+ * (`POST /v1/terminal/refund`) und die Aufhebung (`POST /v1/terminal/cancel`)
17
+ * — beide erben denselben Fehlerpfad wie `payment` (`error.detail.terminalHttpStatus`,
18
+ * siehe `HpsConnectTerminalError.isTerminalBusy`).
19
+ */
20
+ /** Adresse des Ziel-Terminals — bei jedem Aufruf mitgegeben, Connect selbst ist zustandslos. */
21
+ export interface HpsConnectTarget {
22
+ /** IP oder Hostname des Terminals im Kassen-Netz. */
23
+ host: string;
24
+ /** Vorgabe: `8080` (hobex-HPS-Standardport), siehe Connect. */
25
+ port?: number;
26
+ /** Terminal-ID, wie am Geraet angezeigt (fuehrende Nullen werden toleriert). */
27
+ tid: string;
28
+ }
29
+ export interface HpsConnectPaymentOptions extends HpsConnectTarget {
30
+ /** Kennung dieser Zahlung — MUSS vor diesem Aufruf feststehen, siehe `transaction-id.ts`. */
31
+ transactionId: string;
32
+ /** Zu belastender Betrag in **Cent** (ohne Trinkgeld). */
33
+ amountCents: number;
34
+ /** Trinkgeld in **Cent**; das Terminal belastet Betrag + Trinkgeld in einem. */
35
+ tipCents?: number;
36
+ reference?: string;
37
+ currency?: string;
38
+ language?: string;
39
+ }
40
+ export interface HpsConnectTransactionOptions extends HpsConnectTarget {
41
+ transactionId: string;
42
+ }
43
+ export interface HpsConnectRefundOptions extends HpsConnectTarget {
44
+ /** Kennung der Gutschrift SELBST — eine NEUE, nicht die der erstatteten Zahlung. */
45
+ transactionId: string;
46
+ /** Kennung der erstatteten Zahlung. Verlangt Connect zwingend, siehe `_handleRefund`. */
47
+ originalTransactionId: string;
48
+ /** Zu erstattender Betrag in **Cent**. */
49
+ amountCents: number;
50
+ reference?: string;
51
+ currency?: string;
52
+ language?: string;
53
+ }
54
+ export interface HpsConnectCancelOptions extends HpsConnectTarget {
55
+ /** Kennung der URSPRUENGLICHEN Zahlung — keine neue. */
56
+ transactionId: string;
57
+ /** Pflicht: das Terminal weist einen Void ohne Betrag mit `400 Missing amount` ab. */
58
+ amountCents: number;
59
+ currency?: string;
60
+ language?: string;
61
+ }
62
+ /** Austauschbare `fetch`-Umsetzung, minimal genug fuer eine Attrappe im Test. */
63
+ export interface HpsConnectFetchResponse {
64
+ status: number;
65
+ text(): Promise<string>;
66
+ }
67
+ export type HpsConnectFetch = (url: string, init: {
68
+ method: string;
69
+ headers: Record<string, string>;
70
+ body: string;
71
+ signal: AbortSignal;
72
+ }) => Promise<HpsConnectFetchResponse>;
73
+ export interface HpsConnectClientOptions {
74
+ /** Basisadresse des Agenten. Vorgabe `http://127.0.0.1:27182` (README `kasseneck-connect`). */
75
+ baseUrl?: string;
76
+ /** Kopplungstoken, geht als `Authorization: Bearer <token>` mit. */
77
+ token: string;
78
+ /**
79
+ * Zeitlimit fuer `status`/`abort`/`diagnosis` in ms. Vorgabe 30 s — deutlich
80
+ * ueber Connects eigenem `hpsKurzTimeout` (15 s), damit dieser Client nicht
81
+ * VOR Connect selbst aufgibt und dessen Fehlermeldung verschluckt.
82
+ */
83
+ shortTimeoutMs?: number;
84
+ /**
85
+ * Zeitlimit fuer `payment` in ms. Vorgabe 4,5 Minuten — ueber Connects
86
+ * eigenem `hpsZahlungTimeout` (4 Minuten): eine Zahlung blockiert, bis der
87
+ * Karteninhaber fertig ist, das darf nicht vorzeitig aufgegeben werden.
88
+ */
89
+ paymentTimeoutMs?: number;
90
+ /** Eigene `fetch`-Umsetzung (Tests). Vorgabe: globales `fetch`. */
91
+ fetch?: HpsConnectFetch;
92
+ }
93
+ export interface HpsConnectClient {
94
+ payment(options: HpsConnectPaymentOptions): Promise<HpsTransactionResponse>;
95
+ status(options: HpsConnectTransactionOptions): Promise<HpsTransactionResponse>;
96
+ abort(options: HpsConnectTransactionOptions): Promise<HpsTransactionResponse>;
97
+ /** Gutschrift — ein Kartenfluss wie [payment], deshalb dieselbe lange Frist. */
98
+ refund(options: HpsConnectRefundOptions): Promise<HpsTransactionResponse>;
99
+ /** Aufhebung (Storno/Void) — kurzer Aufruf, keine Karteninteraktion. */
100
+ cancel(options: HpsConnectCancelOptions): Promise<HpsTransactionResponse>;
101
+ /** Erreichbarkeits-/Firmware-Check, folgenlos. Rohe Nutzlast, Connect ordnet nichts ein. */
102
+ diagnosis(options: HpsConnectTarget): Promise<Record<string, unknown>>;
103
+ }
104
+ export declare function createHpsConnectClient(options: HpsConnectClientOptions): HpsConnectClient;