@kreiseck/kasseneck-api 0.8.0 → 0.9.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.
- package/CHANGELOG.md +47 -0
- package/dist/cjs/receipt/layout.d.ts +11 -5
- package/dist/cjs/receipt/layout.js +248 -26
- package/dist/esm/receipt/layout.d.ts +11 -5
- package/dist/esm/receipt/layout.js +247 -26
- package/fixtures/belege/karte-eigener.json +44 -0
- package/fixtures/belege/karte-gptom-ios.json +59 -0
- package/fixtures/belege/karte-gptom.json +59 -0
- package/fixtures/belege/karte-hobex-cloud.json +54 -0
- package/fixtures/belege/karte-hobex-hps.json +54 -0
- package/fixtures/belege/karte-mypos.json +55 -0
- package/fixtures/belege/karte-stripe-eps.json +52 -0
- package/fixtures/belege/karte-stripe.json +57 -0
- package/fixtures/belege/karte-sumup.json +53 -0
- package/fixtures/erwartet/karte-eigener.grid32.txt +22 -0
- package/fixtures/erwartet/karte-eigener.grid48.txt +22 -0
- package/fixtures/erwartet/karte-eigener.lines.json +216 -0
- package/fixtures/erwartet/karte-gptom-ios.grid32.txt +34 -0
- package/fixtures/erwartet/karte-gptom-ios.grid48.txt +34 -0
- package/fixtures/erwartet/karte-gptom-ios.lines.json +286 -0
- package/fixtures/erwartet/karte-gptom.grid32.txt +34 -0
- package/fixtures/erwartet/karte-gptom.grid48.txt +34 -0
- package/fixtures/erwartet/karte-gptom.lines.json +286 -0
- package/fixtures/erwartet/karte-hobex-cloud.grid32.txt +29 -0
- package/fixtures/erwartet/karte-hobex-cloud.grid48.txt +29 -0
- package/fixtures/erwartet/karte-hobex-cloud.lines.json +256 -0
- package/fixtures/erwartet/karte-hobex-hps.grid32.txt +33 -0
- package/fixtures/erwartet/karte-hobex-hps.grid48.txt +33 -0
- package/fixtures/erwartet/karte-hobex-hps.lines.json +272 -0
- package/fixtures/erwartet/karte-mypos.grid32.txt +38 -0
- package/fixtures/erwartet/karte-mypos.grid48.txt +36 -0
- package/fixtures/erwartet/karte-mypos.lines.json +353 -0
- package/fixtures/erwartet/karte-stripe-eps.grid32.txt +28 -0
- package/fixtures/erwartet/karte-stripe-eps.grid48.txt +28 -0
- package/fixtures/erwartet/karte-stripe-eps.lines.json +250 -0
- package/fixtures/erwartet/karte-stripe.grid32.txt +31 -0
- package/fixtures/erwartet/karte-stripe.grid48.txt +31 -0
- package/fixtures/erwartet/karte-stripe.lines.json +268 -0
- package/fixtures/erwartet/karte-sumup.grid32.txt +32 -0
- package/fixtures/erwartet/karte-sumup.grid48.txt +30 -0
- package/fixtures/erwartet/karte-sumup.lines.json +316 -0
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/kasse-texte.json +1 -1
- package/fixtures/manifest.json +54 -0
- package/fixtures/oberflaeche.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,53 @@ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
|
|
|
4
4
|
es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
|
|
5
5
|
nur der Grund überlebt den nächsten Umbau.
|
|
6
6
|
|
|
7
|
+
## 0.9.0
|
|
8
|
+
|
|
9
|
+
### Alle Kartenzahlungsbloecke im Belegmodell — nicht nur Hobex
|
|
10
|
+
|
|
11
|
+
**Anlass:** Am Bon aus der Flutter-App standen bei einer Stripe-Zahlung Marke,
|
|
12
|
+
letzte vier Ziffern, 3-D Secure, Betrag, Zahlzeitpunkt und Referenz. Dasselbe
|
|
13
|
+
Blatt als PDF geoeffnet sagte nur „Zahlungsart: Kartenzahlung". Betroffen waren
|
|
14
|
+
vier der sieben Anbieter: GP Tom, SumUp, myPOS und Stripe.
|
|
15
|
+
|
|
16
|
+
**Ursache:** Bis 0.8.0 trug dieses Modell nur den Hobex-Block, begruendet damit,
|
|
17
|
+
die uebrigen Anbieter druckten „in ihren eigenen Apps". Das Argument gilt fuer
|
|
18
|
+
die Frage, wer das TERMINAL bedient — nicht dafuer, was auf dem BELEGDOKUMENT
|
|
19
|
+
steht. Aus der einen Regel wurde beim Bau des Zeilenmodells die andere. Im
|
|
20
|
+
Backend hatte die vorherige PDF-Erzeugung (`generateReceiptPdf`) alle sieben
|
|
21
|
+
Anbieter; mit der Umstellung auf dieses Modell fielen vier still weg.
|
|
22
|
+
|
|
23
|
+
- Neu: `gpTomBlock`, `sumupBlock`, `myposBlock`, `stripeBlock` — Zeile fuer
|
|
24
|
+
Zeile portiert aus `print_paper.dart` des Dart-Zwillings, inklusive der
|
|
25
|
+
Eigenheiten: GP Tom schreibt `transactionType` im Plugin-`toMap` mit
|
|
26
|
+
Tippfehler (`transacitonType`), myPOS liefert den Zeitpunkt als
|
|
27
|
+
`YYMMDDhhmmss` ohne Trenner, Stripe kennt neben Karte auch EPS.
|
|
28
|
+
- `stripeReceiptLines` wird exportiert, wie im Dart-Paket: Druck und Anzeige
|
|
29
|
+
sollen dieselbe Quelle haben, nicht zwei.
|
|
30
|
+
- Ein myPOS-Zeitpunkt, der nicht wie `YYMMDDhhmmss` aussieht, wird
|
|
31
|
+
**unveraendert** durchgereicht statt zerschnitten — eine halb geratene
|
|
32
|
+
Uhrzeit auf einem Beleg ist schlimmer als ein roher Wert.
|
|
33
|
+
- Ein unbekannter Anbieter ergibt weiterhin keinen Block, still und ohne
|
|
34
|
+
Fehler: ein Beleg muss sich immer zeigen lassen.
|
|
35
|
+
|
|
36
|
+
### Neun Golden-Belege fuer Kartenzahlungen
|
|
37
|
+
|
|
38
|
+
**Anlass:** Von den 22 Golden-Belegen trug **keiner** eine Kartenzahlung.
|
|
39
|
+
Deshalb konnte der Verlust der vier Bloecke passieren, ohne dass irgendetwas
|
|
40
|
+
rot wurde.
|
|
41
|
+
|
|
42
|
+
- Neu: `karte-hobex-hps`, `karte-hobex-cloud`, `karte-gptom`, `karte-gptom-ios`,
|
|
43
|
+
`karte-sumup`, `karte-mypos`, `karte-stripe`, `karte-stripe-eps`,
|
|
44
|
+
`karte-eigener`. Die Paare decken beide Zweige ab, wo es welche gibt
|
|
45
|
+
(Unterschriftsfeld ja/nein, Karte gegen EPS, Tippfehler-Schluessel).
|
|
46
|
+
- Neu: eine Pruefung, die die Anbieterliste **aus dem Enum** nimmt statt aus
|
|
47
|
+
einer Handliste — wer einen Anbieter aufnimmt, wird rot, bis ein Golden-Beleg
|
|
48
|
+
dazuliegt. Sie fand beim ersten Lauf sofort zwei weitere Luecken
|
|
49
|
+
(`gpTomIos`, `hobexCloudApi`).
|
|
50
|
+
- Neu: eine zweite Pruefung, dass ein Beleg MIT Terminaldaten auch wirklich
|
|
51
|
+
einen Block traegt. Ein Golden-Beleg allein beweist nur, dass die Datei da
|
|
52
|
+
ist.
|
|
53
|
+
|
|
7
54
|
## 0.7.2
|
|
8
55
|
|
|
9
56
|
### `55` („PIN falsch") ist eine gemessene Host-Ablehnung → `declined`
|
|
@@ -22,11 +22,15 @@ import type { PosPaperSize } from '../printing/escpos.js';
|
|
|
22
22
|
*
|
|
23
23
|
* Bewusst **nicht** enthalten: Firmenlogo und Rasterbilder (brauchen
|
|
24
24
|
* Bildverarbeitung, die im Browser anders aussieht als in Node), PDF-Erzeugung
|
|
25
|
-
* und Druckeransteuerung.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* und Druckeransteuerung.
|
|
26
|
+
*
|
|
27
|
+
* Die anbieterspezifischen Kartenzahlungsbloecke sind dagegen **alle**
|
|
28
|
+
* enthalten (Hobex, GP Tom, SumUp, myPOS, Stripe). Bis 0.8.0 trug das Modell
|
|
29
|
+
* nur den Hobex-Block, weil die Browser-Kasse nur das HPS-Terminal selbst
|
|
30
|
+
* bedient und die uebrigen Anbieter „in ihren eigenen Apps drucken". Das
|
|
31
|
+
* Argument gilt fuer die Frage, wer das TERMINAL bedient — nicht dafuer, was
|
|
32
|
+
* auf dem BELEGDOKUMENT steht. Aus der einen Regel wurde die andere, und
|
|
33
|
+
* derselbe Beleg trug im PDF weniger als auf dem Bon.
|
|
30
34
|
*/
|
|
31
35
|
/** Ausrichtung einer Zeile bzw. einer Spalte. */
|
|
32
36
|
export type LayoutAlign = 'left' | 'center' | 'right';
|
|
@@ -129,6 +133,8 @@ export interface BuildReceiptLayoutOptions {
|
|
|
129
133
|
* diesem Paket ausnahmslos ganzzahlig gerechnet).
|
|
130
134
|
*/
|
|
131
135
|
export declare function formatCents(cents: number): string;
|
|
136
|
+
/** Zwilling von `stripeReceiptLines` (print_paper.dart). */
|
|
137
|
+
export declare function stripeReceiptLines(daten: Record<string, unknown>, cardPaymentId: string | null): string[];
|
|
132
138
|
/**
|
|
133
139
|
* Hinweis auf die Steuerbefreiung, den ein Kleinunternehmerbeleg tragen muss —
|
|
134
140
|
* ohne ihn stuende in der USt-Tabelle „D 0 %" ohne jede Begruendung, und ab
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SMALL_BUSINESS_NOTICE = exports.AKTUELLES_REGELWERK = void 0;
|
|
4
4
|
exports.formatCents = formatCents;
|
|
5
|
+
exports.stripeReceiptLines = stripeReceiptLines;
|
|
5
6
|
exports.receiptSignatureFailed = receiptSignatureFailed;
|
|
6
7
|
exports.receiptSignatureIsTest = receiptSignatureIsTest;
|
|
7
8
|
exports.receiptIsZero = receiptIsZero;
|
|
@@ -71,6 +72,36 @@ function originalDatum(timeStamp) {
|
|
|
71
72
|
* `typeof null === 'object'` ist, ergab das beim Lesen von `.label` einen
|
|
72
73
|
* nackten TypeError.
|
|
73
74
|
*/
|
|
75
|
+
/**
|
|
76
|
+
* Die anbieterspezifischen Kartenzahlungsbloecke.
|
|
77
|
+
*
|
|
78
|
+
* WARUM DIESE BLOECKE HIER STEHEN
|
|
79
|
+
*
|
|
80
|
+
* Bis 0.8.0 trug dieses Modell nur den Hobex-Block, mit der Begruendung, die
|
|
81
|
+
* uebrigen Anbieter druckten "in ihren eigenen Apps". Das Argument gilt fuer
|
|
82
|
+
* die Frage, wer das TERMINAL bedient -- nicht dafuer, was auf dem
|
|
83
|
+
* BELEGDOKUMENT steht. Aus der einen Regel wurde die andere, und damit
|
|
84
|
+
* verschwanden vier Bloecke: Wer denselben Beleg als PDF oeffnete, sah "nur
|
|
85
|
+
* Kartenzahlung", waehrend der Bon daneben Marke, letzte Ziffern, Betrag und
|
|
86
|
+
* Referenz auswies. Vorher (backend `generateReceiptPdf`) hatte das PDF alle
|
|
87
|
+
* sieben Anbieter.
|
|
88
|
+
*
|
|
89
|
+
* Gebaut werden die Zeilen deshalb hier, an genau EINER Stelle. Wer sie
|
|
90
|
+
* anderswo noch einmal baut, oeffnet die Drift wieder.
|
|
91
|
+
*
|
|
92
|
+
* Vorbild ist `print_paper.dart` im Dart-Paket (`_gpTom`, `_hobexHps`,
|
|
93
|
+
* `_sumup`, `_mypos`, `_stripe`); die Fixtures unter `fixtures/belege/karte-*`
|
|
94
|
+
* halten je Anbieter eine Zeile-fuer-Zeile-Probe fest.
|
|
95
|
+
*/
|
|
96
|
+
/** Ein Feld aus `cardPaymentData` als Text; fehlend/null wird leer. */
|
|
97
|
+
function kartenFeld(daten, schluessel) {
|
|
98
|
+
const v = daten[schluessel];
|
|
99
|
+
return v == null ? '' : String(v);
|
|
100
|
+
}
|
|
101
|
+
/** Nicht-leere Teile mit Mittelpunkt verbinden. */
|
|
102
|
+
function verbunden(teile) {
|
|
103
|
+
return teile.filter(Boolean).join(' · ');
|
|
104
|
+
}
|
|
74
105
|
/**
|
|
75
106
|
* Kartenzahlungsblock fuer Hobex (HPS am Geraet wie Cloud-API): die Felder
|
|
76
107
|
* kommen aus `cardPaymentData` in der Form von [hobexReceiptToCardPaymentData]
|
|
@@ -78,43 +109,234 @@ function originalDatum(timeStamp) {
|
|
|
78
109
|
* Antwortcode. Bei CVM "1" verlangt die Karte eine Unterschrift; dafuer
|
|
79
110
|
* bekommt der Bon eine Linie. Der PAN haengt beim HPS das Ablaufdatum mit
|
|
80
111
|
* Unterstrich an ("…4720_2810") — gedruckt wird nur die maskierte Nummer.
|
|
81
|
-
*
|
|
82
|
-
* Andere Anbieter (GP Tom, SumUp, myPOS, Stripe) drucken in ihren eigenen
|
|
83
|
-
* Apps; ihre Bloecke bleiben bewusst draussen (siehe Kopfkommentar).
|
|
84
112
|
*/
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
const daten = receipt.cardPaymentData;
|
|
88
|
-
if ((provider !== 'hobexHps' && provider !== 'hobexCloudApi') || daten == null) {
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
const feld = (k) => {
|
|
92
|
-
const v = daten[k];
|
|
93
|
-
return v == null ? '' : String(v);
|
|
94
|
-
};
|
|
95
|
-
const paar = (teile) => teile.filter(Boolean).join(' · ');
|
|
96
|
-
const pan = feld('cardNumber').split('_')[0] ?? '';
|
|
97
|
-
const rc = feld('responseCode');
|
|
113
|
+
function hobexBlock(daten) {
|
|
114
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
98
115
|
const zeilen = [textZeile('Hobex Beleg', 'center', true)];
|
|
99
116
|
const inhalt = [
|
|
100
117
|
feld('date'),
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
verbunden([feld('tid') && `TID ${feld('tid')}`, feld('no') && `Nr. ${feld('no')}`]),
|
|
119
|
+
verbunden([feld('type'), feld('cardBrand')]),
|
|
120
|
+
feld('cardNumber').split('_')[0] ?? '',
|
|
121
|
+
feld('responseCode') && `RC ${feld('responseCode')}`,
|
|
122
|
+
];
|
|
123
|
+
for (const zeile of inhalt) {
|
|
124
|
+
if (zeile)
|
|
125
|
+
zeilen.push(textZeile(zeile, 'center'));
|
|
126
|
+
}
|
|
127
|
+
if (feld('cvm') === '1')
|
|
128
|
+
zeilen.push(...unterschriftsfeld());
|
|
129
|
+
return zeilen;
|
|
130
|
+
}
|
|
131
|
+
/** Zwei Leerzeilen, Linie, Wort -- das Unterschriftsfeld auf dem Bon. */
|
|
132
|
+
function unterschriftsfeld() {
|
|
133
|
+
return [
|
|
134
|
+
{ kind: 'space', lines: 2 },
|
|
135
|
+
textZeile('------------------', 'center'),
|
|
136
|
+
textZeile('Unterschrift', 'center'),
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* GP-Tom-`transactionType`: das Plugin-`toMap` schreibt den Schluessel mit
|
|
141
|
+
* Tippfehler (`transacitonType`), aeltere Daten tragen ihn gar nicht. Beide
|
|
142
|
+
* Schreibweisen werden gelesen -- ein Beleg von damals soll nicht anders
|
|
143
|
+
* aussehen als einer von heute.
|
|
144
|
+
*/
|
|
145
|
+
function gpTomVorgangsart(daten) {
|
|
146
|
+
switch (daten.transactionType ?? daten.transacitonType) {
|
|
147
|
+
case 1: return 'Sale';
|
|
148
|
+
case 2: return 'Void';
|
|
149
|
+
case 3: return 'Refund';
|
|
150
|
+
case 4: return 'Close Batch';
|
|
151
|
+
default: return '';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** GP-Tom-Betrag: ganze Zahl = Cent, sonst schon ein Eurobetrag. */
|
|
155
|
+
function gpTomBetrag(wert) {
|
|
156
|
+
if (typeof wert !== 'number')
|
|
157
|
+
return '-';
|
|
158
|
+
return Number.isInteger(wert) ? formatCents(wert) : wert.toFixed(2).replace('.', ',');
|
|
159
|
+
}
|
|
160
|
+
function gpTomBlock(daten) {
|
|
161
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
162
|
+
const art = gpTomVorgangsart(daten);
|
|
163
|
+
const zeilen = [textZeile('GP Tom Beleg', 'center', true)];
|
|
164
|
+
const inhalt = [
|
|
165
|
+
`Batch: ${feld('batchNumber')}`,
|
|
166
|
+
`Receipt: ${feld('externalTransactionID')}`,
|
|
167
|
+
`TID: ${feld('terminalID')}`,
|
|
168
|
+
feld('emvAid'),
|
|
169
|
+
(daten.emvAppLable != null || daten.cardDataEntry != null)
|
|
170
|
+
? `${feld('emvAppLable')} ${feld('cardDataEntry')}`.trim() : '',
|
|
171
|
+
feld('cardNumber'),
|
|
172
|
+
`${art ? `${art} ` : ''}Amount ${feld('currencyCode')} ${gpTomBetrag(daten.amount)}`,
|
|
173
|
+
daten.pinOk === true ? 'PIN OK' : 'PIN NOT OK',
|
|
174
|
+
`Authorization Code ${feld('approvedCode')}`,
|
|
175
|
+
`Sequence Number: ${feld('sequenceNumber')}`,
|
|
105
176
|
];
|
|
106
177
|
for (const zeile of inhalt) {
|
|
107
178
|
if (zeile)
|
|
108
179
|
zeilen.push(textZeile(zeile, 'center'));
|
|
109
180
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
181
|
+
return zeilen;
|
|
182
|
+
}
|
|
183
|
+
function sumupBlock(daten) {
|
|
184
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
185
|
+
const betrag = typeof daten.amount === 'number'
|
|
186
|
+
? (Number.isInteger(daten.amount) ? formatCents(daten.amount) : daten.amount.toFixed(2).replace('.', ','))
|
|
187
|
+
: '-';
|
|
188
|
+
return [
|
|
189
|
+
textZeile('Sumup Beleg', 'center', true),
|
|
190
|
+
paarZeile('Kartentyp:', feld('cardType') || 'n/a'),
|
|
191
|
+
paarZeile('Kartennummer:', `**** **** **** ${feld('cardLastDigits')}`),
|
|
192
|
+
paarZeile('Zahlungstyp:', feld('paymentType') || 'n/a'),
|
|
193
|
+
paarZeile('Gesamtbetrag:', `${betrag} ${feld('currency')}`.trim()),
|
|
194
|
+
paarZeile('Transaktionscode:', feld('transactionCode') || '-'),
|
|
195
|
+
paarZeile('Modus:', feld('entryMode').toUpperCase()),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* myPOS liefert den Zeitpunkt als `YYMMDDhhmmss` ohne Trenner. Ein Wert, der
|
|
200
|
+
* nicht so aussieht, wird UNVERAENDERT durchgereicht statt zerschnitten: eine
|
|
201
|
+
* halb geratene Uhrzeit auf einem Beleg ist schlimmer als ein roher Wert.
|
|
202
|
+
*/
|
|
203
|
+
function myposZeitpunkt(wert) {
|
|
204
|
+
const t = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(wert);
|
|
205
|
+
if (!t)
|
|
206
|
+
return wert;
|
|
207
|
+
return `${t[3]}.${t[2]}.20${t[1]} ${t[4]}:${t[5]}:${t[6]}`;
|
|
208
|
+
}
|
|
209
|
+
function myposBlock(daten) {
|
|
210
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
211
|
+
const zeilen = [
|
|
212
|
+
textZeile('MyPos Beleg', 'center', true),
|
|
213
|
+
paarZeile('TERMINAL ID:', feld('TID') || '-'),
|
|
214
|
+
paarZeile('DATUM:', myposZeitpunkt(feld('date_time'))),
|
|
215
|
+
];
|
|
216
|
+
if (feld('application_name'))
|
|
217
|
+
zeilen.push(textZeile(feld('application_name'), 'center', true));
|
|
218
|
+
zeilen.push(paarZeile('KARTE:', feld('pan') || '-'));
|
|
219
|
+
if (daten.signature_required === true)
|
|
220
|
+
zeilen.push(...unterschriftsfeld());
|
|
221
|
+
zeilen.push(paarZeile('STAN:', feld('STAN') ? feld('STAN').padStart(6, '0') : '-'));
|
|
222
|
+
zeilen.push(paarZeile('AUTH. CODE:', feld('authorization_code') || '-'));
|
|
223
|
+
zeilen.push(paarZeile('RRN:', feld('reference_number') || '-'));
|
|
224
|
+
zeilen.push(paarZeile('AID:', feld('AID') || '-'));
|
|
225
|
+
return zeilen;
|
|
226
|
+
}
|
|
227
|
+
const STRIPE_KARTENART = {
|
|
228
|
+
debit: 'Debitkarte',
|
|
229
|
+
credit: 'Kreditkarte',
|
|
230
|
+
prepaid: 'Prepaid-Karte',
|
|
231
|
+
};
|
|
232
|
+
const STRIPE_WALLET = {
|
|
233
|
+
apple_pay: 'Apple Pay',
|
|
234
|
+
google_pay: 'Google Pay',
|
|
235
|
+
};
|
|
236
|
+
/** EPS-Bank-Slug (`bank_austria`) -> Anzeigename (`Bank Austria`). */
|
|
237
|
+
function stripeEpsBank(slug) {
|
|
238
|
+
return slug.split('_').filter(Boolean)
|
|
239
|
+
.map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');
|
|
240
|
+
}
|
|
241
|
+
function stripeMarkenZeile(daten) {
|
|
242
|
+
const teile = [];
|
|
243
|
+
if (daten.cardBrand != null)
|
|
244
|
+
teile.push(String(daten.cardBrand));
|
|
245
|
+
const art = STRIPE_KARTENART[String(daten.cardFunding)];
|
|
246
|
+
if (art)
|
|
247
|
+
teile.push(art);
|
|
248
|
+
let zeile = teile.join(' ');
|
|
249
|
+
if (daten.wallet != null) {
|
|
250
|
+
const wallet = STRIPE_WALLET[String(daten.wallet)] ?? String(daten.wallet);
|
|
251
|
+
zeile = zeile ? `${zeile} (${wallet})` : `(${wallet})`;
|
|
252
|
+
}
|
|
253
|
+
return zeile;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Stripe-Zahlzeitpunkt: Sekunden seit Epoche, gedruckt in **Wiener** Zeit.
|
|
257
|
+
* Ueber `toViennaWallClock`, nicht ueber die lokale Zeitzone -- ein Beleg
|
|
258
|
+
* traegt Ortszeit des Betriebs, nicht die des Geraets, das ihn oeffnet.
|
|
259
|
+
*/
|
|
260
|
+
function stripeZeitpunkt(sekunden) {
|
|
261
|
+
const t = (0, vienna_time_js_1.toViennaWallClock)(new Date(sekunden * 1000));
|
|
262
|
+
const zwei = (n) => String(n).padStart(2, '0');
|
|
263
|
+
return `${zwei(t.day)}.${zwei(t.month)}.${t.year} ${zwei(t.hour)}:${zwei(t.minute)}`;
|
|
264
|
+
}
|
|
265
|
+
/** Zwilling von `stripeReceiptLines` (print_paper.dart). */
|
|
266
|
+
function stripeReceiptLines(daten, cardPaymentId) {
|
|
267
|
+
const zeilen = [];
|
|
268
|
+
const art = daten.paymentMethodType == null ? null : String(daten.paymentMethodType);
|
|
269
|
+
if (art === 'card') {
|
|
270
|
+
const marke = stripeMarkenZeile(daten);
|
|
271
|
+
if (marke)
|
|
272
|
+
zeilen.push(marke);
|
|
273
|
+
if (daten.cardLastDigits != null)
|
|
274
|
+
zeilen.push(`**** **** **** ${daten.cardLastDigits}`);
|
|
275
|
+
if (daten.threeDSecure === 'authenticated')
|
|
276
|
+
zeilen.push('3-D Secure: ja');
|
|
277
|
+
}
|
|
278
|
+
else if (art === 'eps') {
|
|
279
|
+
if (daten.epsBank != null)
|
|
280
|
+
zeilen.push(`EPS - ${stripeEpsBank(String(daten.epsBank))}`);
|
|
281
|
+
}
|
|
282
|
+
else if (art != null) {
|
|
283
|
+
zeilen.push(art.toUpperCase());
|
|
284
|
+
}
|
|
285
|
+
if (typeof daten.amount === 'number' && Number.isInteger(daten.amount)) {
|
|
286
|
+
const waehrung = daten.currency == null ? 'EUR' : String(daten.currency).toUpperCase();
|
|
287
|
+
zeilen.push(`Gesamtbetrag ${formatCents(daten.amount)} ${waehrung}`);
|
|
288
|
+
}
|
|
289
|
+
if (typeof daten.paidAt === 'number' && Number.isInteger(daten.paidAt)) {
|
|
290
|
+
zeilen.push(`Bezahlt: ${stripeZeitpunkt(daten.paidAt)}`);
|
|
291
|
+
}
|
|
292
|
+
if (daten.statementDescriptor != null)
|
|
293
|
+
zeilen.push(`Abrechnung: ${daten.statementDescriptor}`);
|
|
294
|
+
if (cardPaymentId != null)
|
|
295
|
+
zeilen.push(`Referenz: ${cardPaymentId}`);
|
|
296
|
+
return zeilen;
|
|
297
|
+
}
|
|
298
|
+
function stripeBlock(daten, cardPaymentId) {
|
|
299
|
+
const zeilen = [textZeile('Online-Zahlung (Stripe)', 'center', true)];
|
|
300
|
+
for (const zeile of stripeReceiptLines(daten, cardPaymentId)) {
|
|
301
|
+
zeilen.push(textZeile(zeile, 'center'));
|
|
114
302
|
}
|
|
115
|
-
zeilen.push({ kind: 'space', lines: 1 });
|
|
116
303
|
return zeilen;
|
|
117
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Der Kartenzahlungsblock des Belegs -- fuer JEDEN Anbieter, den das Backend
|
|
307
|
+
* kennt. Ohne `cardPaymentData` bleibt der Block weg; ein unbekannter Anbieter
|
|
308
|
+
* ebenfalls, still und ohne Fehler: ein Beleg muss sich immer zeigen lassen.
|
|
309
|
+
*/
|
|
310
|
+
function kartenblock(receipt) {
|
|
311
|
+
const daten = receipt.cardPaymentData;
|
|
312
|
+
if (daten == null)
|
|
313
|
+
return [];
|
|
314
|
+
const zeilen = (() => {
|
|
315
|
+
switch (receipt.creditCardProvider) {
|
|
316
|
+
case 'hobexHps':
|
|
317
|
+
case 'hobexCloudApi':
|
|
318
|
+
return hobexBlock(daten);
|
|
319
|
+
case 'gpTomAndroid':
|
|
320
|
+
case 'gpTomIos':
|
|
321
|
+
return gpTomBlock(daten);
|
|
322
|
+
case 'sumup':
|
|
323
|
+
return sumupBlock(daten);
|
|
324
|
+
case 'myposPro':
|
|
325
|
+
return myposBlock(daten);
|
|
326
|
+
case 'stripe':
|
|
327
|
+
return stripeBlock(daten, receipt.cardPaymentId ?? null);
|
|
328
|
+
default:
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
})();
|
|
332
|
+
if (zeilen.length === 0)
|
|
333
|
+
return [];
|
|
334
|
+
// Eine Leerzeile hinter JEDEM Block, nicht nur hinter manchen. Das Vorbild
|
|
335
|
+
// ist hier uneinheitlich (nur `_sumup` setzt am Ende ein `addFeed()`), was
|
|
336
|
+
// den Abstand zur Fusszeile vom Anbieter abhaengig machte. Die Leerzeile
|
|
337
|
+
// gehoert aber zum Blatt, nicht zum Terminal.
|
|
338
|
+
return [...zeilen, { kind: 'space', lines: 1 }];
|
|
339
|
+
}
|
|
118
340
|
function zahlungsartText(wert) {
|
|
119
341
|
if (wert != null && typeof wert === 'object') {
|
|
120
342
|
return wert.label;
|
|
@@ -708,7 +930,7 @@ function buildReceiptLayout(receipt, company, options = {}) {
|
|
|
708
930
|
lines.push({ kind: 'qr', data: receipt.qr });
|
|
709
931
|
lines.push({ kind: 'space', lines: 1 });
|
|
710
932
|
// --- Kartenzahlungsblock (Hobex) — wie im Vorbild UNTER dem QR-Code.
|
|
711
|
-
lines.push(...
|
|
933
|
+
lines.push(...kartenblock(receipt));
|
|
712
934
|
// --- Dankestext und Fusszeilen
|
|
713
935
|
if (company.thanksMessage.length > 0) {
|
|
714
936
|
lines.push({ kind: 'space', lines: 1 });
|
|
@@ -22,11 +22,15 @@ import type { PosPaperSize } from '../printing/escpos.js';
|
|
|
22
22
|
*
|
|
23
23
|
* Bewusst **nicht** enthalten: Firmenlogo und Rasterbilder (brauchen
|
|
24
24
|
* Bildverarbeitung, die im Browser anders aussieht als in Node), PDF-Erzeugung
|
|
25
|
-
* und Druckeransteuerung.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* und Druckeransteuerung.
|
|
26
|
+
*
|
|
27
|
+
* Die anbieterspezifischen Kartenzahlungsbloecke sind dagegen **alle**
|
|
28
|
+
* enthalten (Hobex, GP Tom, SumUp, myPOS, Stripe). Bis 0.8.0 trug das Modell
|
|
29
|
+
* nur den Hobex-Block, weil die Browser-Kasse nur das HPS-Terminal selbst
|
|
30
|
+
* bedient und die uebrigen Anbieter „in ihren eigenen Apps drucken". Das
|
|
31
|
+
* Argument gilt fuer die Frage, wer das TERMINAL bedient — nicht dafuer, was
|
|
32
|
+
* auf dem BELEGDOKUMENT steht. Aus der einen Regel wurde die andere, und
|
|
33
|
+
* derselbe Beleg trug im PDF weniger als auf dem Bon.
|
|
30
34
|
*/
|
|
31
35
|
/** Ausrichtung einer Zeile bzw. einer Spalte. */
|
|
32
36
|
export type LayoutAlign = 'left' | 'center' | 'right';
|
|
@@ -129,6 +133,8 @@ export interface BuildReceiptLayoutOptions {
|
|
|
129
133
|
* diesem Paket ausnahmslos ganzzahlig gerechnet).
|
|
130
134
|
*/
|
|
131
135
|
export declare function formatCents(cents: number): string;
|
|
136
|
+
/** Zwilling von `stripeReceiptLines` (print_paper.dart). */
|
|
137
|
+
export declare function stripeReceiptLines(daten: Record<string, unknown>, cardPaymentId: string | null): string[];
|
|
132
138
|
/**
|
|
133
139
|
* Hinweis auf die Steuerbefreiung, den ein Kleinunternehmerbeleg tragen muss —
|
|
134
140
|
* ohne ihn stuende in der USt-Tabelle „D 0 %" ohne jede Begruendung, und ab
|