@kreiseck/kasseneck-api 0.6.46 → 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 (58) 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 +19 -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/partner/ablauf.d.ts +47 -0
  9. package/dist/cjs/partner/ablauf.js +99 -0
  10. package/dist/cjs/partner/api.d.ts +68 -0
  11. package/dist/cjs/partner/api.js +44 -0
  12. package/dist/cjs/partner/auth.d.ts +33 -0
  13. package/dist/cjs/partner/auth.js +52 -0
  14. package/dist/cjs/partner/betrieb.d.ts +48 -0
  15. package/dist/cjs/partner/betrieb.js +136 -0
  16. package/dist/cjs/partner/endpunkte.d.ts +142 -0
  17. package/dist/cjs/partner/endpunkte.js +488 -0
  18. package/dist/cjs/partner/fehler.d.ts +72 -0
  19. package/dist/cjs/partner/fehler.js +199 -0
  20. package/dist/cjs/partner/index.d.ts +28 -0
  21. package/dist/cjs/partner/index.js +84 -0
  22. package/dist/cjs/partner/secret.d.ts +66 -0
  23. package/dist/cjs/partner/secret.js +95 -0
  24. package/dist/cjs/partner/typen.d.ts +399 -0
  25. package/dist/cjs/partner/typen.js +33 -0
  26. package/dist/cjs/partner/webhook-signatur.d.ts +95 -0
  27. package/dist/cjs/partner/webhook-signatur.js +158 -0
  28. package/dist/cjs/partner/webhooks.d.ts +211 -0
  29. package/dist/cjs/partner/webhooks.js +269 -0
  30. package/dist/esm/client/aufrufe.d.ts +1 -1
  31. package/dist/esm/client/aufrufe.js +19 -0
  32. package/dist/esm/client/errors.d.ts +31 -1
  33. package/dist/esm/client/errors.js +97 -1
  34. package/dist/esm/client/transport.js +8 -8
  35. package/dist/esm/partner/ablauf.d.ts +47 -0
  36. package/dist/esm/partner/ablauf.js +95 -0
  37. package/dist/esm/partner/api.d.ts +68 -0
  38. package/dist/esm/partner/api.js +41 -0
  39. package/dist/esm/partner/auth.d.ts +33 -0
  40. package/dist/esm/partner/auth.js +48 -0
  41. package/dist/esm/partner/betrieb.d.ts +48 -0
  42. package/dist/esm/partner/betrieb.js +132 -0
  43. package/dist/esm/partner/endpunkte.d.ts +142 -0
  44. package/dist/esm/partner/endpunkte.js +474 -0
  45. package/dist/esm/partner/fehler.d.ts +72 -0
  46. package/dist/esm/partner/fehler.js +189 -0
  47. package/dist/esm/partner/index.d.ts +28 -0
  48. package/dist/esm/partner/index.js +27 -0
  49. package/dist/esm/partner/secret.d.ts +66 -0
  50. package/dist/esm/partner/secret.js +90 -0
  51. package/dist/esm/partner/typen.d.ts +399 -0
  52. package/dist/esm/partner/typen.js +30 -0
  53. package/dist/esm/partner/webhook-signatur.d.ts +95 -0
  54. package/dist/esm/partner/webhook-signatur.js +153 -0
  55. package/dist/esm/partner/webhooks.d.ts +211 -0
  56. package/dist/esm/partner/webhooks.js +257 -0
  57. package/fixtures/oberflaeche.json +131 -3
  58. package/package.json +13 -1
@@ -85,6 +85,84 @@ function unbedenklich(wert, geheimnisse) {
85
85
  }
86
86
  return wert;
87
87
  }
