@kreiseck/kasseneck-api 0.6.46 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/README.md +158 -2
- package/dist/cjs/client/aufrufe.d.ts +1 -1
- package/dist/cjs/client/aufrufe.js +19 -0
- package/dist/cjs/client/errors.d.ts +31 -1
- package/dist/cjs/client/errors.js +98 -1
- package/dist/cjs/client/transport.js +7 -7
- package/dist/cjs/partner/ablauf.d.ts +47 -0
- package/dist/cjs/partner/ablauf.js +99 -0
- package/dist/cjs/partner/api.d.ts +68 -0
- package/dist/cjs/partner/api.js +44 -0
- package/dist/cjs/partner/auth.d.ts +33 -0
- package/dist/cjs/partner/auth.js +52 -0
- package/dist/cjs/partner/betrieb.d.ts +48 -0
- package/dist/cjs/partner/betrieb.js +136 -0
- package/dist/cjs/partner/endpunkte.d.ts +142 -0
- package/dist/cjs/partner/endpunkte.js +488 -0
- package/dist/cjs/partner/fehler.d.ts +72 -0
- package/dist/cjs/partner/fehler.js +199 -0
- package/dist/cjs/partner/index.d.ts +28 -0
- package/dist/cjs/partner/index.js +84 -0
- package/dist/cjs/partner/secret.d.ts +66 -0
- package/dist/cjs/partner/secret.js +95 -0
- package/dist/cjs/partner/typen.d.ts +399 -0
- package/dist/cjs/partner/typen.js +33 -0
- package/dist/cjs/partner/webhook-signatur.d.ts +95 -0
- package/dist/cjs/partner/webhook-signatur.js +158 -0
- package/dist/cjs/partner/webhooks.d.ts +211 -0
- package/dist/cjs/partner/webhooks.js +269 -0
- package/dist/esm/client/aufrufe.d.ts +1 -1
- package/dist/esm/client/aufrufe.js +19 -0
- package/dist/esm/client/errors.d.ts +31 -1
- package/dist/esm/client/errors.js +97 -1
- package/dist/esm/client/transport.js +8 -8
- package/dist/esm/partner/ablauf.d.ts +47 -0
- package/dist/esm/partner/ablauf.js +95 -0
- package/dist/esm/partner/api.d.ts +68 -0
- package/dist/esm/partner/api.js +41 -0
- package/dist/esm/partner/auth.d.ts +33 -0
- package/dist/esm/partner/auth.js +48 -0
- package/dist/esm/partner/betrieb.d.ts +48 -0
- package/dist/esm/partner/betrieb.js +132 -0
- package/dist/esm/partner/endpunkte.d.ts +142 -0
- package/dist/esm/partner/endpunkte.js +474 -0
- package/dist/esm/partner/fehler.d.ts +72 -0
- package/dist/esm/partner/fehler.js +189 -0
- package/dist/esm/partner/index.d.ts +28 -0
- package/dist/esm/partner/index.js +27 -0
- package/dist/esm/partner/secret.d.ts +66 -0
- package/dist/esm/partner/secret.js +90 -0
- package/dist/esm/partner/typen.d.ts +399 -0
- package/dist/esm/partner/typen.js +30 -0
- package/dist/esm/partner/webhook-signatur.d.ts +95 -0
- package/dist/esm/partner/webhook-signatur.js +153 -0
- package/dist/esm/partner/webhooks.d.ts +211 -0
- package/dist/esm/partner/webhooks.js +257 -0
- package/fixtures/oberflaeche.json +131 -3
- package/package.json +13 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Was ein Betrieb ueber die Schnittstelle mitbringen darf — Feld fuer Feld.
|
|
3
|
+
*
|
|
4
|
+
* **Unbekannte Felder werden abgewiesen, nicht stillschweigend verworfen.**
|
|
5
|
+
* Vorher verschwand ein `iban` oder ein vertippter Feldname spurlos: der
|
|
6
|
+
* Partner glaubte, er habe die Steuernummer geschickt, und Kasseneck hatte
|
|
7
|
+
* nichts. Ein Feld, das man schickt und das nichts bewirkt, ist der teuerste
|
|
8
|
+
* Fehler in einer Schnittstelle, weil ihn niemand bemerkt.
|
|
9
|
+
*
|
|
10
|
+
* Seitdem antwortet `createPartnerCustomer` mit `validation` und dem genauen
|
|
11
|
+
* Feldpfad — verschachtelt und je Kontakt: `address.land`,
|
|
12
|
+
* `tax_details.ustid`, `contacts.1.abteilung`.
|
|
13
|
+
*
|
|
14
|
+
* Zwei Dinge halten diese Seite dagegen:
|
|
15
|
+
*
|
|
16
|
+
* - der Typ [Betrieb] (typen.ts) hat genau diese Felder, und TypeScript meldet
|
|
17
|
+
* ein ueberzaehliges schon beim Tippen;
|
|
18
|
+
* - [unbekannteBetriebsfelder] beantwortet dieselbe Frage zur Laufzeit — fuer
|
|
19
|
+
* Daten, die aus einer Datenbank oder einem Formular kommen und deshalb nie
|
|
20
|
+
* durch die Typpruefung gelaufen sind.
|
|
21
|
+
*
|
|
22
|
+
* **Die Wahrheit bleibt der Server.** Dieser Client weist nichts von sich aus
|
|
23
|
+
* ab: eine spaetere Backend-Fassung darf ein Feld ergaenzen, ohne dass ein
|
|
24
|
+
* aelterer Client es blockiert. [unbekannteBetriebsfelder] ist die Vorschau,
|
|
25
|
+
* nicht das Tor.
|
|
26
|
+
*
|
|
27
|
+
* Quelle der Liste: `partner-core.BETRIEB_FELDER` im Backend.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Jedes erlaubte Feld als Pfad. `[]` markiert eine Liste — im Fehlerpfad des
|
|
31
|
+
* Servers steht dort der Index (`contacts.0.name`).
|
|
32
|
+
*
|
|
33
|
+
* Bewusst flach und nicht verschachtelt: so ist es EINE Liste, die der
|
|
34
|
+
* Zwilling Zeile fuer Zeile nachhalten kann. Das Schema fuer die Pruefung
|
|
35
|
+
* entsteht daraus (siehe unten) und nicht daneben.
|
|
36
|
+
*/
|
|
37
|
+
export const BETRIEB_FELDER = [
|
|
38
|
+
'companyName',
|
|
39
|
+
'legalForm',
|
|
40
|
+
'state',
|
|
41
|
+
'industry',
|
|
42
|
+
'companyRegister',
|
|
43
|
+
'court',
|
|
44
|
+
'web',
|
|
45
|
+
'phone',
|
|
46
|
+
'email',
|
|
47
|
+
'billingEmail',
|
|
48
|
+
'address.street',
|
|
49
|
+
'address.number',
|
|
50
|
+
'address.zip',
|
|
51
|
+
'address.city',
|
|
52
|
+
'taxDetails.taxNumber',
|
|
53
|
+
'taxDetails.vatId',
|
|
54
|
+
'taxDetails.gln',
|
|
55
|
+
'taxDetails.smallBusiness',
|
|
56
|
+
'contacts[].name',
|
|
57
|
+
'contacts[].email',
|
|
58
|
+
'contacts[].phone',
|
|
59
|
+
'contacts[].roles',
|
|
60
|
+
'taxAdvisor.name',
|
|
61
|
+
'taxAdvisor.email',
|
|
62
|
+
'taxAdvisor.phone',
|
|
63
|
+
'taxAdvisor.mayContact',
|
|
64
|
+
];
|
|
65
|
+
/** Das Schema entsteht aus [BETRIEB_FELDER] — eine Quelle, keine zweite Liste. */
|
|
66
|
+
const SCHEMA = (() => {
|
|
67
|
+
const wurzel = {};
|
|
68
|
+
for (const pfad of BETRIEB_FELDER) {
|
|
69
|
+
const teile = pfad.split('.');
|
|
70
|
+
let stand = wurzel;
|
|
71
|
+
teile.forEach((rohes, i) => {
|
|
72
|
+
const liste = rohes.endsWith('[]');
|
|
73
|
+
const name = liste ? rohes.slice(0, -2) : rohes;
|
|
74
|
+
if (i === teile.length - 1) {
|
|
75
|
+
stand[name] = true;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (liste) {
|
|
79
|
+
const vorhanden = stand[name];
|
|
80
|
+
const eintrag = Array.isArray(vorhanden) ? vorhanden[0] : {};
|
|
81
|
+
stand[name] = [eintrag];
|
|
82
|
+
stand = eintrag;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const vorhanden = stand[name];
|
|
86
|
+
const unter = vorhanden !== undefined && vorhanden !== true && !Array.isArray(vorhanden) ? vorhanden : {};
|
|
87
|
+
stand[name] = unter;
|
|
88
|
+
stand = unter;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return wurzel;
|
|
92
|
+
})();
|
|
93
|
+
function istObjekt(wert) {
|
|
94
|
+
return wert !== null && typeof wert === 'object' && !Array.isArray(wert);
|
|
95
|
+
}
|
|
96
|
+
function sammle(eingabe, schema, pfad) {
|
|
97
|
+
if (!istObjekt(eingabe))
|
|
98
|
+
return [];
|
|
99
|
+
const raus = [];
|
|
100
|
+
for (const [name, wert] of Object.entries(eingabe)) {
|
|
101
|
+
const voll = pfad ? `${pfad}.${name}` : name;
|
|
102
|
+
const erlaubt = schema[name];
|
|
103
|
+
if (erlaubt === undefined) {
|
|
104
|
+
raus.push(voll);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (erlaubt === true)
|
|
108
|
+
continue;
|
|
109
|
+
if (Array.isArray(erlaubt)) {
|
|
110
|
+
// Ein falscher Typ ist kein unbekanntes Feld — den meldet der Server als
|
|
111
|
+
// eigenen Formfehler auf demselben Pfad.
|
|
112
|
+
if (!Array.isArray(wert))
|
|
113
|
+
continue;
|
|
114
|
+
wert.forEach((eintrag, idx) => raus.push(...sammle(eintrag, erlaubt[0], `${voll}.${idx}`)));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
raus.push(...sammle(wert, erlaubt, voll));
|
|
118
|
+
}
|
|
119
|
+
return raus;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Die Feldpfade eines Betriebs, die [BETRIEB_FELDER] nicht kennt — dieselbe
|
|
123
|
+
* Ableitung wie im Backend, also dieselben Pfade wie in `data.errors[].field`.
|
|
124
|
+
* Leer heisst: aus dieser Sicht ist nichts ueberzaehlig.
|
|
125
|
+
*
|
|
126
|
+
* Sagt **nichts** ueber die Werte: Steuernummer, UID, PLZ und Gericht prueft
|
|
127
|
+
* das Backend mit `@kreiseck/validator`. Diese Funktion beantwortet nur die
|
|
128
|
+
* Frage „schicke ich etwas, das dort niemand erwartet?".
|
|
129
|
+
*/
|
|
130
|
+
export function unbekannteBetriebsfelder(business) {
|
|
131
|
+
return sammle(business, SCHEMA, '');
|
|
132
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die Aufrufe der Partner-API — Betriebe, Signatur, Kassen.
|
|
3
|
+
*
|
|
4
|
+
* Jede Funktion nimmt den Transport als ersten Parameter und ist einzeln
|
|
5
|
+
* importierbar; die Fassade [createPartnerApi] bindet ihn nur einmal.
|
|
6
|
+
*
|
|
7
|
+
* **Was hier geprueft wird und was nicht.** Vor dem Senden prueft dieser Client
|
|
8
|
+
* nur, was er ohne den Server wissen kann: dass eine Kennung ueberhaupt da ist,
|
|
9
|
+
* dass eine Liste nicht leer ist, dass eine Zahl im erlaubten Bereich liegt.
|
|
10
|
+
* Die fachliche Pruefung der Betriebsdaten (Steuernummer samt Pruefziffer, UID,
|
|
11
|
+
* PLZ, Gericht) macht das Backend mit `@kreiseck/validator` — sie hier zu
|
|
12
|
+
* wiederholen hiesse, zwei Wahrheiten zu haben, von denen eine veraltet.
|
|
13
|
+
* Ein Formfehler kommt als `KasseneckApiError` mit `code:"validation"` zurueck;
|
|
14
|
+
* `partnerFeldFehler(fehler)` macht `data.errors[]` daraus. **Es entsteht dabei
|
|
15
|
+
* nichts** — der Aufruf ist folgenlos wiederholbar.
|
|
16
|
+
*
|
|
17
|
+
* Nach dem Senden wird nichts hart gecastet: fehlt ein zugesagtes Feld, wirft
|
|
18
|
+
* der Aufruf `KasseneckValidationError` mit `scope:'response'` statt spaeter
|
|
19
|
+
* einen `TypeError` an unpassender Stelle.
|
|
20
|
+
*/
|
|
21
|
+
import type { InternerTransport } from '../client/aufrufe.js';
|
|
22
|
+
import type { ActivateCashregisterResult, CreateCashregisterOptions, CreateCashregisterResult, CreateCustomerOptions, CreateCustomerResult, CustomerCredentials, FonLinkResult, KassenListe, Kunde, KundenListe, ListCustomersOptions, PartnerInfo, RequestSignatureResult, SignaturStand } from './typen.js';
|
|
23
|
+
/**
|
|
24
|
+
* Wer bin ich, in welcher Umgebung, mit welchen Rechten — und welche Apps
|
|
25
|
+
* gehoeren mir. `apps[].id` ist die `appId` fuer [createPartnerCustomer].
|
|
26
|
+
*
|
|
27
|
+
* Der guenstigste Selbsttest beim Hochfahren: er beweist Schluessel, Umgebung
|
|
28
|
+
* und Rechte in einem Aufruf.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getPartnerInfo(rufen: InternerTransport): Promise<PartnerInfo>;
|
|
31
|
+
/**
|
|
32
|
+
* Legt einen Betrieb an.
|
|
33
|
+
*
|
|
34
|
+
* **Ohne Panel-Zugang**, solange nicht `access:{invite:true}` dabeisteht:
|
|
35
|
+
* viele Betriebe arbeiten ausschliesslich in der App des Partners. Fuer die
|
|
36
|
+
* Einladung braucht das Partner-Konto ausserdem
|
|
37
|
+
* `partner.canCreateAccess`.
|
|
38
|
+
*
|
|
39
|
+
* **`env` waehlt die Umgebung.** Ohne Angabe entscheidet der Schluessel; ein
|
|
40
|
+
* Live-Schluessel darf mit `env:"test"` einen Testbetrieb anlegen, ein
|
|
41
|
+
* Test-Schluessel niemals einen Live-Betrieb (`live_not_allowed`).
|
|
42
|
+
*
|
|
43
|
+
* **`idempotencyKey` benutzen.** Ein verlorener Antwortweg ist kein
|
|
44
|
+
* Sonderfall, und ohne Schluessel legt der zweite Versuch einen zweiten Betrieb
|
|
45
|
+
* an. Mit Schluessel kommt die gespeicherte Antwort zurueck (`replayed:true`)
|
|
46
|
+
* — auch dann, wenn der Rumpf inzwischen abweicht. Die eigene Kundennummer ist
|
|
47
|
+
* der natuerliche Wert dafuer.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createPartnerCustomer(rufen: InternerTransport, optionen: CreateCustomerOptions): Promise<CreateCustomerResult>;
|
|
50
|
+
/** Betriebe dieses Partners, seitenweise. `cursor` aus der Antwort setzt fort. */
|
|
51
|
+
export declare function listPartnerCustomers(rufen: InternerTransport, optionen?: ListCustomersOptions): Promise<KundenListe>;
|
|
52
|
+
/** Ein Betrieb mit allem, was der Partner ueber ihn sehen darf — nie Geheimnisse. */
|
|
53
|
+
export declare function getPartnerCustomer(rufen: InternerTransport, customerId: string): Promise<Kunde>;
|
|
54
|
+
/**
|
|
55
|
+
* Schickt dem Betrieb den Einrichtungs-Link fuer seinen FinanzOnline-Zugang.
|
|
56
|
+
* Ohne diesen Zugang gibt es live keine Signatureinheit (`fon_missing`).
|
|
57
|
+
*
|
|
58
|
+
* Die Antwort nennt den Empfaenger **maskiert** — die Adresse gibt das Backend
|
|
59
|
+
* nie im Klartext aus.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Ist diese E-Mail-Adresse noch als Kasseneck-Zugang frei?
|
|
63
|
+
*
|
|
64
|
+
* Nur noetig, wenn der Betrieb einen eigenen Zugang zum Kundenpanel bekommen
|
|
65
|
+
* soll (`access.invite: true`) — dann wird die Adresse sein Login und darf
|
|
66
|
+
* noch keines sein. Ohne Einladung ist eine belegte Adresse kein Hindernis.
|
|
67
|
+
*
|
|
68
|
+
* Der Sinn ist der Zeitpunkt: ohne diese Frage faellt `email_taken` erst nach
|
|
69
|
+
* einem ganzen ausgefuellten Formular auf. Die Antwort sagt NUR ja oder nein —
|
|
70
|
+
* nie, wem die Adresse gehoert.
|
|
71
|
+
*/
|
|
72
|
+
export declare function checkPartnerCustomerEmail(rufen: InternerTransport, email: string): Promise<boolean>;
|
|
73
|
+
export declare function sendPartnerCustomerFonLink(rufen: InternerTransport, customerId: string): Promise<FonLinkResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Beantragt die Signatureinheit. Kasseneck laesst die Karte beim
|
|
76
|
+
* Vertrauensdiensteanbieter **auf diesen Betrieb** ausstellen und meldet sie
|
|
77
|
+
* bei FinanzOnline an; einen Vorrat fertiger Karten gibt es nicht.
|
|
78
|
+
*
|
|
79
|
+
* Der Antrag erzeugt sofort ein Signatur-OBJEKT: `antrag.requestId` ist
|
|
80
|
+
* zugleich die `signaturId`, auf die sich eine Kasse beruft — auch solange
|
|
81
|
+
* noch keine Karte zugewiesen ist.
|
|
82
|
+
*
|
|
83
|
+
* **Je Betrieb laeuft nur ein Antrag.** Ein zweiter Aufruf liefert den
|
|
84
|
+
* laufenden zurueck (`replayed:true`) und ist damit folgenlos wiederholbar.
|
|
85
|
+
* Eine WEITERE Signatur (Ersatzkarte, zweiter Standort) entsteht nur mit
|
|
86
|
+
* `additional:true` — hoechstens zehn je Betrieb (`signature_limit`). Der
|
|
87
|
+
* Abschluss kommt als Ereignis `signature.ready`, nicht als Antwort auf diesen
|
|
88
|
+
* Aufruf.
|
|
89
|
+
*/
|
|
90
|
+
export declare function requestCustomerSignature(rufen: InternerTransport, customerId: string, optionen?: {
|
|
91
|
+
art?: string;
|
|
92
|
+
additional?: boolean;
|
|
93
|
+
}): Promise<RequestSignatureResult>;
|
|
94
|
+
/** Stand der Signatur eines Betriebs samt aller Antraege und des FON-Zugangs. */
|
|
95
|
+
export declare function getCustomerSignatureStatus(rufen: InternerTransport, customerId: string): Promise<SignaturStand>;
|
|
96
|
+
/**
|
|
97
|
+
* Legt eine Kasse an.
|
|
98
|
+
*
|
|
99
|
+
* **Jede Kasse bezieht sich auf eine Signatur.** Ohne eine einzige — auch eine
|
|
100
|
+
* noch laufende zaehlt — entsteht keine (`signature_missing`); bei mehreren
|
|
101
|
+
* muss `signaturId` dastehen (`signature_ambiguous`).
|
|
102
|
+
*
|
|
103
|
+
* **Darf vor der fertigen Signatur aufgerufen werden:** die Kasse bleibt dann
|
|
104
|
+
* auf `entwurf` und geht von selbst live, sobald IHRE Signatur bereit ist
|
|
105
|
+
* (`automatic:true`, Vorgabe). `inbetriebnahme.reason` sagt, warum gerade
|
|
106
|
+
* nichts lief: `signature_not_ready` oder `automatik_aus`.
|
|
107
|
+
*
|
|
108
|
+
* Hoechstens 20 Kassen je Betrieb (`cashregister_limit`); ohne gebuchtes Modul
|
|
109
|
+
* `module_inactive`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function createCustomerCashregister(rufen: InternerTransport, optionen: CreateCashregisterOptions): Promise<CreateCashregisterResult>;
|
|
112
|
+
/**
|
|
113
|
+
* Nimmt eine Kasse in Betrieb — von Hand, wenn `automatic:false` gilt oder
|
|
114
|
+
* ein Lauf abgebrochen ist.
|
|
115
|
+
*
|
|
116
|
+
* **Jeder Schritt der Kette ist idempotent**, der Startbeleg entsteht nach
|
|
117
|
+
* RKSV genau einmal und ein vorhandener wird erkannt. Ein Wiederholungsaufruf
|
|
118
|
+
* setzt deshalb an der Bruchstelle an und macht nichts doppelt; eine bereits
|
|
119
|
+
* laufende Kasse antwortet mit `unchanged:true`. Das ist der eine
|
|
120
|
+
* veraendernde Aufruf dieses Clients, der ohne Idempotenzschluessel gefahrlos
|
|
121
|
+
* wiederholbar ist — weil der Server ihn so gebaut hat.
|
|
122
|
+
*/
|
|
123
|
+
export declare function activateCashregister(rufen: InternerTransport, customerId: string, cashregisterId: string): Promise<ActivateCashregisterResult>;
|
|
124
|
+
/** Die Kassen eines Betriebs samt Stand der Inbetriebnahme — **nie** Token. */
|
|
125
|
+
export declare function listCustomerCashregisters(rufen: InternerTransport, customerId: string): Promise<KassenListe>;
|
|
126
|
+
/**
|
|
127
|
+
* Holt die **Geheimnisse des Betriebs**: seinen `api_key` und die Token seiner
|
|
128
|
+
* Kassen. Damit signiert eine App in seinem Namen Belege — und ein Beleg ist
|
|
129
|
+
* nach RKSV nicht zuruecknehmbar.
|
|
130
|
+
*
|
|
131
|
+
* Braucht den Scope `credentials:read`, der **nicht** zum Standardsatz gehoert
|
|
132
|
+
* und keinem bestehenden Schluessel nachtraeglich hinzugefuegt wird; dafuer
|
|
133
|
+
* wird ein eigener Schluessel angelegt. Jeder Abruf wird mitgeschrieben
|
|
134
|
+
* (Partner, Schluessel, Zeitpunkt) und ist fuer den Betrieb sichtbar.
|
|
135
|
+
*
|
|
136
|
+
* **Nur verschluesselt speichern. Nie protokollieren, nie in eine Mail, nie in
|
|
137
|
+
* einen Fehlerbericht.** Die Werte kommen darum als [KasseneckSecret] und
|
|
138
|
+
* nicht als `string` zurueck: `console.log`, `JSON.stringify` und jede
|
|
139
|
+
* Zeichenketten-Umwandlung zeigen eine Maske, heraus kommt man nur ueber
|
|
140
|
+
* `.reveal()`.
|
|
141
|
+
*/
|
|
142
|
+
export declare function getCustomerCredentials(rufen: InternerTransport, customerId: string): Promise<CustomerCredentials>;
|