@kreiseck/kasseneck-api 0.6.45 → 0.7.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 (70) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +158 -2
  3. package/dist/cjs/client/aufrufe.d.ts +1 -1
  4. package/dist/cjs/client/aufrufe.js +20 -0
  5. package/dist/cjs/client/errors.d.ts +31 -1
  6. package/dist/cjs/client/errors.js +98 -1
  7. package/dist/cjs/client/transport.js +7 -7
  8. package/dist/cjs/kasse/index.d.ts +1 -0
  9. package/dist/cjs/kasse/index.js +3 -0
  10. package/dist/cjs/kasse/trinkgeld.d.ts +10 -0
  11. package/dist/cjs/kasse/trinkgeld.js +27 -0
  12. package/dist/cjs/partner/ablauf.d.ts +47 -0
  13. package/dist/cjs/partner/ablauf.js +99 -0
  14. package/dist/cjs/partner/api.d.ts +68 -0
  15. package/dist/cjs/partner/api.js +44 -0
  16. package/dist/cjs/partner/auth.d.ts +33 -0
  17. package/dist/cjs/partner/auth.js +52 -0
  18. package/dist/cjs/partner/betrieb.d.ts +48 -0
  19. package/dist/cjs/partner/betrieb.js +136 -0
  20. package/dist/cjs/partner/endpunkte.d.ts +142 -0
  21. package/dist/cjs/partner/endpunkte.js +488 -0
  22. package/dist/cjs/partner/fehler.d.ts +72 -0
  23. package/dist/cjs/partner/fehler.js +199 -0
  24. package/dist/cjs/partner/index.d.ts +28 -0
  25. package/dist/cjs/partner/index.js +84 -0
  26. package/dist/cjs/partner/secret.d.ts +66 -0
  27. package/dist/cjs/partner/secret.js +95 -0
  28. package/dist/cjs/partner/typen.d.ts +399 -0
  29. package/dist/cjs/partner/typen.js +33 -0
  30. package/dist/cjs/partner/webhook-signatur.d.ts +95 -0
  31. package/dist/cjs/partner/webhook-signatur.js +158 -0
  32. package/dist/cjs/partner/webhooks.d.ts +211 -0
  33. package/dist/cjs/partner/webhooks.js +269 -0
  34. package/dist/cjs/register/pairing.d.ts +8 -1
  35. package/dist/cjs/register/pairing.js +8 -1
  36. package/dist/esm/client/aufrufe.d.ts +1 -1
  37. package/dist/esm/client/aufrufe.js +20 -0
  38. package/dist/esm/client/errors.d.ts +31 -1
  39. package/dist/esm/client/errors.js +97 -1
  40. package/dist/esm/client/transport.js +8 -8
  41. package/dist/esm/kasse/index.d.ts +1 -0
  42. package/dist/esm/kasse/index.js +1 -0
  43. package/dist/esm/kasse/trinkgeld.d.ts +10 -0
  44. package/dist/esm/kasse/trinkgeld.js +24 -0
  45. package/dist/esm/partner/ablauf.d.ts +47 -0
  46. package/dist/esm/partner/ablauf.js +95 -0
  47. package/dist/esm/partner/api.d.ts +68 -0
  48. package/dist/esm/partner/api.js +41 -0
  49. package/dist/esm/partner/auth.d.ts +33 -0
  50. package/dist/esm/partner/auth.js +48 -0
  51. package/dist/esm/partner/betrieb.d.ts +48 -0
  52. package/dist/esm/partner/betrieb.js +132 -0
  53. package/dist/esm/partner/endpunkte.d.ts +142 -0
  54. package/dist/esm/partner/endpunkte.js +474 -0
  55. package/dist/esm/partner/fehler.d.ts +72 -0
  56. package/dist/esm/partner/fehler.js +189 -0
  57. package/dist/esm/partner/index.d.ts +28 -0
  58. package/dist/esm/partner/index.js +27 -0
  59. package/dist/esm/partner/secret.d.ts +66 -0
  60. package/dist/esm/partner/secret.js +90 -0
  61. package/dist/esm/partner/typen.d.ts +399 -0
  62. package/dist/esm/partner/typen.js +30 -0
  63. package/dist/esm/partner/webhook-signatur.d.ts +95 -0
  64. package/dist/esm/partner/webhook-signatur.js +153 -0
  65. package/dist/esm/partner/webhooks.d.ts +211 -0
  66. package/dist/esm/partner/webhooks.js +257 -0
  67. package/dist/esm/register/pairing.d.ts +8 -1
  68. package/dist/esm/register/pairing.js +8 -1
  69. package/fixtures/oberflaeche.json +132 -3
  70. package/package.json +13 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,154 @@
