@kreiseck/kasseneck-api 0.28.0 → 0.30.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 (113) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +41 -5
  3. package/dist/cjs/client/api.d.ts +3 -3
  4. package/dist/cjs/client/index.d.ts +1 -1
  5. package/dist/cjs/client/receipts.d.ts +32 -3
  6. package/dist/cjs/client/receipts.js +121 -6
  7. package/dist/cjs/enums/keck-payment-method.d.ts +10 -0
  8. package/dist/cjs/enums/keck-payment-method.js +6 -0
  9. package/dist/cjs/index.d.ts +2 -2
  10. package/dist/cjs/index.js +6 -2
  11. package/dist/cjs/models/cancellation.d.ts +11 -1
  12. package/dist/cjs/models/cancellation.js +15 -0
  13. package/dist/cjs/models/index.d.ts +2 -0
  14. package/dist/cjs/models/index.js +7 -1
  15. package/dist/cjs/models/payment-errors.d.ts +22 -0
  16. package/dist/cjs/models/payment-errors.js +46 -0
  17. package/dist/cjs/models/receipt-payment.d.ts +85 -0
  18. package/dist/cjs/models/receipt-payment.js +40 -0
  19. package/dist/cjs/models/receipt-summary.d.ts +9 -0
  20. package/dist/cjs/models/receipt-summary.js +2 -0
  21. package/dist/cjs/models/receipt.d.ts +9 -0
  22. package/dist/cjs/models/receipt.js +3 -0
  23. package/dist/cjs/partner/fehler.d.ts +28 -2
  24. package/dist/cjs/partner/fehler.js +55 -11
  25. package/dist/cjs/partner/index.d.ts +1 -1
  26. package/dist/cjs/partner/typen.d.ts +9 -2
  27. package/dist/cjs/receipt/layout.js +112 -10
  28. package/dist/esm/client/api.d.ts +3 -3
  29. package/dist/esm/client/index.d.ts +1 -1
  30. package/dist/esm/client/receipts.d.ts +32 -3
  31. package/dist/esm/client/receipts.js +121 -6
  32. package/dist/esm/enums/keck-payment-method.d.ts +10 -0
  33. package/dist/esm/enums/keck-payment-method.js +6 -0
  34. package/dist/esm/index.d.ts +2 -2
  35. package/dist/esm/index.js +1 -1
  36. package/dist/esm/models/cancellation.d.ts +11 -1
  37. package/dist/esm/models/cancellation.js +15 -0
  38. package/dist/esm/models/index.d.ts +2 -0
  39. package/dist/esm/models/index.js +2 -0
  40. package/dist/esm/models/payment-errors.d.ts +22 -0
  41. package/dist/esm/models/payment-errors.js +42 -0
  42. package/dist/esm/models/receipt-payment.d.ts +85 -0
  43. package/dist/esm/models/receipt-payment.js +36 -0
  44. package/dist/esm/models/receipt-summary.d.ts +9 -0
  45. package/dist/esm/models/receipt-summary.js +2 -0
  46. package/dist/esm/models/receipt.d.ts +9 -0
  47. package/dist/esm/models/receipt.js +3 -0
  48. package/dist/esm/partner/fehler.d.ts +28 -2
  49. package/dist/esm/partner/fehler.js +55 -11
  50. package/dist/esm/partner/index.d.ts +1 -1
  51. package/dist/esm/partner/typen.d.ts +9 -2
  52. package/dist/esm/receipt/layout.js +112 -10
  53. package/fixtures/belege/split-bar-rueckgeld.json +58 -0
  54. package/fixtures/belege/split-eine-karte-trinkgeld.json +75 -0
  55. package/fixtures/belege/split-karte-karte-bar.json +102 -0
  56. package/fixtures/belege/split-langer-betrag.json +74 -0
  57. package/fixtures/belege/split-tischrunde-trinkgeld.json +152 -0
  58. package/fixtures/belege/split-trinkgeld-karte.json +82 -0
  59. package/fixtures/belege/split-zwei-karten-gleicher-anbieter.json +89 -0
  60. package/fixtures/belege/storno-split-teil.json +71 -0
  61. package/fixtures/belege/storno-split-voll.json +100 -0
  62. package/fixtures/erwartet/split-bar-rueckgeld.blatt32.json +194 -0
  63. package/fixtures/erwartet/split-bar-rueckgeld.blatt48.json +182 -0
  64. package/fixtures/erwartet/split-bar-rueckgeld.grid32.txt +28 -0
  65. package/fixtures/erwartet/split-bar-rueckgeld.grid48.txt +26 -0
  66. package/fixtures/erwartet/split-bar-rueckgeld.lines.json +286 -0
  67. package/fixtures/erwartet/split-eine-karte-trinkgeld.blatt32.json +242 -0
  68. package/fixtures/erwartet/split-eine-karte-trinkgeld.blatt48.json +224 -0
  69. package/fixtures/erwartet/split-eine-karte-trinkgeld.grid32.txt +36 -0
  70. package/fixtures/erwartet/split-eine-karte-trinkgeld.grid48.txt +33 -0
  71. package/fixtures/erwartet/split-eine-karte-trinkgeld.lines.json +371 -0
  72. package/fixtures/erwartet/split-karte-karte-bar.blatt32.json +356 -0
  73. package/fixtures/erwartet/split-karte-karte-bar.blatt48.json +338 -0
  74. package/fixtures/erwartet/split-karte-karte-bar.grid32.txt +55 -0
  75. package/fixtures/erwartet/split-karte-karte-bar.grid48.txt +52 -0
  76. package/fixtures/erwartet/split-karte-karte-bar.lines.json +519 -0
  77. package/fixtures/erwartet/split-langer-betrag.blatt32.json +266 -0
  78. package/fixtures/erwartet/split-langer-betrag.blatt48.json +260 -0
  79. package/fixtures/erwartet/split-langer-betrag.grid32.txt +40 -0
  80. package/fixtures/erwartet/split-langer-betrag.grid48.txt +39 -0
  81. package/fixtures/erwartet/split-langer-betrag.lines.json +343 -0
  82. package/fixtures/erwartet/split-tischrunde-trinkgeld.blatt32.json +470 -0
  83. package/fixtures/erwartet/split-tischrunde-trinkgeld.blatt48.json +434 -0
  84. package/fixtures/erwartet/split-tischrunde-trinkgeld.grid32.txt +74 -0
  85. package/fixtures/erwartet/split-tischrunde-trinkgeld.grid48.txt +68 -0
  86. package/fixtures/erwartet/split-tischrunde-trinkgeld.lines.json +740 -0
  87. package/fixtures/erwartet/split-trinkgeld-karte.blatt32.json +272 -0
  88. package/fixtures/erwartet/split-trinkgeld-karte.blatt48.json +254 -0
  89. package/fixtures/erwartet/split-trinkgeld-karte.grid32.txt +41 -0
  90. package/fixtures/erwartet/split-trinkgeld-karte.grid48.txt +38 -0
  91. package/fixtures/erwartet/split-trinkgeld-karte.lines.json +428 -0
  92. package/fixtures/erwartet/split-zwei-karten-gleicher-anbieter.blatt32.json +326 -0
  93. package/fixtures/erwartet/split-zwei-karten-gleicher-anbieter.blatt48.json +296 -0
  94. package/fixtures/erwartet/split-zwei-karten-gleicher-anbieter.grid32.txt +50 -0
  95. package/fixtures/erwartet/split-zwei-karten-gleicher-anbieter.grid48.txt +45 -0
  96. package/fixtures/erwartet/split-zwei-karten-gleicher-anbieter.lines.json +504 -0
  97. package/fixtures/erwartet/storno-split-teil.blatt32.json +230 -0
  98. package/fixtures/erwartet/storno-split-teil.blatt48.json +224 -0
  99. package/fixtures/erwartet/storno-split-teil.grid32.txt +34 -0
  100. package/fixtures/erwartet/storno-split-teil.grid48.txt +33 -0
  101. package/fixtures/erwartet/storno-split-teil.lines.json +304 -0
  102. package/fixtures/erwartet/storno-split-voll.blatt32.json +320 -0
  103. package/fixtures/erwartet/storno-split-voll.blatt48.json +314 -0
  104. package/fixtures/erwartet/storno-split-voll.grid32.txt +49 -0
  105. package/fixtures/erwartet/storno-split-voll.grid48.txt +48 -0
  106. package/fixtures/erwartet/storno-split-voll.lines.json +410 -0
  107. package/fixtures/hobex-hps-codes.json +1 -1
  108. package/fixtures/kasse-texte.json +1 -1
  109. package/fixtures/manifest.json +72 -0
  110. package/fixtures/oberflaeche.json +12 -6
  111. package/fixtures/rechnung-api.schema.json +1 -1
  112. package/fixtures/rechnung-texte.json +1 -1
  113. package/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ import { KeckPaymentMethod, type KeckPaymentMethodKey, CreditCardProvider } from '../enums/index.js';
