@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
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Webhooks: Endpunkte verwalten und eingehende Zustellungen auswerten.
3
+ *
4
+ * Die Signaturpruefung liegt daneben in `webhook-signatur.ts` — sie braucht
5
+ * weder Transport noch Schluessel und laeuft in jedem Empfaenger, auch in
6
+ * einem, der sonst nichts von diesem Paket benutzt.
7
+ */
8
+ import type { InternerTransport } from '../client/aufrufe.js';
9
+ import { type VerifyWebhookOptions, type WebhookVerifyReason } from './webhook-signatur.js';
10
+ /**
11
+ * Alle Ereignisse, die ein Webhook abonnieren **und proben** kann. Ein
12
+ * Endpunkt bekommt ausschliesslich die, die in seiner `events`-Liste stehen.
13
+ *
14
+ * Kasseneck fuehrt daneben interne Ereignisse (etwa den Abschluss eines
15
+ * Auftragsverarbeitungsvertrags). Sie stehen hier bewusst nicht: sie lassen
16
+ * sich weder abonnieren noch mit [sendPartnerWebhookTest] ausloesen, und ein
17
+ * Name in dieser Liste, den niemand bestellen kann, waere ein Versprechen ohne
18
+ * Deckung.
19
+ */
20
+ export declare const PARTNER_WEBHOOK_EVENTS: readonly ["customer.created", "customer.updated", "customer.status_changed", "customer.fon_verified", "customer.live_enabled", "signature.requested", "signature.ready", "signature.failed", "cashregister.created", "cashregister.live", "cashregister.failed", "app.version.accepted", "app.version.rejected", "webhook.test"];
21
+ export type PartnerWebhookEventType = typeof PARTNER_WEBHOOK_EVENTS[number];
22
+ export declare function istPartnerWebhookEvent(wert: unknown): wert is PartnerWebhookEventType;
23
+ /**
24
+ * Die Felder des Umschlags, so wie er auf der Leitung liegt.
25
+ *
26
+ * Als Liste, damit der Zwilling sie nachhaelt: `test` kam spaeter dazu, und
27
+ * genau ein solches Feld verschwindet sonst auf einer Seite, ohne dass etwas
28
+ * rot wird.
29
+ */
30
+ export declare const WEBHOOK_UMSCHLAG_FELDER: readonly ["id", "type", "createdAt", "partnerId", "test", "data"];
31
+ /**
32
+ * Die Huelle jeder Zustellung. `type` bleibt bewusst offen fuer unbekannte
33
+ * Werte (`(string & {})`): ein spaeter ergaenztes Ereignis soll einen
34
+ * Empfaenger nicht zum Absturz bringen, sondern in seinem `default`-Zweig
35
+ * landen.
36
+ */
37
+ export interface PartnerWebhookEvent<T = Record<string, unknown>> {
38
+ /** `evt_…` — die Kennung, auf die **entdoppelt** wird. */
39
+ id: string;
40
+ type: PartnerWebhookEventType | (string & {});
41
+ createdAt: number;
42
+ partnerId: string;
43
+ /**
44
+ * **Probe oder Ernstfall.** Eine mit [sendPartnerWebhookTest] ausgeloeste
45
+ * Zustellung traegt `test: true` im Umschlag; ein echtes Ereignis fuehrt das
46
+ * Feld gar nicht, hier steht dann `false`.
47
+ *
48
+ * Diese Zeile gehoert an den Anfang jedes Handlers:
49
+ *
50
+ * ```ts
51
+ * if (ereignis.test) return;
52
+ * ```
53
+ *
54
+ * Ohne sie haelt jemand eine Probe fuer echt und schreibt seinem Kunden, die
55
+ * Kasse sei fertig. Eine Probe traegt eine erkennbar erfundene Nutzlast — nur
56
+ * sieht man das erst, wenn man hinsieht.
57
+ */
58
+ test: boolean;
59
+ data: T;
60
+ }
61
+ export type WebhookEventResult = {
62
+ ok: true;
63
+ event: PartnerWebhookEvent;
64
+ timestampSec: number;
65
+ } | {
66
+ ok: false;
67
+ reason: WebhookVerifyReason | 'body-not-json' | 'body-not-event';
68
+ };
69
+ /**
70
+ * Prueft die Signatur **und** liest das Ereignis — in dieser Reihenfolge. Wer
71
+ * zuerst parst und dann prueft, hat den fremden Rumpf schon durch seinen Code
72
+ * laufen lassen.
73
+ *
74
+ * **Entdoppeln nicht vergessen:** dieselbe Zustellung kann mehrfach ankommen
75
+ * (Wiederholung nach einer Antwort, die unterwegs verloren ging). Massgeblich
76
+ * ist `event.id`, nicht der Kopf `X-Kasseneck-Delivery` — der ist bei
77
+ * Wiederholungen derselbe, aber er beantwortet die Frage "habe ich dieses
78
+ * Ereignis schon verarbeitet?" nur fuer genau diesen Endpunkt.
79
+ *
80
+ * Antworte innerhalb von 10 Sekunden mit 2xx und erledige die Arbeit danach.
81
+ * Eine ausbleibende Antwort wird bis zu fuenfmal wiederholt (1 min, 5 min,
82
+ * 30 min, 2 h, 12 h) und gilt dann als fehlgeschlagen.
83
+ */
84
+ export declare function parseWebhookEvent(optionen: VerifyWebhookOptions): Promise<WebhookEventResult>;
85
+ export interface PartnerWebhook {
86
+ webhookId: string;
87
+ url: string;
88
+ events: string[];
89
+ active: boolean;
90
+ description: string | null;
91
+ createdAt: number | null;
92
+ lastDelivery: number | null;
93
+ /** Fehlversuche in Folge — steigt der Wert, stimmt beim Empfaenger etwas nicht. */
94
+ consecutiveFailures: number;
95
+ }
96
+ export interface CreateWebhookOptions {
97
+ /** Vollstaendige `https://`-Adresse. */
98
+ url: string;
99
+ /** Mindestens eines; unbekannte Namen lehnt das Backend ab. */
100
+ events: (PartnerWebhookEventType | (string & {}))[];
101
+ /** Hoechstens 120 Zeichen. */
102
+ description?: string;
103
+ active?: boolean;
104
+ }
105
+ export interface CreateWebhookResult {
106
+ webhook: PartnerWebhook;
107
+ /**
108
+ * Das Secret fuer die Signaturpruefung. **Es kommt genau einmal** — beim
109
+ * Anlegen. Danach gibt das Backend es nie wieder aus; wer es verliert, legt
110
+ * einen neuen Endpunkt an.
111
+ *
112
+ * Bewusst ein `string` und kein [KasseneckSecret]: es gehoert dem Partner
113
+ * selbst und nicht einem fremden Betrieb, und es muss unveraendert in die
114
+ * eigene Konfiguration wandern. Verschluesselt speichern gilt trotzdem.
115
+ */
116
+ secret: string;
117
+ }
118
+ export interface WebhookPatch {
119
+ url?: string;
120
+ events?: (PartnerWebhookEventType | (string & {}))[];
121
+ description?: string;
122
+ active?: boolean;
123
+ }
124
+ export interface WebhookListe {
125
+ webhooks: PartnerWebhook[];
126
+ /** Der Katalog: Ereignisname und deutscher Text, so wie das Panel ihn zeigt. */
127
+ events: {
128
+ key: string;
129
+ text: string;
130
+ }[];
131
+ }
132
+ export interface WebhookZustellung {
133
+ deliveryId: string;
134
+ webhookId: string;
135
+ event: string;
136
+ eventId: string;
137
+ /** `offen`, `zugestellt` oder `fehlgeschlagen`. */
138
+ status: string;
139
+ attempts: number;
140
+ letzterVersuchAt: number | null;
141
+ naechsterVersuchAt: number | null;
142
+ statusCode: number | null;
143
+ /** Auszug der Antwort des Empfaengers, hoechstens 500 Zeichen. */
144
+ response: string | null;
145
+ createdAt: number | null;
146
+ }
147
+ /**
148
+ * Legt einen Webhook-Endpunkt an. Hoechstens [WEBHOOK_LIMIT] je Partner
149
+ * (`webhook_limit`).
150
+ *
151
+ * **Das Secret in der Antwort ist der einzige Weg dazu.** Es sofort dorthin
152
+ * schreiben, wo der Empfaenger es liest — nicht in ein Protokoll.
153
+ */
154
+ export declare function createPartnerWebhook(rufen: InternerTransport, optionen: CreateWebhookOptions): Promise<CreateWebhookResult>;
155
+ /**
156
+ * Ein neues Secret fuer denselben Endpunkt.
157
+ *
158
+ * Der Webhook behaelt seine `webhookId` und seine Ereignisse — nur das
159
+ * Geheimnis wechselt. Wer den Endpunkt stattdessen loescht und neu anlegt,
160
+ * verliert seine Zustellungshistorie und muss die Ereignisse neu ankreuzen.
161
+ *
162
+ * Das alte Secret gilt ab der Antwort NICHT mehr: die naechste Zustellung ist
163
+ * schon mit dem neuen signiert. Erst speichern, dann weiterarbeiten.
164
+ */
165
+ export declare function rotatePartnerWebhookSecret(rufen: InternerTransport, webhookId: string): Promise<CreateWebhookResult>;
166
+ /** Die Webhook-Endpunkte dieses Partners samt Ereignis-Katalog. */
167
+ export declare function listPartnerWebhooks(rufen: InternerTransport): Promise<WebhookListe>;
168
+ /**
169
+ * Aendert einen Endpunkt. Nur die genannten Felder — ein leeres `patch` lehnt
170
+ * das Backend ab (`validation`, Feld `patch`), statt stillschweigend nichts zu
171
+ * tun.
172
+ */
173
+ export declare function updatePartnerWebhook(rufen: InternerTransport, webhookId: string, patch: WebhookPatch): Promise<PartnerWebhook>;
174
+ /** Loescht einen Endpunkt. Danach kommt dort nichts mehr an. */
175
+ export declare function deletePartnerWebhook(rufen: InternerTransport, webhookId: string): Promise<string>;
176
+ export interface WebhookTestResult {
177
+ eventId: string;
178
+ /** Welches Ereignis geprobt wurde — ohne Angabe `webhook.test`. */
179
+ ereignis: string;
180
+ deliveries: unknown[];
181
+ }
182
+ /**
183
+ * Schickt eine Probe an genau diesen Endpunkt.
184
+ *
185
+ * Ohne `event` kommt `webhook.test` — der Nachweis, dass die Leitung steht und
186
+ * die eigene Signaturpruefung gegen echte Bytes laeuft. **Mit `event` kommt
187
+ * genau das Ereignis, das der Empfaenger behandeln soll**, mit einer
188
+ * glaubwuerdigen Nutzlast: wer auf `signature.ready` hin seinen Kunden
189
+ * benachrichtigt, probt das einmal, statt auf eine echte Karte zu warten. Eine
190
+ * Leitungsprobe beweist nichts ueber die Behandlung des Ernstfalls.
191
+ *
192
+ * Der Endpunkt muss das Ereignis abonnieren (`event_not_subscribed`) und aktiv
193
+ * sein (`webhook_inactive`); ein unbekannter Name ist ein `validation`-Fehler
194
+ * auf dem Feld `event`.
195
+ *
196
+ * **Jede Probe traegt `test: true` im Umschlag** ([PartnerWebhookEvent.test]).
197
+ *
198
+ * Der Backend-Endpunkt heisst `sendPartnerWebhookTest`; dieser Client behaelt
199
+ * den Namen bei, damit ein Leser der Doku und ein Leser des Codes dasselbe
200
+ * suchen.
201
+ */
202
+ export declare function sendPartnerWebhookTest(rufen: InternerTransport, webhookId: string, event?: PartnerWebhookEventType | (string & {})): Promise<WebhookTestResult>;
203
+ /**
204
+ * Die letzten Zustellversuche — mit `webhookId` fuer einen Endpunkt, ohne ihn
205
+ * fuer alle. Die Stelle, an der sich „mein Server bekommt nichts" klaeren
206
+ * laesst, ohne bei Kasseneck nachzufragen.
207
+ */
208
+ export declare function listPartnerWebhookDeliveries(rufen: InternerTransport, optionen?: {
209
+ webhookId?: string;
210
+ limit?: number;
211
+ }): Promise<WebhookZustellung[]>;
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * Webhooks: Endpunkte verwalten und eingehende Zustellungen auswerten.
4
+ *
5
+ * Die Signaturpruefung liegt daneben in `webhook-signatur.ts` — sie braucht
6
+ * weder Transport noch Schluessel und laeuft in jedem Empfaenger, auch in
7
+ * einem, der sonst nichts von diesem Paket benutzt.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.WEBHOOK_UMSCHLAG_FELDER = exports.PARTNER_WEBHOOK_EVENTS = void 0;
11
+ exports.istPartnerWebhookEvent = istPartnerWebhookEvent;
12
+ exports.parseWebhookEvent = parseWebhookEvent;
13
+ exports.createPartnerWebhook = createPartnerWebhook;
14
+ exports.rotatePartnerWebhookSecret = rotatePartnerWebhookSecret;
15
+ exports.listPartnerWebhooks = listPartnerWebhooks;
16
+ exports.updatePartnerWebhook = updatePartnerWebhook;
17
+ exports.deletePartnerWebhook = deletePartnerWebhook;
18
+ exports.sendPartnerWebhookTest = sendPartnerWebhookTest;
19
+ exports.listPartnerWebhookDeliveries = listPartnerWebhookDeliveries;
20
+ const errors_js_1 = require("../client/errors.js");
21
+ const webhook_signatur_js_1 = require("./webhook-signatur.js");
22
+ // ---------------------------------------------------------------------------
23
+ // Ereignisse
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Alle Ereignisse, die ein Webhook abonnieren **und proben** kann. Ein
27
+ * Endpunkt bekommt ausschliesslich die, die in seiner `events`-Liste stehen.
28
+ *
29
+ * Kasseneck fuehrt daneben interne Ereignisse (etwa den Abschluss eines
30
+ * Auftragsverarbeitungsvertrags). Sie stehen hier bewusst nicht: sie lassen
31
+ * sich weder abonnieren noch mit [sendPartnerWebhookTest] ausloesen, und ein
32
+ * Name in dieser Liste, den niemand bestellen kann, waere ein Versprechen ohne
33
+ * Deckung.
34
+ */
35
+ exports.PARTNER_WEBHOOK_EVENTS = [
36
+ 'customer.created',
37
+ 'customer.updated',
38
+ 'customer.status_changed',
39
+ 'customer.fon_verified',
40
+ 'customer.live_enabled',
41
+ 'signature.requested',
42
+ 'signature.ready',
43
+ 'signature.failed',
44
+ 'cashregister.created',
45
+ 'cashregister.live',
46
+ 'cashregister.failed',
47
+ 'app.version.accepted',
48
+ 'app.version.rejected',
49
+ 'webhook.test',
50
+ ];
51
+ function istPartnerWebhookEvent(wert) {
52
+ return typeof wert === 'string' && exports.PARTNER_WEBHOOK_EVENTS.includes(wert);
53
+ }
54
+ /**
55
+ * Die Felder des Umschlags, so wie er auf der Leitung liegt.
56
+ *
57
+ * Als Liste, damit der Zwilling sie nachhaelt: `test` kam spaeter dazu, und
58
+ * genau ein solches Feld verschwindet sonst auf einer Seite, ohne dass etwas
59
+ * rot wird.
60
+ */
61
+ exports.WEBHOOK_UMSCHLAG_FELDER = ['id', 'type', 'createdAt', 'partnerId', 'test', 'data'];
62
+ /**
63
+ * Prueft die Signatur **und** liest das Ereignis — in dieser Reihenfolge. Wer
64
+ * zuerst parst und dann prueft, hat den fremden Rumpf schon durch seinen Code
65
+ * laufen lassen.
66
+ *
67
+ * **Entdoppeln nicht vergessen:** dieselbe Zustellung kann mehrfach ankommen
68
+ * (Wiederholung nach einer Antwort, die unterwegs verloren ging). Massgeblich
69
+ * ist `event.id`, nicht der Kopf `X-Kasseneck-Delivery` — der ist bei
70
+ * Wiederholungen derselbe, aber er beantwortet die Frage "habe ich dieses
71
+ * Ereignis schon verarbeitet?" nur fuer genau diesen Endpunkt.
72
+ *
73
+ * Antworte innerhalb von 10 Sekunden mit 2xx und erledige die Arbeit danach.
74
+ * Eine ausbleibende Antwort wird bis zu fuenfmal wiederholt (1 min, 5 min,
75
+ * 30 min, 2 h, 12 h) und gilt dann als fehlgeschlagen.
76
+ */
77
+ async function parseWebhookEvent(optionen) {
78
+ const geprueft = await (0, webhook_signatur_js_1.verifyWebhookSignature)(optionen);
79
+ if (!geprueft.ok)
80
+ return { ok: false, reason: geprueft.reason };
81
+ const text = typeof optionen.body === 'string' ? optionen.body : new TextDecoder('utf-8').decode(optionen.body);
82
+ let roh;
83
+ try {
84
+ roh = JSON.parse(text);
85
+ }
86
+ catch {
87
+ return { ok: false, reason: 'body-not-json' };
88
+ }
89
+ if (roh === null || typeof roh !== 'object' || Array.isArray(roh))
90
+ return { ok: false, reason: 'body-not-event' };
91
+ const e = roh;
92
+ if (typeof e['id'] !== 'string' || typeof e['type'] !== 'string')
93
+ return { ok: false, reason: 'body-not-event' };
94
+ return {
95
+ ok: true,
96
+ timestampSec: geprueft.timestampSec,
97
+ event: {
98
+ id: e['id'],
99
+ type: e['type'],
100
+ createdAt: typeof e['createdAt'] === 'number' ? e['createdAt'] : 0,
101
+ partnerId: typeof e['partnerId'] === 'string' ? e['partnerId'] : '',
102
+ // Nur ein ausdrueckliches `true` ist eine Probe. Alles andere — auch ein
103
+ // fehlendes Feld — ist der Ernstfall; im Zweifel lieber einmal zu viel
104
+ // gearbeitet als eine echte Kasse fuer eine Probe gehalten.
105
+ test: e['test'] === true,
106
+ data: e['data'] !== null && typeof e['data'] === 'object' && !Array.isArray(e['data'])
107
+ ? e['data']
108
+ : {},
109
+ },
110
+ };
111
+ }
112
+ function objekt(wert) {
113
+ return wert !== null && typeof wert === 'object' && !Array.isArray(wert) ? wert : {};
114
+ }
115
+ function webhook(eintrag) {
116
+ const w = objekt(eintrag);
117
+ return {
118
+ webhookId: typeof w['webhookId'] === 'string' ? w['webhookId'] : '',
119
+ url: typeof w['url'] === 'string' ? w['url'] : '',
120
+ events: Array.isArray(w['events']) ? w['events'].filter((e) => typeof e === 'string') : [],
121
+ active: w['active'] !== false,
122
+ description: typeof w['description'] === 'string' ? w['description'] : null,
123
+ createdAt: typeof w['createdAt'] === 'number' ? w['createdAt'] : null,
124
+ lastDelivery: typeof w['lastDelivery'] === 'number' ? w['lastDelivery'] : null,
125
+ consecutiveFailures: typeof w['consecutiveFailures'] === 'number' ? w['consecutiveFailures'] : 0,
126
+ };
127
+ }
128
+ /**
129
+ * Legt einen Webhook-Endpunkt an. Hoechstens [WEBHOOK_LIMIT] je Partner
130
+ * (`webhook_limit`).
131
+ *
132
+ * **Das Secret in der Antwort ist der einzige Weg dazu.** Es sofort dorthin
133
+ * schreiben, wo der Empfaenger es liest — nicht in ein Protokoll.
134
+ */
135
+ async function createPartnerWebhook(rufen, optionen) {
136
+ const url = typeof optionen?.url === 'string' ? optionen.url.trim() : '';
137
+ if (!url)
138
+ throw new errors_js_1.KasseneckValidationError('createPartnerWebhook', 'url fehlt', 'request');
139
+ if (!Array.isArray(optionen.events) || optionen.events.length === 0) {
140
+ throw new errors_js_1.KasseneckValidationError('createPartnerWebhook', 'events ist leer — ein Endpunkt ohne Ereignis bekaeme nie etwas', 'request');
141
+ }
142
+ const daten = objekt(await rufen('createPartnerWebhook', {
143
+ url,
144
+ events: optionen.events,
145
+ description: optionen.description,
146
+ active: optionen.active,
147
+ }));
148
+ const secret = daten['secret'];
149
+ if (typeof secret !== 'string' || !secret) {
150
+ throw new errors_js_1.KasseneckValidationError('createPartnerWebhook', 'Antwort enthaelt kein secret — ohne es laesst sich keine Zustellung pruefen', 'response');
151
+ }
152
+ return { webhook: webhook(daten['webhook']), secret };
153
+ }
154
+ /**
155
+ * Ein neues Secret fuer denselben Endpunkt.
156
+ *
157
+ * Der Webhook behaelt seine `webhookId` und seine Ereignisse — nur das
158
+ * Geheimnis wechselt. Wer den Endpunkt stattdessen loescht und neu anlegt,
159
+ * verliert seine Zustellungshistorie und muss die Ereignisse neu ankreuzen.
160
+ *
161
+ * Das alte Secret gilt ab der Antwort NICHT mehr: die naechste Zustellung ist
162
+ * schon mit dem neuen signiert. Erst speichern, dann weiterarbeiten.
163
+ */
164
+ async function rotatePartnerWebhookSecret(rufen, webhookId) {
165
+ const id = typeof webhookId === 'string' ? webhookId.trim() : '';
166
+ if (!id)
167
+ throw new errors_js_1.KasseneckValidationError('rotatePartnerWebhookSecret', 'webhookId fehlt', 'request');
168
+ const daten = objekt(await rufen('rotatePartnerWebhookSecret', { webhookId: id }));
169
+ const secret = daten['secret'];
170
+ if (typeof secret !== 'string' || !secret) {
171
+ throw new errors_js_1.KasseneckValidationError('rotatePartnerWebhookSecret', 'Antwort enthaelt kein secret — dann waere der Wechsel nicht nachvollziehbar', 'response');
172
+ }
173
+ return { webhook: webhook(daten['webhook']), secret };
174
+ }
175
+ /** Die Webhook-Endpunkte dieses Partners samt Ereignis-Katalog. */
176
+ async function listPartnerWebhooks(rufen) {
177
+ const daten = objekt(await rufen('listPartnerWebhooks'));
178
+ return {
179
+ webhooks: (Array.isArray(daten['webhooks']) ? daten['webhooks'] : []).map(webhook),
180
+ events: (Array.isArray(daten['events']) ? daten['events'] : []).map((e) => ({
181
+ key: typeof objekt(e)['key'] === 'string' ? objekt(e)['key'] : '',
182
+ text: typeof objekt(e)['text'] === 'string' ? objekt(e)['text'] : '',
183
+ })),
184
+ };
185
+ }
186
+ /**
187
+ * Aendert einen Endpunkt. Nur die genannten Felder — ein leeres `patch` lehnt
188
+ * das Backend ab (`validation`, Feld `patch`), statt stillschweigend nichts zu
189
+ * tun.
190
+ */
191
+ async function updatePartnerWebhook(rufen, webhookId, patch) {
192
+ const id = typeof webhookId === 'string' ? webhookId.trim() : '';
193
+ if (!id)
194
+ throw new errors_js_1.KasseneckValidationError('updatePartnerWebhook', 'webhookId fehlt', 'request');
195
+ if (patch === null || typeof patch !== 'object' || Object.keys(patch).length === 0) {
196
+ throw new errors_js_1.KasseneckValidationError('updatePartnerWebhook', 'patch nennt keine Aenderung', 'request');
197
+ }
198
+ const daten = objekt(await rufen('updatePartnerWebhook', { webhookId: id, patch }));
199
+ return webhook(daten['webhook']);
200
+ }
201
+ /** Loescht einen Endpunkt. Danach kommt dort nichts mehr an. */
202
+ async function deletePartnerWebhook(rufen, webhookId) {
203
+ const id = typeof webhookId === 'string' ? webhookId.trim() : '';
204
+ if (!id)
205
+ throw new errors_js_1.KasseneckValidationError('deletePartnerWebhook', 'webhookId fehlt', 'request');
206
+ const daten = objekt(await rufen('deletePartnerWebhook', { webhookId: id }));
207
+ return typeof daten['webhookId'] === 'string' ? daten['webhookId'] : id;
208
+ }
209
+ /**
210
+ * Schickt eine Probe an genau diesen Endpunkt.
211
+ *
212
+ * Ohne `event` kommt `webhook.test` — der Nachweis, dass die Leitung steht und
213
+ * die eigene Signaturpruefung gegen echte Bytes laeuft. **Mit `event` kommt
214
+ * genau das Ereignis, das der Empfaenger behandeln soll**, mit einer
215
+ * glaubwuerdigen Nutzlast: wer auf `signature.ready` hin seinen Kunden
216
+ * benachrichtigt, probt das einmal, statt auf eine echte Karte zu warten. Eine
217
+ * Leitungsprobe beweist nichts ueber die Behandlung des Ernstfalls.
218
+ *
219
+ * Der Endpunkt muss das Ereignis abonnieren (`event_not_subscribed`) und aktiv
220
+ * sein (`webhook_inactive`); ein unbekannter Name ist ein `validation`-Fehler
221
+ * auf dem Feld `event`.
222
+ *
223
+ * **Jede Probe traegt `test: true` im Umschlag** ([PartnerWebhookEvent.test]).
224
+ *
225
+ * Der Backend-Endpunkt heisst `sendPartnerWebhookTest`; dieser Client behaelt
226
+ * den Namen bei, damit ein Leser der Doku und ein Leser des Codes dasselbe
227
+ * suchen.
228
+ */
229
+ async function sendPartnerWebhookTest(rufen, webhookId, event) {
230
+ const id = typeof webhookId === 'string' ? webhookId.trim() : '';
231
+ if (!id)
232
+ throw new errors_js_1.KasseneckValidationError('sendPartnerWebhookTest', 'webhookId fehlt', 'request');
233
+ const ereignis = typeof event === 'string' ? event.trim() : '';
234
+ const daten = objekt(await rufen('sendPartnerWebhookTest', { webhookId: id, event: ereignis || undefined }));
235
+ return {
236
+ eventId: typeof daten['eventId'] === 'string' ? daten['eventId'] : '',
237
+ ereignis: typeof daten['ereignis'] === 'string' ? daten['ereignis'] : ereignis || 'webhook.test',
238
+ deliveries: Array.isArray(daten['deliveries']) ? daten['deliveries'] : [],
239
+ };
240
+ }
241
+ /**
242
+ * Die letzten Zustellversuche — mit `webhookId` fuer einen Endpunkt, ohne ihn
243
+ * fuer alle. Die Stelle, an der sich „mein Server bekommt nichts" klaeren
244
+ * laesst, ohne bei Kasseneck nachzufragen.
245
+ */
246
+ async function listPartnerWebhookDeliveries(rufen, optionen = {}) {
247
+ if (optionen.limit !== undefined && (!Number.isInteger(optionen.limit) || optionen.limit < 1 || optionen.limit > 200)) {
248
+ throw new errors_js_1.KasseneckValidationError('listPartnerWebhookDeliveries', 'limit muss zwischen 1 und 200 liegen', 'request');
249
+ }
250
+ const daten = objekt(await rufen('listPartnerWebhookDeliveries', { webhookId: optionen.webhookId, limit: optionen.limit }));
251
+ return (Array.isArray(daten['deliveries']) ? daten['deliveries'] : []).map((eintrag) => {
252
+ const z = objekt(eintrag);
253
+ const zahl = (wert) => (typeof wert === 'number' && Number.isFinite(wert) ? wert : null);
254
+ const txt = (wert) => (typeof wert === 'string' ? wert : null);
255
+ return {
256
+ deliveryId: txt(z['deliveryId']) ?? '',
257
+ webhookId: txt(z['webhookId']) ?? '',
258
+ event: txt(z['event']) ?? '',
259
+ eventId: txt(z['eventId']) ?? '',
260
+ status: txt(z['status']) ?? '',
261
+ attempts: zahl(z['attempts']) ?? 0,
262
+ letzterVersuchAt: zahl(z['letzterVersuchAt']),
263
+ naechsterVersuchAt: zahl(z['naechsterVersuchAt']),
264
+ statusCode: zahl(z['statusCode']),
265
+ response: txt(z['response']),
266
+ createdAt: zahl(z['createdAt']),
267
+ };
268
+ });
269
+ }
@@ -159,7 +159,14 @@ export interface RegisterUserPerms {
159
159
  tipAssign?: boolean;
160
160
  [weiteresRecht: string]: boolean | RegisterScope | undefined;
161
161
  }