1
+ # Änderungen
2
+
3
+ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
4
+ es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
5
+ nur der Grund überlebt den nächsten Umbau.
6
+
7
+ ## 0.7.0
8
+
9
+ ### Neu: Unterpfad `./partner` — die Partner-API
10
+
11
+ Alles, was ein Partner-Softwarehaus über die Kasseneck-Schnittstelle tut, in
12
+ einem eigenen Unterpfad. Er liegt bewusst **nicht** in der Wurzel: der
13
+ Partner-Schlüssel gehört auf einen Server (er kann Betriebe anlegen und deren
14
+ Geheimnisse holen), und die Kassen-Seite des Pakets soll ihn nicht in ein
15
+ Browser-Bündel ziehen.
16
+
17
+ Neue öffentliche Symbole:
18
+
19
+ - **Anmeldung und Fassade** — `partnerKeyAuth`, `partnerKeyEnv`,
20
+ `createPartnerApi`, `PartnerApi`, `PartnerApiOptions`, `PartnerKeyAuthOptions`.
21
+ - **Betriebe** — `createPartnerCustomer`, `listPartnerCustomers`,
22
+ `getPartnerCustomer`, `sendPartnerCustomerFonLink`, `getPartnerInfo`.
23
+ - **Signatur und Kassen** — `requestCustomerSignature`,
24
+ `getCustomerSignatureStatus`, `createCustomerCashregister`,
25
+ `activateCashregister`, `listCustomerCashregisters`, `getCustomerCredentials`.
26
+ - **Webhooks** — `createPartnerWebhook`, `listPartnerWebhooks`,
27
+ `updatePartnerWebhook`, `deletePartnerWebhook`, `sendPartnerWebhookTest`,
28
+ `listPartnerWebhookDeliveries`, `parseWebhookEvent`,
29
+ `verifyWebhookSignature`, `parseSignatureHeader`, `PARTNER_WEBHOOK_EVENTS`,
30
+ `istPartnerWebhookEvent`, `WEBHOOK_UMSCHLAG_FELDER` und die Konstanten
31
+ `WEBHOOK_SIGNATURE_HEADER`,
32
+ `WEBHOOK_EVENT_HEADER`, `WEBHOOK_DELIVERY_HEADER`, `WEBHOOK_TOLERANCE_SEC`,
33
+ `WEBHOOK_RETRY_PLAN_SEC`, `WEBHOOK_MAX_ATTEMPTS`, `WEBHOOK_TIMEOUT_MS`,
34
+ `WEBHOOK_LIMIT`.
35
+ - **Ablauf und Fehler** — `PARTNER_ABLAUF`, `naechsterSchritt`,
36
+ `PARTNER_FEHLER_CODES`, `PARTNER_PORTAL_FEHLER_CODES`,
37
+ `istPartnerFehlerCode`, `istPartnerPortalFehlerCode`, `istPartnerFehler`,
38
+ `partnerFehlerCode`, `partnerFehlerRat`, `partnerFeldFehler`,
39
+ `partnerWartezeitSek`, `SCOPE_CREDENTIALS`.
40
+ - **Betriebsdaten** — `BETRIEB_FELDER`, `unbekannteBetriebsfelder`,
41
+ `PARTNER_ENVS`.
42
+ - **Geheimnisse** — `KasseneckSecret`, `SECRET_MASKE`.
43
+ - Dazu die Typen der Nutzlasten (`Betrieb`, `Kunde`, `Kasse`, `SignaturAntrag`,
44
+ `CustomerCredentials`, …).
45
+
46
+ Warum die einzelnen Entscheidungen so gefallen sind:
47
+
48
+ - **`KasseneckSecret` statt `string` für die Zugangsdaten eines Betriebs.**
49
+ `getCustomerCredentials` liefert den `api_key` des Betriebs und die Token
50
+ seiner Kassen; wer sie hat, kann in seinem Namen Belege signieren, und ein
51
+ Beleg ist nach RKSV nicht zurücknehmbar. Ein `string` in einem Antwortobjekt
52
+ landet aber in `console.log`, in `JSON.stringify` und im Rumpf eines
53
+ Fehlerberichts. Der Klartext liegt deshalb in einer `WeakMap` neben der
54
+ Instanz — am Objekt hängt kein Feld, das ihn trägt — und `toString`,
55
+ `toJSON`, `Symbol.toPrimitive` sowie der Node-Inspektor zeigen eine Maske.
56
+ Heraus kommt man nur über `.reveal()`; genau diese Stellen findet eine Suche.
57
+ - **Die Signaturprüfung liegt fertig im Paket, nicht als Beispiel in der Doku.**
58
+ Sie ist der Teil, den Integratoren am häufigsten falsch bauen, und ein Fehler
59
+ fällt dort nie auf: eine zu lasche Prüfung lässt jeden durch und meldet nichts.
60
+ Umgesetzt mit WebCrypto statt `node:crypto`, damit das Paket weiterhin ohne
61
+ Laufzeitabhängigkeit auskommt — deshalb ist sie asynchron.
62
+ - **`env` bei `createPartnerCustomer`.** Ohne Angabe entscheidet der Schlüssel.
63
+ Ein **Live**-Schlüssel darf `env:"test"` verlangen — das ist der vorgesehene
64
+ Weg, die ganze Kette zu proben, ohne sich einen zweiten Schlüssel zu holen.
65
+ Umgekehrt nie: ein Test-Schlüssel mit `env:"live"` bekommt `live_not_allowed`,
66
+ und es entsteht nichts. Der Client prüft das **nicht** selbst vor: der Server
67
+ ist die eine Wahrheit, und ein zweiter Torwächter im Paket wäre der, der
68
+ irgendwann veraltet.
69
+ - **Verträge wirken im Partner-Weg nicht mehr** (Backend-Stand 2026-08-31).
70
+ Keine Antwort führt `avv`, `naechsteSchritte` kennt keinen AVV-Schritt, und
71
+ bei der Inbetriebnahme gibt es kein `vertrag_offen` mehr. Weggefallen sind
72
+ deshalb `reportCustomerVertrag`, `vertragOffenRat`, `vertragOffenRatFuer`,
73
+ `AVV_MODI`, `AVV_STATUS`, `avvErfuellt`, `avvSperrt`, `avvStatusText`,
74
+ `istAvvModus`, die Option `avvModus` und der Ablaufschritt `avv`. Der Typ
75
+ `AvvStand` **bleibt** und wird weiterhin gelesen, wenn eine Antwort ihn doch
76
+ führt — vorausgesetzt wird er nirgends. `customer.avv_accepted` steht nicht
77
+ mehr in `PARTNER_WEBHOOK_EVENTS`: es ist ein internes Ereignis, das ein
78
+ Partner weder abonnieren noch proben kann, und ein Name in dieser Liste, den
79
+ niemand bestellen kann, ist ein Versprechen ohne Deckung.
80
+ - **Der Fehlerkatalog ist vollständig** — 28 Codes der Schnittstelle
81
+ (`PARTNER_FEHLER_CODES`) und 12 des Partner-Portals
82
+ (`PARTNER_PORTAL_FEHLER_CODES`), jeder mit Handlungssatz. Quelle ist
83
+ `docs/api/fehlercodes.json` im Backend. Ein Code, den nur eine Seite kennt,
84
+ ist für einen Aufrufer nicht von „gibt es nicht" zu unterscheiden; eine halbe
85
+ Liste ist deshalb schlimmer als keine.
86
+ - **`test: true` im Webhook-Umschlag.** `sendPartnerWebhookTest` nimmt jetzt ein
87
+ `event` und löst damit **jedes abonnierte Ereignis** mit glaubwürdiger
88
+ Nutzlast aus — eine Leitungsprobe beweist nichts über die Behandlung des
89
+ Ernstfalls. Damit eine Probe nicht für echt gehalten wird, trägt sie
90
+ `test: true` im Umschlag; `PartnerWebhookEvent.test` führt das Feld und ist
91
+ bei echten Ereignissen `false`. Die Zeile `if (ereignis.test) return;` gehört
92
+ an den Anfang jedes Handlers — ohne sie schreibt jemand seinem Kunden, die
93
+ Kasse sei fertig.
94
+ - **Betriebsdaten werden streng geprüft.** Das Backend weist ein unbekanntes
95
+ Feld ab, statt es stillschweigend zu verwerfen, und nennt den vollen Pfad
96
+ (`address.land`, `contacts.0.rolle`). Der Typ `Betrieb` führt deshalb genau
97
+ die Liste aus `partner-core.BETRIEB_FELDER`, und `unbekannteBetriebsfelder`
98
+ beantwortet dieselbe Frage zur Laufzeit — für Daten aus Datenbank oder
99
+ Formular, die nie durch die Typprüfung gelaufen sind. Abgewiesen wird hier
100
+ nichts: die Wahrheit bleibt der Server, sonst blockierte ein alter Client ein
101
+ neues Feld.
102
+ - **`createCustomerCashregister` ohne `name`, mit `signaturId`.** Kassennamen
103
+ vergibt Kasseneck (sie sind gleich der `cashregisterId`); ein gesendetes
104
+ `name` wäre ein `validation`-Fehler. Dafür bezieht sich **jede** Kasse auf
105
+ eine Signatur: ohne eine einzige `signature_missing`, bei mehreren
106
+ `signature_ambiguous` ohne `signaturId`. `requestCustomerSignature` nimmt
107
+ `weitere`, um eine zusätzliche Signatur zu beantragen (höchstens zehn,
108
+ `signature_limit`).
109
+ - **`getPartnerInfo().partner.darfZugangEinrichten`** und `zugang.einladen` mit
110
+ Vorgabe **false**: ein Zugang zum Kundenpanel legt einen Login auf eine fremde
111
+ Adresse an und schickt eine Mail dorthin. Fehlt das Feld, gilt NEIN — eine
112
+ Berechtigung, die nicht ausdrücklich dasteht, hat man nicht.
113
+
114
+ ### Geändert: `KasseneckApiError` trägt `code` und `details`
115
+
116
+ Bisher blieb vom `data` einer Fehlerantwort nichts übrig. Die Partner-API legt
117
+ ihre Entscheidung aber nicht in den Text, sondern in `data.code`
118
+ (`live_not_allowed`, `signature_not_ready`, `activation_failed` samt
119
+ `data.schritt`); ohne diese Felder müsste ein Aufrufer die deutsche `message`
120
+ nach Zeichenketten durchsuchen — die Art Kopplung, die beim nächsten
121
+ Formulierungsschliff still bricht.
122
+
123
+ Die Nutzlast wird dabei **gesiebt** (`fehlerDetails`) und nicht durchgereicht:
124
+ höchstens vier Ebenen tief, 50 Einträge je Ebene, Zeichenketten bis 300 Zeichen,
125
+ nur bezeichner-förmige Schlüssel — und kein Wert, der mit einem der gesendeten
126
+ Geheimnisse überlappt. Damit gilt dieselbe Zusage wie für `causeDigest`.
127
+
128
+ Additiv: der dritte Konstruktorparameter hat einen Vorgabewert, bestehende
129
+ Aufrufer und `catch`-Zweige bleiben unverändert.
130
+
131
+ ### Vertrag
132
+
133
+ `AUFRUFE` und damit `fixtures/oberflaeche.json` führen 18 Aufrufe mehr (die
134
+ Partner-Endpunkte). Der Flutter-Zwilling `kasseneck_api` und die
135
+ Hosting-Weiterleitungen in `kasseneck-web` lesen diese Liste — beide müssen
136
+ nachziehen, sonst laufen die Aufrufe in Produktion auf die HTML-Seite statt auf
137
+ die Function.
138
+
139
+ **Neuer Abschnitt `partner` in der Vertragsdatei.** Der Partner-Teil wäre sonst
140
+ als reine Namensliste über den Vertrag gegangen: die Aufrufe hätte er geführt,
141
+ die Fehlercodes, die Webhook-Ereignisse, die Betriebsfelder, die Umgebungen,
142
+ die Felder des Webhook-Umschlags und den Wiederholungsplan nicht — und genau
143
+ die pflegt der Zwilling von Hand nach. Ein Fehlercode, den nur eine Seite kennt,
144
+ hätte auf der anderen keinen Handlungssatz und wäre für einen Aufrufer nicht von
145
+ „gibt es nicht" zu unterscheiden. `scripts/oberflaeche.mjs` liest den
146
+ Partner-Namensraum genauso ab wie den Kassen-Namensraum; eine neu angelegte
147
+ Liste landet damit von selbst im Vertrag statt still zu fehlen.
148
+
149
+ **Und die Gegenrichtung: `test/fixtures/dart-partner.json`.** Der Vertrag in
150
+ `fixtures/` wird drüben geprüft — dieses Repo sähe eine Lücke erst im nächsten
151
+ Zwillingslauf im anderen Repo, an einem anderen Tag. Der eingecheckte Abzug der
152
+ Dart-Seite (dasselbe Muster wie `dart-enums.json`, samt `_quelle`) macht
153
+ `npm test` hier rot, sobald ein Fehlercode, ein Ereignis, ein Betriebsfeld, eine
154
+ Umgebung oder die Marke `test` nur in einer der beiden Sprachen ankommt.
package/README.md CHANGED
@@ -8,7 +8,8 @@ im Test gegengeprüft, damit die beiden Pakete nicht auseinanderdriften.
8
8
  Das Paket deckt ab: Belege ausstellen und stornieren, Belege und Kassen