2
+ /**
3
+ * Eine Zahlung am Beleg — Zwilling der Eintraege in `payments`, wie
4
+ * `functions/gemeinsam/zahlungen-core.js` (`pruefeZahlungen`) sie normalisiert
5
+ * und ablegt.
6
+ *
7
+ * Ein Beleg kann ueber mehrere Zahlungen bezahlt sein (zwei Karten, Rest bar).
8
+ * Dann steht die Liste unter `payments`, und das Einzelfeld `paymentMethod`
9
+ * ist `mixed`, sobald verschiedene Zahlarten vorkommen (zwei Karten ergeben
10
+ * `creditCard`). Altbelege haben keine Liste; dort gilt weiter
11
+ * `paymentMethod` samt Kartenfeldern.
12
+ *
13
+ * Betraege in ganzen Cent: am Verkauf positiv, am Storno negativ (Rueckzahlung).
14
+ * `method`/`provider` sind beim Lesen wie ueberall entweder der bekannte
15
+ * Enum-Eintrag oder der rohe Schluessel (siehe `enum-payload.ts`).
16
+ */
17
+ export interface ReceiptPayment {
18
+ /** Vom Server vergeben (`p1`, `p2`, …); Bezugspunkt fuer `refundOf`. */
19
+ id?: string;
20
+ method: KeckPaymentMethod | string;
21
+ amountCents: number;
22
+ /** Nur bei Barzahlung: gegebener Betrag. */
23
+ tenderedCents?: number;
24
+ /** Nur neben `tenderedCents`: Rueckgeld (vom Server gerechnet). */
25
+ changeCents?: number;
26
+ /** Kartenanbieter bzw. Terminal. */
27
+ provider?: CreditCardProvider | string;
28
+ /** Kennung der Zahlung am Terminal/Anbieter. */
29
+ providerPaymentId?: string;
30
+ /** Terminaldaten fuer den Kartenblock (frueher `cardPaymentData`). */
31
+ providerData?: Record<string, unknown>;
32
+ /** Nur am Storno: `id` der Originalzahlung, die erstattet wird. */
33
+ refundOf?: string;
34
+ /**
35
+ * Trinkgeld, das mit dieser Zahlung gegeben wurde, in ganzen Cent — TEIL
36
+ * von `amountCents` (das Terminal bucht Betrag + Trinkgeld zusammen). Nur
37
+ * an Verkaufs-/Trainingsbelegen.
38
+ */
39
+ tipCents?: number;
40
+ }
41
+ /** Nutzlast-Form einer Zahlung (Backend-Dokument bzw. Antwort). */
42
+ export interface ReceiptPaymentPayload {
43
+ id?: string;
44
+ method: string;
45
+ amountCents: number;
46
+ tenderedCents?: number;
47
+ changeCents?: number;
48
+ provider?: string;
49
+ providerPaymentId?: string;
50
+ providerData?: Record<string, unknown>;
51
+ refundOf?: string;
52
+ tipCents?: number;
53
+ }
54
+ /**
55
+ * Eine Zahlung, wie der Aufrufer sie an `createReceipt`/`sellReceipt`/
56
+ * `cancelReceipt` schickt. `id` und `changeCents` vergibt der Server.
57
+ *
58
+ * - `method`: jede Zahlungsart ausser `mixed`.
59
+ * - `amountCents`: ganze Cent, am Verkauf > 0, am Storno < 0.
60
+ * - `tenderedCents`: nur bei Barzahlung am Verkauf, hoechstens an einer Zahlung.
61
+ * - `provider`/`providerPaymentId`/`providerData`: bei Karten Pflicht
62
+ * (`providerPaymentId` ausser bei `custom`), bei `online` optional, sonst
63
+ * nicht erlaubt; am Storno beschreiben sie die Erstattung und sind optional.
64
+ * - `refundOf`: nur am Storno.
65
+ * - `tipCents`: Trinkgeld dieser Zahlung, ganze Cent > 0, Teil von
66
+ * `amountCents`; nur am Verkauf, nie neben `tip` (den Rest prueft der Server).
67
+ */
68
+ export interface ReceiptPaymentInput {
69
+ method: KeckPaymentMethod | KeckPaymentMethodKey;
70
+ amountCents: number;
71
+ tenderedCents?: number;
72
+ provider?: CreditCardProvider;
73
+ providerPaymentId?: string;
74
+ providerData?: Record<string, unknown>;
75
+ refundOf?: string;
76
+ tipCents?: number;
77
+ }
78
+ /** Lesepfad: nur vorhandene Felder uebernehmen, nichts ergaenzen. */
79
+ export declare function fromReceiptPaymentPayload(payload: ReceiptPaymentPayload): ReceiptPayment;
80
+ /**
81
+ * Schreibpfad: Zahlungen zurueck in die Nutzlast — streng wie
82
+ * `toReceiptPayload`: ein roher, unbekannter Schluessel bei `method` oder
83
+ * `provider` wirft.
84
+ */
85
+ export declare function toReceiptPaymentPayload(payment: ReceiptPayment): ReceiptPaymentPayload;
@@ -0,0 +1,36 @@
1
+ import { KeckPaymentMethod, CreditCardProvider } from '../enums/index.js';
2
+ import { readEnumKey, requireEnumKey } from './enum-payload.js';
3
+ /** Lesepfad: nur vorhandene Felder uebernehmen, nichts ergaenzen. */
4
+ export function fromReceiptPaymentPayload(payload) {
5
+ return {
6
+ ...(typeof payload.id === 'string' ? { id: payload.id } : {}),
7
+ method: readEnumKey(KeckPaymentMethod, payload.method),
8
+ amountCents: payload.amountCents,
9
+ ...(typeof payload.tenderedCents === 'number' ? { tenderedCents: payload.tenderedCents } : {}),
10
+ ...(typeof payload.changeCents === 'number' ? { changeCents: payload.changeCents } : {}),
11
+ ...(typeof payload.provider === 'string' ? { provider: readEnumKey(CreditCardProvider, payload.provider) } : {}),
12
+ ...(typeof payload.providerPaymentId === 'string' ? { providerPaymentId: payload.providerPaymentId } : {}),
13
+ ...(payload.providerData != null && typeof payload.providerData === 'object' ? { providerData: payload.providerData } : {}),
14
+ ...(typeof payload.refundOf === 'string' ? { refundOf: payload.refundOf } : {}),
15
+ ...(typeof payload.tipCents === 'number' ? { tipCents: payload.tipCents } : {}),
16
+ };
17
+ }
18
+ /**
19
+ * Schreibpfad: Zahlungen zurueck in die Nutzlast — streng wie
20
+ * `toReceiptPayload`: ein roher, unbekannter Schluessel bei `method` oder
21
+ * `provider` wirft.
22
+ */
23
+ export function toReceiptPaymentPayload(payment) {
24
+ return {
25
+ ...(payment.id !== undefined ? { id: payment.id } : {}),
26
+ method: typeof payment.method === 'object' ? payment.method.value : requireEnumKey(KeckPaymentMethod, payment.method, 'Zahlungsart').value,
27
+ amountCents: payment.amountCents,
28
+ ...(payment.tenderedCents !== undefined ? { tenderedCents: payment.tenderedCents } : {}),
29
+ ...(payment.changeCents !== undefined ? { changeCents: payment.changeCents } : {}),
30
+ ...(payment.provider !== undefined ? { provider: requireEnumKey(CreditCardProvider, payment.provider, 'Kartenanbieter') } : {}),
31
+ ...(payment.providerPaymentId !== undefined ? { providerPaymentId: payment.providerPaymentId } : {}),
32
+ ...(payment.providerData !== undefined ? { providerData: payment.providerData } : {}),
33
+ ...(payment.refundOf !== undefined ? { refundOf: payment.refundOf } : {}),
34
+ ...(payment.tipCents !== undefined ? { tipCents: payment.tipCents } : {}),
35
+ };
36
+ }
@@ -1,4 +1,5 @@
1
1
  import { KeckPaymentMethod, ReceiptType } from '../enums/index.js';
