@kreiseck/kasseneck-api 0.27.3 → 0.29.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 (36) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +87 -2
  3. package/dist/cjs/partner/ablauf.d.ts +4 -3
  4. package/dist/cjs/partner/ablauf.js +4 -3
  5. package/dist/cjs/partner/api.d.ts +20 -8
  6. package/dist/cjs/partner/api.js +12 -1
  7. package/dist/cjs/partner/endpunkte.d.ts +9 -12
  8. package/dist/cjs/partner/endpunkte.js +85 -26
  9. package/dist/cjs/partner/fehler.d.ts +28 -2
  10. package/dist/cjs/partner/fehler.js +58 -9
  11. package/dist/cjs/partner/index.d.ts +7 -3
  12. package/dist/cjs/partner/index.js +7 -2
  13. package/dist/cjs/partner/typen.d.ts +152 -35
  14. package/dist/cjs/partner/typen.js +4 -0
  15. package/dist/cjs/partner/webhooks.d.ts +76 -14
  16. package/dist/cjs/partner/webhooks.js +40 -12
  17. package/dist/esm/partner/ablauf.d.ts +4 -3
  18. package/dist/esm/partner/ablauf.js +4 -3
  19. package/dist/esm/partner/api.d.ts +20 -8
  20. package/dist/esm/partner/api.js +11 -1
  21. package/dist/esm/partner/endpunkte.d.ts +9 -12
  22. package/dist/esm/partner/endpunkte.js +85 -26
  23. package/dist/esm/partner/fehler.d.ts +28 -2
  24. package/dist/esm/partner/fehler.js +58 -9
  25. package/dist/esm/partner/index.d.ts +7 -3
  26. package/dist/esm/partner/index.js +5 -1
  27. package/dist/esm/partner/typen.d.ts +152 -35
  28. package/dist/esm/partner/typen.js +4 -0
  29. package/dist/esm/partner/webhooks.d.ts +76 -14
  30. package/dist/esm/partner/webhooks.js +40 -12
  31. package/fixtures/hobex-hps-codes.json +1 -1
  32. package/fixtures/kasse-texte.json +1 -1
  33. package/fixtures/oberflaeche.json +12 -4
  34. package/fixtures/rechnung-api.schema.json +1 -1
  35. package/fixtures/rechnung-texte.json +1 -1
  36. package/package.json +1 -1
@@ -26,11 +26,11 @@ const webhook_signatur_js_1 = require("./webhook-signatur.js");
26
26
  * Alle Ereignisse, die ein Webhook abonnieren **und proben** kann. Ein
27
27
  * Endpunkt bekommt ausschliesslich die, die in seiner `events`-Liste stehen.
28
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.
29
+ * **Noch nicht in der Liste:** `customer.avv_accepted` und
30
+ * `customer.terms_accepted`. Das Backend bietet sie inzwischen zum Abonnieren
31
+ * an; die Liste wird mit dem Dart-Zwilling gemeinsam erweitert, weil beide
32
+ * gegeneinander geprueft werden. Abonnieren geht trotzdem schon (`events`
33
+ * nimmt jeden Namen), die Nutzlast beschreibt [ContractAcceptedEventData].
34
34
  */