9
9
  auflisten, Berichte herunterladen, Status bei FinanzOnline abfragen,
10
10
  Stripe-Zahllinks und Hobex-Cloud-Zahlungen, das Beleg-Layout und die
11
- ESC/POS-Erzeugung für den Bondrucker.
11
+ ESC/POS-Erzeugung für den Bondrucker — und unter `./partner` die
12
+ **Partner-API**: Betriebe anlegen und bis zur laufenden Kasse begleiten.
12
13
 
13
14
  **Es läuft im Browser und in Node.** ESM ist das Hauptformat, CommonJS liegt
14
15
  daneben; beides mit eigenen Typdeklarationen. Node ab 20.18 (`fetch` muss
@@ -167,6 +168,123 @@ sich nicht stornieren, ein voll stornierter Beleg nicht noch einmal. Am
167
168
  gelesenen Original liefert `remainingQuantities(receipt)` die Reste vorab (für
168
169
  den Storno-Dialog); die Wahrheit hat der Server.
169
170
 
171
+ ## Partner-API (`./partner`)
172
+
173
+ Für Softwarehäuser, die Kasseneck in ihr eigenes Produkt einbauen: Betriebe
174
+ anlegen, bis zur laufenden Kasse begleiten und danach in ihrem Namen Belege
175
+ signieren.
176
+
177
+ **Was die Endpunkte tun, steht in der Referenz** —
178
+ `docs/api/partner.md` (ausführlich) und `docs/api/partner.llms.txt` (kompakt,
179
+ für Werkzeuge und Sprachmodelle). Dieses README wiederholt sie nicht; hier
180
+ steht, wie man den Client benutzt.
181
+
182
+ Der Partner-Schlüssel (`pk_live_…`) gehört auf einen **Server**. Er kann
183
+ Betriebe anlegen und — mit dem Zusatz-Scope `credentials:read` — deren
184
+ Geheimnisse holen.
185
+
186
+ ```ts
187
+ import { createPartnerApi, istPartnerFehler } from '@kreiseck/kasseneck-api/partner';
188
+
189
+ const partner = createPartnerApi({ partnerKey: process.env.KASSENECK_PARTNER_KEY! });
190
+
191
+ const { customerId } = await partner.createPartnerCustomer({
192
+ appId: 'app_…',
193
+ idempotencyKey: kundennummer, // die eigene — schützt vor Doppelanlage
194
+ betrieb: { /* Stammdaten, siehe Referenz */ } as never,
195
+ // env: 'test' — auch mit einem LIVE-Schlüssel erlaubt: so probt man die
196
+ // ganze Kette, ohne sich einen zweiten Schlüssel zu holen. Umgekehrt nie.
197
+ });
198
+
199
+ await partner.sendPartnerCustomerFonLink(customerId);
200
+ // … auf das Ereignis customer.fon_verified warten …
201
+ await partner.requestCustomerSignature(customerId);
202
+ // … auf signature.ready warten …
203
+ await partner.createCustomerCashregister({ customerId }); // automatisch:true ist Vorgabe
204
+ ```
205
+
206
+ Die Reihenfolge ist hart, und jeder Schritt beschwert sich mit einem eigenen
207
+ Code, wenn ein vorheriger fehlt. Sie steht als Daten im Paket
208
+ (`PARTNER_ABLAUF`), und zu jedem Code gibt es einen Handlungssatz:
209
+
210
+ ```ts
211
+ try {
212
+ await partner.activateCashregister(customerId, cashregisterId);
213
+ } catch (fehler) {
214
+ if (istPartnerFehler(fehler, 'signature_not_ready')) {
215
+ // Die Signatur DIESER Kasse ist noch nicht bereit — auf signature.ready warten.
216
+ console.error(partner.fehlerRat('signature_not_ready'));
217
+ }
218
+ }
219
+ ```
220
+
221
+ ### Eine Probe ist keine Kasse
222
+
223
+ `sendPartnerWebhookTest(webhookId, 'cashregister.live')` löst genau das
224
+ Ereignis aus, das der eigene Handler behandeln soll — eine Leitungsprobe
225
+ beweist nichts über die Behandlung des Ernstfalls. Damit niemand eine Probe
226
+ für echt hält, trägt sie `test: true` im Umschlag:
227
+
228
+ ```ts
229
+ const geprueft = await parseWebhookEvent({ secret, signatureHeader, body, });
230
+ if (!geprueft.ok) return antwort(400);
231
+
232
+ if (geprueft.event.test) return antwort(200); // Probe: nichts weiter tun
233
+ ```
234
+
235
+ Ohne diese Zeile schreibt jemand seinem Kunden, die Kasse sei fertig.
236
+
237
+ ### Zugangsdaten sind Geheimnisse eines Dritten
238
+
239
+ `getCustomerCredentials` liefert den `api_key` des Betriebs und die Token
240
+ seiner Kassen. Wer sie hat, kann in seinem Namen Belege signieren — und ein
241
+ Beleg ist nach RKSV nicht zurücknehmbar. Sie kommen deshalb **nicht als
242
+ `string`**, sondern in einer Hülle, die sich nicht versehentlich ausgeben
243
+ lässt:
244
+
245
+ ```ts
246
+ const zugang = await partner.getCustomerCredentials(customerId);
247
+
248
+ console.log(zugang); // [apiKey «verborgen»] — kein Klartext
249
+ JSON.stringify(zugang); // ebenso
250
+ `${zugang.apiKey}`; // ebenso
251
+
252
+ speichereVerschluesselt(zugang.apiKey.reveal()); // der einzige Weg heraus
253
+ ```
254
+
255
+ Nur verschlüsselt speichern, nie protokollieren, nie in eine Mail oder einen
256
+ Fehlerbericht. Jeder Abruf wird mitgeschrieben und ist für den Betrieb
257
+ sichtbar.
258
+
259
+ ### Eingehende Webhooks prüfen
260
+
261
+ Das ist die Stelle, an der Integrationen am häufigsten scheitern — deshalb
262
+ liegt sie fertig im Paket. Vier Dinge müssen stimmen: der **rohe** Rumpf, das
263
+ Zeitfenster gegen Wiedereinspielung, ein zeitkonstanter Vergleich, und jede
264
+ Ausnahme als Ablehnung.
265
+
266
+ ```ts
267
+ import express from 'express';
268
+ import { parseWebhookEvent } from '@kreiseck/kasseneck-api/partner';
269
+
270
+ const app = express();
271
+
272
+ // express.raw VOR jedem JSON-Parser: signiert sind die Bytes, die ankommen.
273
+ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) => {
274
+ const ergebnis = await parseWebhookEvent({
275
+ secret: process.env.KASSENECK_WEBHOOK_SECRET!,
276
+ signatureHeader: req.header('X-Kasseneck-Signature'),
277
+ body: req.body, // Buffer — nicht req.body nach JSON.parse
278
+ });
279
+ if (!ergebnis.ok) return res.status(400).send(ergebnis.reason);
280
+
281
+ // Innerhalb von 10 s antworten, Arbeit danach. Zustellungen können sich
282
+ // wiederholen: auf event.id entdoppeln.
283
+ res.sendStatus(200);
284
+ await verarbeite(ergebnis.event);
285
+ });
286
+ ```
287
+
170
288
  ## Unterpfade
171
289
 
172
290
  | Unterpfad | Inhalt |
@@ -177,6 +295,7 @@ den Storno-Dialog); die Wahrheit hat der Server.
177
295
  | `…/payments` | Stripe-Zahllinks und Hobex-Cloud (beides HTTP-Endpunkte des Backends). |