2
+ import { type ReceiptPayment, type ReceiptPaymentPayload } from './receipt-payment.js';
2
3
  /**
3
4
  * Belegzeile einer Liste — die Form, die `listMyReceipts` liefert
4
5
  * (`projectReceiptForCustomer` in functions/index.js).
@@ -63,6 +64,13 @@ export interface ReceiptSummary {
63
64
  stornoStand: 'offen' | 'teil' | 'voll';
64
65
  /** Nur am Nullbeleg, nur fuer Kassen-Benutzer: Anlass (monthly, annual, annual_replacement, outage_end, final, manual). */
65
66
  zeroKind?: ZeroKind;
67
+ /**
68
+ * Zahlungsliste, nur bei Belegen, die eine tragen. Die Liste liefert nur
69
+ * die oeffentlichen Felder (id, method, amountCents, provider,
70
+ * tenderedCents, changeCents, refundOf, tipCents) — Anbieter-Interna
71
+ * (`providerPaymentId`, `providerData`) bleiben im Backend.
72
+ */
73
+ payments?: ReceiptPayment[];
66
74
  }
67
75
  export interface ReceiptSummaryPayload {
68
76
  receiptId?: string | null;
@@ -90,5 +98,6 @@ export interface ReceiptSummaryPayload {
90
98
  cancellationReason?: string | null;
91
99
  stornoStand?: string | null;
92
100
  zeroKind?: string | null;
101
+ payments?: ReceiptPaymentPayload[] | null;
93
102
  }
94
103
  export declare function fromReceiptSummaryPayload(payload: ReceiptSummaryPayload): ReceiptSummary;
@@ -1,6 +1,7 @@
1
1
  import { KeckPaymentMethod, ReceiptType } from '../enums/index.js';
2
2
  import { euroToCents } from '../money.js';
3
3
  import { readEnumKey } from './enum-payload.js';
4
+ import { fromReceiptPaymentPayload } from './receipt-payment.js';
4
5
  /**
5
6
  * Belegzeile einer Liste — die Form, die `listMyReceipts` liefert
6
7
  * (`projectReceiptForCustomer` in functions/index.js).
@@ -48,5 +49,6 @@ export function fromReceiptSummaryPayload(payload) {
48
49
  ...(payload.cancellationReason ? { cancellationReason: payload.cancellationReason } : {}),
49
50
  stornoStand: payload.stornoStand === 'teil' || payload.stornoStand === 'voll' ? payload.stornoStand : 'offen',
50
51
  ...(istZeroKind(payload.zeroKind) ? { zeroKind: payload.zeroKind } : {}),
52
+ ...(Array.isArray(payload.payments) ? { payments: payload.payments.map(fromReceiptPaymentPayload) } : {}),
51
53
  };
52
54
  }
@@ -3,6 +3,7 @@ import { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead
3
3
  import { type Voucher, type VoucherPayload } from './voucher.js';
4
4
  import type { Cancellation, CancellationOf, CancellationReason } from './cancellation.js';
5
5
  import { type ZeroKind } from './receipt-summary.js';
6
+ import { type ReceiptPayment, type ReceiptPaymentPayload } from './receipt-payment.js';
6
7
  /**
7
8
  * Beleg — Zwilling der Beleg-Nutzlast von `KasseneckReceipt` in
8
9
  * kasseneck_api/lib/models/kasseneck_receipt.dart (nur der signatur-/
@@ -54,6 +55,12 @@ export interface Receipt {
54
55
  cancellations?: Cancellation[];
55
56
  /** Nur an Nullbelegen: Anlass (monthly, annual, annual_replacement, outage_end, final, manual). */
56
57
  zeroKind?: ZeroKind;
58
+ /**
59
+ * Zahlungsliste (mehrere Zahlungen je Beleg), nur vorhanden, wenn der Beleg
60
+ * sie traegt. Altbelege haben keine; dort gelten `paymentMethod` und die
61
+ * Kartenfelder. Siehe [ReceiptPayment].
62
+ */
63
+ payments?: ReceiptPayment[];
57
64
  }
