@kreiseck/kasseneck-api 0.7.2 → 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/kasse/index.d.ts +1 -0
- package/dist/cjs/kasse/index.js +7 -1
- package/dist/cjs/kasse/texte.d.ts +336 -0
- package/dist/cjs/kasse/texte.js +150 -0
- package/dist/cjs/receipt/layout.d.ts +11 -5
- package/dist/cjs/receipt/layout.js +248 -26
- package/dist/esm/kasse/index.d.ts +1 -0
- package/dist/esm/kasse/index.js +2 -0
- package/dist/esm/kasse/texte.d.ts +336 -0
- package/dist/esm/kasse/texte.js +145 -0
- 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-meldungen-faelle.json +32 -0
- package/fixtures/kasse-texte.json +385 -0
- package/fixtures/manifest.json +54 -0
- package/fixtures/oberflaeche.json +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Was die Kasse selbst sagt — auf dem Bildschirm, in beiden Huellen.
|
|
3
|
+
*
|
|
4
|
+
* Die Browser-Kasse und die Kassen-App sind eine Kasse. Was der Kassier bei
|
|
5
|
+
* einem Fehler liest, steht hier genau einmal; beide Apps beziehen den Satz
|
|
6
|
+
* von hier, und wer ihn aendert, aendert ihn auf beiden Seiten oder gar nicht.
|
|
7
|
+
*
|
|
8
|
+
* Nicht hier: die fachlichen Saetze des Backends („Kopplungs-Code ist
|
|
9
|
+
* abgelaufen.", „PIN falsch, noch 2 Versuche"). Die gehen unveraendert durch.
|
|
10
|
+
*
|
|
11
|
+
* Ein Satz sagt, was los ist, und was zu tun ist. Nie Innereien.
|
|
12
|
+
*
|
|
13
|
+
* Schluessel: `bereich.name`. Ein Schluessel wird nie umgedeutet — wer den
|
|
14
|
+
* Sinn aendert, legt einen neuen an. `nur` nennt die Seite, wenn ein Satz nur
|
|
15
|
+
* auf einer Plattform vorkommen kann.
|
|
16
|
+
*/
|
|
17
|
+
const MELDUNGEN_ROH = {
|
|
18
|
+
// --- Transport -----------------------------------------------------------
|
|
19
|
+
'netz.keine_verbindung': { text: 'Keine Verbindung zum Server. Bitte die Internetverbindung prüfen und erneut versuchen.' },
|
|
20
|
+
'netz.zeitablauf': { text: 'Der Server antwortet nicht. Bitte die Internetverbindung prüfen und erneut versuchen.' },
|
|
21
|
+
'server.unerwartet': { text: 'Der Server hat unerwartet geantwortet (HTTP {status}). Bitte den Support verständigen.', platzhalter: ['status'] },
|
|
22
|
+
// --- Kopplung ------------------------------------------------------------
|
|
23
|
+
'kopplung.code_fehlt': { text: 'Bitte den Kopplungs-Code eingeben.' },
|
|
24
|
+
'kopplung.fehlgeschlagen': { text: 'Die Kopplung ist fehlgeschlagen. Bitte erneut versuchen.' },
|
|
25
|
+
'kopplung.unvollstaendig': { text: 'Die Kopplung ist unvollständig zurückgekommen. Bitte im Panel einen neuen Code erzeugen und noch einmal versuchen.' },
|
|
26
|
+
'geraet.browser_speicher': { text: 'Dieses Gerät konnte nicht gespeichert werden — der Browser-Speicher steht nicht zur Verfügung. Bitte den privaten Modus verlassen.', nur: ['web'] },
|
|
27
|
+
// --- Anmeldung und Sitzung -----------------------------------------------
|
|
28
|
+
'anmeldung.pin_fehlt': { text: 'Bitte die PIN eingeben.' },
|
|
29
|
+
'anmeldung.fehlgeschlagen': { text: 'Die Anmeldung ist fehlgeschlagen. Bitte erneut versuchen.' },
|
|
30
|
+
'anmeldung.nicht_abgeschlossen': { text: 'Die Anmeldung konnte nicht abgeschlossen werden. Bitte erneut versuchen.' },
|
|
31
|
+
'anmeldung.wird_abgeschlossen': { text: 'Anmeldung wird gerade abgeschlossen — bitte gleich noch einmal drücken.' },
|
|
32
|
+
'sitzung.abgelaufen': { text: 'Die Sitzung ist abgelaufen. Bitte erneut anmelden.' },
|
|
33
|
+
'sitzung.abgelaufen_ohne_netz': { text: 'Keine Verbindung zum Server — die Sitzung ist abgelaufen. Bitte erneut anmelden.' },
|
|
34
|
+
'sitzung.keine': { text: 'Keine Sitzung.' },
|
|
35
|
+
'rechte.nicht_aenderbar': { text: 'Das darfst du nicht ändern — das Recht dafür vergibt der Inhaber im Panel.' },
|
|
36
|
+
// --- Verkauf und Positionen ----------------------------------------------
|
|
37
|
+
'kasse.laden_fehlgeschlagen': { text: 'Die Kasse konnte nicht geladen werden.' },
|
|
38
|
+
'artikel.laden_fehlgeschlagen': { text: 'Die Artikel konnten nicht geladen werden.' },
|
|
39
|
+
'artikel.keine_freigegeben': { text: 'Keine Artikel an dieser Kasse. Im Panel unter „Kacheln & Gruppen“ freischalten.' },
|
|
40
|
+
'artikel.keine_gefunden': { text: 'Keine Artikel gefunden.' },
|
|
41
|
+
'artikel.steuersatz_unbekannt': { text: '„{name}“ hat einen Steuersatz, den die Kasse nicht kennt.', platzhalter: ['name'] },
|
|
42
|
+
'position.betrag_fehlt': { text: 'Bitte einen Betrag eingeben.' },
|
|
43
|
+
'position.bezeichnung_fehlt': { text: 'Bitte eine Bezeichnung eingeben — sie steht am Beleg.' },
|
|
44
|
+
'rabatt.prozent_ungueltig': { text: 'Bitte einen Prozentwert zwischen 0,1 und 100 eingeben.' },
|
|
45
|
+
// --- Abschluss -----------------------------------------------------------
|
|
46
|
+
'abschluss.fehlgeschlagen': { text: 'Der Abschluss ist fehlgeschlagen.' },
|
|
47
|
+
'abschluss.unklar': { text: 'Unklar, ob der Beleg entstanden ist — die Antwort kam nicht an. Bitte nicht noch einmal abschließen: der Beleg kann bereits erstellt und signiert sein. Im Panel unter „Belege“ nachsehen; nur wenn er dort fehlt, den Verkauf erneut abschließen.' },
|
|
48
|
+
'abschluss.nullbeleg_fehlgeschlagen': { text: 'Der Nullbeleg konnte nicht erstellt werden.' },
|
|
49
|
+
'abschluss.signatur_ausgefallen': { text: 'Die Signatureinheit hat nicht geantwortet. Der Beleg ist gültig und trägt den Vermerk „Sicherheitseinrichtung ausgefallen“.' },
|
|
50
|
+
// --- Kartenzahlung -------------------------------------------------------
|
|
51
|
+
'kartenzahlung.nicht_moeglich': { text: 'Kartenzahlung nicht möglich.' },
|
|
52
|
+
'kartenzahlung.nicht_zustande': { text: 'Die Kartenzahlung ist nicht zustande gekommen.' },
|
|
53
|
+
'kartenzahlung.nicht_gestartet': { text: 'Kartenzahlung nicht gestartet: {grund}', platzhalter: ['grund'] },
|
|
54
|
+
'kartenzahlung.unklar': { text: 'Unklar, ob die Kartenzahlung durchgegangen ist — die Verbindung zum Terminal riss ab. Bitte am Terminal-Beleg nachsehen, BEVOR neu kassiert wird: die Karte kann belastet sein.' },
|
|
55
|
+
// Die Kennung ist der einzige Anker, um die Zahlung am Terminal-Beleg wiederzufinden — liegt sie vor, gilt dieser Satz statt kartenzahlung.unklar.
|
|
56
|
+
'kartenzahlung.unklar_mit_kennung': { text: 'Unklar, ob die Kartenzahlung durchgegangen ist — die Verbindung zum Terminal riss ab. Bitte am Terminal-Beleg nachsehen, BEVOR neu kassiert wird: die Karte kann belastet sein. Kennung der Zahlung: {kennung}.', platzhalter: ['kennung'] },
|
|
57
|
+
'kartenzahlung.connect_nicht_verbunden': { text: 'Kartenzahlung nicht möglich: Kasseneck Connect ist nicht verbunden — Einstellungen → Kasseneck Connect.', nur: ['web'] },
|
|
58
|
+
'terminal.keines_gefunden': { text: 'Kein Hobex-Terminal gefunden — ist es eingeschaltet und im selben Netz wie dieser Rechner?', nur: ['web'] },
|
|
59
|
+
'terminal.nicht_bereit': { text: 'Terminal antwortet, ist aber nicht betriebsbereit: {antwort} — TID prüfen.', platzhalter: ['antwort'], nur: ['web'] },
|
|
60
|
+
'gptom.app_fehlt': { text: 'Die GP-Tom-App ist auf diesem Gerät nicht da.', nur: ['app'] },
|
|
61
|
+
'gptom.suche_fehlgeschlagen': { text: 'GP Tom: Suche nach der App fehlgeschlagen: {grund}', platzhalter: ['grund'], nur: ['app'] },
|
|
62
|
+
'gptom.zahlung_fehlgeschlagen': { text: 'GP Tom: Zahlung fehlgeschlagen: {grund}', platzhalter: ['grund'], nur: ['app'] },
|
|
63
|
+
'gptom.terminal_antwortet_nicht': { text: 'Das Terminal hat nicht geantwortet: {grund}', platzhalter: ['grund'], nur: ['app'] },
|
|
64
|
+
'gptom.zahlung_nicht_abgeschlossen': { text: 'Die Zahlung wurde nicht abgeschlossen ({code}).', platzhalter: ['code'], nur: ['app'] },
|
|
65
|
+
// --- Belege und Storno ---------------------------------------------------
|
|
66
|
+
'belege.laden_fehlgeschlagen': { text: 'Die Belege konnten nicht geladen werden.' },
|
|
67
|
+
'belege.keine_im_zeitraum': { text: 'Keine Belege in diesem Zeitraum.' },
|
|
68
|
+
'beleg.laden_fehlgeschlagen': { text: 'Der Beleg konnte nicht geladen werden.' },
|
|
69
|
+
'beleg.nicht_gefunden': { text: 'Der Beleg wurde nicht gefunden.' },
|
|
70
|
+
'storno.fehlgeschlagen': { text: 'Das Storno ist fehlgeschlagen.' },
|
|
71
|
+
'storno.grund_fehlt': { text: 'Bitte einen Grund wählen.' },
|
|
72
|
+
'storno.position_fehlt': { text: 'Bitte mindestens eine Position wählen.' },
|
|
73
|
+
// --- Druck ---------------------------------------------------------------
|
|
74
|
+
'druck.fehlgeschlagen': { text: 'Der Ausdruck ist fehlgeschlagen.' },
|
|
75
|
+
'druck.nicht_moeglich': { text: 'Druck nicht möglich: {grund}', platzhalter: ['grund'] },
|
|
76
|
+
'druck.testdruck_fehlgeschlagen': { text: 'Der Testdruck ist fehlgeschlagen.' },
|
|
77
|
+
'druck.testdruck_nicht_moeglich': { text: 'Der Testdruck ist fehlgeschlagen: {grund}', platzhalter: ['grund'] },
|
|
78
|
+
'druck.kein_drucker': { text: 'Kein Bondrucker eingerichtet — in den Einstellungen unter Drucker & Lade.' },
|
|
79
|
+
'druck.drucker_nicht_erreichbar': { text: 'Drucker nicht erreichbar.' },
|
|
80
|
+
'druck.job_abgelaufen': { text: 'Drucker hat den Beleg nicht abgeholt (abgelaufen).', nur: ['web'] },
|
|
81
|
+
'druck.kein_drucker_gefunden': { text: 'Kein Drucker gefunden — ist er eingeschaltet und im selben Netz wie dieser Rechner?', nur: ['web'] },
|
|
82
|
+
'druck.nur_chrome': { text: '{weg}-Druck geht nur in Chrome oder Edge (Windows, Mac, Android) — nicht in Safari und nicht am iPad.', platzhalter: ['weg'], nur: ['web'] },
|
|
83
|
+
'druck.kein_weg_drucker': { text: 'Kein {weg}-Drucker verbunden — „{weg}-Drucker verbinden“ und den Drucker im Dialog wählen.', platzhalter: ['weg'], nur: ['web'] },
|
|
84
|
+
'bluetooth.aus': { text: 'Bluetooth ist ausgeschaltet. Bitte einschalten und erneut suchen.', nur: ['app'] },
|
|
85
|
+
'bluetooth.freigabe_fehlt': { text: 'Bitte die Freigabe in den Geräte-Einstellungen erteilen.', nur: ['app'] },
|
|
86
|
+
'bluetooth.suche_fehlgeschlagen': { text: 'Die Suche ist fehlgeschlagen: {grund}', platzhalter: ['grund'], nur: ['app'] },
|
|
87
|
+
// --- Kasseneck Connect (nur Browser) -------------------------------------
|
|
88
|
+
'connect.antwortet_nicht': { text: 'Kasseneck Connect antwortet nicht — läuft das Programm auf diesem Rechner?', nur: ['web'] },
|
|
89
|
+
'connect.code_abgelaufen': { text: 'Der Kopplungs-Code ist abgelaufen — im Agent einen neuen erzeugen („kasseneck-connect pair“).', nur: ['web'] },
|
|
90
|
+
'connect.zu_viele_versuche': { text: 'Zu viele Fehlversuche — eine Minute warten und noch einmal versuchen.', nur: ['web'] },
|
|
91
|
+
'connect.drucker_antwortet_nicht': { text: 'Der Drucker antwortet nicht — Strom, Netzwerk und IP prüfen.', nur: ['web'] },
|
|
92
|
+
'connect.nicht_gekoppelt': { text: 'Diese Kasse ist mit Kasseneck Connect nicht gekoppelt — in den Einstellungen „Koppeln“ drücken.', nur: ['web'] },
|
|
93
|
+
'connect.drucker_unbekannt': { text: 'Diesen Drucker kennt Kasseneck Connect nicht (mehr) — bitte neu suchen.', nur: ['web'] },
|
|
94
|
+
'connect.ursprung_nicht_freigegeben': { text: 'Kasseneck Connect nimmt von dieser Adresse nichts an (Ursprung nicht freigegeben).', nur: ['web'] },
|
|
95
|
+
'connect.nicht_installiert': { text: 'Kasseneck Connect nicht gefunden — bitte installieren.', nur: ['web'] },
|
|
96
|
+
// --- Einstellungen -------------------------------------------------------
|
|
97
|
+
'einstellungen.laden_fehlgeschlagen': { text: 'Die Einstellungen konnten nicht geladen werden.' },
|
|
98
|
+
'einstellungen.speichern_fehlgeschlagen': { text: 'Die Einstellung konnte nicht gespeichert werden.' },
|
|
99
|
+
'einstellungen.noch_nicht_geladen': { text: 'Noch nicht geladen — bitte kurz warten oder neu anmelden.' },
|
|
100
|
+
'einstellungen.ip_ungueltig': { text: 'Keine gültige IP-Adresse (z. B. 192.168.1.50).' },
|
|
101
|
+
'entkopplung.fehlgeschlagen': { text: 'Entkoppeln fehlgeschlagen.' },
|
|
102
|
+
'logo.hochladen_fehlgeschlagen': { text: 'Hochladen fehlgeschlagen.' },
|
|
103
|
+
'logo.entfernen_fehlgeschlagen': { text: 'Entfernen fehlgeschlagen.' },
|
|
104
|
+
// --- Nur App -------------------------------------------------------------
|
|
105
|
+
'app.nicht_im_browser': { text: 'Die Kassen-App läuft nicht im Browser — dafür gibt es kasse.kasseneck.at.', nur: ['app'] },
|
|
106
|
+
'app.im_browser_oeffnen': { text: 'Bitte im Browser öffnen: {ziel}', platzhalter: ['ziel'], nur: ['app'] },
|
|
107
|
+
};
|
|
108
|
+
// Auf den gemeinsamen Typ gebracht: `Object.entries(MELDUNGEN)` liefert sonst
|
|
109
|
+
// pro Schluessel den engsten Literaltyp, und `platzhalter`/`nur` waeren nur
|
|
110
|
+
// auf manchen Zweigen der Vereinigung vorhanden.
|
|
111
|
+
export const MELDUNGEN = MELDUNGEN_ROH;
|
|
112
|
+
/**
|
|
113
|
+
* In welcher Reihenfolge ein Fehler eingeordnet wird — auf beiden Seiten
|
|
114
|
+
* dieselbe. Die Arten:
|
|
115
|
+
* api — HTTP 200, `status:'error'`: der Satz des Backends, woertlich
|
|
116
|
+
* klartext — schon fuer den Bildschirm geschrieben: sein eigener Text
|
|
117
|
+
* zeitablauf — die Frist lief ab, die Anfrage war draussen
|
|
118
|
+
* netz — keine Verbindung zustande gekommen oder abgerissen
|
|
119
|
+
* unerwartet — der Server hat geantwortet, aber nicht wie zugesagt
|
|
120
|
+
* (HTML statt JSON, 500, fehlende Huelle); `status` = HTTP-Code
|
|
121
|
+
* sonst — alles Uebrige ist technisch: der Ersatzsatz des Vorgangs
|
|
122
|
+
*/
|
|
123
|
+
export const FEHLERREGELN = [
|
|
124
|
+
{ art: 'api', verhalten: 'server_text' },
|
|
125
|
+
{ art: 'klartext', verhalten: 'eigener_text' },
|
|
126
|
+
{ art: 'zeitablauf', schluessel: 'netz.zeitablauf' },
|
|
127
|
+
{ art: 'netz', schluessel: 'netz.keine_verbindung' },
|
|
128
|
+
{ art: 'unerwartet', schluessel: 'server.unerwartet' },
|
|
129
|
+
{ art: 'sonst', verhalten: 'ersatz' },
|
|
130
|
+
];
|
|
131
|
+
/** Der Satz zum Schluessel, Platzhalter ersetzt. Fehlt ein Wert, wirft es — ein `{status}` am Tresen waere schlimmer. */
|
|
132
|
+
export function meldung(schluessel, werte = {}) {
|
|
133
|
+
const eintrag = MELDUNGEN[schluessel];
|
|
134
|
+
return eintrag.text.replace(/\{([a-z]+)\}/g, (_, name) => {
|
|
135
|
+
const wert = werte[name];
|
|
136
|
+
if (wert === undefined)
|
|
137
|
+
throw new Error(`meldung(${schluessel}): Platzhalter {${name}} ohne Wert`);
|
|
138
|
+
return String(wert);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** Gilt der Satz auf dieser Seite? */
|
|
142
|
+
export function meldungGiltFuer(schluessel, seite) {
|
|
143
|
+
const eintrag = MELDUNGEN[schluessel];
|
|
144
|
+
return eintrag.nur === undefined || eintrag.nur.includes(seite);
|
|
145
|
+
}
|
|
@@ -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
|
|
@@ -60,6 +60,36 @@ function originalDatum(timeStamp) {
|
|
|
60
60
|
* `typeof null === 'object'` ist, ergab das beim Lesen von `.label` einen
|
|
61
61
|
* nackten TypeError.
|
|
62
62
|
*/
|
|
63
|
+
/**
|
|
64
|
+
* Die anbieterspezifischen Kartenzahlungsbloecke.
|
|
65
|
+
*
|
|
66
|
+
* WARUM DIESE BLOECKE HIER STEHEN
|
|
67
|
+
*
|
|
68
|
+
* Bis 0.8.0 trug dieses Modell nur den Hobex-Block, mit der Begruendung, die
|
|
69
|
+
* uebrigen Anbieter druckten "in ihren eigenen Apps". Das Argument gilt fuer
|
|
70
|
+
* die Frage, wer das TERMINAL bedient -- nicht dafuer, was auf dem
|
|
71
|
+
* BELEGDOKUMENT steht. Aus der einen Regel wurde die andere, und damit
|
|
72
|
+
* verschwanden vier Bloecke: Wer denselben Beleg als PDF oeffnete, sah "nur
|
|
73
|
+
* Kartenzahlung", waehrend der Bon daneben Marke, letzte Ziffern, Betrag und
|
|
74
|
+
* Referenz auswies. Vorher (backend `generateReceiptPdf`) hatte das PDF alle
|
|
75
|
+
* sieben Anbieter.
|
|
76
|
+
*
|
|
77
|
+
* Gebaut werden die Zeilen deshalb hier, an genau EINER Stelle. Wer sie
|
|
78
|
+
* anderswo noch einmal baut, oeffnet die Drift wieder.
|
|
79
|
+
*
|
|
80
|
+
* Vorbild ist `print_paper.dart` im Dart-Paket (`_gpTom`, `_hobexHps`,
|
|
81
|
+
* `_sumup`, `_mypos`, `_stripe`); die Fixtures unter `fixtures/belege/karte-*`
|
|
82
|
+
* halten je Anbieter eine Zeile-fuer-Zeile-Probe fest.
|
|
83
|
+
*/
|
|
84
|
+
/** Ein Feld aus `cardPaymentData` als Text; fehlend/null wird leer. */
|
|
85
|
+
function kartenFeld(daten, schluessel) {
|
|
86
|
+
const v = daten[schluessel];
|
|
87
|
+
return v == null ? '' : String(v);
|
|
88
|
+
}
|
|
89
|
+
/** Nicht-leere Teile mit Mittelpunkt verbinden. */
|
|
90
|
+
function verbunden(teile) {
|
|
91
|
+
return teile.filter(Boolean).join(' · ');
|
|
92
|
+
}
|
|
63
93
|
/**
|
|
64
94
|
* Kartenzahlungsblock fuer Hobex (HPS am Geraet wie Cloud-API): die Felder
|
|
65
95
|
* kommen aus `cardPaymentData` in der Form von [hobexReceiptToCardPaymentData]
|
|
@@ -67,43 +97,234 @@ function originalDatum(timeStamp) {
|
|
|
67
97
|
* Antwortcode. Bei CVM "1" verlangt die Karte eine Unterschrift; dafuer
|
|
68
98
|
* bekommt der Bon eine Linie. Der PAN haengt beim HPS das Ablaufdatum mit
|
|
69
99
|
* Unterstrich an ("…4720_2810") — gedruckt wird nur die maskierte Nummer.
|
|
70
|
-
*
|
|
71
|
-
* Andere Anbieter (GP Tom, SumUp, myPOS, Stripe) drucken in ihren eigenen
|
|
72
|
-
* Apps; ihre Bloecke bleiben bewusst draussen (siehe Kopfkommentar).
|
|
73
100
|
*/
|
|
74
|
-
function
|
|
75
|
-
const
|
|
76
|
-
const daten = receipt.cardPaymentData;
|
|
77
|
-
if ((provider !== 'hobexHps' && provider !== 'hobexCloudApi') || daten == null) {
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
const feld = (k) => {
|
|
81
|
-
const v = daten[k];
|
|
82
|
-
return v == null ? '' : String(v);
|
|
83
|
-
};
|
|
84
|
-
const paar = (teile) => teile.filter(Boolean).join(' · ');
|
|
85
|
-
const pan = feld('cardNumber').split('_')[0] ?? '';
|
|
86
|
-
const rc = feld('responseCode');
|
|
101
|
+
function hobexBlock(daten) {
|
|
102
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
87
103
|
const zeilen = [textZeile('Hobex Beleg', 'center', true)];
|
|
88
104
|
const inhalt = [
|
|
89
105
|
feld('date'),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
verbunden([feld('tid') && `TID ${feld('tid')}`, feld('no') && `Nr. ${feld('no')}`]),
|
|
107
|
+
verbunden([feld('type'), feld('cardBrand')]),
|
|
108
|
+
feld('cardNumber').split('_')[0] ?? '',
|
|
109
|
+
feld('responseCode') && `RC ${feld('responseCode')}`,
|
|
110
|
+
];
|
|
111
|
+
for (const zeile of inhalt) {
|
|
112
|
+
if (zeile)
|
|
113
|
+
zeilen.push(textZeile(zeile, 'center'));
|
|
114
|
+
}
|
|
115
|
+
if (feld('cvm') === '1')
|
|
116
|
+
zeilen.push(...unterschriftsfeld());
|
|
117
|
+
return zeilen;
|
|
118
|
+
}
|
|
119
|
+
/** Zwei Leerzeilen, Linie, Wort -- das Unterschriftsfeld auf dem Bon. */
|
|
120
|
+
function unterschriftsfeld() {
|
|
121
|
+
return [
|
|
122
|
+
{ kind: 'space', lines: 2 },
|
|
123
|
+
textZeile('------------------', 'center'),
|
|
124
|
+
textZeile('Unterschrift', 'center'),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* GP-Tom-`transactionType`: das Plugin-`toMap` schreibt den Schluessel mit
|
|
129
|
+
* Tippfehler (`transacitonType`), aeltere Daten tragen ihn gar nicht. Beide
|
|
130
|
+
* Schreibweisen werden gelesen -- ein Beleg von damals soll nicht anders
|
|
131
|
+
* aussehen als einer von heute.
|
|
132
|
+
*/
|
|
133
|
+
function gpTomVorgangsart(daten) {
|
|
134
|
+
switch (daten.transactionType ?? daten.transacitonType) {
|
|
135
|
+
case 1: return 'Sale';
|
|
136
|
+
case 2: return 'Void';
|
|
137
|
+
case 3: return 'Refund';
|
|
138
|
+
case 4: return 'Close Batch';
|
|
139
|
+
default: return '';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** GP-Tom-Betrag: ganze Zahl = Cent, sonst schon ein Eurobetrag. */
|
|
143
|
+
function gpTomBetrag(wert) {
|
|
144
|
+
if (typeof wert !== 'number')
|
|
145
|
+
return '-';
|
|
146
|
+
return Number.isInteger(wert) ? formatCents(wert) : wert.toFixed(2).replace('.', ',');
|
|
147
|
+
}
|
|
148
|
+
function gpTomBlock(daten) {
|
|
149
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
150
|
+
const art = gpTomVorgangsart(daten);
|
|
151
|
+
const zeilen = [textZeile('GP Tom Beleg', 'center', true)];
|
|
152
|
+
const inhalt = [
|
|
153
|
+
`Batch: ${feld('batchNumber')}`,
|
|
154
|
+
`Receipt: ${feld('externalTransactionID')}`,
|
|
155
|
+
`TID: ${feld('terminalID')}`,
|
|
156
|
+
feld('emvAid'),
|
|
157
|
+
(daten.emvAppLable != null || daten.cardDataEntry != null)
|
|
158
|
+
? `${feld('emvAppLable')} ${feld('cardDataEntry')}`.trim() : '',
|
|
159
|
+
feld('cardNumber'),
|
|
160
|
+
`${art ? `${art} ` : ''}Amount ${feld('currencyCode')} ${gpTomBetrag(daten.amount)}`,
|
|
161
|
+
daten.pinOk === true ? 'PIN OK' : 'PIN NOT OK',
|
|
162
|
+
`Authorization Code ${feld('approvedCode')}`,
|
|
163
|
+
`Sequence Number: ${feld('sequenceNumber')}`,
|
|
94
164
|
];
|
|
95
165
|
for (const zeile of inhalt) {
|
|
96
166
|
if (zeile)
|
|
97
167
|
zeilen.push(textZeile(zeile, 'center'));
|
|
98
168
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
169
|
+
return zeilen;
|
|
170
|
+
}
|
|
171
|
+
function sumupBlock(daten) {
|
|
172
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
173
|
+
const betrag = typeof daten.amount === 'number'
|
|
174
|
+
? (Number.isInteger(daten.amount) ? formatCents(daten.amount) : daten.amount.toFixed(2).replace('.', ','))
|
|
175
|
+
: '-';
|
|
176
|
+
return [
|
|
177
|
+
textZeile('Sumup Beleg', 'center', true),
|
|
178
|
+
paarZeile('Kartentyp:', feld('cardType') || 'n/a'),
|
|
179
|
+
paarZeile('Kartennummer:', `**** **** **** ${feld('cardLastDigits')}`),
|
|
180
|
+
paarZeile('Zahlungstyp:', feld('paymentType') || 'n/a'),
|
|
181
|
+
paarZeile('Gesamtbetrag:', `${betrag} ${feld('currency')}`.trim()),
|
|
182
|
+
paarZeile('Transaktionscode:', feld('transactionCode') || '-'),
|
|
183
|
+
paarZeile('Modus:', feld('entryMode').toUpperCase()),
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* myPOS liefert den Zeitpunkt als `YYMMDDhhmmss` ohne Trenner. Ein Wert, der
|
|
188
|
+
* nicht so aussieht, wird UNVERAENDERT durchgereicht statt zerschnitten: eine
|
|
189
|
+
* halb geratene Uhrzeit auf einem Beleg ist schlimmer als ein roher Wert.
|
|
190
|
+
*/
|
|
191
|
+
function myposZeitpunkt(wert) {
|
|
192
|
+
const t = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(wert);
|
|
193
|
+
if (!t)
|
|
194
|
+
return wert;
|
|
195
|
+
return `${t[3]}.${t[2]}.20${t[1]} ${t[4]}:${t[5]}:${t[6]}`;
|
|
196
|
+
}
|
|
197
|
+
function myposBlock(daten) {
|
|
198
|
+
const feld = (k) => kartenFeld(daten, k);
|
|
199
|
+
const zeilen = [
|
|
200
|
+
textZeile('MyPos Beleg', 'center', true),
|
|
201
|
+
paarZeile('TERMINAL ID:', feld('TID') || '-'),
|
|
202
|
+
paarZeile('DATUM:', myposZeitpunkt(feld('date_time'))),
|
|
203
|
+
];
|
|
204
|
+
if (feld('application_name'))
|
|
205
|
+
zeilen.push(textZeile(feld('application_name'), 'center', true));
|
|
206
|
+
zeilen.push(paarZeile('KARTE:', feld('pan') || '-'));
|
|
207
|
+
if (daten.signature_required === true)
|
|
208
|
+
zeilen.push(...unterschriftsfeld());
|
|
209
|
+
zeilen.push(paarZeile('STAN:', feld('STAN') ? feld('STAN').padStart(6, '0') : '-'));
|
|
210
|
+
zeilen.push(paarZeile('AUTH. CODE:', feld('authorization_code') || '-'));
|
|
211
|
+
zeilen.push(paarZeile('RRN:', feld('reference_number') || '-'));
|
|
212
|
+
zeilen.push(paarZeile('AID:', feld('AID') || '-'));
|
|
213
|
+
return zeilen;
|
|
214
|
+
}
|
|
215
|
+
const STRIPE_KARTENART = {
|
|
216
|
+
debit: 'Debitkarte',
|
|
217
|
+
credit: 'Kreditkarte',
|
|
218
|
+
prepaid: 'Prepaid-Karte',
|
|
219
|
+
};
|
|
220
|
+
const STRIPE_WALLET = {
|
|
221
|
+
apple_pay: 'Apple Pay',
|
|
222
|
+
google_pay: 'Google Pay',
|
|
223
|
+
};
|
|
224
|
+
/** EPS-Bank-Slug (`bank_austria`) -> Anzeigename (`Bank Austria`). */
|
|
225
|
+
function stripeEpsBank(slug) {
|
|
226
|
+
return slug.split('_').filter(Boolean)
|
|
227
|
+
.map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');
|
|
228
|
+
}
|
|
229
|
+
function stripeMarkenZeile(daten) {
|
|
230
|
+
const teile = [];
|
|
231
|
+
if (daten.cardBrand != null)
|
|
232
|
+
teile.push(String(daten.cardBrand));
|
|
233
|
+
const art = STRIPE_KARTENART[String(daten.cardFunding)];
|
|
234
|
+
if (art)
|
|
235
|
+
teile.push(art);
|
|
236
|
+
let zeile = teile.join(' ');
|
|
237
|
+
if (daten.wallet != null) {
|
|
238
|
+
const wallet = STRIPE_WALLET[String(daten.wallet)] ?? String(daten.wallet);
|
|
239
|
+
zeile = zeile ? `${zeile} (${wallet})` : `(${wallet})`;
|
|
240
|
+
}
|
|
241
|
+
return zeile;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Stripe-Zahlzeitpunkt: Sekunden seit Epoche, gedruckt in **Wiener** Zeit.
|
|
245
|
+
* Ueber `toViennaWallClock`, nicht ueber die lokale Zeitzone -- ein Beleg
|
|
246
|
+
* traegt Ortszeit des Betriebs, nicht die des Geraets, das ihn oeffnet.
|
|
247
|
+
*/
|
|
248
|
+
function stripeZeitpunkt(sekunden) {
|
|
249
|
+
const t = toViennaWallClock(new Date(sekunden * 1000));
|
|
250
|
+
const zwei = (n) => String(n).padStart(2, '0');
|
|
251
|
+
return `${zwei(t.day)}.${zwei(t.month)}.${t.year} ${zwei(t.hour)}:${zwei(t.minute)}`;
|
|
252
|
+
}
|
|
253
|
+
/** Zwilling von `stripeReceiptLines` (print_paper.dart). */
|
|
254
|
+
export function stripeReceiptLines(daten, cardPaymentId) {
|
|
255
|
+
const zeilen = [];
|
|
256
|
+
const art = daten.paymentMethodType == null ? null : String(daten.paymentMethodType);
|
|
257
|
+
if (art === 'card') {
|
|
258
|
+
const marke = stripeMarkenZeile(daten);
|
|
259
|
+
if (marke)
|
|
260
|
+
zeilen.push(marke);
|
|
261
|
+
if (daten.cardLastDigits != null)
|
|
262
|
+
zeilen.push(`**** **** **** ${daten.cardLastDigits}`);
|
|
263
|
+
if (daten.threeDSecure === 'authenticated')
|
|
264
|
+
zeilen.push('3-D Secure: ja');
|
|
265
|
+
}
|
|
266
|
+
else if (art === 'eps') {
|
|
267
|
+
if (daten.epsBank != null)
|
|
268
|
+
zeilen.push(`EPS - ${stripeEpsBank(String(daten.epsBank))}`);
|
|
269
|
+
}
|
|
270
|
+
else if (art != null) {
|
|
271
|
+
zeilen.push(art.toUpperCase());
|
|
272
|
+
}
|
|
273
|
+
if (typeof daten.amount === 'number' && Number.isInteger(daten.amount)) {
|
|
274
|
+
const waehrung = daten.currency == null ? 'EUR' : String(daten.currency).toUpperCase();
|
|
275
|
+
zeilen.push(`Gesamtbetrag ${formatCents(daten.amount)} ${waehrung}`);
|
|
276
|
+
}
|
|
277
|
+
if (typeof daten.paidAt === 'number' && Number.isInteger(daten.paidAt)) {
|
|
278
|
+
zeilen.push(`Bezahlt: ${stripeZeitpunkt(daten.paidAt)}`);
|
|
279
|
+
}
|
|
280
|
+
if (daten.statementDescriptor != null)
|
|
281
|
+
zeilen.push(`Abrechnung: ${daten.statementDescriptor}`);
|
|
282
|
+
if (cardPaymentId != null)
|
|
283
|
+
zeilen.push(`Referenz: ${cardPaymentId}`);
|
|
284
|
+
return zeilen;
|
|
285
|
+
}
|
|
286
|
+
function stripeBlock(daten, cardPaymentId) {
|
|
287
|
+
const zeilen = [textZeile('Online-Zahlung (Stripe)', 'center', true)];
|
|
288
|
+
for (const zeile of stripeReceiptLines(daten, cardPaymentId)) {
|
|
289
|
+
zeilen.push(textZeile(zeile, 'center'));
|
|
103
290
|
}
|
|
104
|
-
zeilen.push({ kind: 'space', lines: 1 });
|
|
105
291
|
return zeilen;
|
|
106
292
|
}
|
|
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.
|
|
297
|
+
*/
|
|
298
|
+
function kartenblock(receipt) {
|
|
299
|
+
const daten = receipt.cardPaymentData;
|
|
300
|
+
if (daten == null)
|
|
301
|
+
return [];
|
|
302
|
+
const zeilen = (() => {
|
|
303
|
+
switch (receipt.creditCardProvider) {
|
|
304
|
+
case 'hobexHps':
|
|
305
|
+
case 'hobexCloudApi':
|
|
306
|
+
return hobexBlock(daten);
|
|
307
|
+
case 'gpTomAndroid':
|
|
308
|
+
case 'gpTomIos':
|
|
309
|
+
return gpTomBlock(daten);
|
|
310
|
+
case 'sumup':
|
|
311
|
+
return sumupBlock(daten);
|
|
312
|
+
case 'myposPro':
|
|
313
|
+
return myposBlock(daten);
|
|
314
|
+
case 'stripe':
|
|
315
|
+
return stripeBlock(daten, receipt.cardPaymentId ?? null);
|
|
316
|
+
default:
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
})();
|
|
320
|
+
if (zeilen.length === 0)
|
|
321
|
+
return [];
|
|
322
|
+
// Eine Leerzeile hinter JEDEM Block, nicht nur hinter manchen. Das Vorbild
|
|
323
|
+
// ist hier uneinheitlich (nur `_sumup` setzt am Ende ein `addFeed()`), was
|
|
324
|
+
// den Abstand zur Fusszeile vom Anbieter abhaengig machte. Die Leerzeile
|
|
325
|
+
// gehoert aber zum Blatt, nicht zum Terminal.
|
|
326
|
+
return [...zeilen, { kind: 'space', lines: 1 }];
|
|
327
|
+
}
|
|
107
328
|
function zahlungsartText(wert) {
|
|
108
329
|
if (wert != null && typeof wert === 'object') {
|
|
109
330
|
return wert.label;
|
|
@@ -697,7 +918,7 @@ export function buildReceiptLayout(receipt, company, options = {}) {
|
|
|
697
918
|
lines.push({ kind: 'qr', data: receipt.qr });
|
|
698
919
|
lines.push({ kind: 'space', lines: 1 });
|
|
699
920
|
// --- Kartenzahlungsblock (Hobex) — wie im Vorbild UNTER dem QR-Code.
|
|
700
|
-
lines.push(...
|
|
921
|
+
lines.push(...kartenblock(receipt));
|
|
701
922
|
// --- Dankestext und Fusszeilen
|
|
702
923
|
if (company.thanksMessage.length > 0) {
|
|
703
924
|
lines.push({ kind: 'space', lines: 1 });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"company": {
|
|
3
|
+
"companyName": "Bäckerei Muster",
|
|
4
|
+
"street": "Hauptplatz 1",
|
|
5
|
+
"zip": "4830",
|
|
6
|
+
"city": "Hallstatt",
|
|
7
|
+
"phone": "+43 6134 12345",
|
|
8
|
+
"uid": "ATU12345678",
|
|
9
|
+
"taxnr": "",
|
|
10
|
+
"isSmallBusiness": false,
|
|
11
|
+
"footer1": "Vielen Dank für Ihren Einkauf",
|
|
12
|
+
"footer2": "www.baeckerei-muster.at",
|
|
13
|
+
"thanksMessage": [],
|
|
14
|
+
"showKreiseckLogo": false
|
|
15
|
+
},
|
|
16
|
+
"receipt": {
|
|
17
|
+
"receiptId": "AT0-KASSE1-66",
|
|
18
|
+
"cashregisterId": "KASSE1",
|
|
19
|
+
"timeStamp": "2026-08-13T10:15:30",
|
|
20
|
+
"items": [
|
|
21
|
+
{
|
|
22
|
+
"name": "Behandlung",
|
|
23
|
+
"quantity": 1,
|
|
24
|
+
"unitPriceCents": 12000,
|
|
25
|
+
"vatRate": 20
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"vouchers": [],
|
|
29
|
+
"paymentMethod": "creditCard",
|
|
30
|
+
"turnoverCounterAES256ICM": "UMSATZ",
|
|
31
|
+
"signaturePreviousReceipt": "VORGAENGER",
|
|
32
|
+
"certificateSerialNumber": "6F0404F0",
|
|
33
|
+
"receiptType": "standard",
|
|
34
|
+
"sig": "eyJhbGciOiJFUzI1NiJ9.QVQx.SIGNATURWERT",
|
|
35
|
+
"qr": "_R1-AT1_KASSE1_AT0-KASSE1-66_2026-08-13T10:15:30_120,00_0,00_0,00_0,00_0,00_UMSATZ_VORGAENGER_6F0404F0_SIGNATUR",
|
|
36
|
+
"fullReceiptId": "VOLL",
|
|
37
|
+
"customerDetails": [],
|
|
38
|
+
"legalMessage": [],
|
|
39
|
+
"creditCardProvider": "custom"
|
|
40
|
+
},
|
|
41
|
+
"options": {
|
|
42
|
+
"paperSize": "mm80"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"company": {
|
|
3
|
+
"companyName": "Bäckerei Muster",
|
|
4
|
+
"street": "Hauptplatz 1",
|
|
5
|
+
"zip": "4830",
|
|
6
|
+
"city": "Hallstatt",
|
|
7
|
+
"phone": "+43 6134 12345",
|
|
8
|
+
"uid": "ATU12345678",
|
|
9
|
+
"taxnr": "",
|
|
10
|
+
"isSmallBusiness": false,
|
|
11
|
+
"footer1": "Vielen Dank für Ihren Einkauf",
|
|
12
|
+
"footer2": "www.baeckerei-muster.at",
|
|
13
|
+
"thanksMessage": [],
|
|
14
|
+
"showKreiseckLogo": false
|
|
15
|
+
},
|
|
16
|
+
"receipt": {
|
|
17
|
+
"receiptId": "AT0-KASSE1-67",
|
|
18
|
+
"cashregisterId": "KASSE1",
|
|
19
|
+
"timeStamp": "2026-08-13T10:15:30",
|
|
20
|
+
"items": [
|
|
21
|
+
{
|
|
22
|
+
"name": "Behandlung",
|
|
23
|
+
"quantity": 1,
|
|
24
|
+
"unitPriceCents": 12000,
|
|
25
|
+
"vatRate": 20
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"vouchers": [],
|
|
29
|
+
"paymentMethod": "creditCard",
|
|
30
|
+
"turnoverCounterAES256ICM": "UMSATZ",
|
|
31
|
+
"signaturePreviousReceipt": "VORGAENGER",
|
|
32
|
+
"certificateSerialNumber": "6F0404F0",
|
|
33
|
+
"receiptType": "standard",
|
|
34
|
+
"sig": "eyJhbGciOiJFUzI1NiJ9.QVQx.SIGNATURWERT",
|
|
35
|
+
"qr": "_R1-AT1_KASSE1_AT0-KASSE1-67_2026-08-13T10:15:30_120,00_0,00_0,00_0,00_0,00_UMSATZ_VORGAENGER_6F0404F0_SIGNATUR",
|
|
36
|
+
"fullReceiptId": "VOLL",
|
|
37
|
+
"customerDetails": [],
|
|
38
|
+
"legalMessage": [],
|
|
39
|
+
"creditCardProvider": "gpTomIos",
|
|
40
|
+
"cardPaymentData": {
|
|
41
|
+
"batchNumber": 12,
|
|
42
|
+
"externalTransactionID": "TX-9911",
|
|
43
|
+
"terminalID": "T-55",
|
|
44
|
+
"emvAid": "A0000000031010",
|
|
45
|
+
"emvAppLable": "VISA CREDIT",
|
|
46
|
+
"cardDataEntry": "CONTACTLESS",
|
|
47
|
+
"cardNumber": "**** **** **** 4720",
|
|
48
|
+
"currencyCode": "EUR",
|
|
49
|
+
"amount": 12000,
|
|
50
|
+
"pinOk": false,
|
|
51
|
+
"approvedCode": "123456",
|
|
52
|
+
"sequenceNumber": 42,
|
|
53
|
+
"transacitonType": 3
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"options": {
|
|
57
|
+
"paperSize": "mm80"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"company": {
|
|
3
|
+
"companyName": "Bäckerei Muster",
|
|
4
|
+
"street": "Hauptplatz 1",
|
|
5
|
+
"zip": "4830",
|
|
6
|
+
"city": "Hallstatt",
|
|
7
|
+
"phone": "+43 6134 12345",
|
|
8
|
+
"uid": "ATU12345678",
|
|
9
|
+
"taxnr": "",
|
|
10
|
+
"isSmallBusiness": false,
|
|
11
|
+
"footer1": "Vielen Dank für Ihren Einkauf",
|
|
12
|
+
"footer2": "www.baeckerei-muster.at",
|
|
13
|
+
"thanksMessage": [],
|
|
14
|
+
"showKreiseckLogo": false
|
|
15
|
+
},
|
|
16
|
+
"receipt": {
|
|
17
|
+
"receiptId": "AT0-KASSE1-61",
|
|
18
|
+
"cashregisterId": "KASSE1",
|
|
19
|
+
"timeStamp": "2026-08-13T10:15:30",
|
|
20
|
+
"items": [
|
|
21
|
+
{
|
|
22
|
+
"name": "Behandlung",
|
|
23
|
+
"quantity": 1,
|
|
24
|
+
"unitPriceCents": 12000,
|
|
25
|
+
"vatRate": 20
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"vouchers": [],
|
|
29
|
+
"paymentMethod": "creditCard",
|
|
30
|
+
"turnoverCounterAES256ICM": "UMSATZ",
|
|
31
|
+
"signaturePreviousReceipt": "VORGAENGER",
|
|
32
|
+
"certificateSerialNumber": "6F0404F0",
|
|
33
|
+
"receiptType": "standard",
|
|
34
|
+
"sig": "eyJhbGciOiJFUzI1NiJ9.QVQx.SIGNATURWERT",
|
|
35
|
+
"qr": "_R1-AT1_KASSE1_AT0-KASSE1-61_2026-08-13T10:15:30_120,00_0,00_0,00_0,00_0,00_UMSATZ_VORGAENGER_6F0404F0_SIGNATUR",
|
|
36
|
+
"fullReceiptId": "VOLL",
|
|
37
|
+
"customerDetails": [],
|
|
38
|
+
"legalMessage": [],
|
|
39
|
+
"creditCardProvider": "gpTomAndroid",
|
|
40
|
+
"cardPaymentData": {
|
|
41
|
+
"batchNumber": 12,
|
|
42
|
+
"externalTransactionID": "TX-9911",
|
|
43
|
+
"terminalID": "T-55",
|
|
44
|
+
"emvAid": "A0000000031010",
|
|
45
|
+
"emvAppLable": "VISA CREDIT",
|
|
46
|
+
"cardDataEntry": "CONTACTLESS",
|
|
47
|
+
"cardNumber": "**** **** **** 4720",
|
|
48
|
+
"currencyCode": "EUR",
|
|
49
|
+
"amount": 12000,
|
|
50
|
+
"pinOk": true,
|
|
51
|
+
"approvedCode": "123456",
|
|
52
|
+
"sequenceNumber": 42,
|
|
53
|
+
"transactionType": 1
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"options": {
|
|
57
|
+
"paperSize": "mm80"
|
|
58
|
+
}
|
|
59
|
+
}
|