88
+ /**
89
+ * Grenzen fuer die gesiebte Fehler-Nutzlast (siehe [fehlerDetails]). Sie stehen
90
+ * als benannte Konstanten hier, weil ein Test sie namentlich prueft — eine
91
+ * spaeter heraufgesetzte Grenze soll auffallen und nicht als Zahl im Code
92
+ * untergehen.
93
+ */
94
+ const DETAIL_TIEFE = 4;
95
+ const DETAIL_EINTRAEGE = 50;
96
+ const DETAIL_TEXT_MAX = 300;
97
+ /** Schluessel eines Detail-Objekts: bezeichner-foermig, sonst faellt der Eintrag weg. */
98
+ const DETAIL_SCHLUESSEL = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
99
+ /**
100
+ * Siebt das `data` einer Fehlerantwort zu einer Form, die man gefahrlos an
101
+ * einem Fehler mitfuehren kann.
102
+ *
103
+ * **Warum ueberhaupt:** die Partner-API legt ihre Entscheidung nicht in den
104
+ * Text, sondern in `data.code` (`vertrag_offen`, `signature_not_ready`,
105
+ * `activation_failed` samt `data.schritt`). Ohne diese Felder muesste ein
106
+ * Aufrufer die deutsche `message` nach Zeichenketten durchsuchen — genau die
107
+ * Kopplung, die beim naechsten Formulierungsschliff still bricht.
108
+ *
109
+ * **Warum gesiebt und nicht durchgereicht:** siehe Modulkommentar. Der Rumpf
110
+ * kommt ueber fremde Proxys, und ein Fehler landet in Protokollen. Deshalb
111
+ * ueberlebt nur, was flach, klein und bezeichner-foermig benannt ist — und
112
+ * kein Wert, der mit einem der gesendeten Geheimnisse ueberlappt. Damit gilt
113
+ * hier dieselbe Zusage wie fuer [causeDigest], und `geheimnisse` hat aus
114
+ * demselben Grund **keinen** Vorgabewert.
115
+ */
116
+ export function fehlerDetails(daten, geheimnisse) {
117
+ const gesiebt = sieben(daten, geheimnisse, 0);
118
+ return gesiebt !== null && typeof gesiebt === 'object' && !Array.isArray(gesiebt)
119
+ ? gesiebt
120
+ : {};
121
+ }
122
+ function sieben(wert, geheimnisse, tiefe) {
123
+ if (wert === null || typeof wert === 'boolean')
124
+ return wert;
125
+ // Nur endliche Zahlen: NaN und Infinity ueberstehen JSON.stringify nicht und
126
+ // staenden in einem Fehlerbericht als `null` ohne jede Aussage.
127
+ if (typeof wert === 'number')
128
+ return Number.isFinite(wert) ? wert : undefined;
129
+ if (typeof wert === 'string') {
130
+ if (wert.length > DETAIL_TEXT_MAX)
131
+ return undefined;
132
+ for (const geheim of geheimnisse) {
133
+ if (geheim && (geheim.includes(wert) || wert.includes(geheim)))
134
+ return undefined;
135
+ }
136
+ return wert;
137
+ }
138
+ if (tiefe >= DETAIL_TIEFE)
139
+ return undefined;
140
+ if (Array.isArray(wert)) {
141
+ const liste = [];
142
+ for (const eintrag of wert.slice(0, DETAIL_EINTRAEGE)) {
143
+ const s = sieben(eintrag, geheimnisse, tiefe + 1);
144
+ if (s !== undefined)
145
+ liste.push(s);
146
+ }
147
+ return liste;
148
+ }
149
+ if (typeof wert !== 'object')
150
+ return undefined;
151
+ const raus = {};
152
+ let gezaehlt = 0;
153
+ for (const [schluessel, eintrag] of Object.entries(wert)) {
154
+ if (gezaehlt >= DETAIL_EINTRAEGE)
155
+ break;
156
+ if (!DETAIL_SCHLUESSEL.test(schluessel))
157
+ continue;
158
+ const s = sieben(eintrag, geheimnisse, tiefe + 1);
159
+ if (s === undefined)
160
+ continue;
161
+ raus[schluessel] = s;
162
+ gezaehlt += 1;
163
+ }
164
+ return raus;
165
+ }
88
166
  /** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
89
167
  export class KasseneckApiError extends Error {
90
168
  name = 'KasseneckApiError';
@@ -92,10 +170,28 @@ export class KasseneckApiError extends Error {
92
170
  functionName;
93
171
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
94
172
  serverMessage;
95
- constructor(functionName, serverMessage) {
173
+ /**
174
+ * Maschinenlesbarer Fehlercode aus `data.code`, sofern die Antwort einen
175
+ * fuehrt (`vertrag_offen`, `rate_limited`, …). Die aelteren Endpunkte des
176
+ * Backends antworten ohne Code — dann `undefined`.
177
+ */
178
+ code;
179
+ /**
180
+ * Die uebrige Fehler-Nutzlast, gesiebt (siehe [fehlerDetails]): `schritt`,
181
+ * `rc`, `retryAfterSec`, `errors[]` und was der jeweilige Endpunkt sonst
182
+ * beilegt. Immer ein Objekt, notfalls ein leeres.
183
+ */
184
+ details;
185
+ constructor(functionName, serverMessage, details = {}) {
96
186
  super(`${functionName} fehlgeschlagen: ${serverMessage}`);
97
187
  this.functionName = functionName;
98
188
  this.serverMessage = serverMessage;
189
+ this.details = details;
190
+ // Der Code steht in `details` und zusaetzlich als eigenes Feld: das eine
191
+ // ist die Nutzlast, das andere die Frage, die ein Aufrufer wirklich
192
+ // stellt. Nur Bezeichner gelten — ein Freitext waere kein Code.
193
+ const code = details['code'];
194
+ this.code = typeof code === 'string' && BEZEICHNER.test(code) ? code : undefined;
99
195
  }
100
196
  }