58
65
  export interface ReceiptPayload {
59
66
  qr: string;
@@ -78,6 +85,8 @@ export interface ReceiptPayload {
78
85
  customProjectId: string | null;
79
86
  /** Nur bei Belegen mit Trinkgeld (siehe [Receipt.tipCents]). */
80
87
  tipCents?: number | null;
88
+ /** Nur bei Belegen mit Zahlungsliste (siehe [Receipt.payments]). */
89
+ payments?: ReceiptPaymentPayload[] | null;
81
90
  }
82
91
  /**
83
92
  * Nutzlast-Form, die dieses Paket **liest**. Unterscheidet sich von
@@ -3,6 +3,7 @@ import { readEnumKey, requireEnumKey } from './enum-payload.js';
3
3
  import { toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, } from './receipt-item.js';
4
4
  import { toVoucherPayload, fromVoucherPayload } from './voucher.js';
5
5
  import { istZeroKind } from './receipt-summary.js';
6
+ import { fromReceiptPaymentPayload, toReceiptPaymentPayload } from './receipt-payment.js';
6
7
  /**
7
8
  * Schreibt die Nutzlast in der **neuen** Form — sie **normalisiert, statt zu
8
9
  * spiegeln**: ein Beleg, der in der v1-Positionsform gelesen wurde, geht hier
@@ -43,6 +44,7 @@ export function toReceiptPayload(receipt) {
43
44
  signatureSuccess: receipt.signatureSuccess ?? null,
44
45
  customProjectId: receipt.customProjectId ?? null,
45
46
  ...(receipt.tipCents != null ? { tipCents: receipt.tipCents } : {}),
47
+ ...(receipt.payments != null ? { payments: receipt.payments.map(toReceiptPaymentPayload) } : {}),
46
48
  };
47
49
  }
48
50
  // Nullbelege haben keine Positionen -> items kann fehlen/null sein (siehe Dart-Vorbild).
@@ -78,6 +80,7 @@ export function fromReceiptPayload(payload) {
78
80
  ...(payload.cancellationReason ? { cancellationReason: payload.cancellationReason } : {}),
79
81
  ...(istZeroKind(payload.zeroKind) ? { zeroKind: payload.zeroKind } : {}),
80
82
  ...(payload.cancellations ? { cancellations: payload.cancellations.map(leseStorno) } : {}),
83
+ ...(Array.isArray(payload.payments) ? { payments: payload.payments.map(fromReceiptPaymentPayload) } : {}),
81
84
  };
82
85
  }
83
86
  function leseStorno(eintrag) {
@@ -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", "kennung_fehlt", "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", "vertrag_offen", "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,21 +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',
46
71
  // FinanzOnline-Link
47
- 'kennung_fehlt',
72
+ 'tax_number_missing',
48
73
  // Signatur
49
74
  'fon_missing',
50
75
  'signature_pending',
51
- 'request_not_found',
52
76
  'signature_missing',
53
77
  'signature_unknown',
54
78
  'signature_ambiguous',
@@ -59,7 +83,17 @@ export const PARTNER_FEHLER_CODES = [
59
83
  'module_inactive',
60
84
  'cashregister_limit',
61
85
  'cashregister_not_found',
62
- 'vertrag_offen',
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',
63
97
  'activation_failed',
64
98
  // Webhooks
65
99
  'webhook_limit',
@@ -109,28 +143,38 @@ const RAT = {
109
143
  rate_limited: 'Zu viele Aufrufe. data.retryAfterSec Sekunden warten und denselben Aufruf wiederholen.',
110
144
  app_not_found: 'Die appId gibt es nicht. getPartnerInfo liefert die eigenen Apps samt id.',
111
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.',
112
- kein_partnerbetrieb: 'Dieser Betrieb gehoert nicht zu diesem Partner-Konto. Die eigenen stehen in listPartnerCustomers.',
113
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.',
114
147
  customer_exists: 'Diesen Betrieb gibt es schon (data.customerId). Mit derselben customerId weiterarbeiten.',
115
148
  customer_conflict: 'Die Steuernummer ist bei Kasseneck bereits registriert. Die Zuordnung zum Partner macht Kasseneck — hello@kasseneck.at.',
116
149
  customer_limit: 'Das Tageslimit fuer neue Betriebe ist erreicht (data.max, data.resetAt). Morgen weiter.',
117
- zugang_nicht_erlaubt: '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).',
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).',
118
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.',
119
152
  no_email: 'Im Konto des Betriebs steht keine E-Mail-Adresse. Ohne sie geht weder eine Einladung noch der FinanzOnline-Link hinaus.',
120
- kennung_fehlt: '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.',
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.',
121
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.',
122
155
  signature_pending: 'Fuer diesen Betrieb laeuft bereits ein Antrag. Auf signature.ready warten.',
123
- request_not_found: 'Diese signaturId gibt es nicht. getCustomerSignatureStatus nennt die des Betriebs.',
124
156
  signature_missing: 'Der Betrieb hat ueberhaupt keine Signatur, und jede Kasse bezieht sich auf eine. Zuerst requestCustomerSignature.',
125
157
  signature_unknown: 'Die genannte signatureRequestId gehoert nicht zu diesem Betrieb. getCustomerSignatureStatus nennt die seinen.',
126
158
  signature_ambiguous: 'Der Betrieb hat mehrere Signaturen; welche die Kasse benutzt, muss dastehen. Eine aus data.choices als signatureRequestId mitgeben.',
127
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.',
128
160
  signature_limit: 'Hoechstens zehn Signaturen je Betrieb. Eine bestehende benutzen, statt mit additional:true eine weitere zu beantragen.',
129
161
  signature_failed: 'FinanzOnline hat die Anmeldung abgelehnt (data.rc). Kasseneck klaert das — hello@kasseneck.at.',
130
- 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.',
131
163
  cashregister_limit: 'Hoechstens 20 Registrierkassen je Betrieb. Eine bestehende nutzen.',
132
164
  cashregister_not_found: 'Diese cashregisterId gibt es bei diesem Betrieb nicht.',
133
- vertrag_offen: '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.',
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.',
134
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.',
135
179
  webhook_limit: 'Hoechstens 10 Webhook-Endpunkte je Partner. Einen ungenutzten loeschen.',
136
180
  webhook_inactive: 'Der Webhook steht auf active:false. Zuerst aktivieren, dann erneut proben.',
@@ -27,6 +27,6 @@ export { getPartnerInfo, createPartnerCustomer, listPartnerCustomers, checkPartn
27
27
  export { createPartnerWebhook, listPartnerWebhooks, rotatePartnerWebhookSecret, updatePartnerWebhook, deletePartnerWebhook, sendPartnerWebhookTest, listPartnerWebhookDeliveries, parseWebhookEvent, istPartnerWebhookEvent, PARTNER_WEBHOOK_EVENTS, WEBHOOK_UMSCHLAG_FELDER, type PartnerWebhookEvent, type PartnerWebhookEventType, type PartnerWebhook, type CreateWebhookOptions, type CreateWebhookResult, type DeleteWebhookResult, type WebhookApiVersion, type WebhookDeliveryStatus, type WebhookTestZustellung, type ContractKind, type ContractSource, type ContractAcceptedEventData, type WebhookPatch, type WebhookListe, type WebhookZustellung, type WebhookTestResult, type WebhookEventResult, } from './webhooks.js';
28
28
  export { verifyWebhookSignature, parseSignatureHeader, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_EVENT_HEADER, WEBHOOK_DELIVERY_HEADER, WEBHOOK_TOLERANCE_SEC, WEBHOOK_RETRY_PLAN_SEC, WEBHOOK_MAX_ATTEMPTS, WEBHOOK_TIMEOUT_MS, WEBHOOK_LIMIT, type VerifyWebhookOptions, type WebhookVerifyResult, type WebhookVerifyReason, } from './webhook-signatur.js';
29
29
  export { PARTNER_ENVS } from './typen.js';
30
- export type { PartnerEnv, PartnerScope, PartnerApp, PartnerInfo, LegalForm, AustrianState, ContactRole, AvvMode, FeeInterval, PartnerFee, SignatureHistoryReason, RequestSignatureOptions, CustomerSignature, Rechtsform, Bundesland, KontaktRolle, BetriebAdresse, BetriebSteuer, BetriebKontakt, BetriebSteuerberater, Betrieb, CreateCustomerOptions, CreateCustomerResult, KundenStatus, KundenZeile, AvvStand, VertragStand, KundenFonStand, ListCustomersOptions, KundenListe, Kunde, FonLinkResult, SignaturAntragStatus, SignaturHistorieEintrag, SignaturAntrag, RequestSignatureResult, SignaturStand, KassenSchritt, KassenStatus, Kasse, CreateCashregisterOptions, CreateCashregisterResult, ActivateCashregisterResult, KassenListe, CustomerCashregisterCredential, CustomerCredentials, } from './typen.js';
30
+ export type { PartnerEnv, PartnerScope, PartnerApp, PartnerInfo, LegalForm, AustrianState, ContactRole, AvvMode, FeeInterval, PartnerFee, SignatureHistoryReason, SignatureErrorCode, RequestSignatureOptions, CustomerSignature, Rechtsform, Bundesland, KontaktRolle, BetriebAdresse, BetriebSteuer, BetriebKontakt, BetriebSteuerberater, Betrieb, CreateCustomerOptions, CreateCustomerResult, KundenStatus, KundenZeile, AvvStand, VertragStand, KundenFonStand, ListCustomersOptions, KundenListe, Kunde, FonLinkResult, SignaturAntragStatus, SignaturHistorieEintrag, SignaturAntrag, RequestSignatureResult, SignaturStand, KassenSchritt, KassenStatus, Kasse, CreateCashregisterOptions, CreateCashregisterResult, ActivateCashregisterResult, KassenListe, CustomerCashregisterCredential, CustomerCredentials, } from './typen.js';
31
31
  /** `credentials:read` — nicht im Standardsatz, siehe typen.ts. */
32
32
  export { SCOPE_CREDENTIALS } from './typen.js';
@@ -329,6 +329,13 @@ export type SignaturAntragStatus = 'requested' | 'assigned' | 'registered' | 're
329
329
  * `finanzonline` hiessen unter `/v1` `karte_eingetragen` und `fon`.
330
330
  */
331
331
  export type SignatureHistoryReason = 'api' | 'portal' | 'card_entered' | 'finanzonline' | 'automation_off' | 'test_environment' | 'no_stock' | (string & {});
332
+ /**
333
+ * `error.code` eines Signaturantrags oder einer Signatur (`SignaturAntrag`,
334
+ * `CustomerSignature`). Hiess unter `/v1` `kunde_nicht_gefunden` /
335
+ * `unvollstaendig` / `fon_fehler`; dieser Client spricht seit 0.28.0 nur noch
336
+ * `/v3` und sieht darum nur die englische Form.
337
+ */
338
+ export type SignatureErrorCode = 'customer_not_found' | 'incomplete' | 'finanzonline_error' | (string & {});
332
339
  export interface SignaturHistorieEintrag {
333
340
  from: SignaturAntragStatus | null;
334
341
  to: SignaturAntragStatus;
@@ -344,7 +351,7 @@ export interface SignaturAntrag {
344
351
  vdaId: string | null;
345
352
  signatureId: string | null;
346
353
  error: {
347
- code: string | null;
354
+ code: SignatureErrorCode | null;
348
355
  message: string | null;
349
356
  rc: string | null;
350
357
  } | null;
@@ -383,7 +390,7 @@ export interface CustomerSignature {
383
390
  requestId: string | null;
384
391
  signatureId: string | null;
385
392
  error: {
386
- code: string | null;
393
+ code: SignatureErrorCode | null;
387
394
  message: string | null;
388
395
  rc: string | null;
389
396
  } | null;
@@ -291,16 +291,13 @@ function stripeBlock(daten, cardPaymentId) {
291
291
  return zeilen;
292
292
  }
293
293
  /**
294
- * Der Kartenzahlungsblock des Belegs -- fuer JEDEN Anbieter, den das Backend
295
- * kennt. Ohne `cardPaymentData` bleibt der Block weg; ein unbekannter Anbieter
296
- * ebenfalls, still und ohne Fehler: ein Beleg muss sich immer zeigen lassen.
294
+ * Der Kartenzahlungsblock eines Anbieters -- fuer JEDEN Anbieter, den das
295
+ * Backend kennt. Ein unbekannter Anbieter bekommt keinen Block, still und
296
+ * ohne Fehler: ein Beleg muss sich immer zeigen lassen.
297
297
  */
298
- function kartenblock(receipt) {
299
- const daten = receipt.cardPaymentData;
300
- if (daten == null)
301
- return [];
298
+ function anbieterBlock(provider, daten, id) {
302
299
  const zeilen = (() => {
303
- switch (receipt.creditCardProvider) {
300
+ switch (provider) {
304
301
  case 'hobexHps':
305
302
  case 'hobexCloudApi':
306
303
  return hobexBlock(daten);
@@ -312,7 +309,7 @@ function kartenblock(receipt) {
312
309
  case 'myposPro':
313
310
  return myposBlock(daten);
314
311
  case 'stripe':
315
- return stripeBlock(daten, receipt.cardPaymentId ?? null);
312
+ return stripeBlock(daten, id);
316
313
  default:
317
314
  return [];
318
315
  }
@@ -325,12 +322,117 @@ function kartenblock(receipt) {
325
322
  // gehoert aber zum Blatt, nicht zum Terminal.
326
323
  return [...zeilen, { kind: 'space', lines: 1 }];
327
324
  }
325
+ /**
326
+ * Die Kartenzahlungsbloecke des Belegs. Mit Zahlungsliste (`payments`) je
327
+ * Zahlung mit Anbieter und Terminaldaten ein Block, in Zahlungsreihenfolge --
328
+ * zwei Karten desselben Anbieters ergeben zwei Bloecke; die alten
329
+ * Einzelfelder zaehlen dann nicht. Bei mehreren Zahlungen steht vor jedem
330
+ * Block, nicht fett und zentriert, die Nummer der Zahlung aus der
331
+ * Aufschluesselung („2. Kartenzahlung") -- so ist jeder Block seiner Zeile
332
+ * zuzuordnen. Ohne (oder mit leerer) Liste der bisherige Weg ueber
333
+ * `creditCardProvider`/`cardPaymentData`/`cardPaymentId` -- ebenso bei
334
+ * hoechstens einer Zahlung ohne `providerData`, wenn die Altfelder gesetzt sind.
335
+ */
336
+ function kartenblock(receipt) {
337
+ const zahlungen = receipt.payments;
338
+ // Eine einzelne Zahlung ohne Terminaldaten neben gesetzten Altfeldern
339
+ // (creditCardProvider + cardPaymentData): der bisherige Block, wie ohne Liste.
340
+ const altfelderTragen = zahlungen != null && zahlungen.length <= 1
341
+ && zahlungen.every((z) => z.providerData == null)
342
+ && receipt.creditCardProvider != null && receipt.cardPaymentData != null;
343
+ if (zahlungen != null && zahlungen.length > 0 && !altfelderTragen) {
344
+ const zeilen = [];
345
+ zahlungen.forEach((zahlung, i) => {
346
+ if (zahlung.provider == null || zahlung.providerData == null)
347
+ return;
348
+ const block = anbieterBlock(zahlung.provider, zahlung.providerData, zahlung.providerPaymentId ?? null);
349
+ if (block.length === 0)
350
+ return;
351
+ if (zahlungen.length > 1)
352
+ zeilen.push(textZeile(zahlungNummerText(zahlung, i), 'center'));
353
+ zeilen.push(...block);
354
+ });
355
+ return zeilen;
356
+ }
357
+ const daten = receipt.cardPaymentData;
358
+ if (daten == null)
359
+ return [];
360
+ return anbieterBlock(receipt.creditCardProvider, daten, receipt.cardPaymentId ?? null);
361
+ }
328
362
  function zahlungsartText(wert) {
329
363
  if (wert != null && typeof wert === 'object') {
330
364
  return wert.label;
331
365
  }
332
366
  return typeof wert === 'string' ? wert : '';
333
367
  }
368
+ /** Barzahlarten -- Zwilling von `BAR_ZAHLARTEN` (functions/gemeinsam/tip-core.js). */
369
+ const BAR_ZAHLARTEN = ['cash', 'uberCash', 'boltCash'];
370
+ function zahlartSchluessel(wert) {
371
+ if (wert != null && typeof wert === 'object')
372
+ return wert.value;
373
+ return typeof wert === 'string' ? wert : '';
374
+ }
375
+ /**
376
+ * Nummerierter Text einer Zahlung („1. Kartenzahlung", „3. Barzahlung") --
377
+ * in der Aufschluesselung und ueber ihrem Kartenblock dieselbe Nummer, in
378
+ * Zahlungsreihenfolge ab 1.
379
+ */
380
+ function zahlungNummerText(zahlung, index) {
381
+ return `${index + 1}. ${zahlungsartText(zahlung.method)}`;
382
+ }
383
+ /** „davon Trinkgeld" eingerueckt unter einer Zahlung, die Trinkgeld traegt. */
384
+ function zahlungTrinkgeldZeilen(zahlung) {
385
+ if (typeof zahlung.tipCents !== 'number' || zahlung.tipCents <= 0)
386
+ return [];
387
+ return [paarZeile(' davon Trinkgeld', `${formatCents(zahlung.tipCents)} €`, 8, 4)];
388
+ }
389
+ /**
390
+ * Gegeben/Rueckgeld unter einer Barzahlung mit `tenderedCents`. Fehlt
391
+ * `changeCents` (der Server rechnet es sonst selbst), ergibt es sich aus
392
+ * gegeben minus Betrag.
393
+ */
394
+ function barZeilen(zahlung) {
395
+ if (!BAR_ZAHLARTEN.includes(zahlartSchluessel(zahlung.method)) || typeof zahlung.tenderedCents !== 'number')
396
+ return [];
397
+ const rueckgeld = typeof zahlung.changeCents === 'number' ? zahlung.changeCents : zahlung.tenderedCents - zahlung.amountCents;
398
+ return [
399
+ paarZeile(' Gegeben:', `${formatCents(zahlung.tenderedCents)} €`, 8, 4),
400
+ paarZeile(' Rückgeld:', `${formatCents(rueckgeld)} €`, 8, 4),
401
+ ];
402
+ }
403
+ /**
404
+ * Die Zahlungszeilen unter „Gesamt:".
405
+ *
406
+ * - Ohne Zahlungsliste: „Zahlungsart: <Label>" aus `paymentMethod`, wie immer.
407
+ * - Genau eine Zahlung: dieselbe Zeile, Label aus der Zahlung.
408
+ * - Mehrere Zahlungen (auch zwei Karten, die `paymentMethod: creditCard`
409
+ * ergeben -- entschieden wird an der Laenge, nie an `mixed`): „Zahlungsarten:"
410
+ * und je Zahlung eine nummerierte Zeile mit Betrag, ohne Anbieter (der
411
+ * steht im Kartenblock mit derselben Nummer). Breite 8:4 -- auf 58 mm
412
+ * bleiben der Betragsspalte elf Zeichen, genug fuer „-9999,99 €".
413
+ *
414
+ * Unter jeder Zahlung in dieser Reihenfolge: „davon Trinkgeld" (bei
415
+ * `tipCents`), dann bei einer Barzahlung mit gegebenem Betrag „Gegeben:" und
416
+ * „Rückgeld:" -- alles, was zu einer Zahlung gehoert, zwei Zeichen eingerueckt,
417
+ * die Zahlung selbst buendig.
418
+ */
419
+ function zahlungsZeilen(receipt) {
420
+ const zahlungen = receipt.payments;
421
+ if (zahlungen == null || zahlungen.length === 0) {
422
+ return [paarZeile('Zahlungsart:', zahlungsartText(receipt.paymentMethod))];
423
+ }
424
+ if (zahlungen.length === 1) {
425
+ const zahlung = zahlungen[0];
426
+ return [paarZeile('Zahlungsart:', zahlungsartText(zahlung.method)), ...zahlungTrinkgeldZeilen(zahlung), ...barZeilen(zahlung)];
427
+ }
428
+ const zeilen = [textZeile('Zahlungsarten:')];
429
+ zahlungen.forEach((zahlung, i) => {
430
+ zeilen.push(paarZeile(zahlungNummerText(zahlung, i), `${formatCents(zahlung.amountCents)} €`, 8, 4));
431
+ zeilen.push(...zahlungTrinkgeldZeilen(zahlung));
432
+ zeilen.push(...barZeilen(zahlung));
433
+ });
434
+ return zeilen;
435
+ }
334
436
  // -------------------------------------------------------- Kleinunternehmer
335
437
  /**
336
438
  * Hinweis auf die Steuerbefreiung, den ein Kleinunternehmerbeleg tragen muss —
@@ -898,7 +1000,7 @@ export function buildReceiptLayout(receipt, company, options = {}) {
898
1000
  lines.push({ kind: 'rule', char: '-' });
899
1001
  }
900
1002
  lines.push(paarZeile('Gesamt:', `${formatCents(summeCents)} €`));
901
- lines.push(paarZeile('Zahlungsart:', zahlungsartText(receipt.paymentMethod)));
1003
+ lines.push(...zahlungsZeilen(receipt));
902
1004
  lines.push({ kind: 'space', lines: 1 });
903
1005
  // --- Rechtshinweise und Ausfallhinweis
904
1006
  const hinweise = company.isSmallBusiness