178
296
  | `…/register` | Anmeldung der Browser-Kasse: Gerät koppeln und entkoppeln, Benutzer auflisten, per PIN anmelden, Sitzung erneuern und beenden. |
179
297
  | `…/kasse` | Kachel-Kasse: Kassen-Einstellungen (betriebsweit / je Gerät), Artikelgruppen und Artikel für Kacheln, Rabattverteilung je Steuersatz, Reichweiten der Kassen-Rechte |
298
+ | `…/partner` | Partner-API: Betriebe anlegen, FinanzOnline-Link, Signatur, Kassen, Zugangsdaten, Webhooks samt Signaturprüfung. **Gehört auf einen Server.** |
180
299
  | `…/react` | Dünner React-Adapter, der ein Beleg-Layout zeichnet. Braucht React. |
181
300
  | `…/fixtures/*` | Golden-Belege (JSON): Eingaben `belege/<name>.json`, zugesagte Zeilenausgabe `erwartet/<name>.lines.json`, `manifest.json` mit Prüfsummen — dieselben Dateien prüfen Backend, Browser-Kasse und Flutter-Paket. |
182
301
 
@@ -199,12 +318,33 @@ PDF-Erzeugung.
199
318
  npm test # Testsuite in drei Zeitzonen (Wien, UTC, Kiritimati)