101
197
  const GRUND_TEXT = {
@@ -1,4 +1,4 @@
1
- import { KasseneckApiError, KasseneckAuthError, KasseneckHttpError, KasseneckNetworkError, KasseneckValidationError, causeDigest, } from './errors.js';
1
+ import { KasseneckApiError, KasseneckAuthError, KasseneckHttpError, KasseneckNetworkError, KasseneckValidationError, causeDigest, fehlerDetails, } from './errors.js';
2
2
  /**
3
3
  * Transport zum Kasseneck-Backend: ein Aufruf ist ein POST an
4
4
  * `<basis>/<funktionsname>` mit dem JSON-Rumpf `{ "params": { … } }` — wie im
@@ -129,7 +129,7 @@ function createCore(options) {
129
129
  if (antwort.status !== 200) {
130
130
  throw new KasseneckHttpError(fehlerName, antwort.status, inhaltstyp, 'server-error');
131
131
  }
132
- return auswerten(koerper, fehlerName, antwort.status, inhaltstyp);
132
+ return auswerten(koerper, fehlerName, antwort.status, inhaltstyp, geheimnisse);
133
133
  }
134
134
  finally {
135
135
  // Ohne Abraeumen haelt der Wecker den Node-Prozess bis zum Zeitlimit wach.
@@ -154,7 +154,7 @@ const alsBytes = async (antwort, functionName) => {
154
154
  return new Uint8Array(await antwort.arrayBuffer());
155
155
  };
156
156
  /** Auswertung des JSON-Wegs: Huelle aufloesen, Nutzlast zurueckgeben. */
157
- function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
157
+ function jsonAuswerten(text, functionName, statusCode, inhaltstyp, geheimnisse) {
158
158
  if (!text.trim()) {
159
159
  throw new KasseneckHttpError(functionName, statusCode, inhaltstyp, 'empty-body');
160
160
  }
@@ -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.data, geheimnisse);
180
180
  }
181
181
  /**
182
182
  * Auswertung des Binaerwegs. Der Kern der Zusage: **ein Aufrufer bekommt nie
@@ -191,7 +191,7 @@ function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
191
191
  * das Backend hier erzeugt (pdf-lib). Alles, was nicht so anfaengt, ist kein
192
192
  * Bericht — und wird dann daraufhin angesehen, ob es die Fehlerhuelle ist.
193
193
  */
194
- function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp) {
194
+ function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp, geheimnisse) {
195
195
  if (bytes.length === 0) {
196
196
  throw new KasseneckHttpError(functionName, statusCode, inhaltstyp, 'empty-body');
197
197
  }
@@ -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.data, geheimnisse);
226
226
  }
227
227
  /** `%PDF` am Anfang — die Kennung jeder PDF-Datei. */
228
228
  function istPdf(bytes) {
@@ -235,9 +235,9 @@ function alsHuelle(wert) {
235
235
  }
236
236
  return wert;
237
237
  }