35
35
  exports.PARTNER_WEBHOOK_EVENTS = [
36
36
  'customer.created',
@@ -112,16 +112,30 @@ async function parseWebhookEvent(optionen) {
112
112
  function objekt(wert) {
113
113
  return wert !== null && typeof wert === 'object' && !Array.isArray(wert) ? wert : {};
114
114
  }
115
+ function letzteZustellung(wert) {
116
+ if (wert === null || typeof wert !== 'object' || Array.isArray(wert))
117
+ return null;
118
+ const z = wert;
119
+ return {
120
+ at: typeof z['at'] === 'number' ? z['at'] : null,
121
+ status: typeof z['status'] === 'string' ? z['status'] : '',
122
+ statusCode: typeof z['statusCode'] === 'number' ? z['statusCode'] : null,
123
+ };
124
+ }
115
125
  function webhook(eintrag) {
116
126
  const w = objekt(eintrag);
117
127
  return {
118
128
  webhookId: typeof w['webhookId'] === 'string' ? w['webhookId'] : '',
129
+ // Fehlt das Feld, ist es ein Bestands-Webhook: der spricht v1. Kein
130
+ // `v3` aus Kulanz, sonst verzweigt ein Empfaenger auf englische Werte,
131
+ // die nie kommen.
132
+ apiVersion: typeof w['apiVersion'] === 'string' && w['apiVersion'] ? w['apiVersion'] : 'v1',
119
133
  url: typeof w['url'] === 'string' ? w['url'] : '',
120
134
  events: Array.isArray(w['events']) ? w['events'].filter((e) => typeof e === 'string') : [],
121
135
  active: w['active'] !== false,
122
136
  description: typeof w['description'] === 'string' ? w['description'] : null,
123
137
  createdAt: typeof w['createdAt'] === 'number' ? w['createdAt'] : null,
124
- lastDelivery: typeof w['lastDelivery'] === 'number' ? w['lastDelivery'] : null,
138
+ lastDelivery: letzteZustellung(w['lastDelivery']),
125
139
  consecutiveFailures: typeof w['consecutiveFailures'] === 'number' ? w['consecutiveFailures'] : 0,
126
140
  };
127
141
  }
@@ -198,13 +212,19 @@ async function updatePartnerWebhook(rufen, webhookId, patch) {
198
212
  const daten = objekt(await rufen('updatePartnerWebhook', { webhookId: id, patch }));
199
213
  return webhook(daten['webhook']);
200
214
  }
201
- /** Loescht einen Endpunkt. Danach kommt dort nichts mehr an. */
215
+ /**
216
+ * Loescht einen Endpunkt. Danach kommt dort nichts mehr an; offene
217
+ * Zustellungen werden verworfen (`dropped`).
218
+ */
202
219
  async function deletePartnerWebhook(rufen, webhookId) {
203
220
  const id = typeof webhookId === 'string' ? webhookId.trim() : '';
204
221
  if (!id)
205
222
  throw new errors_js_1.KasseneckValidationError('deletePartnerWebhook', 'webhookId fehlt', 'request');
206
223
  const daten = objekt(await rufen('deletePartnerWebhook', { webhookId: id }));
207
- return typeof daten['webhookId'] === 'string' ? daten['webhookId'] : id;
224
+ return {
225
+ webhookId: typeof daten['webhookId'] === 'string' ? daten['webhookId'] : id,
226
+ deleted: daten['deleted'] === true,
227
+ };
208
228
  }
209
229
  /**
210
230
  * Schickt eine Probe an genau diesen Endpunkt.
@@ -234,8 +254,16 @@ async function sendPartnerWebhookTest(rufen, webhookId, event) {
234
254
  const daten = objekt(await rufen('sendPartnerWebhookTest', { webhookId: id, event: ereignis || undefined }));
235
255
  return {
236
256
  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'] : [],
257
+ event: typeof daten['event'] === 'string' ? daten['event'] : ereignis || 'webhook.test',
258
+ deliveries: (Array.isArray(daten['deliveries']) ? daten['deliveries'] : []).map((eintrag) => {
259
+ const z = objekt(eintrag);
260
+ return {
261
+ deliveryId: typeof z['deliveryId'] === 'string' ? z['deliveryId'] : '',
262
+ webhookId: typeof z['webhookId'] === 'string' ? z['webhookId'] : id,
263
+ status: typeof z['status'] === 'string' ? z['status'] : '',
264
+ statusCode: typeof z['statusCode'] === 'number' ? z['statusCode'] : null,
265
+ };
266
+ }),
239
267
  };
240
268
  }
241
269
  /**
@@ -259,8 +287,8 @@ async function listPartnerWebhookDeliveries(rufen, optionen = {}) {
259
287
  eventId: txt(z['eventId']) ?? '',
260
288
  status: txt(z['status']) ?? '',
261
289
  attempts: zahl(z['attempts']) ?? 0,
262
- letzterVersuchAt: zahl(z['letzterVersuchAt']),
263
- naechsterVersuchAt: zahl(z['naechsterVersuchAt']),
290
+ lastAttemptAt: zahl(z['lastAttemptAt']),
291
+ nextAttemptAt: zahl(z['nextAttemptAt']),
264
292
  statusCode: zahl(z['statusCode']),
265
293
  response: txt(z['response']),
266
294
  createdAt: zahl(z['createdAt']),
@@ -10,9 +10,10 @@
10
10
  * Fehler. Hier steht sie vorher — abfragbar, ausgebbar, und in
11
11
  * [naechsterSchritt] auch beantwortbar.
12
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.
13
+ * **Kein eigener Vertragsschritt.** AVV und Nutzungsvertrag bestaetigt der
14
+ * Betrieb ueber denselben Einrichtungs-Link wie seinen FinanzOnline-Zugang
15
+ * (Schritt `fon`). Live geht ohne beide keine Kasse live
16
+ * (`vertrag_offen`); den Stand zeigen `avv` und `terms` am Betrieb.
16
17
  *
17
18
  * Zwei Dinge laufen bewusst **parallel**: der Signaturantrag und das Anlegen
18
19
  * der Kasse. Eine mit `automatic:true` angelegte Kasse wartet, bis die
@@ -10,9 +10,10 @@
10
10
  * Fehler. Hier steht sie vorher — abfragbar, ausgebbar, und in
11
11
  * [naechsterSchritt] auch beantwortbar.
12
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.
13
+ * **Kein eigener Vertragsschritt.** AVV und Nutzungsvertrag bestaetigt der
14
+ * Betrieb ueber denselben Einrichtungs-Link wie seinen FinanzOnline-Zugang
15
+ * (Schritt `fon`). Live geht ohne beide keine Kasse live
16
+ * (`vertrag_offen`); den Stand zeigen `avv` und `terms` am Betrieb.
16
17
  *
17
18
  * Zwei Dinge laufen bewusst **parallel**: der Signaturantrag und das Anlegen
18
19
  * der Kasse. Eine mit `automatic:true` angelegte Kasse wartet, bis die
@@ -5,12 +5,27 @@
5
5
  * Funktionen und bleiben einzeln importierbar.
6
6
  */
7
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';
8
+ import { type CreateWebhookOptions, type CreateWebhookResult, type DeleteWebhookResult, 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, RequestSignatureOptions, RequestSignatureResult, SignaturStand } from './typen.js';
10
+ /**
11
+ * Die Basis-URL der Partner-API: `/v3`, die englische Fassung.
12
+ *
13
+ * Nur der Partner-Teil spricht `/v3`. Belege, Rechnungen, Kasse und Zahlungen
14
+ * laufen weiter ueber [DEFAULT_BASE_URL] (`/v1`), bis es fuer sie eine `/v3`
15
+ * gibt. Die Partner-Endpunkte unter `/v1` antworten weiter, deutsch und
16
+ * abgekuendigt (Kopfzeilen `Deprecation`/`Sunset`); dieser Client spricht sie
17
+ * nicht mehr.
18
+ */
19
+ export declare const PARTNER_BASE_URL = "https://api.kasseneck.at/v3";
10
20
  export interface PartnerApiOptions {
11
21
  /** Partner-Schluessel `pk_test_…` / `pk_live_…`. Gehoert auf einen Server. */
12
22
  partnerKey: string;
13
- /** Abweichende Basis-URL; Vorgabe `https://api.kasseneck.at/v1`. */
23
+ /**
24
+ * Abweichende Basis-URL; Vorgabe [PARTNER_BASE_URL]
25
+ * (`https://api.kasseneck.at/v3`). Die Antworten werden in der Form der
26
+ * `/v3` gelesen: eine `/v1`-Adresse hier liefert deutsche Werte, die diese
27
+ * Typen nicht beschreiben.
28
+ */
14
29
  baseUrl?: string;
15
30
  /** Zeitlimit je Aufruf in Millisekunden. */
16
31
  timeoutMs?: number;
@@ -23,10 +38,7 @@ export interface PartnerApi {
23
38
  listPartnerCustomers(optionen?: ListCustomersOptions): Promise<KundenListe>;
24
39
  getPartnerCustomer(customerId: string): Promise<Kunde>;
25
40
  sendPartnerCustomerFonLink(customerId: string): Promise<FonLinkResult>;
26
- requestCustomerSignature(customerId: string, optionen?: {
27
- art?: string;
28
- additional?: boolean;
29
- }): Promise<RequestSignatureResult>;
41
+ requestCustomerSignature(customerId: string, optionen?: RequestSignatureOptions): Promise<RequestSignatureResult>;
30
42
  getCustomerSignatureStatus(customerId: string): Promise<SignaturStand>;
31
43
  createCustomerCashregister(optionen: CreateCashregisterOptions): Promise<CreateCashregisterResult>;
32
44
  activateCashregister(customerId: string, cashregisterId: string): Promise<ActivateCashregisterResult>;
@@ -42,7 +54,7 @@ export interface PartnerApi {
42
54
  createPartnerWebhook(optionen: CreateWebhookOptions): Promise<CreateWebhookResult>;
43
55
  listPartnerWebhooks(): Promise<WebhookListe>;
44
56
  updatePartnerWebhook(webhookId: string, patch: WebhookPatch): Promise<PartnerWebhook>;
45
- deletePartnerWebhook(webhookId: string): Promise<string>;
57
+ deletePartnerWebhook(webhookId: string): Promise<DeleteWebhookResult>;
46
58
  /**
47
59
  * Neues Secret fuer denselben Endpunkt — dieselbe `webhookId`, dieselben
48
60
  * Ereignisse. Das alte gilt ab der Antwort nicht mehr.
@@ -9,10 +9,20 @@ import { partnerKeyAuth } from './auth.js';
9
9
  import { partnerFehlerRat } from './fehler.js';
10
10
  import { activateCashregister, createCustomerCashregister, createPartnerCustomer, getCustomerCredentials, getCustomerSignatureStatus, getPartnerCustomer, getPartnerInfo, checkPartnerCustomerEmail, listCustomerCashregisters, listPartnerCustomers, requestCustomerSignature, sendPartnerCustomerFonLink, } from './endpunkte.js';
11
11
  import { createPartnerWebhook, deletePartnerWebhook, listPartnerWebhookDeliveries, listPartnerWebhooks, rotatePartnerWebhookSecret, sendPartnerWebhookTest, updatePartnerWebhook, } from './webhooks.js';
12
+ /**
13
+ * Die Basis-URL der Partner-API: `/v3`, die englische Fassung.
14
+ *
15
+ * Nur der Partner-Teil spricht `/v3`. Belege, Rechnungen, Kasse und Zahlungen
16
+ * laufen weiter ueber [DEFAULT_BASE_URL] (`/v1`), bis es fuer sie eine `/v3`
17
+ * gibt. Die Partner-Endpunkte unter `/v1` antworten weiter, deutsch und
18
+ * abgekuendigt (Kopfzeilen `Deprecation`/`Sunset`); dieser Client spricht sie
19
+ * nicht mehr.
20
+ */
21
+ export const PARTNER_BASE_URL = 'https://api.kasseneck.at/v3';
12
22
  export function createPartnerApi(optionen) {
13
23
  const rufen = createTransport({
14
24
  auth: partnerKeyAuth({ partnerKey: optionen.partnerKey }),
15
- baseUrl: optionen.baseUrl,
25
+ baseUrl: optionen.baseUrl ?? PARTNER_BASE_URL,
16
26
  timeoutMs: optionen.timeoutMs,
17
27
  fetch: optionen.fetch,
18
28
  });
@@ -19,7 +19,7 @@
19
19
  * einen `TypeError` an unpassender Stelle.
20
20
  */
21
21
  import type { InternerTransport } from '../client/aufrufe.js';
22
- import type { ActivateCashregisterResult, CreateCashregisterOptions, CreateCashregisterResult, CreateCustomerOptions, CreateCustomerResult, CustomerCredentials, FonLinkResult, KassenListe, Kunde, KundenListe, ListCustomersOptions, PartnerInfo, RequestSignatureResult, SignaturStand } from './typen.js';
22
+ import type { ActivateCashregisterResult, CreateCashregisterOptions, CreateCashregisterResult, CreateCustomerOptions, CreateCustomerResult, CustomerCredentials, FonLinkResult, KassenListe, Kunde, KundenListe, ListCustomersOptions, PartnerInfo, RequestSignatureOptions, RequestSignatureResult, SignaturStand } from './typen.js';
23
23
  /**
24
24
  * Wer bin ich, in welcher Umgebung, mit welchen Rechten — und welche Apps
25
25
  * gehoeren mir. `apps[].id` ist die `appId` fuer [createPartnerCustomer].
@@ -76,8 +76,8 @@ export declare function sendPartnerCustomerFonLink(rufen: InternerTransport, cus
76
76
  * Vertrauensdiensteanbieter **auf diesen Betrieb** ausstellen und meldet sie
77
77
  * bei FinanzOnline an; einen Vorrat fertiger Karten gibt es nicht.
78
78
  *
79
- * Der Antrag erzeugt sofort ein Signatur-OBJEKT: `antrag.requestId` ist
80
- * zugleich die `signaturId`, auf die sich eine Kasse beruft — auch solange
79
+ * Der Antrag erzeugt sofort ein Signatur-OBJEKT: `request.requestId` ist
80
+ * zugleich die `signatureRequestId`, auf die sich eine Kasse beruft — auch solange
81
81
  * noch keine Karte zugewiesen ist.
82
82
  *
83
83
  * **Je Betrieb laeuft nur ein Antrag.** Ein zweiter Aufruf liefert den
@@ -87,23 +87,20 @@ export declare function sendPartnerCustomerFonLink(rufen: InternerTransport, cus
87
87
  * Abschluss kommt als Ereignis `signature.ready`, nicht als Antwort auf diesen
88
88
  * Aufruf.
89
89
  */
90
- export declare function requestCustomerSignature(rufen: InternerTransport, customerId: string, optionen?: {
91
- art?: string;
92
- additional?: boolean;
93
- }): Promise<RequestSignatureResult>;
94
- /** Stand der Signatur eines Betriebs samt aller Antraege und des FON-Zugangs. */
90
+ export declare function requestCustomerSignature(rufen: InternerTransport, customerId: string, optionen?: RequestSignatureOptions): Promise<RequestSignatureResult>;
91
+ /** Stand der Signatur eines Betriebs samt aller Signaturen, Antraege und des FON-Zugangs. */
95
92
  export declare function getCustomerSignatureStatus(rufen: InternerTransport, customerId: string): Promise<SignaturStand>;
96
93
  /**
97
94
  * Legt eine Kasse an.
98
95
  *
99
96
  * **Jede Kasse bezieht sich auf eine Signatur.** Ohne eine einzige — auch eine
100
97
  * noch laufende zaehlt — entsteht keine (`signature_missing`); bei mehreren
101
- * muss `signaturId` dastehen (`signature_ambiguous`).
98
+ * muss `signatureRequestId` dastehen (`signature_ambiguous`).
102
99
  *
103
100
  * **Darf vor der fertigen Signatur aufgerufen werden:** die Kasse bleibt dann
104
- * auf `entwurf` und geht von selbst live, sobald IHRE Signatur bereit ist
105
- * (`automatic:true`, Vorgabe). `inbetriebnahme.reason` sagt, warum gerade
106
- * nichts lief: `signature_not_ready` oder `automatik_aus`.
101
+ * auf `draft` und geht von selbst live, sobald IHRE Signatur bereit ist
102
+ * (`automatic:true`, Vorgabe). `activation.reason` sagt, warum gerade
103
+ * nichts lief: `signature_not_ready` oder `automation_off`.
107
104
  *
108
105
  * Hoechstens 20 Kassen je Betrieb (`cashregister_limit`); ohne gebuchtes Modul
109
106
  * `module_inactive`.
@@ -45,6 +45,19 @@ function zahlOderNull(wert) {
45
45
  function jaNein(wert, rueckfall = false) {
46
46
  return typeof wert === 'boolean' ? wert : rueckfall;
47
47
  }
48
+ /**
49
+ * Das gebuchte Entgelt (`fee`), falls die Antwort eines fuehrt. Ohne Betrag
50
+ * ist es keines: ein erfundenes `0` saehe aus wie ein kostenloser Posten.
51
+ */
52
+ function entgelt(wert) {
53
+ if (wert === null || typeof wert !== 'object' || Array.isArray(wert))
54
+ return null;
55
+ const f = wert;
56
+ const cents = zahlOderNull(f['cents']);
57
+ if (cents === null)
58
+ return null;
59
+ return { cents, interval: text(f['interval']), test: jaNein(f['test']) };
60
+ }
48
61
  /**
49
62
  * Verlangt ein Feld der Antwort. Der Fehler nennt das Feld und den Vorgang,
50
63
  * damit ein Aufrufer nicht raten muss, welcher der Aufrufe etwas anderes
@@ -158,6 +171,7 @@ export async function createPartnerCustomer(rufen, optionen) {
158
171
  appId: text(daten['appId'], appId),
159
172
  access: { invited: jaNein(zugang['invited']), sentTo: textOderNull(zugang['sentTo']) },
160
173
  nextSteps: liste(daten['nextSteps']).filter((s) => typeof s === 'string'),
174
+ fee: entgelt(daten['fee']),
161
175
  replayed: jaNein(daten['replayed']),
162
176
  };
163
177
  }
@@ -186,26 +200,38 @@ function kundenZeile(eintrag) {
186
200
  appId: textOderNull(k['appId']),
187
201
  env: text(k['env']) === 'test' ? 'test' : 'live',
188
202
  createdAt: zahlOderNull(k['createdAt']),
203
+ fon: fonStand(k['fon']),
189
204
  avv: avvStand(k['avv']),
205
+ terms: vertragStand(k['terms']),
190
206
  };
191
207
  }
192
- /**
193
- * Der Vertragsstand, **falls** die Antwort ihn ueberhaupt fuehrt — heute tut
194
- * sie das nicht, dann bleibt es bei `null`. Kein erfundenes `offen`: „nicht
195
- * mitgeliefert" und „nicht bestaetigt" duerfen fuer einen Aufrufer nicht
196
- * dasselbe sein.
197
- */
198
- function avvStand(wert) {
208
+ function fonStand(wert) {
199
209
  if (wert === null || typeof wert !== 'object' || Array.isArray(wert))
200
210
  return null;
201
- const a = wert;
211
+ const f = wert;
202
212
  return {
203
- status: text(a['status']),
204
- version: textOderNull(a['version']),
205
- confirmedAt: zahlOderNull(a['confirmedAt']),
206
- mode: textOderNull(a['mode']),
213
+ configured: jaNein(f['configured']),
214
+ linkSentAt: zahlOderNull(f['linkSentAt']),
215
+ linkOpenedAt: zahlOderNull(f['linkOpenedAt']),
207
216
  };
208
217
  }
218
+ function vertragStand(wert) {
219
+ if (wert === null || typeof wert !== 'object' || Array.isArray(wert))
220
+ return null;
221
+ const v = wert;
222
+ return { status: text(v['status']), version: textOderNull(v['version']), confirmedAt: zahlOderNull(v['confirmedAt']) };
223
+ }
224
+ /**
225
+ * Der AVV-Stand, **falls** die Antwort ihn fuehrt; sonst `null`. Kein
226
+ * erfundenes `pending`: „nicht mitgeliefert" und „nicht bestaetigt" duerfen
227
+ * fuer einen Aufrufer nicht dasselbe sein.
228
+ */
229
+ function avvStand(wert) {
230
+ const v = vertragStand(wert);
231
+ if (!v)
232
+ return null;
233
+ return { ...v, mode: textOderNull(wert['mode']) };
234
+ }
209
235
  /** Ein Betrieb mit allem, was der Partner ueber ihn sehen darf — nie Geheimnisse. */
210
236
  export async function getPartnerCustomer(rufen, customerId) {
211
237
  const id = pflicht(customerId, 'getPartnerCustomer', 'customerId');
@@ -220,7 +246,13 @@ export async function getPartnerCustomer(rufen, customerId) {
220
246
  createdAt: zahlOderNull(k['createdAt']),
221
247
  createdVia: textOderNull(k['createdVia']),
222
248
  business: objekt(k['business']),
223
- fon: { configured: jaNein(fon['configured']), verifiedAt: zahlOderNull(fon['verifiedAt']) },
249
+ fon: {
250
+ configured: jaNein(fon['configured']),
251
+ verifiedAt: zahlOderNull(fon['verifiedAt']),
252
+ linkSentAt: zahlOderNull(fon['linkSentAt']),
253
+ linkSentTo: textOderNull(fon['linkSentTo']),
254
+ linkOpenedAt: zahlOderNull(fon['linkOpenedAt']),
255
+ },
224
256
  access: zugang === null || typeof zugang !== 'object'
225
257
  ? null
226
258
  : {
@@ -274,7 +306,7 @@ function antrag(eintrag) {
274
306
  requestId: text(a['requestId']),
275
307
  status: text(a['status']),
276
308
  statusText: text(a['statusText']),
277
- art: text(a['art'], 'signature_card'),
309
+ kind: text(a['kind'], 'signature_card'),
278
310
  vdaId: textOderNull(a['vdaId']),
279
311
  signatureId: textOderNull(a['signatureId']),
280
312
  error: fehler === null || typeof fehler !== 'object'
@@ -290,8 +322,8 @@ function antrag(eintrag) {
290
322
  history: liste(a['history']).map((h) => {
291
323
  const e = objekt(h);
292
324
  return {
293
- von: textOderNull(e['von']),
294
- nach: text(e['nach']),
325
+ from: textOderNull(e['from']),
326
+ to: text(e['to']),
295
327
  at: zahlOderNull(e['at']) ?? 0,
296
328
  reason: textOderNull(e['reason']),
297
329
  };
@@ -303,8 +335,8 @@ function antrag(eintrag) {
303
335
  * Vertrauensdiensteanbieter **auf diesen Betrieb** ausstellen und meldet sie
304
336
  * bei FinanzOnline an; einen Vorrat fertiger Karten gibt es nicht.
305
337
  *
306
- * Der Antrag erzeugt sofort ein Signatur-OBJEKT: `antrag.requestId` ist
307
- * zugleich die `signaturId`, auf die sich eine Kasse beruft — auch solange
338
+ * Der Antrag erzeugt sofort ein Signatur-OBJEKT: `request.requestId` ist
339
+ * zugleich die `signatureRequestId`, auf die sich eine Kasse beruft — auch solange
308
340
  * noch keine Karte zugewiesen ist.
309
341
  *
310
342
  * **Je Betrieb laeuft nur ein Antrag.** Ein zweiter Aufruf liefert den
@@ -318,27 +350,54 @@ export async function requestCustomerSignature(rufen, customerId, optionen = {})
318
350
  const id = pflicht(customerId, 'requestCustomerSignature', 'customerId');
319
351
  const daten = objekt(await rufen('requestCustomerSignature', {
320
352
  customerId: id,
321
- art: optionen.art,
353
+ kind: optionen.kind,
322
354
  additional: optionen.additional,
323
355
  }));
324
356
  return {
325
357
  request: antrag(verlangt(daten['request'], 'requestCustomerSignature', 'request')),
326
358
  replayed: jaNein(daten['replayed']),
327
359
  note: textOderNull(daten['note']),
360
+ fee: entgelt(daten['fee']),
328
361
  };
329
362
  }
330
- /** Stand der Signatur eines Betriebs samt aller Antraege und des FON-Zugangs. */
363
+ function signaturEinzeln(eintrag) {
364
+ const s = objekt(eintrag);
365
+ const fehler = s['error'];
366
+ return {
367
+ signatureRequestId: text(s['signatureRequestId']),
368
+ status: text(s['status']),
369
+ statusText: text(s['statusText']),
370
+ inProgress: jaNein(s['inProgress']),
371
+ ready: jaNein(s['ready']),
372
+ kind: text(s['kind'], 'signature_card'),
373
+ vdaId: textOderNull(s['vdaId']),
374
+ requestId: textOderNull(s['requestId']),
375
+ signatureId: textOderNull(s['signatureId']),
376
+ error: fehler === null || typeof fehler !== 'object'
377
+ ? null
378
+ : {
379
+ code: textOderNull(objekt(fehler)['code']),
380
+ message: textOderNull(objekt(fehler)['message']),
381
+ rc: textOderNull(objekt(fehler)['rc']),
382
+ },
383
+ createdAt: zahlOderNull(s['createdAt']),
384
+ updatedAt: zahlOderNull(s['updatedAt']),
385
+ };
386
+ }
387
+ /** Stand der Signatur eines Betriebs samt aller Signaturen, Antraege und des FON-Zugangs. */
331
388
  export async function getCustomerSignatureStatus(rufen, customerId) {
332
389
  const id = pflicht(customerId, 'getCustomerSignatureStatus', 'customerId');
333
390
  const daten = objekt(await rufen('getCustomerSignatureStatus', { customerId: id }));
334
- const signatur = objekt(daten['signatur']);
391
+ const signatur = objekt(daten['signature']);
335
392
  const fon = objekt(daten['fon']);
336
393
  return {
337
- signatur: {
394
+ customerId: text(daten['customerId'], id),
395
+ signature: {
338
396
  ready: jaNein(signatur['ready']),
339
397
  signatureId: textOderNull(signatur['signatureId']),
340
398
  vdaId: textOderNull(signatur['vdaId']),
341
399
  },
400
+ signatures: liste(daten['signatures']).map(signaturEinzeln),
342
401
  requests: liste(daten['requests']).map(antrag),
343
402
  fon: { present: jaNein(fon['present']), verifiedAt: zahlOderNull(fon['verifiedAt']) },
344
403
  };
@@ -378,12 +437,12 @@ function kasse(eintrag) {
378
437
  *
379
438
  * **Jede Kasse bezieht sich auf eine Signatur.** Ohne eine einzige — auch eine
380
439
  * noch laufende zaehlt — entsteht keine (`signature_missing`); bei mehreren
381
- * muss `signaturId` dastehen (`signature_ambiguous`).
440
+ * muss `signatureRequestId` dastehen (`signature_ambiguous`).
382
441
  *
383
442
  * **Darf vor der fertigen Signatur aufgerufen werden:** die Kasse bleibt dann
384
- * auf `entwurf` und geht von selbst live, sobald IHRE Signatur bereit ist
385
- * (`automatic:true`, Vorgabe). `inbetriebnahme.reason` sagt, warum gerade
386
- * nichts lief: `signature_not_ready` oder `automatik_aus`.
443
+ * auf `draft` und geht von selbst live, sobald IHRE Signatur bereit ist
444
+ * (`automatic:true`, Vorgabe). `activation.reason` sagt, warum gerade
445
+ * nichts lief: `signature_not_ready` oder `automation_off`.
387
446
  *
388
447
  * Hoechstens 20 Kassen je Betrieb (`cashregister_limit`); ohne gebuchtes Modul
389
448
  * `module_inactive`.
@@ -19,6 +19,32 @@
19
19
  * unterscheiden. Deshalb stehen hier BEIDE Flaechen: die der Schnittstelle
20
20
  * ([PARTNER_FEHLER_CODES]) und die des Partner-Portals
21
21
  * ([PARTNER_PORTAL_FEHLER_CODES]).
22
+ *
23
+ * **Seit 0.29.0 englisch, wie der Rest von `/v3`.** Dieser Client spricht seit
24
+ * 0.28.0 ausschliesslich `/v3` (`PARTNER_BASE_URL`): die paar deutschen Codes,
25
+ * die der Server bis dahin noch roh durchreichte, kommen jetzt genauso englisch
26
+ * an wie alle anderen. [PARTNER_FEHLER_CODES] fuehrt deshalb ausnahmslos die
27
+ * `/v3`-Schreibweise aus `fehlercodes.json`s `v3`-Zuordnung (deutsch -> englisch);
28
+ * ein Aufrufer bekommt vom Server nie mehr die deutsche Form.
29
+ *
30
+ * **`kein_partnerbetrieb` und `request_not_found` fehlen absichtlich.** Beide
31
+ * sind admin-only (`partner-endpoints.js`, ausserhalb von `FEHLER_KATALOG`) und
32
+ * erreichen `/v3` nie: ein Partner-Aufruf kann sie unter keinem Pfad bekommen.
33
+ * Sie standen frueher versehentlich in dieser Liste; `dart-partner.json` (ein
34
+ * eingefrorener Abzug aus Dart 5.3.0, bevor das korrigiert wurde) fuehrt sie
35
+ * weiterhin, siehe die Ausnahme in `test/partner-enums.test.ts`.
36
+ *
37
+ * **Die Vertrags-Codes (`kind_not_allowed`, `mode_not_allowed`,
38
+ * `power_of_attorney_missing`, `not_found`, `no_version`,
39
+ * `unknown_version`, `text_changed`, `already_accepted`) stehen hier, obwohl
40
+ * dieses Paket `reportCustomerContract` nicht anbietet.** Der Katalog des
41
+ * Backends fuehrt sie mit `flaeche: 'api'`/`'beide'`, dieselbe Regel wie bei
42
+ * den Portal-Codes oben: vollstaendig heisst vollstaendig, auch fuer einen
43
+ * Endpunkt, den (noch) kein Aufruf dieses Clients ausloest. Ein Server-Update,
44
+ * das den Endpunkt ergaenzt, bräuchte dann keinen zweiten Fehlerkatalog-Umbau.
45
+ * `not_required` fehlt bewusst: derselbe gemeinsame Server-Zweig wie die
46
+ * anderen sechs, aber ueber die Partner-API nicht erreichbar (der Server hat
47
+ * ihn aus `FEHLER_KATALOG` entfernt, siehe `partner-core.js` im Backend).
22
48
  */
23
49
  /**
24
50
  * Alle Codes, die die **Schnittstelle** kennt. Als Liste und nicht nur als
@@ -27,7 +53,7 @@
27
53
  *
28
54
  * Reihenfolge und Bestand wie im Abzug des Backends.
29
55
  */
30
- export declare const PARTNER_FEHLER_CODES: readonly ["validation", "rate_limited", "app_not_found", "app_not_accepted", "kein_partnerbetrieb", "live_not_allowed", "customer_exists", "customer_conflict", "customer_limit", "zugang_nicht_erlaubt", "email_taken", "no_email", "fon_missing", "signature_pending", "request_not_found", "signature_missing", "signature_unknown", "signature_ambiguous", "signature_not_ready", "signature_limit", "signature_failed", "module_inactive", "cashregister_limit", "cashregister_not_found", "activation_failed", "webhook_limit", "webhook_inactive", "event_not_subscribed"];
56
+ export declare const PARTNER_FEHLER_CODES: readonly ["validation", "rate_limited", "app_not_found", "app_not_accepted", "live_not_allowed", "customer_exists", "customer_conflict", "customer_limit", "access_not_allowed", "email_taken", "no_email", "tax_number_missing", "fon_missing", "signature_pending", "signature_missing", "signature_unknown", "signature_ambiguous", "signature_not_ready", "signature_limit", "signature_failed", "module_inactive", "cashregister_limit", "cashregister_not_found", "contracts_pending", "kind_not_allowed", "mode_not_allowed", "power_of_attorney_missing", "not_found", "no_version", "unknown_version", "text_changed", "already_accepted", "activation_failed", "webhook_limit", "webhook_inactive", "event_not_subscribed"];
31
57
  /**
32
58
  * Die Codes, die nur im **Partner-Portal** entstehen — beim Pflegen der App,
33
59
  * der Schluessel, der Mitglieder und der Signaturkarten.
@@ -58,7 +84,7 @@ export declare function istPartnerFehler(error: unknown, code: PartnerCode): boo
58
84
  export interface PartnerFeldFehler {
59
85
  /**
60
86
  * Der Feldpfad, so wie er im gesendeten Betrieb steht — verschachtelt und je
61
- * Kontakt: `address.land`, `tax_details.ustid`, `contacts.1.abteilung`.
87
+ * Kontakt: `address.zip`, `taxDetails.taxNumber`, `contacts.0.email`.
62
88
  */
63
89
  field: string;
64
90
  message: string;
@@ -19,6 +19,32 @@
19
19
  * unterscheiden. Deshalb stehen hier BEIDE Flaechen: die der Schnittstelle
20
20
  * ([PARTNER_FEHLER_CODES]) und die des Partner-Portals
21
21
  * ([PARTNER_PORTAL_FEHLER_CODES]).
22
+ *
23
+ * **Seit 0.29.0 englisch, wie der Rest von `/v3`.** Dieser Client spricht seit
24
+ * 0.28.0 ausschliesslich `/v3` (`PARTNER_BASE_URL`): die paar deutschen Codes,
25
+ * die der Server bis dahin noch roh durchreichte, kommen jetzt genauso englisch
26
+ * an wie alle anderen. [PARTNER_FEHLER_CODES] fuehrt deshalb ausnahmslos die
27
+ * `/v3`-Schreibweise aus `fehlercodes.json`s `v3`-Zuordnung (deutsch -> englisch);
28
+ * ein Aufrufer bekommt vom Server nie mehr die deutsche Form.
29
+ *
30
+ * **`kein_partnerbetrieb` und `request_not_found` fehlen absichtlich.** Beide
31
+ * sind admin-only (`partner-endpoints.js`, ausserhalb von `FEHLER_KATALOG`) und
32
+ * erreichen `/v3` nie: ein Partner-Aufruf kann sie unter keinem Pfad bekommen.
33
+ * Sie standen frueher versehentlich in dieser Liste; `dart-partner.json` (ein
34
+ * eingefrorener Abzug aus Dart 5.3.0, bevor das korrigiert wurde) fuehrt sie
35
+ * weiterhin, siehe die Ausnahme in `test/partner-enums.test.ts`.
36
+ *
37
+ * **Die Vertrags-Codes (`kind_not_allowed`, `mode_not_allowed`,
38
+ * `power_of_attorney_missing`, `not_found`, `no_version`,
39
+ * `unknown_version`, `text_changed`, `already_accepted`) stehen hier, obwohl
40
+ * dieses Paket `reportCustomerContract` nicht anbietet.** Der Katalog des
41
+ * Backends fuehrt sie mit `flaeche: 'api'`/`'beide'`, dieselbe Regel wie bei
42
+ * den Portal-Codes oben: vollstaendig heisst vollstaendig, auch fuer einen
43
+ * Endpunkt, den (noch) kein Aufruf dieses Clients ausloest. Ein Server-Update,
44
+ * das den Endpunkt ergaenzt, bräuchte dann keinen zweiten Fehlerkatalog-Umbau.
45
+ * `not_required` fehlt bewusst: derselbe gemeinsame Server-Zweig wie die
46
+ * anderen sechs, aber ueber die Partner-API nicht erreichbar (der Server hat
47
+ * ihn aus `FEHLER_KATALOG` entfernt, siehe `partner-core.js` im Backend).
22
48
  */
23
49
  import { KasseneckApiError } from '../client/errors.js';
24
50
  /**
@@ -34,19 +60,19 @@ export const PARTNER_FEHLER_CODES = [
34
60
  'rate_limited',
35
61
  'app_not_found',
36
62
  'app_not_accepted',
37
- 'kein_partnerbetrieb',
38
63
  'live_not_allowed',
39
64
  // Betrieb anlegen
40
65
  'customer_exists',
41
66
  'customer_conflict',
42
67
  'customer_limit',
43
- 'zugang_nicht_erlaubt',
68
+ 'access_not_allowed',
44
69
  'email_taken',
45
70
  'no_email',
71
+ // FinanzOnline-Link
72
+ 'tax_number_missing',
46
73
  // Signatur
47
74
  'fon_missing',
48
75
  'signature_pending',
49
- 'request_not_found',
50
76
  'signature_missing',
51
77
  'signature_unknown',
52
78
  'signature_ambiguous',
@@ -57,6 +83,17 @@ export const PARTNER_FEHLER_CODES = [
57
83
  'module_inactive',
58
84
  'cashregister_limit',
59
85
  'cashregister_not_found',
86
+ 'contracts_pending',
87
+ // Vertraege (reportCustomerContract: dieses Paket bietet den Endpunkt nicht
88
+ // an, der Katalog fuehrt die Codes trotzdem vollstaendig, siehe oben)
89
+ 'kind_not_allowed',
90
+ 'mode_not_allowed',
91
+ 'power_of_attorney_missing',
92
+ 'not_found',
93
+ 'no_version',
94
+ 'unknown_version',
95
+ 'text_changed',
96
+ 'already_accepted',
60
97
  'activation_failed',
61
98
  // Webhooks
62
99
  'webhook_limit',
@@ -106,26 +143,38 @@ const RAT = {
106
143
  rate_limited: 'Zu viele Aufrufe. data.retryAfterSec Sekunden warten und denselben Aufruf wiederholen.',
107
144
  app_not_found: 'Die appId gibt es nicht. getPartnerInfo liefert die eigenen Apps samt id.',
108
145
  app_not_accepted: 'Diese App hat noch keine abgenommene Version. Mit einem pk_test_-Schluessel oder mit env:"test" geht es sofort weiter; live erst nach der Abnahme.',
109
- kein_partnerbetrieb: 'Dieser Betrieb gehoert nicht zu diesem Partner-Konto. Die eigenen stehen in listPartnerCustomers.',
110
146
  live_not_allowed: 'Ein Test-Schluessel erzeugt nichts Echtes. Fuer einen Live-Betrieb den Live-Schluessel nehmen — umgekehrt darf ein Live-Schluessel mit env:"test" sehr wohl einen Testbetrieb anlegen.',
111
147
  customer_exists: 'Diesen Betrieb gibt es schon (data.customerId). Mit derselben customerId weiterarbeiten.',
112
148
  customer_conflict: 'Die Steuernummer ist bei Kasseneck bereits registriert. Die Zuordnung zum Partner macht Kasseneck — hello@kasseneck.at.',
113
149
  customer_limit: 'Das Tageslimit fuer neue Betriebe ist erreicht (data.max, data.resetAt). Morgen weiter.',
114
- zugang_nicht_erlaubt: 'Fuer dieses Partner-Konto sind Zugaenge zum Kundenpanel nicht freigeschaltet — es entstand NICHTS, auch kein Betrieb. Ohne zugang{invite:true} erneut anlegen oder die Freischaltung erfragen (Stand: getPartnerInfo.partner.canCreateAccess).',
150
+ access_not_allowed: 'Fuer dieses Partner-Konto sind Zugaenge zum Kundenpanel nicht freigeschaltet — es entstand NICHTS, auch kein Betrieb. Ohne access{invite:true} erneut anlegen oder die Freischaltung erfragen (Stand: getPartnerInfo.partner.canCreateAccess).',
115
151
  email_taken: 'Fuer diese E-Mail gibt es schon einen Kasseneck-Zugang. Eine andere Adresse waehlen, auf die Einladung verzichten oder den Betrieb zuordnen lassen.',
116
152
  no_email: 'Im Konto des Betriebs steht keine E-Mail-Adresse. Ohne sie geht weder eine Einladung noch der FinanzOnline-Link hinaus.',
153
+ tax_number_missing: 'Am Betrieb ist keine Steuernummer hinterlegt, ohne sie gibt es keinen Einrichtungs-Link. Die Steuernummer bei Kasseneck nachtragen lassen (hello@kasseneck.at), dann sendPartnerCustomerFonLink erneut.',
117
154
  fon_missing: 'Der Betrieb hat noch keinen FinanzOnline-Zugang. sendPartnerCustomerFonLink senden und customer.fon_verified abwarten. Betrifft das ANMELDEN der Signatureinheit, nicht das Beantragen.',
118
155
  signature_pending: 'Fuer diesen Betrieb laeuft bereits ein Antrag. Auf signature.ready warten.',
119
- request_not_found: 'Diese signaturId gibt es nicht. getCustomerSignatureStatus nennt die des Betriebs.',
120
156
  signature_missing: 'Der Betrieb hat ueberhaupt keine Signatur, und jede Kasse bezieht sich auf eine. Zuerst requestCustomerSignature.',
121
- signature_unknown: 'Die genannte signaturId gehoert nicht zu diesem Betrieb. getCustomerSignatureStatus nennt die seinen.',
122
- signature_ambiguous: 'Der Betrieb hat mehrere Signaturen; welche die Kasse benutzt, muss dastehen. Eine aus data.choices als signaturId mitgeben.',
157
+ signature_unknown: 'Die genannte signatureRequestId gehoert nicht zu diesem Betrieb. getCustomerSignatureStatus nennt die seinen.',
158
+ signature_ambiguous: 'Der Betrieb hat mehrere Signaturen; welche die Kasse benutzt, muss dastehen. Eine aus data.choices als signatureRequestId mitgeben.',
123
159
  signature_not_ready: 'Die Signatur DIESER Kasse ist noch nicht bereit. Auf signature.ready warten; eine mit automatic:true angelegte Kasse geht danach von selbst live.',
124
160
  signature_limit: 'Hoechstens zehn Signaturen je Betrieb. Eine bestehende benutzen, statt mit additional:true eine weitere zu beantragen.',
125
161
  signature_failed: 'FinanzOnline hat die Anmeldung abgelehnt (data.rc). Kasseneck klaert das — hello@kasseneck.at.',
126
- module_inactive: 'Das Modul (data.modul) ist fuer diesen Betrieb nicht gebucht. Kasseneck schaltet es frei.',
162
+ module_inactive: 'Das Modul (data.module, z. B. "cash_register", Text in data.detail) ist fuer diesen Betrieb nicht gebucht. Kasseneck schaltet es frei.',
127
163
  cashregister_limit: 'Hoechstens 20 Registrierkassen je Betrieb. Eine bestehende nutzen.',
128
164
  cashregister_not_found: 'Diese cashregisterId gibt es bei diesem Betrieb nicht.',
165
+ contracts_pending: 'Nur live: der Betrieb hat Auftragsverarbeitungs- und Nutzungsvertrag noch nicht bestaetigt. An der Kasse aendert sich nichts. Den Betrieb ueber den Einrichtungs-Link bestaetigen lassen (sendPartnerCustomerFonLink, Stand in avv/terms von getPartnerCustomer), danach activateCashregister erneut.',
166
+ // -- Vertraege (reportCustomerContract) ------------------------------------
167
+ // Dieser Client bietet den Endpunkt (noch) nicht an; die Codes stehen hier
168
+ // nur fuer die Katalogseite und fuer einen Aufrufer, der die rohe Antwort
169
+ // selbst auswertet (siehe Kopfkommentar der Datei).
170
+ kind_not_allowed: 'reportCustomerContract mit einer anderen Art als "avv". Der Vollmachtsweg nimmt nur den Auftragsverarbeitungsvertrag entgegen. Dieser Client bietet den Endpunkt nicht an.',
171
+ mode_not_allowed: 'Der Vollmachtsweg ist fuer dieses Partner-Konto nicht freigeschaltet. Kasseneck fragen (hello@kasseneck.at).',
172
+ power_of_attorney_missing: 'Der Partnervertrag mit dem Vollmachts-Kapitel ist noch nicht bestaetigt. Erst danach nimmt der Vollmachtsweg Meldungen entgegen.',
173
+ not_found: 'Die genannte customerId gehoert nicht zu diesem Partner-Konto oder existiert nicht. listPartnerCustomers nennt die eigenen.',
174
+ no_version: 'Fuer die gemeldete Vertragsart gibt es derzeit keine gueltige Fassung. Bei Kasseneck nachfragen.',
175
+ unknown_version: 'Die gemeldete Vertragsversion gibt es nicht. Die aktuell geltende Fassung neu abrufen.',
176
+ text_changed: 'Der gezeigte Vertragstext hat sich seit dem Laden geaendert (data.textHash traegt die aktuell geltende Pruefsumme). Neu laden und danach erneut bestaetigen lassen.',
177
+ already_accepted: 'Diese Fassung ist bereits bestaetigt (data.contractId). Nichts weiter zu tun.',
129
178
  activation_failed: 'Die Inbetriebnahme blieb an data.step haengen (ggf. data.rc). activateCashregister erneut aufrufen — jeder Schritt ist idempotent, der Lauf setzt an der Bruchstelle an.',
130
179
  webhook_limit: 'Hoechstens 10 Webhook-Endpunkte je Partner. Einen ungenutzten loeschen.',
131
180
  webhook_inactive: 'Der Webhook steht auf active:false. Zuerst aktivieren, dann erneut proben.',