162
- /** Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen dagegen. */
162
+ /**
163
+ * Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen
164
+ * dagegen, und ueber `fixtures/oberflaeche.json` steht die Liste im Vertrag.
165
+ *
166
+ * `satisfies` schliesst die eine Richtung: ein Name, den [RegisterUserPerms]
167
+ * nicht fuehrt (Tippfehler, umbenanntes Recht), kommt hier nicht durch.
168
+ * Die Gegenrichtung schliesst [_RechteVollstaendig] direkt darunter.
169
+ */
163
170
  export declare const REGISTER_PERMS: readonly ["sell", "cancel", "articles", "layout", "reports", "takeover", "cancelScope", "receiptsScope", "drawer", "discount", "tipAssign"];
164
171
  /** Reichweite lesen, mit der Migration des Backends (register-auth.js). */
165
172
  export declare function cancelScopeOf(perms: RegisterUserPerms | null | undefined): RegisterScope;
@@ -48,7 +48,14 @@ const settings_js_1 = require("../kasse/settings.js");
48
48
  * es auch halten.
49
49
  */
50
50
  const ohneAnmeldung = () => ({ headers: {}, params: {} });
51
- /** Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen dagegen. */
51
+ /**
52
+ * Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen
53
+ * dagegen, und ueber `fixtures/oberflaeche.json` steht die Liste im Vertrag.
54
+ *
55
+ * `satisfies` schliesst die eine Richtung: ein Name, den [RegisterUserPerms]
56
+ * nicht fuehrt (Tippfehler, umbenanntes Recht), kommt hier nicht durch.
57
+ * Die Gegenrichtung schliesst [_RechteVollstaendig] direkt darunter.
58
+ */
52
59
  exports.REGISTER_PERMS = [
53
60
  'sell', 'cancel', 'articles', 'layout', 'reports', 'takeover',
54
61
  'cancelScope', 'receiptsScope', 'drawer', 'discount', 'tipAssign',
@@ -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>;
@@ -1,31 +1,51 @@
1
1
  export const AUFRUFE = [
2
+ 'activateCashregister',
2
3
  'cancelReceipt',
4
+ 'createCustomerCashregister',
5
+ 'createPartnerCustomer',
6
+ 'checkPartnerCustomerEmail',
7
+ 'createPartnerWebhook',
3
8
  'createPaymentLinkStripe',
4
9
  'createPrintJob',
5
10
  'createReceipt',
11
+ 'deletePartnerWebhook',
6
12
  'downloadDailyReport',
7
13
  'downloadReport',
8
14
  'endRegisterSession',
9
15
  'financeWebService',
10
16
  'generateFullReceiptId',
17
+ 'getCustomerCredentials',
18
+ 'getCustomerSignatureStatus',
11
19
  'getFirstReceiptDate',
12
20
  'getKasseSettings',
21
+ 'getPartnerCustomer',
22
+ 'getPartnerInfo',
13
23
  'getPrintJob',
14
24
  'getReceipt',
15
25
  'hobexPayApi',
16
26
  'hobexRefundApi',
27
+ 'listCustomerCashregisters',
17
28
  'listMyArticleGroups',
18
29
  'listMyArticles',
19
30
  'listMyCashregisters',
20
31
  'listMyPrinters',
21
32
  'listMyReceipts',
33
+ 'listMyTipRecipients',
34
+ 'listPartnerCustomers',
35
+ 'listPartnerWebhookDeliveries',
36
+ 'listPartnerWebhooks',
22
37
  'listRegisterUsersForDevice',
23
38
  'pairRegisterDevice',
24
39
  'registerPinLogin',
25
40
  'registerUserLogin',
26
41
  'renewRegisterSession',
42
+ 'requestCustomerSignature',
43
+ 'sendPartnerCustomerFonLink',
44
+ 'sendPartnerWebhookTest',
27
45
  'setMyKasseSettings',
28
46
  'setMyRegisterDeviceSettings',
29
47
  'stripeCaptureIntent',
30
48
  'unpairRegisterDevice',
49
+ 'rotatePartnerWebhookSecret',
50
+ 'updatePartnerWebhook',
31
51
  ];
@@ -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';