238
- function fachfehler(functionName, message) {
238
+ function fachfehler(functionName, message, daten, geheimnisse) {
239
239
  const meldung = typeof message === 'string' && message.trim() ? message : 'Unbekannter Fehler';
240
- return new KasseneckApiError(functionName, meldung);
240
+ return new KasseneckApiError(functionName, meldung, fehlerDetails(daten, geheimnisse));
241
241
  }
242
242
  /**
243
243
  * Nutzlast aus Auth- und Aufruferparametern. Auth-Parameter bilden die
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Der Weg vom Vertragsabschluss bis zum ersten Beleg — als Daten, nicht als
3
+ * Fliesstext.
4
+ *
5
+ * **Warum das im Paket steht und nicht nur in der Doku:** die Kette hat eine
6
+ * harte Reihenfolge, und jeder Schritt scheitert mit einem eigenen Code, wenn
7
+ * ein vorheriger fehlt (`fon_missing`, `signature_missing`,
8
+ * `signature_not_ready`).
9
+ * Wer die Reihenfolge nur aus einer Fehlermeldung lernt, lernt sie einmal je
10
+ * Fehler. Hier steht sie vorher — abfragbar, ausgebbar, und in
11
+ * [naechsterSchritt] auch beantwortbar.
12
+ *
13
+ * **Ohne Vertragsschritt.** Auftragsverarbeitungsvertraege wirken in diesem
14
+ * Weg nicht mehr (Stand 2026-08-31): kein Aufruf meldet einen, keine Kasse
15
+ * bleibt deswegen stehen.
16
+ *
17
+ * Zwei Dinge laufen bewusst **parallel**: der Signaturantrag und das Anlegen
18
+ * der Kasse. Eine mit `automatic:true` angelegte Kasse wartet, bis die
19
+ * Signatur bereit ist, und geht dann von selbst live. Der Kundenstatus nennt
20
+ * darum immer nur den **weitesten erreichten** Meilenstein, nicht die einzige
21
+ * laufende Arbeit.
22
+ */
23
+ import type { KundenStatus } from './typen.js';
24
+ /** Ein Schritt der Kette. */
25
+ export interface AblaufSchritt {
26
+ key: string;
27
+ /** Was in diesem Schritt passiert. */
28
+ text: string;
29
+ /** Der Aufruf, der ihn ausloest — `null`, wenn hier nur gewartet wird. */
30
+ aufruf: string | null;
31
+ /** Das Ereignis, das seinen Abschluss meldet — `null`, wenn es sofort feststeht. */
32
+ wartetAuf: string | null;
33
+ /** Der Fehlercode, mit dem ein spaeterer Aufruf sich beschwert, wenn dieser Schritt fehlt. */
34
+ fehltCode: string | null;
35
+ }
36
+ /**
37
+ * Die Kette in ihrer Reihenfolge. Sie beschreibt den **Live-Weg**; mit einem
38
+ * `pk_test_`-Schluessel entfallen die FinanzOnline-Schritte, und die Signatur
39
+ * ist sofort bereit (AT100-Testkarte — die damit erzeugten Belege sind keine
40
+ * gueltigen RKSV-Belege).
41
+ */
42
+ export declare const PARTNER_ABLAUF: readonly AblaufSchritt[];
43
+ /**
44
+ * Der Schritt, an dem ein Betrieb mit diesem Status steht — `null` fuer
45
+ * `gesperrt` und fuer jeden Status, den dieses Paket nicht kennt.
46
+ */
47
+ export declare function naechsterSchritt(status: KundenStatus): AblaufSchritt | null;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Der Weg vom Vertragsabschluss bis zum ersten Beleg — als Daten, nicht als
3
+ * Fliesstext.
4
+ *
5
+ * **Warum das im Paket steht und nicht nur in der Doku:** die Kette hat eine
6
+ * harte Reihenfolge, und jeder Schritt scheitert mit einem eigenen Code, wenn
7
+ * ein vorheriger fehlt (`fon_missing`, `signature_missing`,
8
+ * `signature_not_ready`).
9
+ * Wer die Reihenfolge nur aus einer Fehlermeldung lernt, lernt sie einmal je
10
+ * Fehler. Hier steht sie vorher — abfragbar, ausgebbar, und in
11
+ * [naechsterSchritt] auch beantwortbar.
12
+ *
13
+ * **Ohne Vertragsschritt.** Auftragsverarbeitungsvertraege wirken in diesem
14
+ * Weg nicht mehr (Stand 2026-08-31): kein Aufruf meldet einen, keine Kasse
15
+ * bleibt deswegen stehen.
16
+ *
17
+ * Zwei Dinge laufen bewusst **parallel**: der Signaturantrag und das Anlegen
18
+ * der Kasse. Eine mit `automatic:true` angelegte Kasse wartet, bis die
19
+ * Signatur bereit ist, und geht dann von selbst live. Der Kundenstatus nennt
20
+ * darum immer nur den **weitesten erreichten** Meilenstein, nicht die einzige
21
+ * laufende Arbeit.
22
+ */
23
+ /**
24
+ * Die Kette in ihrer Reihenfolge. Sie beschreibt den **Live-Weg**; mit einem
25
+ * `pk_test_`-Schluessel entfallen die FinanzOnline-Schritte, und die Signatur
26
+ * ist sofort bereit (AT100-Testkarte — die damit erzeugten Belege sind keine
27
+ * gueltigen RKSV-Belege).
28
+ */
29
+ export const PARTNER_ABLAUF = [
30
+ {
31
+ key: 'business',
32
+ text: 'Betrieb anlegen (idempotencyKey = eigene Kundennummer).',
33
+ aufruf: 'createPartnerCustomer',
34
+ wartetAuf: 'customer.created',
35
+ fehltCode: null,
36
+ },
37
+ {
38
+ key: 'fon',
39
+ text: 'FinanzOnline einrichten: Link an den Betrieb, der Betrieb traegt seinen Zugang ein.',
40
+ aufruf: 'sendPartnerCustomerFonLink',
41
+ wartetAuf: 'customer.fon_verified',
42
+ fehltCode: 'fon_missing',
43
+ },
44
+ {
45
+ key: 'signature',
46
+ text: 'Signatureinheit beantragen. Kasseneck weist eine Karte zu und meldet sie bei FinanzOnline an.',
47
+ aufruf: 'requestCustomerSignature',
48
+ wartetAuf: 'signature.ready',
49
+ fehltCode: 'signature_missing',
50
+ },
51
+ {
52
+ key: 'cashregister',
53
+ text: 'Kasse anlegen. Mit automatic:true (Vorgabe) geht sie von selbst live, sobald die Signatur bereit ist — ' +
54
+ 'sie darf deshalb schon vorher angelegt werden.',
55
+ aufruf: 'createCustomerCashregister',
56
+ wartetAuf: 'cashregister.live',
57
+ fehltCode: 'cashregister_not_found',
58
+ },
59
+ {
60
+ key: 'zugangsdaten',
61
+ text: 'Zugangsdaten des Betriebs holen (Scope credentials:read). Geheimnisse — nur verschluesselt speichern.',
62
+ aufruf: 'getCustomerCredentials',
63
+ wartetAuf: null,
64
+ fehltCode: null,
65
+ },
66
+ {
67
+ key: 'belege',
68
+ text: 'Belege signieren: Bearer = apiKey des Betriebs, Kopfzeile cashregister-token = Token der Kasse.',
69
+ aufruf: 'createReceipt',
70
+ wartetAuf: null,
71
+ fehltCode: null,
72
+ },
73
+ ];
74
+ /**
75
+ * Welcher Meilenstein einem Kundenstatus entspricht. Die Zuordnung ist
76
+ * absichtlich grob: der Status nennt den weitesten erreichten Punkt, nicht die
77
+ * laufende Arbeit — fuer den genauen Verlauf sind die Ereignisse `signature.*`
78
+ * und `cashregister.*` da.
79
+ */
80
+ const STATUS_SCHRITT = {
81
+ created: 'fon',
82
+ fon_configured: 'signature',
83
+ signature_requested: 'signature',
84
+ signature_ready: 'cashregister',
85
+ cashregister_created: 'cashregister',
86
+ live: 'zugangsdaten',
87
+ };
88
+ /**
89
+ * Der Schritt, an dem ein Betrieb mit diesem Status steht — `null` fuer
90
+ * `gesperrt` und fuer jeden Status, den dieses Paket nicht kennt.
91
+ */
92
+ export function naechsterSchritt(status) {
93
+ const key = STATUS_SCHRITT[status];
94
+ return key ? PARTNER_ABLAUF.find((s) => s.key === key) ?? null : null;
95
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Fassade ueber den Partner-Aufrufen: Schluessel einmal binden, dann rufen.
3
+ *
4
+ * Wie [createKasseneckApi] bewusst **keine** Klasse — die Aufrufe sind freie
5
+ * Funktionen und bleiben einzeln importierbar.
6
+ */
7
+ import { type FetchLike } from '../client/transport.js';
8
+ import { type CreateWebhookOptions, type CreateWebhookResult, type PartnerWebhook, type WebhookListe, type PartnerWebhookEventType, type WebhookPatch, type WebhookTestResult, type WebhookZustellung } from './webhooks.js';
9
+ import type { ActivateCashregisterResult, CreateCashregisterOptions, CreateCashregisterResult, CreateCustomerOptions, CreateCustomerResult, CustomerCredentials, FonLinkResult, KassenListe, Kunde, KundenListe, ListCustomersOptions, PartnerInfo, RequestSignatureResult, SignaturStand } from './typen.js';
10
+ export interface PartnerApiOptions {
11
+ /** Partner-Schluessel `pk_test_…` / `pk_live_…`. Gehoert auf einen Server. */
12
+ partnerKey: string;
13
+ /** Abweichende Basis-URL; Vorgabe `https://api.kasseneck.at/v1`. */
14
+ baseUrl?: string;
15
+ /** Zeitlimit je Aufruf in Millisekunden. */
16
+ timeoutMs?: number;
17
+ /** Eigene `fetch`-Umsetzung (Tests, Proxys). */
18
+ fetch?: FetchLike;
19
+ }
20
+ export interface PartnerApi {
21
+ getPartnerInfo(): Promise<PartnerInfo>;
22
+ createPartnerCustomer(optionen: CreateCustomerOptions): Promise<CreateCustomerResult>;
23
+ listPartnerCustomers(optionen?: ListCustomersOptions): Promise<KundenListe>;
24
+ getPartnerCustomer(customerId: string): Promise<Kunde>;
25
+ sendPartnerCustomerFonLink(customerId: string): Promise<FonLinkResult>;
26
+ requestCustomerSignature(customerId: string, optionen?: {
27
+ art?: string;
28
+ additional?: boolean;
29
+ }): Promise<RequestSignatureResult>;
30
+ getCustomerSignatureStatus(customerId: string): Promise<SignaturStand>;
31
+ createCustomerCashregister(optionen: CreateCashregisterOptions): Promise<CreateCashregisterResult>;
32
+ activateCashregister(customerId: string, cashregisterId: string): Promise<ActivateCashregisterResult>;
33
+ listCustomerCashregisters(customerId: string): Promise<KassenListe>;
34
+ /** Geheimnisse des Betriebs — siehe `getCustomerCredentials` in endpunkte.ts. */
35
+ getCustomerCredentials(customerId: string): Promise<CustomerCredentials>;
36
+ /**
37
+ * Ist diese Adresse als Kasseneck-Zugang noch frei? Nur noetig, wenn der
38
+ * Betrieb einen eigenen Login bekommen soll — sonst faellt `email_taken`
39
+ * erst nach dem ganzen Formular auf.
40
+ */
41
+ checkPartnerCustomerEmail(email: string): Promise<boolean>;
42
+ createPartnerWebhook(optionen: CreateWebhookOptions): Promise<CreateWebhookResult>;
43
+ listPartnerWebhooks(): Promise<WebhookListe>;
44
+ updatePartnerWebhook(webhookId: string, patch: WebhookPatch): Promise<PartnerWebhook>;
45
+ deletePartnerWebhook(webhookId: string): Promise<string>;
46
+ /**
47
+ * Neues Secret fuer denselben Endpunkt — dieselbe `webhookId`, dieselben
48
+ * Ereignisse. Das alte gilt ab der Antwort nicht mehr.
49
+ */
50
+ rotatePartnerWebhookSecret(webhookId: string): Promise<CreateWebhookResult>;
51
+ /**
52
+ * Eine Probe an einen Endpunkt — ohne `event` die Leitungsprobe
53
+ * `webhook.test`, mit `event` genau der Fall, den der Empfaenger behandeln
54
+ * soll. Jede Probe traegt `test: true` im Umschlag.
55
+ */
56
+ sendPartnerWebhookTest(webhookId: string, event?: PartnerWebhookEventType | (string & {})): Promise<WebhookTestResult>;
57
+ listPartnerWebhookDeliveries(optionen?: {
58
+ webhookId?: string;
59
+ limit?: number;
60
+ }): Promise<WebhookZustellung[]>;
61
+ /**
62
+ * Der Handlungssatz zu einem beliebigen Fehlercode der Partner-API. Gehoert
63
+ * in die eigene Fehlermeldung, damit ein Anwender nicht in der Doku
64
+ * nachschlagen muss.
65
+ */
66
+ fehlerRat(code: string): string | undefined;
67
+ }
68
+ export declare function createPartnerApi(optionen: PartnerApiOptions): PartnerApi;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Fassade ueber den Partner-Aufrufen: Schluessel einmal binden, dann rufen.
3
+ *
4
+ * Wie [createKasseneckApi] bewusst **keine** Klasse — die Aufrufe sind freie
5
+ * Funktionen und bleiben einzeln importierbar.
6
+ */
7
+ import { createTransport } from '../client/transport.js';
8
+ import { partnerKeyAuth } from './auth.js';
9
+ import { partnerFehlerRat } from './fehler.js';
10
+ import { activateCashregister, createCustomerCashregister, createPartnerCustomer, getCustomerCredentials, getCustomerSignatureStatus, getPartnerCustomer, getPartnerInfo, checkPartnerCustomerEmail, listCustomerCashregisters, listPartnerCustomers, requestCustomerSignature, sendPartnerCustomerFonLink, } from './endpunkte.js';
11
+ import { createPartnerWebhook, deletePartnerWebhook, listPartnerWebhookDeliveries, listPartnerWebhooks, rotatePartnerWebhookSecret, sendPartnerWebhookTest, updatePartnerWebhook, } from './webhooks.js';
12
+ export function createPartnerApi(optionen) {
13
+ const rufen = createTransport({
14
+ auth: partnerKeyAuth({ partnerKey: optionen.partnerKey }),
15
+ baseUrl: optionen.baseUrl,
16
+ timeoutMs: optionen.timeoutMs,
17
+ fetch: optionen.fetch,
18
+ });
19
+ return {
20
+ getPartnerInfo: () => getPartnerInfo(rufen),
21
+ createPartnerCustomer: (o) => createPartnerCustomer(rufen, o),
22
+ listPartnerCustomers: (o) => listPartnerCustomers(rufen, o),
23
+ getPartnerCustomer: (id) => getPartnerCustomer(rufen, id),
24
+ sendPartnerCustomerFonLink: (id) => sendPartnerCustomerFonLink(rufen, id),
25
+ requestCustomerSignature: (id, o) => requestCustomerSignature(rufen, id, o),
26
+ getCustomerSignatureStatus: (id) => getCustomerSignatureStatus(rufen, id),
27
+ createCustomerCashregister: (o) => createCustomerCashregister(rufen, o),
28
+ activateCashregister: (kunde, kasse) => activateCashregister(rufen, kunde, kasse),
29
+ listCustomerCashregisters: (id) => listCustomerCashregisters(rufen, id),
30
+ getCustomerCredentials: (id) => getCustomerCredentials(rufen, id),
31
+ checkPartnerCustomerEmail: (email) => checkPartnerCustomerEmail(rufen, email),
32
+ createPartnerWebhook: (o) => createPartnerWebhook(rufen, o),
33
+ listPartnerWebhooks: () => listPartnerWebhooks(rufen),
34
+ updatePartnerWebhook: (id, patch) => updatePartnerWebhook(rufen, id, patch),
35
+ deletePartnerWebhook: (id) => deletePartnerWebhook(rufen, id),
36
+ rotatePartnerWebhookSecret: (id) => rotatePartnerWebhookSecret(rufen, id),
37
+ sendPartnerWebhookTest: (id, event) => sendPartnerWebhookTest(rufen, id, event),
38
+ listPartnerWebhookDeliveries: (o) => listPartnerWebhookDeliveries(rufen, o),
39
+ fehlerRat: (code) => partnerFehlerRat(code),
40
+ };
41
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Anmeldung eines Partner-Servers: der Partner-Schluessel als Bearer.
3
+ *
4
+ * Ein dritter Weg neben `apiKeyAuth` (Geraete) und `registerUserAuth`
5
+ * (Browser-Kasse) — und der einzige, mit dem die Partner-Endpunkte antworten.
6
+ * Er traegt **keine** Kopfzeile `cashregister-token`: ein Partner arbeitet nie
7
+ * an einer Kasse, sondern ueber Betriebe.
8
+ *
9
+ * **Der Schluessel gehoert auf einen Server.** Er kann Betriebe anlegen und —
10
+ * mit `credentials:read` — deren Geheimnisse holen. In einer Browser- oder
11
+ * Mobil-App ist er ausgeliefert, nicht hinterlegt.
12
+ */
13
+ import type { KasseneckAuth } from '../client/auth.js';
14
+ import type { PartnerEnv } from './typen.js';
15
+ /**
16
+ * Die Umgebung eines Partner-Schluessels, ohne Netzaufruf — `null`, wenn es
17
+ * keiner ist. Nuetzlich fuer die Zusicherung „auf diesem Server laeuft nur
18
+ * `pk_live_`" beim Hochfahren.
19
+ */
20
+ export declare function partnerKeyEnv(schluessel: string): PartnerEnv | null;
21
+ export interface PartnerKeyAuthOptions {
22
+ /** Partner-Schluessel `pk_test_…` / `pk_live_…`. */
23
+ partnerKey: string;
24
+ }
25
+ /**
26
+ * Anmeldung per Partner-Schluessel.
27
+ *
28
+ * Die Form wird hier geprueft und nicht erst vom Server: ein vertauschter
29
+ * `kr_live_`-Schluessel (der eines Betriebs) faellt sonst als nichtssagendes
30
+ * „ungueltiger Schluessel" auf, obwohl er tadellos ist — nur eben fuer einen
31
+ * anderen Weg. Die Meldung nennt nie den Wert, nur seine Art.
32
+ */
33
+ export declare function partnerKeyAuth(options: PartnerKeyAuthOptions): KasseneckAuth;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Anmeldung eines Partner-Servers: der Partner-Schluessel als Bearer.
3
+ *
4
+ * Ein dritter Weg neben `apiKeyAuth` (Geraete) und `registerUserAuth`
5
+ * (Browser-Kasse) — und der einzige, mit dem die Partner-Endpunkte antworten.
6
+ * Er traegt **keine** Kopfzeile `cashregister-token`: ein Partner arbeitet nie
7
+ * an einer Kasse, sondern ueber Betriebe.
8
+ *
9
+ * **Der Schluessel gehoert auf einen Server.** Er kann Betriebe anlegen und —
10
+ * mit `credentials:read` — deren Geheimnisse holen. In einer Browser- oder
11
+ * Mobil-App ist er ausgeliefert, nicht hinterlegt.
12
+ */
13
+ import { KasseneckAuthError } from '../client/errors.js';
14
+ /**
15
+ * Form eines Partner-Schluessels: `pk_test_…` bzw. `pk_live_…`. Der Rest ist
16
+ * opak — die Laenge kann sich aendern, das Praefix nicht (an ihm haengt die
17
+ * Umgebung).
18
+ */
19
+ const SCHLUESSEL_FORM = /^pk_(test|live)_[A-Za-z0-9_-]{16,}$/;
20
+ /**
21
+ * Die Umgebung eines Partner-Schluessels, ohne Netzaufruf — `null`, wenn es
22
+ * keiner ist. Nuetzlich fuer die Zusicherung „auf diesem Server laeuft nur
23
+ * `pk_live_`" beim Hochfahren.
24
+ */
25
+ export function partnerKeyEnv(schluessel) {
26
+ const treffer = SCHLUESSEL_FORM.exec(typeof schluessel === 'string' ? schluessel.trim() : '');
27
+ return treffer ? treffer[1] : null;
28
+ }
29
+ /**
30
+ * Anmeldung per Partner-Schluessel.
31
+ *
32
+ * Die Form wird hier geprueft und nicht erst vom Server: ein vertauschter
33
+ * `kr_live_`-Schluessel (der eines Betriebs) faellt sonst als nichtssagendes
34
+ * „ungueltiger Schluessel" auf, obwohl er tadellos ist — nur eben fuer einen
35
+ * anderen Weg. Die Meldung nennt nie den Wert, nur seine Art.
36
+ */
37
+ export function partnerKeyAuth(options) {
38
+ const schluessel = typeof options?.partnerKey === 'string' ? options.partnerKey.trim() : '';
39
+ if (!schluessel) {
40
+ throw new KasseneckAuthError('partnerKeyAuth: partnerKey fehlt');
41
+ }
42
+ if (!partnerKeyEnv(schluessel)) {
43
+ throw new KasseneckAuthError('partnerKeyAuth: partnerKey hat nicht die Form pk_test_… / pk_live_… — ein Betriebsschluessel (kr_…) passt hier nicht');
44
+ }
45
+ // Pro Aufruf ein frisches Objekt: der Transport darf daran schreiben, ohne
46
+ // die naechste Anfrage zu vergiften (wie apiKeyAuth).
47
+ return () => ({ headers: { Authorization: `Bearer ${schluessel}` }, params: {} });
48
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Was ein Betrieb ueber die Schnittstelle mitbringen darf — Feld fuer Feld.
3
+ *
4
+ * **Unbekannte Felder werden abgewiesen, nicht stillschweigend verworfen.**
5
+ * Vorher verschwand ein `iban` oder ein vertippter Feldname spurlos: der
6
+ * Partner glaubte, er habe die Steuernummer geschickt, und Kasseneck hatte
7
+ * nichts. Ein Feld, das man schickt und das nichts bewirkt, ist der teuerste
8
+ * Fehler in einer Schnittstelle, weil ihn niemand bemerkt.
9
+ *
10
+ * Seitdem antwortet `createPartnerCustomer` mit `validation` und dem genauen
11
+ * Feldpfad — verschachtelt und je Kontakt: `address.land`,
12
+ * `tax_details.ustid`, `contacts.1.abteilung`.
13
+ *
14
+ * Zwei Dinge halten diese Seite dagegen:
15
+ *
16
+ * - der Typ [Betrieb] (typen.ts) hat genau diese Felder, und TypeScript meldet
17
+ * ein ueberzaehliges schon beim Tippen;
18
+ * - [unbekannteBetriebsfelder] beantwortet dieselbe Frage zur Laufzeit — fuer
19
+ * Daten, die aus einer Datenbank oder einem Formular kommen und deshalb nie
20
+ * durch die Typpruefung gelaufen sind.
21
+ *
22
+ * **Die Wahrheit bleibt der Server.** Dieser Client weist nichts von sich aus
23
+ * ab: eine spaetere Backend-Fassung darf ein Feld ergaenzen, ohne dass ein
24
+ * aelterer Client es blockiert. [unbekannteBetriebsfelder] ist die Vorschau,
25
+ * nicht das Tor.
26
+ *
27
+ * Quelle der Liste: `partner-core.BETRIEB_FELDER` im Backend.
28
+ */
29
+ /**
30
+ * Jedes erlaubte Feld als Pfad. `[]` markiert eine Liste — im Fehlerpfad des
31
+ * Servers steht dort der Index (`contacts.0.name`).
32
+ *
33
+ * Bewusst flach und nicht verschachtelt: so ist es EINE Liste, die der
34
+ * Zwilling Zeile fuer Zeile nachhalten kann. Das Schema fuer die Pruefung
35
+ * entsteht daraus (siehe unten) und nicht daneben.
36
+ */
37
+ export declare const BETRIEB_FELDER: readonly ["companyName", "legalForm", "state", "industry", "companyRegister", "court", "web", "phone", "email", "billingEmail", "address.street", "address.number", "address.zip", "address.city", "taxDetails.taxNumber", "taxDetails.vatId", "taxDetails.gln", "taxDetails.smallBusiness", "contacts[].name", "contacts[].email", "contacts[].phone", "contacts[].roles", "taxAdvisor.name", "taxAdvisor.email", "taxAdvisor.phone", "taxAdvisor.mayContact"];
38
+ export type BetriebFeld = typeof BETRIEB_FELDER[number];
39
+ /**
40
+ * Die Feldpfade eines Betriebs, die [BETRIEB_FELDER] nicht kennt — dieselbe
41
+ * Ableitung wie im Backend, also dieselben Pfade wie in `data.errors[].field`.
42
+ * Leer heisst: aus dieser Sicht ist nichts ueberzaehlig.
43
+ *
44
+ * Sagt **nichts** ueber die Werte: Steuernummer, UID, PLZ und Gericht prueft
45
+ * das Backend mit `@kreiseck/validator`. Diese Funktion beantwortet nur die
46
+ * Frage „schicke ich etwas, das dort niemand erwartet?".
47
+ */
48
+ export declare function unbekannteBetriebsfelder(business: unknown): string[];