200
319
  npm run build # ESM- und CJS-Bau nach dist/, inkl. Prüfung der exports
201
320
  npm run check:consumer # baut den Tarball und übersetzt zwei Verbraucher (CJS/ESM)
321
+ npm run check:erreichbar # fragt die öffentliche Adresse: antwortet dort zu jedem Aufruf eine Function?
202
322
  ```
203
323
 
204
324
  Die drei Zeitzonen sind kein Übereifer: Zeitfehler sind auf einer Wiener
205
325
  Maschine zufällig richtig. Belegzeiten werden konsequent als **Wiener
206
326
  Wanduhrzeit** gedeutet (`parseServerTimeStamp`), nie über `new Date(text)`.
207
327
 
328
+ ### `check:erreichbar` — spricht als einzige mit `api.kasseneck.at`
329
+
330
+ Testsuite und `check:consumer` laufen gegen Attrappen bzw. gegen den Tarball;
331
+ keine von beiden setzt je einen Aufruf ab. Fehlt einem Aufruf die
332
+ Hosting-Weiterleitung, liefert die veröffentlichte Adresse die
333
+ HTML-Auffangseite statt der Function — und das sieht keine Attrappe.
334
+
335
+ Die Prüfung braucht **keine Zugangsdaten**. Ein Aufruf ohne Anmeldung
336
+ antwortet, wenn dort eine Function steht, mit
337
+ `{"status":"error","message":"Ungültiger Request: Authorization key erwartet."}`.
338
+ Genau das ist der Beweis: Der Aufruf wurde angenommen und die Anmeldung
339
+ geprüft. Eine HTML-Seite oder ein 404 ist der Beweis, dass dort keine Function
340
+ steht. Deshalb prüft das Skript auf ein `status`-Feld und nicht auf Erfolg.
341
+
342
+ Bewusst außerhalb von `npm test`: Sie braucht Netz. Ist keines da, sagt sie es
343
+ und endet mit 0. Aufrufe, die unter `/v1` absichtlich keine Weiterleitung
344
+ haben — der Kassen-Weg über `kasse.kasseneck.at/api`, die Aufrufe mit
345
+ Firebase-ID-Token — stehen mit Grund in `scripts/erreichbarkeit-ausnahmen.json`.
346
+ Wird eine Ausnahme erreichbar, schlägt die Prüfung an: Sonst sänke die Zahl nie.
347
+
208
348
  ## Vertragsdateien für die Zwillinge
209
349
 
210
350
  Dieses Paket ist die Quelle für das Dart-Paket `kasseneck_api` und den
@@ -214,7 +354,7 @@ im Tarball mit und sagen in Maschinenform, worauf sich alle drei geeinigt haben:
214
354
  | Datei | Inhalt |
215
355
  |---|---|
216
356
  | `kasse-settings-standard.json` | Feldnamen und Standardwerte der Kassen-Einstellungen |
217
- | `oberflaeche.json` | Aufrufnamen, Enum-Werte, Rechte-Schlüssel, Tasten-Aktionen |
357
+ | `oberflaeche.json` | Aufrufnamen, Enum-Werte, Rechte-Schlüssel, Tasten-Aktionen, Partner-Listen |
218
358
 
219
359
  Beide werden erzeugt (`npm run fixtures:kasse`, `npm run fixtures:oberflaeche`)
220
360
  und nie von Hand geändert; die CI prüft nach jedem Lauf, dass sie zum Code passen.
@@ -222,6 +362,22 @@ und nie von Hand geändert; die CI prüft nach jedem Lauf, dass sie zum Code pas
222
362
  `oberflaeche.json` trägt die Paketversion. **Nach jedem `npm version` müssen deshalb beide
223
363
  Dateien neu erzeugt und mitcommittet werden**, sonst wird die CI rot.
224
364
 
365
+ ### Und die Gegenrichtung
366
+
367
+ Der Vertrag in `fixtures/` wird **drüben** geprüft: das Dart-Repo zieht ihn und
368
+ hält seine Listen dagegen. Eine Lücke fiele hier deshalb erst im nächsten
369
+ Zwillingslauf im anderen Repo auf — an einem anderen Tag. Dagegen stehen zwei
370
+ von Hand gepflegte Abzüge der Dart-Seite unter `test/fixtures/`, jeder mit
371
+ `_quelle`:
372
+
373
+ | Datei | prüft |
374
+ |---|---|
375
+ | `dart-enums.json` | Belegtyp, Steuersatz, Zahlungsart, Kartenanbieter, Gutschein, Stripe-Modus |
376
+ | `dart-partner.json` | Umgebungen, Fehlercodes (API und Portal), Webhook-Ereignisse, Felder des Webhook-Umschlags samt der Marke `test`, Betriebsfelder, Wiederholungsplan |
377
+
378
+ Sie machen `npm test` rot, sobald ein Wert nur in einer der beiden Sprachen
379
+ ankommt.
380
+
225
381
  ## Lizenz
226
382
 
227
383
  Apache-2.0 — siehe `LICENSE` und `NOTICE`.
@@ -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 ["activateCashregister", "cancelReceipt", "createCustomerCashregister", "createPartnerCustomer", "checkPartnerCustomerEmail", "createPartnerWebhook", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "deletePartnerWebhook", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getCustomerCredentials", "getCustomerSignatureStatus", "getFirstReceiptDate", "getKasseSettings", "getPartnerCustomer", "getPartnerInfo", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "listCustomerCashregisters", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyTipRecipients", "listPartnerCustomers", "listPartnerWebhookDeliveries", "listPartnerWebhooks", "listRegisterUsersForDevice", "pairRegisterDevice", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "requestCustomerSignature", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice", "rotatePartnerWebhookSecret", "updatePartnerWebhook"];
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>;
@@ -2,33 +2,53 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AUFRUFE = void 0;
4
4
  exports.AUFRUFE = [
5
+ 'activateCashregister',
5
6
  'cancelReceipt',
7
+ 'createCustomerCashregister',
8
+ 'createPartnerCustomer',
9
+ 'checkPartnerCustomerEmail',
10
+ 'createPartnerWebhook',
6
11
  'createPaymentLinkStripe',
7
12
  'createPrintJob',
8
13
  'createReceipt',
14
+ 'deletePartnerWebhook',
9
15
  'downloadDailyReport',
10
16
  'downloadReport',
11
17
  'endRegisterSession',
12
18
  'financeWebService',
13
19
  'generateFullReceiptId',
20
+ 'getCustomerCredentials',
21
+ 'getCustomerSignatureStatus',
14
22
  'getFirstReceiptDate',
15
23
  'getKasseSettings',
24
+ 'getPartnerCustomer',
25
+ 'getPartnerInfo',
16
26
  'getPrintJob',
17
27
  'getReceipt',
18
28
  'hobexPayApi',
19
29
  'hobexRefundApi',
30
+ 'listCustomerCashregisters',
20
31
  'listMyArticleGroups',
21
32
  'listMyArticles',
22
33
  'listMyCashregisters',
23
34
  'listMyPrinters',
24
35
  'listMyReceipts',
36
+ 'listMyTipRecipients',
37
+ 'listPartnerCustomers',
38
+ 'listPartnerWebhookDeliveries',
39
+ 'listPartnerWebhooks',
25
40
  'listRegisterUsersForDevice',
26
41
  'pairRegisterDevice',
27
42
  'registerPinLogin',
28
43
  'registerUserLogin',
29
44
  'renewRegisterSession',
45
+ 'requestCustomerSignature',
46
+ 'sendPartnerCustomerFonLink',
47
+ 'sendPartnerWebhookTest',
30
48
  'setMyKasseSettings',
31
49
  'setMyRegisterDeviceSettings',
32
50
  'stripeCaptureIntent',
33
51
  'unpairRegisterDevice',
52
+ 'rotatePartnerWebhookSecret',
53
+ 'updatePartnerWebhook',
34
54
  ];
@@ -69,6 +69,24 @@ export interface CauseDigest {
69
69
  * gesendeten Werte nicht bekannt sind, wird gar nicht verdichtet.
70
70
  */
71
71
  export declare function causeDigest(ursache: unknown, geheimnisse: readonly string[]): CauseDigest;
72
+ /**
73
+ * Siebt das `data` einer Fehlerantwort zu einer Form, die man gefahrlos an
74
+ * einem Fehler mitfuehren kann.
75
+ *
76
+ * **Warum ueberhaupt:** die Partner-API legt ihre Entscheidung nicht in den
77
+ * Text, sondern in `data.code` (`vertrag_offen`, `signature_not_ready`,
78
+ * `activation_failed` samt `data.schritt`). Ohne diese Felder muesste ein
79
+ * Aufrufer die deutsche `message` nach Zeichenketten durchsuchen — genau die
80
+ * Kopplung, die beim naechsten Formulierungsschliff still bricht.
81
+ *
82
+ * **Warum gesiebt und nicht durchgereicht:** siehe Modulkommentar. Der Rumpf
83
+ * kommt ueber fremde Proxys, und ein Fehler landet in Protokollen. Deshalb
84
+ * ueberlebt nur, was flach, klein und bezeichner-foermig benannt ist — und
85
+ * kein Wert, der mit einem der gesendeten Geheimnisse ueberlappt. Damit gilt
86
+ * hier dieselbe Zusage wie fuer [causeDigest], und `geheimnisse` hat aus
87
+ * demselben Grund **keinen** Vorgabewert.
88
+ */
89
+ export declare function fehlerDetails(daten: unknown, geheimnisse: readonly string[]): Record<string, unknown>;
72
90
  /** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
73
91
  export declare class KasseneckApiError extends Error {
74
92
  readonly name = "KasseneckApiError";
@@ -76,7 +94,19 @@ export declare class KasseneckApiError extends Error {
76
94
  readonly functionName: string;
77
95
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
78
96
  readonly serverMessage: string;
79
- constructor(functionName: string, serverMessage: string);
97
+ /**
98
+ * Maschinenlesbarer Fehlercode aus `data.code`, sofern die Antwort einen
99
+ * fuehrt (`vertrag_offen`, `rate_limited`, …). Die aelteren Endpunkte des
100
+ * Backends antworten ohne Code — dann `undefined`.
101
+ */
102
+ readonly code: string | undefined;
103
+ /**
104
+ * Die uebrige Fehler-Nutzlast, gesiebt (siehe [fehlerDetails]): `schritt`,
105
+ * `rc`, `retryAfterSec`, `errors[]` und was der jeweilige Endpunkt sonst
106
+ * beilegt. Immer ein Objekt, notfalls ein leeres.
107
+ */
108
+ readonly details: Record<string, unknown>;
109
+ constructor(functionName: string, serverMessage: string, details?: Record<string, unknown>);
80
110
  }
81
111
  /** Warum die Antwort keine verwertbare Huelle war. */
82
112
  export type HttpFailureReason = 'server-error' | 'empty-body' | 'not-json' | 'missing-status';
@@ -58,6 +58,7 @@
58
58
  Object.defineProperty(exports, "__esModule", { value: true });
59
59
  exports.KasseneckValidationError = exports.KasseneckAuthError = exports.KasseneckNetworkError = exports.KasseneckHttpError = exports.KasseneckApiError = void 0;
60
60
  exports.causeDigest = causeDigest;
61
+ exports.fehlerDetails = fehlerDetails;
61
62
  exports.isKasseneckApiError = isKasseneckApiError;
62
63
  exports.isKasseneckHttpError = isKasseneckHttpError;
63
64
  exports.isKasseneckNetworkError = isKasseneckNetworkError;
@@ -94,6 +95,84 @@ function unbedenklich(wert, geheimnisse) {
94
95
  }
95
96
  return wert;
96
97
  }
98
+ /**
99
+ * Grenzen fuer die gesiebte Fehler-Nutzlast (siehe [fehlerDetails]). Sie stehen
100
+ * als benannte Konstanten hier, weil ein Test sie namentlich prueft — eine
101
+ * spaeter heraufgesetzte Grenze soll auffallen und nicht als Zahl im Code
102
+ * untergehen.
103
+ */
104
+ const DETAIL_TIEFE = 4;
105
+ const DETAIL_EINTRAEGE = 50;
106
+ const DETAIL_TEXT_MAX = 300;
107
+ /** Schluessel eines Detail-Objekts: bezeichner-foermig, sonst faellt der Eintrag weg. */
108
+ const DETAIL_SCHLUESSEL = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
109
+ /**
110
+ * Siebt das `data` einer Fehlerantwort zu einer Form, die man gefahrlos an
111
+ * einem Fehler mitfuehren kann.
112
+ *
113
+ * **Warum ueberhaupt:** die Partner-API legt ihre Entscheidung nicht in den
114
+ * Text, sondern in `data.code` (`vertrag_offen`, `signature_not_ready`,
115
+ * `activation_failed` samt `data.schritt`). Ohne diese Felder muesste ein
116
+ * Aufrufer die deutsche `message` nach Zeichenketten durchsuchen — genau die
117
+ * Kopplung, die beim naechsten Formulierungsschliff still bricht.
118
+ *
119
+ * **Warum gesiebt und nicht durchgereicht:** siehe Modulkommentar. Der Rumpf
120
+ * kommt ueber fremde Proxys, und ein Fehler landet in Protokollen. Deshalb
121
+ * ueberlebt nur, was flach, klein und bezeichner-foermig benannt ist — und
122
+ * kein Wert, der mit einem der gesendeten Geheimnisse ueberlappt. Damit gilt
123
+ * hier dieselbe Zusage wie fuer [causeDigest], und `geheimnisse` hat aus
124
+ * demselben Grund **keinen** Vorgabewert.
125
+ */
126
+ function fehlerDetails(daten, geheimnisse) {
127
+ const gesiebt = sieben(daten, geheimnisse, 0);
128
+ return gesiebt !== null && typeof gesiebt === 'object' && !Array.isArray(gesiebt)
129
+ ? gesiebt
130
+ : {};
131
+ }
132
+ function sieben(wert, geheimnisse, tiefe) {
133
+ if (wert === null || typeof wert === 'boolean')
134
+ return wert;
135
+ // Nur endliche Zahlen: NaN und Infinity ueberstehen JSON.stringify nicht und
136
+ // staenden in einem Fehlerbericht als `null` ohne jede Aussage.
137
+ if (typeof wert === 'number')
138
+ return Number.isFinite(wert) ? wert : undefined;
139
+ if (typeof wert === 'string') {
140
+ if (wert.length > DETAIL_TEXT_MAX)
141
+ return undefined;
142
+ for (const geheim of geheimnisse) {
143
+ if (geheim && (geheim.includes(wert) || wert.includes(geheim)))
144
+ return undefined;
145
+ }
146
+ return wert;
147
+ }
148
+ if (tiefe >= DETAIL_TIEFE)
149
+ return undefined;
150
+ if (Array.isArray(wert)) {
151
+ const liste = [];
152
+ for (const eintrag of wert.slice(0, DETAIL_EINTRAEGE)) {
153
+ const s = sieben(eintrag, geheimnisse, tiefe + 1);
154
+ if (s !== undefined)
155
+ liste.push(s);
156
+ }
157
+ return liste;
158
+ }
159
+ if (typeof wert !== 'object')
160
+ return undefined;
161
+ const raus = {};
162
+ let gezaehlt = 0;
163
+ for (const [schluessel, eintrag] of Object.entries(wert)) {
164
+ if (gezaehlt >= DETAIL_EINTRAEGE)
165
+ break;
166
+ if (!DETAIL_SCHLUESSEL.test(schluessel))
167
+ continue;
168
+ const s = sieben(eintrag, geheimnisse, tiefe + 1);
169
+ if (s === undefined)
170
+ continue;
171
+ raus[schluessel] = s;
172
+ gezaehlt += 1;
173
+ }
174
+ return raus;
175
+ }
97
176
  /** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
98
177
  class KasseneckApiError extends Error {
99
178
  name = 'KasseneckApiError';
@@ -101,10 +180,28 @@ class KasseneckApiError extends Error {
101
180
  functionName;
102
181
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
103
182
  serverMessage;
104
- constructor(functionName, serverMessage) {
183
+ /**
184
+ * Maschinenlesbarer Fehlercode aus `data.code`, sofern die Antwort einen
185
+ * fuehrt (`vertrag_offen`, `rate_limited`, …). Die aelteren Endpunkte des
186
+ * Backends antworten ohne Code — dann `undefined`.
187
+ */
188
+ code;
189
+ /**
190
+ * Die uebrige Fehler-Nutzlast, gesiebt (siehe [fehlerDetails]): `schritt`,
191
+ * `rc`, `retryAfterSec`, `errors[]` und was der jeweilige Endpunkt sonst
192
+ * beilegt. Immer ein Objekt, notfalls ein leeres.
193
+ */
194
+ details;
195
+ constructor(functionName, serverMessage, details = {}) {
105
196
  super(`${functionName} fehlgeschlagen: ${serverMessage}`);
106
197
  this.functionName = functionName;
107
198
  this.serverMessage = serverMessage;
199
+ this.details = details;
200
+ // Der Code steht in `details` und zusaetzlich als eigenes Feld: das eine
201
+ // ist die Nutzlast, das andere die Frage, die ein Aufrufer wirklich
202
+ // stellt. Nur Bezeichner gelten — ein Freitext waere kein Code.
203
+ const code = details['code'];
204
+ this.code = typeof code === 'string' && BEZEICHNER.test(code) ? code : undefined;
108
205
  }
109
206
  }
110
207
  exports.KasseneckApiError = KasseneckApiError;
@@ -134,7 +134,7 @@ function createCore(options) {
134
134
  if (antwort.status !== 200) {
135
135
  throw new errors_js_1.KasseneckHttpError(fehlerName, antwort.status, inhaltstyp, 'server-error');
136
136
  }
137
- return auswerten(koerper, fehlerName, antwort.status, inhaltstyp);
137
+ return auswerten(koerper, fehlerName, antwort.status, inhaltstyp, geheimnisse);
138
138
  }
139
139
  finally {
140
140
  // Ohne Abraeumen haelt der Wecker den Node-Prozess bis zum Zeitlimit wach.
@@ -159,7 +159,7 @@ const alsBytes = async (antwort, functionName) => {
159
159
  return new Uint8Array(await antwort.arrayBuffer());
160
160
  };
161
161
  /** Auswertung des JSON-Wegs: Huelle aufloesen, Nutzlast zurueckgeben. */
162
- function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
162
+ function jsonAuswerten(text, functionName, statusCode, inhaltstyp, geheimnisse) {
163
163
  if (!text.trim()) {
164
164
  throw new errors_js_1.KasseneckHttpError(functionName, statusCode, inhaltstyp, 'empty-body');
165
165
  }
@@ -181,7 +181,7 @@ function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
181
181
  }
182
182
  // Alles, was nicht ausdruecklich Erfolg ist, gilt als fachlicher Fehler —
183
183
  // ein unbekannter Statuswert darf nie stillschweigend als Erfolg durchgehen.
184
- throw fachfehler(functionName, huelle.message);
184
+ throw fachfehler(functionName, huelle.message, huelle.data, geheimnisse);
185
185
  }
186
186
  /**
187
187
  * Auswertung des Binaerwegs. Der Kern der Zusage: **ein Aufrufer bekommt nie
@@ -196,7 +196,7 @@ function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
196
196
  * das Backend hier erzeugt (pdf-lib). Alles, was nicht so anfaengt, ist kein
197
197
  * Bericht — und wird dann daraufhin angesehen, ob es die Fehlerhuelle ist.
198
198
  */
199
- function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp) {
199
+ function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp, geheimnisse) {
200
200
  if (bytes.length === 0) {
201
201
  throw new errors_js_1.KasseneckHttpError(functionName, statusCode, inhaltstyp, 'empty-body');
202
202
  }
@@ -227,7 +227,7 @@ function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp) {
227
227
  }
228
228
  // Derselbe fachliche Fehler wie auf dem JSON-Weg — fuer den Aufrufer macht
229
229
  // es keinen Unterschied, ob er ein PDF oder eine Nutzlast erwartet hat.
230
- throw fachfehler(functionName, huelle.message);
230
+ throw fachfehler(functionName, huelle.message, huelle.data, geheimnisse);
231
231
  }
232
232
  /** `%PDF` am Anfang — die Kennung jeder PDF-Datei. */
233
233
  function istPdf(bytes) {
@@ -240,9 +240,9 @@ function alsHuelle(wert) {
240
240
  }
241
241
  return wert;
242
242
  }
243
- function fachfehler(functionName, message) {
243
+ function fachfehler(functionName, message, daten, geheimnisse) {
244
244
  const meldung = typeof message === 'string' && message.trim() ? message : 'Unbekannter Fehler';
245
- return new errors_js_1.KasseneckApiError(functionName, meldung);
245
+ return new errors_js_1.KasseneckApiError(functionName, meldung, (0, errors_js_1.fehlerDetails)(daten, geheimnisse));
246
246
  }
247
247
  /**
248
248
  * Nutzlast aus Auth- und Aufruferparametern. Auth-Parameter bilden die
@@ -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';