@kreiseck/kasseneck-api 1.0.0-rc.2 → 1.0.0-rc.4
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 +37 -1
- package/README.md +38 -9
- package/dist/cjs/client/errors.d.ts +32 -1
- package/dist/cjs/client/errors.js +77 -4
- package/dist/cjs/client/receipts.d.ts +10 -3
- package/dist/cjs/client/receipts.js +11 -8
- package/dist/cjs/models/cashregister.d.ts +14 -8
- package/dist/cjs/models/cashregister.js +4 -4
- package/dist/cjs/payments/hobex.d.ts +7 -4
- package/dist/cjs/payments/hobex.js +7 -4
- package/dist/cjs/payments/stripe.d.ts +3 -1
- package/dist/cjs/payments/stripe.js +3 -1
- package/dist/cjs/pos/client.d.ts +6 -1
- package/dist/cjs/pos/client.js +15 -3
- package/dist/cjs/pos/drucker.js +22 -3
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client/errors.d.ts +32 -1
- package/dist/esm/client/errors.js +76 -3
- package/dist/esm/client/receipts.d.ts +10 -3
- package/dist/esm/client/receipts.js +11 -8
- package/dist/esm/models/cashregister.d.ts +14 -8
- package/dist/esm/models/cashregister.js +4 -4
- package/dist/esm/payments/hobex.d.ts +7 -4
- package/dist/esm/payments/hobex.js +7 -4
- package/dist/esm/payments/stripe.d.ts +3 -1
- package/dist/esm/payments/stripe.js +3 -1
- package/dist/esm/pos/client.d.ts +6 -1
- package/dist/esm/pos/client.js +15 -3
- package/dist/esm/pos/drucker.js +22 -3
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/invoice-api.schema.json +1 -1
- package/fixtures/invoice-texts.json +1 -1
- package/fixtures/pos-texts.json +1 -1
- package/fixtures/surface.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -126,6 +126,42 @@ an upgrade as long as `/v1` is served.
|
|
|
126
126
|
Reason: an app that read `'rejected'` could charge or refund a card twice;
|
|
127
127
|
a silent resend below the package does the same without any error. Same
|
|
128
128
|
change as in the Dart twin `kasseneck_api` (no `RetryClient`).
|
|
129
|
+
- **Findings of the Dart twin (1.0.0-rc.3).** `listMyCashregisters` reads
|
|
130
|
+
the start receipt under `onboarding.start_receipt_created`/`_transmitted`
|
|
131
|
+
(`_at`) as `/v3` sends it; rc.1 and rc.2 read the internal
|
|
132
|
+
`startbeleg_*` names and reported every register as having no start
|
|
133
|
+
receipt (`CashregisterOnboardingPayload` changes accordingly). The fields
|
|
134
|
+
of `CashregisterOnboarding` are English now as well: `startbelegCreated`
|
|
135
|
+
is `startReceiptCreated`, `startbelegTransmitted` is
|
|
136
|
+
`startReceiptTransmitted`, `startbelegCreatedAt` is `startReceiptCreatedAt`,
|
|
137
|
+
`startbelegTransmittedAt` is `startReceiptTransmittedAt`; the German-name
|
|
138
|
+
guard no longer exempts `startbeleg`.
|
|
139
|
+
`sendReceiptEmail` reads its success reply leniently: without `to` it
|
|
140
|
+
returns the address you sent, without `at` it returns `at: null`
|
|
141
|
+
(`SendReceiptEmailResult.at` is `string | null`); throwing there would
|
|
142
|
+
invite a second email. `listMyPrinters` without `data.printers` and
|
|
143
|
+
`createPrintJob`/`getPrintJob` without `data.jobId` throw a
|
|
144
|
+
`KasseneckValidationError` (`scope: 'response'`) instead of returning an
|
|
145
|
+
empty list or an empty id: "no printer yet" must not look like a broken
|
|
146
|
+
reply, and a job without an id cannot be polled, so the cashier would print
|
|
147
|
+
again. `setMyRegisterDeviceSettings` takes `shortcuts` only as the whole map
|
|
148
|
+
of known actions (as `posSettingsChanges` produces it): the server checks
|
|
149
|
+
keys bound twice only within the map it receives.
|
|
150
|
+
- **Money calls: an error envelope is not a rejection (1.0.0-rc.4).** On
|
|
151
|
+
`hobexPayApi`, `hobexRefundApi` and `stripeCaptureIntent` an error envelope
|
|
152
|
+
without a code is `outcome: 'unknown'`, and so is every code outside the
|
|
153
|
+
23 rejection codes, all raised before the provider is called: the
|
|
154
|
+
sign-in codes (`errorCodes.auth` without the seven of the partner
|
|
155
|
+
access), the edge codes `validation`, `not_found` and
|
|
156
|
+
`internal_translation_error`, the gates `module_inactive` and
|
|
157
|
+
`not_permitted`, and `route_missing`. `dialect_mismatch` and `response_translation_failed` (also
|
|
158
|
+
with `handled: false`) stay `'unknown'` there. rc.2 and rc.3 read an
|
|
159
|
+
envelope without a code as `'rejected'`, and the README called a retry
|
|
160
|
+
after `'rejected'` safe. Reason: the Hobex and Stripe handlers also answer
|
|
161
|
+
with a plain error message after the provider was called ("Error hobex
|
|
162
|
+
details", "Fehler beim Capturing"), so the card may be charged, the refund
|
|
163
|
+
paid or the payment captured. Same list as in the Dart twin
|
|
164
|
+
`kasseneck_api`.
|
|
129
165
|
- **Strict validation before sending.** The 0.x payment fields
|
|
130
166
|
(`paymentMethod`, `paymentMethodFromServer`, `creditCardProvider`,
|
|
131
167
|
`cardPaymentId`, `cardPaymentData`) throw, also from plain JavaScript.
|
|
@@ -252,7 +288,7 @@ texts are English: `storno.ergebnis_unklar` is `cancellation.outcome_unknown`,
|
|
|
252
288
|
`tax.intra_community_supply.title`. Every part is lower case with underscores,
|
|
253
289
|
only country codes stay upper case (`country.AT`). Placeholders are English
|
|
254
290
|
too: `{betrag}` is `{amount}`, `{grund}` is `{reason}`, `{sekunden}` is
|
|
255
|
-
`{seconds}`, `{uid}` is `{vatId}`, and so on for
|
|
291
|
+
`{seconds}`, `{uid}` is `{vatId}`, and so on for 25 of 30 names; pass the
|
|
256
292
|
values under the new names (`messageText('checkout.locked', { reason })`). An
|
|
257
293
|
old name throws as a missing value. `ERROR_RULES` entries use
|
|
258
294
|
`kind`/`behavior`/`key` with the kinds `api`, `plain_text`, `timeout`,
|
package/README.md
CHANGED
|
@@ -427,10 +427,9 @@ silently. For a layout option of 0.x the message names its English successor.
|
|
|
427
427
|
### `outcome: 'unknown'`: never retry, look it up
|
|
428
428
|
|
|
429
429
|
`KasseneckApiError`, `KasseneckHttpError` and `KasseneckNetworkError` carry
|
|
430
|
-
`outcome`. `'rejected'` means
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
it helps depends on the code). `'unknown'` means the operation **may have been carried out**, for
|
|
430
|
+
`outcome`. `'rejected'` means the server turned the request down; whether
|
|
431
|
+
anything else is safe to do depends on the code, and on the money calls it
|
|
432
|
+
is only set for the codes listed below. `'unknown'` means the operation **may have been carried out**, for
|
|
434
433
|
`createReceipt` a signed receipt in the chain. Then never send it again: read
|
|
435
434
|
the result back (`getReceipt`, `listMyReceipts`, the original of a
|
|
436
435
|
cancellation) and continue from there. `isOutcomeUnknown(error)` covers all
|
|
@@ -448,13 +447,39 @@ three classes. The outcome is unknown for:
|
|
|
448
447
|
5xx, and HTTP 200 with the `Kasseneck-Api-Version: v3` marker but an empty,
|
|
449
448
|
non-JSON (also `text/html`) or status-less body (`KasseneckHttpError`,
|
|
450
449
|
`reason` `empty-body`, `not-json` or `missing-status`). HTML without the
|
|
451
|
-
marker stays `route_missing` with `'rejected'`: no function saw the call
|
|
450
|
+
marker stays `route_missing` with `'rejected'`: no function saw the call;
|
|
451
|
+
- on the money calls: every error envelope **without a code**, and every
|
|
452
|
+
code that is not one of the rejection codes below. The Hobex and Stripe
|
|
453
|
+
handlers also answer with a plain error message after the provider was
|
|
454
|
+
called, so an envelope alone does not prove that nothing happened.
|
|
455
|
+
|
|
456
|
+
On the money calls (`hobexPayApi`, `hobexRefundApi`, `stripeCaptureIntent`)
|
|
457
|
+
`outcome` is `'rejected'` only for codes that are produced before the
|
|
458
|
+
provider is called, taken from the `/v3` contract (`errorCodes`):
|
|
459
|
+
|
|
460
|
+
- the sign-in codes (`errorCodes.auth` without the seven of the partner
|
|
461
|
+
access, which never applies to these calls), raised before the handler
|
|
462
|
+
goes on: `method_not_allowed`, `validation` (a required field is missing
|
|
463
|
+
or has the wrong type), `cashregister_token_missing`,
|
|
464
|
+
`cashregister_token_invalid`, `cashregister_not_found`,
|
|
465
|
+
`account_not_found`, `live_not_enabled`, `unauthorized`, `mfa_required`,
|
|
466
|
+
`user_verification_failed`, `admin_required`, `register_user_not_allowed`,
|
|
467
|
+
`register_user_no_business`, `register_user_not_found`, `user_disabled`,
|
|
468
|
+
`session_expired`, `cashregister_not_assigned`,
|
|
469
|
+
`session_other_cashregister`;
|
|
470
|
+
- the edge codes that stop the call before the handler (`errorCodes.edge`):
|
|
471
|
+
`validation` (unknown fields), `not_found`, `internal_translation_error`;
|
|
472
|
+
- the module and permission gates: `module_inactive`, `not_permitted`;
|
|
473
|
+
- `route_missing` (set by the package: no function saw the call).
|
|
474
|
+
|
|
475
|
+
`dialect_mismatch` and `response_translation_failed` (also with
|
|
476
|
+
`handled: false`) stay `'unknown'` there: the handler may have run.
|
|
452
477
|
|
|
453
478
|
On the money calls an unknown outcome means the card may have been charged,
|
|
454
479
|
the refund may have gone through, the payment may have been captured. A
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
480
|
+
failed `hobexRefund` throws a `KasseneckApiError`; it never returns `false`,
|
|
481
|
+
because a `false` on an unclear outcome invites a second refund. Even a
|
|
482
|
+
`'rejected'` is no invitation to resend blindly: fix the cause first.
|
|
458
483
|
|
|
459
484
|
**Never retry these six calls, and never put a retrying layer under the
|
|
460
485
|
package.** A `fetch` passed in `options.fetch` (or a proxy or service worker in
|
|
@@ -615,10 +640,14 @@ const confirmation = await api.sendReceiptEmail({
|
|
|
615
640
|
language: 'de', // optional; the backend currently only uses 'de'
|
|
616
641
|
});
|
|
617
642
|
confirmation.to; // address as the backend logged it (trimmed, lower case)
|
|
618
|
-
confirmation.at; // time, ISO with Vienna offset
|
|
643
|
+
confirmation.at; // time, ISO with Vienna offset, or null
|
|
619
644
|
confirmation.via; // 'own' | 'platform' | 'platform_fallback' | null
|
|
620
645
|
```
|
|
621
646
|
|
|
647
|
+
A success response means the email has gone out. If it lacks `to` or `at`,
|
|
648
|
+
the call still succeeds: `to` falls back to the address you sent and `at` is
|
|
649
|
+
`null`. Throwing there would invite sending the email a second time.
|
|
650
|
+
|
|
622
651
|
The email contains a **link to the public receipt page**, not a PDF
|
|
623
652
|
attachment: the receipt page uses the same line model as screen and printed
|
|
624
653
|
receipt and offers a PDF there. The receipt itself stays untouched (BAO § 131,
|
|
@@ -110,6 +110,34 @@ export declare function fehlerDetails(daten: unknown, geheimnisse: readonly stri
|
|
|
110
110
|
* das Ergebnis nachlesen.
|
|
111
111
|
*/
|
|
112
112
|
export type ErrorOutcome = 'unknown' | 'rejected';
|
|
113
|
+
/**
|
|
114
|
+
* Codes, die auf einem Geldweg `outcome: 'rejected'` ergeben; jeder andere
|
|
115
|
+
* Code und eine Huelle ohne Code sind dort `'unknown'`. Aufgenommen ist nur,
|
|
116
|
+
* was entsteht, bevor das Backend den Zahlungsanbieter anspricht (Vertrag
|
|
117
|
+
* v3, `errorCodes`; dieselbe Liste wie `paymentCallRejectedCodes` im
|
|
118
|
+
* Dart-Zwilling):
|
|
119
|
+
*
|
|
120
|
+
* - `errorCodes.auth` ohne die sieben des Partner-Zugangs (18 Codes):
|
|
121
|
+
* Anmeldung und Pruefung in `checkRequest` laufen vor jeder Zeile des
|
|
122
|
+
* Handlers; `validation` heisst dort Pflichtfeld fehlt oder falscher Typ.
|
|
123
|
+
* Der Partner-Zugang trifft diese `api_key`-Aufrufe mit Kassen-Token nie.
|
|
124
|
+
* - aus `errorCodes.edge`: `not_found` (unbekannter Endpunkt, HTTP 404) und
|
|
125
|
+
* `internal_translation_error` (die Anfrage liess sich nicht uebersetzen,
|
|
126
|
+
* es wurde nichts ausgefuehrt); `validation` des Rands (unbekannte Felder,
|
|
127
|
+
* Rumpf ohne Objekt) steht schon oben.
|
|
128
|
+
* - `module_inactive` und `not_permitted`: das Modul- bzw. Rechte-Tor steht
|
|
129
|
+
* ebenfalls vor dem Anbieter.
|
|
130
|
+
* - `route_missing` (vergibt das Paket): HTML ohne `/v3`-Kennzeichen, keine
|
|
131
|
+
* Function hat den Aufruf gesehen.
|
|
132
|
+
*
|
|
133
|
+
* Nicht darin: `dialect_mismatch` (das Paket vergibt ihn auch, wenn ein Rand
|
|
134
|
+
* ohne `/v3` geantwortet hat, dessen Handler gelaufen sein kann) und
|
|
135
|
+
* `response_translation_failed` (der Handler lief; auch `handled: false`
|
|
136
|
+
* heisst nur, dass seine Antwort ein Fehler war, und der kann hinter dem
|
|
137
|
+
* Anbieteraufruf entstanden sein). Der Sammelfang der Handler ("Error hobex
|
|
138
|
+
* details", "Fehler beim Capturing") antwortet ohne Code.
|
|
139
|
+
*/
|
|
140
|
+
export declare const PAYMENT_CALL_REJECTED_CODES: readonly string[];
|
|
113
141
|
/**
|
|
114
142
|
* Codes, die das Paket selbst vergibt, nicht der Server: `route_missing`
|
|
115
143
|
* (HTML statt Backend, der Aufruf kam nie an) und `response_unreadable`
|
|
@@ -146,7 +174,10 @@ export declare class KasseneckApiError extends Error {
|
|
|
146
174
|
* `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet,
|
|
147
175
|
* Antwort eines Aufrufs mit Wirkung aber unlesbar) und
|
|
148
176
|
* `response_translation_failed` (ausser mit `details.handled === false`);
|
|
149
|
-
* sonst `'rejected'`.
|
|
177
|
+
* sonst `'rejected'`. Auf den Geldwegen (`hobexPayApi`, `hobexRefundApi`,
|
|
178
|
+
* `stripeCaptureIntent`) umgekehrt: `'rejected'` nur mit einem Code aus
|
|
179
|
+
* [PAYMENT_CALL_REJECTED_CODES], ohne Code und mit jedem anderen Code
|
|
180
|
+
* `'unknown'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
|
|
150
181
|
*/
|
|
151
182
|
readonly outcome: ErrorOutcome;
|
|
152
183
|
constructor(functionName: string, serverMessage: string, details?: Record<string, unknown>, code?: string);
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
* als die Zusage.
|
|
74
74
|
*/
|
|
75
75
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76
|
-
exports.KasseneckValidationError = exports.KasseneckAuthError = exports.KasseneckNetworkError = exports.KasseneckHttpError = exports.KasseneckApiError = exports.CLIENT_ERROR_CODES = void 0;
|
|
76
|
+
exports.KasseneckValidationError = exports.KasseneckAuthError = exports.KasseneckNetworkError = exports.KasseneckHttpError = exports.KasseneckApiError = exports.CLIENT_ERROR_CODES = exports.PAYMENT_CALL_REJECTED_CODES = void 0;
|
|
77
77
|
exports.causeDigest = causeDigest;
|
|
78
78
|
exports.fehlerDetails = fehlerDetails;
|
|
79
79
|
exports.isOutcomeUnknown = isOutcomeUnknown;
|
|
@@ -227,7 +227,77 @@ const AUSGANG_UNKLAR_CODES = new Set([
|
|
|
227
227
|
'cancellation_outcome_unknown',
|
|
228
228
|
'response_unreadable',
|
|
229
229
|
]);
|
|
230
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Aufrufe, die Geld bewegen: `hobexPayApi` belastet eine Karte,
|
|
232
|
+
* `hobexRefundApi` erstattet, `stripeCaptureIntent` zieht eine vorgemerkte
|
|
233
|
+
* Zahlung ein. Ihre Handler antworten auch NACH dem Anbieteraufruf mit einer
|
|
234
|
+
* Fehlerhuelle ohne Code ("Error hobex details", "Fehler beim Capturing");
|
|
235
|
+
* dort gilt darum umgekehrt: Ausgang unklar, ausser der Code belegt, dass der
|
|
236
|
+
* Anbieter nie gerufen wurde.
|
|
237
|
+
*/
|
|
238
|
+
const GELDWEGE = new Set(['hobexPayApi', 'hobexRefundApi', 'stripeCaptureIntent']);
|
|
239
|
+
/**
|
|
240
|
+
* Codes, die auf einem Geldweg `outcome: 'rejected'` ergeben; jeder andere
|
|
241
|
+
* Code und eine Huelle ohne Code sind dort `'unknown'`. Aufgenommen ist nur,
|
|
242
|
+
* was entsteht, bevor das Backend den Zahlungsanbieter anspricht (Vertrag
|
|
243
|
+
* v3, `errorCodes`; dieselbe Liste wie `paymentCallRejectedCodes` im
|
|
244
|
+
* Dart-Zwilling):
|
|
245
|
+
*
|
|
246
|
+
* - `errorCodes.auth` ohne die sieben des Partner-Zugangs (18 Codes):
|
|
247
|
+
* Anmeldung und Pruefung in `checkRequest` laufen vor jeder Zeile des
|
|
248
|
+
* Handlers; `validation` heisst dort Pflichtfeld fehlt oder falscher Typ.
|
|
249
|
+
* Der Partner-Zugang trifft diese `api_key`-Aufrufe mit Kassen-Token nie.
|
|
250
|
+
* - aus `errorCodes.edge`: `not_found` (unbekannter Endpunkt, HTTP 404) und
|
|
251
|
+
* `internal_translation_error` (die Anfrage liess sich nicht uebersetzen,
|
|
252
|
+
* es wurde nichts ausgefuehrt); `validation` des Rands (unbekannte Felder,
|
|
253
|
+
* Rumpf ohne Objekt) steht schon oben.
|
|
254
|
+
* - `module_inactive` und `not_permitted`: das Modul- bzw. Rechte-Tor steht
|
|
255
|
+
* ebenfalls vor dem Anbieter.
|
|
256
|
+
* - `route_missing` (vergibt das Paket): HTML ohne `/v3`-Kennzeichen, keine
|
|
257
|
+
* Function hat den Aufruf gesehen.
|
|
258
|
+
*
|
|
259
|
+
* Nicht darin: `dialect_mismatch` (das Paket vergibt ihn auch, wenn ein Rand
|
|
260
|
+
* ohne `/v3` geantwortet hat, dessen Handler gelaufen sein kann) und
|
|
261
|
+
* `response_translation_failed` (der Handler lief; auch `handled: false`
|
|
262
|
+
* heisst nur, dass seine Antwort ein Fehler war, und der kann hinter dem
|
|
263
|
+
* Anbieteraufruf entstanden sein). Der Sammelfang der Handler ("Error hobex
|
|
264
|
+
* details", "Fehler beim Capturing") antwortet ohne Code.
|
|
265
|
+
*/
|
|
266
|
+
exports.PAYMENT_CALL_REJECTED_CODES = Object.freeze([
|
|
267
|
+
// errorCodes.auth ohne Partner-Zugang
|
|
268
|
+
'method_not_allowed',
|
|
269
|
+
'validation',
|
|
270
|
+
'cashregister_token_missing',
|
|
271
|
+
'cashregister_token_invalid',
|
|
272
|
+
'cashregister_not_found',
|
|
273
|
+
'account_not_found',
|
|
274
|
+
'live_not_enabled',
|
|
275
|
+
'unauthorized',
|
|
276
|
+
'mfa_required',
|
|
277
|
+
'user_verification_failed',
|
|
278
|
+
'admin_required',
|
|
279
|
+
'register_user_not_allowed',
|
|
280
|
+
'register_user_no_business',
|
|
281
|
+
'register_user_not_found',
|
|
282
|
+
'user_disabled',
|
|
283
|
+
'session_expired',
|
|
284
|
+
'cashregister_not_assigned',
|
|
285
|
+
'session_other_cashregister',
|
|
286
|
+
// errorCodes.edge vor dem Handler (validation steht oben)
|
|
287
|
+
'not_found',
|
|
288
|
+
'internal_translation_error',
|
|
289
|
+
// Modul- und Rechte-Tor
|
|
290
|
+
'module_inactive',
|
|
291
|
+
'not_permitted',
|
|
292
|
+
// vom Paket vergeben
|
|
293
|
+
'route_missing',
|
|
294
|
+
]);
|
|
295
|
+
const GELDWEG_ABGELEHNT = new Set(exports.PAYMENT_CALL_REJECTED_CODES);
|
|
296
|
+
function ausgangAusCode(functionName, code, details) {
|
|
297
|
+
// `functionName` kann den Vorgang tragen (`financeWebService/<method>`).
|
|
298
|
+
if (GELDWEGE.has(functionName.split('/')[0])) {
|
|
299
|
+
return code !== undefined && GELDWEG_ABGELEHNT.has(code) ? 'rejected' : 'unknown';
|
|
300
|
+
}
|
|
231
301
|
if (code === undefined)
|
|
232
302
|
return 'rejected';
|
|
233
303
|
if (AUSGANG_UNKLAR_CODES.has(code))
|
|
@@ -271,7 +341,10 @@ class KasseneckApiError extends Error {
|
|
|
271
341
|
* `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet,
|
|
272
342
|
* Antwort eines Aufrufs mit Wirkung aber unlesbar) und
|
|
273
343
|
* `response_translation_failed` (ausser mit `details.handled === false`);
|
|
274
|
-
* sonst `'rejected'`.
|
|
344
|
+
* sonst `'rejected'`. Auf den Geldwegen (`hobexPayApi`, `hobexRefundApi`,
|
|
345
|
+
* `stripeCaptureIntent`) umgekehrt: `'rejected'` nur mit einem Code aus
|
|
346
|
+
* [PAYMENT_CALL_REJECTED_CODES], ohne Code und mit jedem anderen Code
|
|
347
|
+
* `'unknown'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
|
|
275
348
|
*/
|
|
276
349
|
outcome;
|
|
277
350
|
constructor(functionName, serverMessage, details = {}, code) {
|
|
@@ -283,7 +356,7 @@ class KasseneckApiError extends Error {
|
|
|
283
356
|
// den Details. So bleibt die Klasse fuer beide Ablageorte dieselbe.
|
|
284
357
|
const kandidat = code !== undefined ? code : details['code'];
|
|
285
358
|
this.code = typeof kandidat === 'string' && BEZEICHNER.test(kandidat) ? kandidat : undefined;
|
|
286
|
-
this.outcome = ausgangAusCode(this.code, details);
|
|
359
|
+
this.outcome = ausgangAusCode(functionName, this.code, details);
|
|
287
360
|
}
|
|
288
361
|
}
|
|
289
362
|
exports.KasseneckApiError = KasseneckApiError;
|
|
@@ -315,10 +315,17 @@ export interface SendReceiptEmailOptions {
|
|
|
315
315
|
}
|
|
316
316
|
/** Was der Versand bestaetigt (Backend: beleg-mail-endpoints.js). */
|
|
317
317
|
export interface SendReceiptEmailResult {
|
|
318
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* Adresse in der Form, in der das Backend sie protokolliert hat (getrimmt,
|
|
320
|
+
* klein). Nennt die Antwort keine, die gesendete (getrimmte) Adresse.
|
|
321
|
+
*/
|
|
319
322
|
to: string;
|
|
320
|
-
/**
|
|
321
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Zeitpunkt des Versands, ISO mit Wiener Zonenoffset
|
|
325
|
+
* (`2026-09-11T14:05:00+02:00`). `null`, wenn die Antwort keinen nennt: der
|
|
326
|
+
* Versand ist trotzdem bestaetigt.
|
|
327
|
+
*/
|
|
328
|
+
at: string | null;
|
|
322
329
|
/**
|
|
323
330
|
* Versandweg: `own` (Postfach des Betriebs), `platform` oder
|
|
324
331
|
* `platform_fallback` ([RECEIPT_EMAIL_VIAS]). `null`, wenn die Antwort ihn
|
|
@@ -395,14 +395,17 @@ async function sendReceiptEmail(transport, options) {
|
|
|
395
395
|
if (options.language !== undefined && options.language !== '')
|
|
396
396
|
params.language = options.language;
|
|
397
397
|
const daten = await transport('sendReceiptEmail', params);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
398
|
+
// Nachsichtig gelesen (wie der Dart-Zwilling): eine Erfolgsantwort heisst,
|
|
399
|
+
// die Mail ist schon verschickt. Ein Fehler wegen eines fehlenden
|
|
400
|
+
// Antwortfelds luede zum zweiten Versand ein; also zurueck, was da ist.
|
|
401
|
+
const gemeldet = daten?.to;
|
|
402
|
+
const zeit = daten?.at;
|
|
403
|
+
const via = index_js_2.RECEIPT_EMAIL_VIAS.includes(daten?.via) ? daten.via : null;
|
|
404
|
+
return {
|
|
405
|
+
to: typeof gemeldet === 'string' && gemeldet.trim() !== '' ? gemeldet : to,
|
|
406
|
+
at: typeof zeit === 'string' && zeit !== '' ? zeit : null,
|
|
407
|
+
via,
|
|
408
|
+
};
|
|
406
409
|
}
|
|
407
410
|
/**
|
|
408
411
|
* Prueft die Gutschein-Kombination eines Belegs und liefert den ersten
|
|
@@ -45,11 +45,11 @@ export interface Cashregister {
|
|
|
45
45
|
*/
|
|
46
46
|
export interface CashregisterOnboarding {
|
|
47
47
|
cashboxRegistered: boolean;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
startReceiptCreated: boolean;
|
|
49
|
+
startReceiptTransmitted: boolean;
|
|
50
50
|
cashboxRegisteredAt?: Date;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
startReceiptCreatedAt?: Date;
|
|
52
|
+
startReceiptTransmittedAt?: Date;
|
|
53
53
|
}
|
|
54
54
|
/** Nutzlast-Form, die dieses Paket liest — die Feldnamen von `listMyCashregisters`. */
|
|
55
55
|
export interface CashregisterPayload {
|
|
@@ -61,13 +61,19 @@ export interface CashregisterPayload {
|
|
|
61
61
|
token?: string | null;
|
|
62
62
|
onboarding?: CashregisterOnboardingPayload | null;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* `onboarding` am Draht `/v3` (fixtures/v3/antworten/kasse.json): der
|
|
66
|
+
* Startbeleg heisst dort `start_receipt_*`. Die inneren Namen
|
|
67
|
+
* `startbeleg_*` sendet nur `/v1`; wer sie hier laese, meldete jede Kasse
|
|
68
|
+
* als „Startbeleg fehlt“.
|
|
69
|
+
*/
|
|
64
70
|
export interface CashregisterOnboardingPayload {
|
|
65
71
|
cashbox_registered?: boolean | null;
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
start_receipt_created?: boolean | null;
|
|
73
|
+
start_receipt_transmitted?: boolean | null;
|
|
68
74
|
cashbox_registered_at?: string | null;
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
start_receipt_created_at?: string | null;
|
|
76
|
+
start_receipt_transmitted_at?: string | null;
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
73
79
|
* Liest eine Kasse aus der Antwort. `id` gewinnt aus der Nutzlast, faellt aber
|
|
@@ -18,11 +18,11 @@ function fromCashregisterPayload(payload, id) {
|
|
|
18
18
|
...(payload.signature_id ? { signatureId: payload.signature_id } : {}),
|
|
19
19
|
onboarding: {
|
|
20
20
|
cashboxRegistered: ob.cashbox_registered === true,
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
startReceiptCreated: ob.start_receipt_created === true,
|
|
22
|
+
startReceiptTransmitted: ob.start_receipt_transmitted === true,
|
|
23
23
|
...zeitfeld('cashboxRegisteredAt', ob.cashbox_registered_at),
|
|
24
|
-
...zeitfeld('
|
|
25
|
-
...zeitfeld('
|
|
24
|
+
...zeitfeld('startReceiptCreatedAt', ob.start_receipt_created_at),
|
|
25
|
+
...zeitfeld('startReceiptTransmittedAt', ob.start_receipt_transmitted_at),
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -47,8 +47,9 @@ export interface HobexTransactionIdOptions {
|
|
|
47
47
|
* nicht.
|
|
48
48
|
*
|
|
49
49
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
50
|
-
* unlesbare Antwort
|
|
51
|
-
*
|
|
50
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
51
|
+
* der Ablehnungscodes, README `outcome`) kann die Karte belastet sein: den Stand
|
|
52
|
+
* ueber die Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
|
|
52
53
|
*/
|
|
53
54
|
export declare function hobexPay(transport: InternerTransport, options: HobexPayOptions): Promise<HobexReceipt>;
|
|
54
55
|
/**
|
|
@@ -62,8 +63,10 @@ export declare function hobexPay(transport: InternerTransport, options: HobexPay
|
|
|
62
63
|
* Wahrheitswert waere hier also immer `true` — eine Luege ueber den
|
|
63
64
|
* Informationsgehalt, an der ein Aufrufer ein `if` aufhaengt, das nie greift.
|
|
64
65
|
* Misserfolg kommt als geworfener Fehler: eine Fehlerhuelle als
|
|
65
|
-
* [KasseneckApiError]
|
|
66
|
-
*
|
|
66
|
+
* [KasseneckApiError], `'rejected'` nur mit einem der Ablehnungscodes
|
|
67
|
+
* (README `outcome`); ohne Code oder mit anderem Code, bei Netz,
|
|
68
|
+
* Zeitlimit, HTTP 5xx und unlesbarer Erfolgsantwort `outcome: 'unknown'`
|
|
69
|
+
* (die Erstattung kann gelaufen sein).
|
|
67
70
|
* Ein `false` an dieser Stelle luede zum zweiten Versuch ein, also zur
|
|
68
71
|
* doppelten Erstattung. **Nie wiederholen**, sondern den Stand nachlesen.
|
|
69
72
|
*
|
|
@@ -43,8 +43,9 @@ const ENDPUNKT_REFUND = 'hobexRefundApi';
|
|
|
43
43
|
* nicht.
|
|
44
44
|
*
|
|
45
45
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
46
|
-
* unlesbare Antwort
|
|
47
|
-
*
|
|
46
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
47
|
+
* der Ablehnungscodes, README `outcome`) kann die Karte belastet sein: den Stand
|
|
48
|
+
* ueber die Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
|
|
48
49
|
*/
|
|
49
50
|
async function hobexPay(transport, options) {
|
|
50
51
|
const params = zahlungsNutzlast(ENDPUNKT_PAY, options);
|
|
@@ -69,8 +70,10 @@ async function hobexPay(transport, options) {
|
|
|
69
70
|
* Wahrheitswert waere hier also immer `true` — eine Luege ueber den
|
|
70
71
|
* Informationsgehalt, an der ein Aufrufer ein `if` aufhaengt, das nie greift.
|
|
71
72
|
* Misserfolg kommt als geworfener Fehler: eine Fehlerhuelle als
|
|
72
|
-
* [KasseneckApiError]
|
|
73
|
-
*
|
|
73
|
+
* [KasseneckApiError], `'rejected'` nur mit einem der Ablehnungscodes
|
|
74
|
+
* (README `outcome`); ohne Code oder mit anderem Code, bei Netz,
|
|
75
|
+
* Zeitlimit, HTTP 5xx und unlesbarer Erfolgsantwort `outcome: 'unknown'`
|
|
76
|
+
* (die Erstattung kann gelaufen sein).
|
|
74
77
|
* Ein `false` an dieser Stelle luede zum zweiten Versuch ein, also zur
|
|
75
78
|
* doppelten Erstattung. **Nie wiederholen**, sondern den Stand nachlesen.
|
|
76
79
|
*
|
|
@@ -59,6 +59,8 @@ export declare function createStripeLink(transport: InternerTransport, options:
|
|
|
59
59
|
* Pflichtparameter.
|
|
60
60
|
*
|
|
61
61
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
62
|
-
* unlesbare Antwort
|
|
62
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
63
|
+
* der Ablehnungscodes, README `outcome`) kann der Einzug gelaufen sein: den Stand
|
|
64
|
+
* nachlesen.
|
|
63
65
|
*/
|
|
64
66
|
export declare function stripeCaptureIntent(transport: InternerTransport, stripeSessionId: string): Promise<StripeCaptureResult>;
|
|
@@ -74,7 +74,9 @@ async function createStripeLink(transport, options) {
|
|
|
74
74
|
* Pflichtparameter.
|
|
75
75
|
*
|
|
76
76
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
77
|
-
* unlesbare Antwort
|
|
77
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
78
|
+
* der Ablehnungscodes, README `outcome`) kann der Einzug gelaufen sein: den Stand
|
|
79
|
+
* nachlesen.
|
|
78
80
|
*/
|
|
79
81
|
async function stripeCaptureIntent(transport, stripeSessionId) {
|
|
80
82
|
if (typeof stripeSessionId !== 'string' || !stripeSessionId.trim()) {
|
package/dist/cjs/pos/client.d.ts
CHANGED
|
@@ -11,7 +11,12 @@ export declare function posSettingsFromWire(data: {
|
|
|
11
11
|
} | null | undefined): PosSettings;
|
|
12
12
|
/** Betriebsweite Einstellungen schreiben (Recht `layout`); liefert den gemischten Stand. */
|
|
13
13
|
export declare function setMyPosSettings(transport: InternerTransport, business: Partial<PosBusinessSettings>): Promise<PosBusinessSettings>;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Geraete-Einstellungen schreiben (Recht `layout`); liefert den gemischten
|
|
16
|
+
* Stand. `shortcuts` nur als ganze Karte aller bekannten Aktionen
|
|
17
|
+
* ([posSettingsChanges] liefert sie bei jeder Tastenaenderung), sonst wirft
|
|
18
|
+
* der Aufruf vor dem Senden.
|
|
19
|
+
*/
|
|
15
20
|
export declare function setMyRegisterDeviceSettings(transport: InternerTransport, deviceId: string, device: Partial<PosDeviceSettings>): Promise<PosDeviceSettings>;
|
|
16
21
|
/** Eingabe von [setMyPosLogo]: ein Bild als Data-URL (PNG, JPEG, SVG) oder das Entfernen. */
|
|
17
22
|
export type SetMyPosLogoOptions = {
|
package/dist/cjs/pos/client.js
CHANGED
|
@@ -57,7 +57,12 @@ async function setMyPosSettings(transport, business) {
|
|
|
57
57
|
const daten = await transport(name, { business });
|
|
58
58
|
return (0, settings_js_1.mergePosSettings)(settings_js_1.POS_BUSINESS_DEFAULTS, objekt(daten?.business));
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Geraete-Einstellungen schreiben (Recht `layout`); liefert den gemischten
|
|
62
|
+
* Stand. `shortcuts` nur als ganze Karte aller bekannten Aktionen
|
|
63
|
+
* ([posSettingsChanges] liefert sie bei jeder Tastenaenderung), sonst wirft
|
|
64
|
+
* der Aufruf vor dem Senden.
|
|
65
|
+
*/
|
|
61
66
|
async function setMyRegisterDeviceSettings(transport, deviceId, device) {
|
|
62
67
|
const name = 'setMyRegisterDeviceSettings';
|
|
63
68
|
if (typeof deviceId !== 'string' || deviceId.trim() === '') {
|
|
@@ -77,8 +82,15 @@ async function setMyRegisterDeviceSettings(transport, deviceId, device) {
|
|
|
77
82
|
throw new errors_js_1.KasseneckValidationError(name, `device.shortcuts.${aktion}: unbekannte Aktion`, 'request');
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
|
-
//
|
|
81
|
-
//
|
|
85
|
+
// Nur die ganze Karte: der Server prueft Doppelbelegungen nur in der
|
|
86
|
+
// gesendeten Karte, eine halbe (`{cash: ['Mod+K']}`) liesse eine
|
|
87
|
+
// Doppelbelegung mit einer gespeicherten Taste durch. posSettingsChanges
|
|
88
|
+
// liefert bei jeder Tastenaenderung die ganze Karte.
|
|
89
|
+
const fehlt = settings_js_1.POS_SHORTCUT_ACTIONS.filter((a) => karte[a] === undefined);
|
|
90
|
+
if (fehlt.length > 0) {
|
|
91
|
+
throw new errors_js_1.KasseneckValidationError(name, `device.shortcuts: ganze Karte senden (posSettingsChanges), es fehlen ${fehlt.join(', ')}`, 'request');
|
|
92
|
+
}
|
|
93
|
+
// Doppelbelegung in der ganzen Karte, also in der ganzen Belegung.
|
|
82
94
|
const konflikt = (0, settings_js_1.posShortcutConflict)(karte);
|
|
83
95
|
if (konflikt) {
|
|
84
96
|
throw new errors_js_1.KasseneckValidationError(name, `device.shortcuts.${konflikt.action}: Taste ${konflikt.key} schon belegt (${konflikt.heldBy})`, 'request');
|
package/dist/cjs/pos/drucker.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.isPrintJobFinished = isPrintJobFinished;
|
|
|
5
5
|
exports.listMyPrinters = listMyPrinters;
|
|
6
6
|
exports.createPrintJob = createPrintJob;
|
|
7
7
|
exports.getPrintJob = getPrintJob;
|
|
8
|
+
const errors_js_1 = require("../client/errors.js");
|
|
8
9
|
const index_js_1 = require("../printing/index.js");
|
|
9
10
|
/** Stand eines Druckjobs (Katalog `DRUCKJOB`). */
|
|
10
11
|
exports.PRINT_JOB_STATUSES = ['pending', 'sent', 'printed', 'failed', 'expired'];
|
|
@@ -29,9 +30,27 @@ const STATUS = new Set(exports.PRINT_JOB_STATUSES);
|
|
|
29
30
|
function status(v) {
|
|
30
31
|
return typeof v === 'string' && STATUS.has(v) ? v : 'unknown';
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Die Kennung eines Jobs aus der Antwort. Ohne sie kann niemand den Job
|
|
35
|
+
* abfragen; ein leerer Text waere ein angeblich angelegter Job, und der
|
|
36
|
+
* Kassier druckte ein zweites Mal.
|
|
37
|
+
*/
|
|
38
|
+
function jobKennung(name, d) {
|
|
39
|
+
const id = d?.jobId;
|
|
40
|
+
if (typeof id !== 'string' || id === '') {
|
|
41
|
+
throw new errors_js_1.KasseneckValidationError(name, 'Antwort enthaelt keine Kennung (data.jobId fehlt)', 'response');
|
|
42
|
+
}
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
32
45
|
async function listMyPrinters(transport) {
|
|
33
46
|
const daten = await transport('listMyPrinters', {});
|
|
34
|
-
|
|
47
|
+
const liste = daten?.printers;
|
|
48
|
+
if (!Array.isArray(liste)) {
|
|
49
|
+
// Keine Liste ist etwas anderes als eine leere Liste: „noch kein Drucker“
|
|
50
|
+
// darf nicht aussehen wie „Antwort kaputt“.
|
|
51
|
+
throw new errors_js_1.KasseneckValidationError('listMyPrinters', 'Antwort enthaelt keine Liste (data.printers fehlt)', 'response');
|
|
52
|
+
}
|
|
53
|
+
return liste.map((r) => {
|
|
35
54
|
const d = (r ?? {});
|
|
36
55
|
const e = d.lastResult && typeof d.lastResult === 'object' ? d.lastResult : null;
|
|
37
56
|
return {
|
|
@@ -60,7 +79,7 @@ async function createPrintJob(transport, o) {
|
|
|
60
79
|
if (o.brandMark === true)
|
|
61
80
|
params.brand = true;
|
|
62
81
|
const daten = await transport('createPrintJob', params);
|
|
63
|
-
return { jobId:
|
|
82
|
+
return { jobId: jobKennung('createPrintJob', daten), status: status(daten?.status), result: null };
|
|
64
83
|
}
|
|
65
84
|
/**
|
|
66
85
|
* Stand eines Druckjobs abfragen. Der Aufrufer fragt, bis [isPrintJobFinished]
|
|
@@ -71,7 +90,7 @@ async function getPrintJob(transport, o) {
|
|
|
71
90
|
const d = await transport('getPrintJob', { printerId: o.printerId, jobId: o.jobId });
|
|
72
91
|
const e = d?.result && typeof d.result === 'object' ? d.result : null;
|
|
73
92
|
return {
|
|
74
|
-
jobId:
|
|
93
|
+
jobId: jobKennung('getPrintJob', d), status: status(d?.status),
|
|
75
94
|
createdAt: zahl(d?.createdAt), sentAt: zahl(d?.sentAt),
|
|
76
95
|
result: e ? { success: e.success === true, code: text(e.code), status: text(e.status), at: zahl(e.at) ?? undefined } : null,
|
|
77
96
|
};
|
package/dist/cjs/version.d.ts
CHANGED
package/dist/cjs/version.js
CHANGED
|
@@ -110,6 +110,34 @@ export declare function fehlerDetails(daten: unknown, geheimnisse: readonly stri
|
|
|
110
110
|
* das Ergebnis nachlesen.
|
|
111
111
|
*/
|
|
112
112
|
export type ErrorOutcome = 'unknown' | 'rejected';
|
|
113
|
+
/**
|
|
114
|
+
* Codes, die auf einem Geldweg `outcome: 'rejected'` ergeben; jeder andere
|
|
115
|
+
* Code und eine Huelle ohne Code sind dort `'unknown'`. Aufgenommen ist nur,
|
|
116
|
+
* was entsteht, bevor das Backend den Zahlungsanbieter anspricht (Vertrag
|
|
117
|
+
* v3, `errorCodes`; dieselbe Liste wie `paymentCallRejectedCodes` im
|
|
118
|
+
* Dart-Zwilling):
|
|
119
|
+
*
|
|
120
|
+
* - `errorCodes.auth` ohne die sieben des Partner-Zugangs (18 Codes):
|
|
121
|
+
* Anmeldung und Pruefung in `checkRequest` laufen vor jeder Zeile des
|
|
122
|
+
* Handlers; `validation` heisst dort Pflichtfeld fehlt oder falscher Typ.
|
|
123
|
+
* Der Partner-Zugang trifft diese `api_key`-Aufrufe mit Kassen-Token nie.
|
|
124
|
+
* - aus `errorCodes.edge`: `not_found` (unbekannter Endpunkt, HTTP 404) und
|
|
125
|
+
* `internal_translation_error` (die Anfrage liess sich nicht uebersetzen,
|
|
126
|
+
* es wurde nichts ausgefuehrt); `validation` des Rands (unbekannte Felder,
|
|
127
|
+
* Rumpf ohne Objekt) steht schon oben.
|
|
128
|
+
* - `module_inactive` und `not_permitted`: das Modul- bzw. Rechte-Tor steht
|
|
129
|
+
* ebenfalls vor dem Anbieter.
|
|
130
|
+
* - `route_missing` (vergibt das Paket): HTML ohne `/v3`-Kennzeichen, keine
|
|
131
|
+
* Function hat den Aufruf gesehen.
|
|
132
|
+
*
|
|
133
|
+
* Nicht darin: `dialect_mismatch` (das Paket vergibt ihn auch, wenn ein Rand
|
|
134
|
+
* ohne `/v3` geantwortet hat, dessen Handler gelaufen sein kann) und
|
|
135
|
+
* `response_translation_failed` (der Handler lief; auch `handled: false`
|
|
136
|
+
* heisst nur, dass seine Antwort ein Fehler war, und der kann hinter dem
|
|
137
|
+
* Anbieteraufruf entstanden sein). Der Sammelfang der Handler ("Error hobex
|
|
138
|
+
* details", "Fehler beim Capturing") antwortet ohne Code.
|
|
139
|
+
*/
|
|
140
|
+
export declare const PAYMENT_CALL_REJECTED_CODES: readonly string[];
|
|
113
141
|
/**
|
|
114
142
|
* Codes, die das Paket selbst vergibt, nicht der Server: `route_missing`
|
|
115
143
|
* (HTML statt Backend, der Aufruf kam nie an) und `response_unreadable`
|
|
@@ -146,7 +174,10 @@ export declare class KasseneckApiError extends Error {
|
|
|
146
174
|
* `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet,
|
|
147
175
|
* Antwort eines Aufrufs mit Wirkung aber unlesbar) und
|
|
148
176
|
* `response_translation_failed` (ausser mit `details.handled === false`);
|
|
149
|
-
* sonst `'rejected'`.
|
|
177
|
+
* sonst `'rejected'`. Auf den Geldwegen (`hobexPayApi`, `hobexRefundApi`,
|
|
178
|
+
* `stripeCaptureIntent`) umgekehrt: `'rejected'` nur mit einem Code aus
|
|
179
|
+
* [PAYMENT_CALL_REJECTED_CODES], ohne Code und mit jedem anderen Code
|
|
180
|
+
* `'unknown'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
|
|
150
181
|
*/
|
|
151
182
|
readonly outcome: ErrorOutcome;
|
|
152
183
|
constructor(functionName: string, serverMessage: string, details?: Record<string, unknown>, code?: string);
|
|
@@ -215,7 +215,77 @@ const AUSGANG_UNKLAR_CODES = new Set([
|
|
|
215
215
|
'cancellation_outcome_unknown',
|
|
216
216
|
'response_unreadable',
|
|
217
217
|
]);
|
|
218
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Aufrufe, die Geld bewegen: `hobexPayApi` belastet eine Karte,
|
|
220
|
+
* `hobexRefundApi` erstattet, `stripeCaptureIntent` zieht eine vorgemerkte
|
|
221
|
+
* Zahlung ein. Ihre Handler antworten auch NACH dem Anbieteraufruf mit einer
|
|
222
|
+
* Fehlerhuelle ohne Code ("Error hobex details", "Fehler beim Capturing");
|
|
223
|
+
* dort gilt darum umgekehrt: Ausgang unklar, ausser der Code belegt, dass der
|
|
224
|
+
* Anbieter nie gerufen wurde.
|
|
225
|
+
*/
|
|
226
|
+
const GELDWEGE = new Set(['hobexPayApi', 'hobexRefundApi', 'stripeCaptureIntent']);
|
|
227
|
+
/**
|
|
228
|
+
* Codes, die auf einem Geldweg `outcome: 'rejected'` ergeben; jeder andere
|
|
229
|
+
* Code und eine Huelle ohne Code sind dort `'unknown'`. Aufgenommen ist nur,
|
|
230
|
+
* was entsteht, bevor das Backend den Zahlungsanbieter anspricht (Vertrag
|
|
231
|
+
* v3, `errorCodes`; dieselbe Liste wie `paymentCallRejectedCodes` im
|
|
232
|
+
* Dart-Zwilling):
|
|
233
|
+
*
|
|
234
|
+
* - `errorCodes.auth` ohne die sieben des Partner-Zugangs (18 Codes):
|
|
235
|
+
* Anmeldung und Pruefung in `checkRequest` laufen vor jeder Zeile des
|
|
236
|
+
* Handlers; `validation` heisst dort Pflichtfeld fehlt oder falscher Typ.
|
|
237
|
+
* Der Partner-Zugang trifft diese `api_key`-Aufrufe mit Kassen-Token nie.
|
|
238
|
+
* - aus `errorCodes.edge`: `not_found` (unbekannter Endpunkt, HTTP 404) und
|
|
239
|
+
* `internal_translation_error` (die Anfrage liess sich nicht uebersetzen,
|
|
240
|
+
* es wurde nichts ausgefuehrt); `validation` des Rands (unbekannte Felder,
|
|
241
|
+
* Rumpf ohne Objekt) steht schon oben.
|
|
242
|
+
* - `module_inactive` und `not_permitted`: das Modul- bzw. Rechte-Tor steht
|
|
243
|
+
* ebenfalls vor dem Anbieter.
|
|
244
|
+
* - `route_missing` (vergibt das Paket): HTML ohne `/v3`-Kennzeichen, keine
|
|
245
|
+
* Function hat den Aufruf gesehen.
|
|
246
|
+
*
|
|
247
|
+
* Nicht darin: `dialect_mismatch` (das Paket vergibt ihn auch, wenn ein Rand
|
|
248
|
+
* ohne `/v3` geantwortet hat, dessen Handler gelaufen sein kann) und
|
|
249
|
+
* `response_translation_failed` (der Handler lief; auch `handled: false`
|
|
250
|
+
* heisst nur, dass seine Antwort ein Fehler war, und der kann hinter dem
|
|
251
|
+
* Anbieteraufruf entstanden sein). Der Sammelfang der Handler ("Error hobex
|
|
252
|
+
* details", "Fehler beim Capturing") antwortet ohne Code.
|
|
253
|
+
*/
|
|
254
|
+
export const PAYMENT_CALL_REJECTED_CODES = Object.freeze([
|
|
255
|
+
// errorCodes.auth ohne Partner-Zugang
|
|
256
|
+
'method_not_allowed',
|
|
257
|
+
'validation',
|
|
258
|
+
'cashregister_token_missing',
|
|
259
|
+
'cashregister_token_invalid',
|
|
260
|
+
'cashregister_not_found',
|
|
261
|
+
'account_not_found',
|
|
262
|
+
'live_not_enabled',
|
|
263
|
+
'unauthorized',
|
|
264
|
+
'mfa_required',
|
|
265
|
+
'user_verification_failed',
|
|
266
|
+
'admin_required',
|
|
267
|
+
'register_user_not_allowed',
|
|
268
|
+
'register_user_no_business',
|
|
269
|
+
'register_user_not_found',
|
|
270
|
+
'user_disabled',
|
|
271
|
+
'session_expired',
|
|
272
|
+
'cashregister_not_assigned',
|
|
273
|
+
'session_other_cashregister',
|
|
274
|
+
// errorCodes.edge vor dem Handler (validation steht oben)
|
|
275
|
+
'not_found',
|
|
276
|
+
'internal_translation_error',
|
|
277
|
+
// Modul- und Rechte-Tor
|
|
278
|
+
'module_inactive',
|
|
279
|
+
'not_permitted',
|
|
280
|
+
// vom Paket vergeben
|
|
281
|
+
'route_missing',
|
|
282
|
+
]);
|
|
283
|
+
const GELDWEG_ABGELEHNT = new Set(PAYMENT_CALL_REJECTED_CODES);
|
|
284
|
+
function ausgangAusCode(functionName, code, details) {
|
|
285
|
+
// `functionName` kann den Vorgang tragen (`financeWebService/<method>`).
|
|
286
|
+
if (GELDWEGE.has(functionName.split('/')[0])) {
|
|
287
|
+
return code !== undefined && GELDWEG_ABGELEHNT.has(code) ? 'rejected' : 'unknown';
|
|
288
|
+
}
|
|
219
289
|
if (code === undefined)
|
|
220
290
|
return 'rejected';
|
|
221
291
|
if (AUSGANG_UNKLAR_CODES.has(code))
|
|
@@ -259,7 +329,10 @@ export class KasseneckApiError extends Error {
|
|
|
259
329
|
* `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet,
|
|
260
330
|
* Antwort eines Aufrufs mit Wirkung aber unlesbar) und
|
|
261
331
|
* `response_translation_failed` (ausser mit `details.handled === false`);
|
|
262
|
-
* sonst `'rejected'`.
|
|
332
|
+
* sonst `'rejected'`. Auf den Geldwegen (`hobexPayApi`, `hobexRefundApi`,
|
|
333
|
+
* `stripeCaptureIntent`) umgekehrt: `'rejected'` nur mit einem Code aus
|
|
334
|
+
* [PAYMENT_CALL_REJECTED_CODES], ohne Code und mit jedem anderen Code
|
|
335
|
+
* `'unknown'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
|
|
263
336
|
*/
|
|
264
337
|
outcome;
|
|
265
338
|
constructor(functionName, serverMessage, details = {}, code) {
|
|
@@ -271,7 +344,7 @@ export class KasseneckApiError extends Error {
|
|
|
271
344
|
// den Details. So bleibt die Klasse fuer beide Ablageorte dieselbe.
|
|
272
345
|
const kandidat = code !== undefined ? code : details['code'];
|
|
273
346
|
this.code = typeof kandidat === 'string' && BEZEICHNER.test(kandidat) ? kandidat : undefined;
|
|
274
|
-
this.outcome = ausgangAusCode(this.code, details);
|
|
347
|
+
this.outcome = ausgangAusCode(functionName, this.code, details);
|
|
275
348
|
}
|
|
276
349
|
}
|
|
277
350
|
const GRUND_TEXT = {
|
|
@@ -315,10 +315,17 @@ export interface SendReceiptEmailOptions {
|
|
|
315
315
|
}
|
|
316
316
|
/** Was der Versand bestaetigt (Backend: beleg-mail-endpoints.js). */
|
|
317
317
|
export interface SendReceiptEmailResult {
|
|
318
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* Adresse in der Form, in der das Backend sie protokolliert hat (getrimmt,
|
|
320
|
+
* klein). Nennt die Antwort keine, die gesendete (getrimmte) Adresse.
|
|
321
|
+
*/
|
|
319
322
|
to: string;
|
|
320
|
-
/**
|
|
321
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Zeitpunkt des Versands, ISO mit Wiener Zonenoffset
|
|
325
|
+
* (`2026-09-11T14:05:00+02:00`). `null`, wenn die Antwort keinen nennt: der
|
|
326
|
+
* Versand ist trotzdem bestaetigt.
|
|
327
|
+
*/
|
|
328
|
+
at: string | null;
|
|
322
329
|
/**
|
|
323
330
|
* Versandweg: `own` (Postfach des Betriebs), `platform` oder
|
|
324
331
|
* `platform_fallback` ([RECEIPT_EMAIL_VIAS]). `null`, wenn die Antwort ihn
|
|
@@ -376,14 +376,17 @@ export async function sendReceiptEmail(transport, options) {
|
|
|
376
376
|
if (options.language !== undefined && options.language !== '')
|
|
377
377
|
params.language = options.language;
|
|
378
378
|
const daten = await transport('sendReceiptEmail', params);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
// Nachsichtig gelesen (wie der Dart-Zwilling): eine Erfolgsantwort heisst,
|
|
380
|
+
// die Mail ist schon verschickt. Ein Fehler wegen eines fehlenden
|
|
381
|
+
// Antwortfelds luede zum zweiten Versand ein; also zurueck, was da ist.
|
|
382
|
+
const gemeldet = daten?.to;
|
|
383
|
+
const zeit = daten?.at;
|
|
384
|
+
const via = RECEIPT_EMAIL_VIAS.includes(daten?.via) ? daten.via : null;
|
|
385
|
+
return {
|
|
386
|
+
to: typeof gemeldet === 'string' && gemeldet.trim() !== '' ? gemeldet : to,
|
|
387
|
+
at: typeof zeit === 'string' && zeit !== '' ? zeit : null,
|
|
388
|
+
via,
|
|
389
|
+
};
|
|
387
390
|
}
|
|
388
391
|
/**
|
|
389
392
|
* Prueft die Gutschein-Kombination eines Belegs und liefert den ersten
|
|
@@ -45,11 +45,11 @@ export interface Cashregister {
|
|
|
45
45
|
*/
|
|
46
46
|
export interface CashregisterOnboarding {
|
|
47
47
|
cashboxRegistered: boolean;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
startReceiptCreated: boolean;
|
|
49
|
+
startReceiptTransmitted: boolean;
|
|
50
50
|
cashboxRegisteredAt?: Date;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
startReceiptCreatedAt?: Date;
|
|
52
|
+
startReceiptTransmittedAt?: Date;
|
|
53
53
|
}
|
|
54
54
|
/** Nutzlast-Form, die dieses Paket liest — die Feldnamen von `listMyCashregisters`. */
|
|
55
55
|
export interface CashregisterPayload {
|
|
@@ -61,13 +61,19 @@ export interface CashregisterPayload {
|
|
|
61
61
|
token?: string | null;
|
|
62
62
|
onboarding?: CashregisterOnboardingPayload | null;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* `onboarding` am Draht `/v3` (fixtures/v3/antworten/kasse.json): der
|
|
66
|
+
* Startbeleg heisst dort `start_receipt_*`. Die inneren Namen
|
|
67
|
+
* `startbeleg_*` sendet nur `/v1`; wer sie hier laese, meldete jede Kasse
|
|
68
|
+
* als „Startbeleg fehlt“.
|
|
69
|
+
*/
|
|
64
70
|
export interface CashregisterOnboardingPayload {
|
|
65
71
|
cashbox_registered?: boolean | null;
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
start_receipt_created?: boolean | null;
|
|
73
|
+
start_receipt_transmitted?: boolean | null;
|
|
68
74
|
cashbox_registered_at?: string | null;
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
start_receipt_created_at?: string | null;
|
|
76
|
+
start_receipt_transmitted_at?: string | null;
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
73
79
|
* Liest eine Kasse aus der Antwort. `id` gewinnt aus der Nutzlast, faellt aber
|
|
@@ -15,11 +15,11 @@ export function fromCashregisterPayload(payload, id) {
|
|
|
15
15
|
...(payload.signature_id ? { signatureId: payload.signature_id } : {}),
|
|
16
16
|
onboarding: {
|
|
17
17
|
cashboxRegistered: ob.cashbox_registered === true,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
startReceiptCreated: ob.start_receipt_created === true,
|
|
19
|
+
startReceiptTransmitted: ob.start_receipt_transmitted === true,
|
|
20
20
|
...zeitfeld('cashboxRegisteredAt', ob.cashbox_registered_at),
|
|
21
|
-
...zeitfeld('
|
|
22
|
-
...zeitfeld('
|
|
21
|
+
...zeitfeld('startReceiptCreatedAt', ob.start_receipt_created_at),
|
|
22
|
+
...zeitfeld('startReceiptTransmittedAt', ob.start_receipt_transmitted_at),
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
25
|
}
|
|
@@ -47,8 +47,9 @@ export interface HobexTransactionIdOptions {
|
|
|
47
47
|
* nicht.
|
|
48
48
|
*
|
|
49
49
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
50
|
-
* unlesbare Antwort
|
|
51
|
-
*
|
|
50
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
51
|
+
* der Ablehnungscodes, README `outcome`) kann die Karte belastet sein: den Stand
|
|
52
|
+
* ueber die Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
|
|
52
53
|
*/
|
|
53
54
|
export declare function hobexPay(transport: InternerTransport, options: HobexPayOptions): Promise<HobexReceipt>;
|
|
54
55
|
/**
|
|
@@ -62,8 +63,10 @@ export declare function hobexPay(transport: InternerTransport, options: HobexPay
|
|
|
62
63
|
* Wahrheitswert waere hier also immer `true` — eine Luege ueber den
|
|
63
64
|
* Informationsgehalt, an der ein Aufrufer ein `if` aufhaengt, das nie greift.
|
|
64
65
|
* Misserfolg kommt als geworfener Fehler: eine Fehlerhuelle als
|
|
65
|
-
* [KasseneckApiError]
|
|
66
|
-
*
|
|
66
|
+
* [KasseneckApiError], `'rejected'` nur mit einem der Ablehnungscodes
|
|
67
|
+
* (README `outcome`); ohne Code oder mit anderem Code, bei Netz,
|
|
68
|
+
* Zeitlimit, HTTP 5xx und unlesbarer Erfolgsantwort `outcome: 'unknown'`
|
|
69
|
+
* (die Erstattung kann gelaufen sein).
|
|
67
70
|
* Ein `false` an dieser Stelle luede zum zweiten Versuch ein, also zur
|
|
68
71
|
* doppelten Erstattung. **Nie wiederholen**, sondern den Stand nachlesen.
|
|
69
72
|
*
|
|
@@ -38,8 +38,9 @@ const ENDPUNKT_REFUND = 'hobexRefundApi';
|
|
|
38
38
|
* nicht.
|
|
39
39
|
*
|
|
40
40
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
41
|
-
* unlesbare Antwort
|
|
42
|
-
*
|
|
41
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
42
|
+
* der Ablehnungscodes, README `outcome`) kann die Karte belastet sein: den Stand
|
|
43
|
+
* ueber die Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
|
|
43
44
|
*/
|
|
44
45
|
export async function hobexPay(transport, options) {
|
|
45
46
|
const params = zahlungsNutzlast(ENDPUNKT_PAY, options);
|
|
@@ -64,8 +65,10 @@ export async function hobexPay(transport, options) {
|
|
|
64
65
|
* Wahrheitswert waere hier also immer `true` — eine Luege ueber den
|
|
65
66
|
* Informationsgehalt, an der ein Aufrufer ein `if` aufhaengt, das nie greift.
|
|
66
67
|
* Misserfolg kommt als geworfener Fehler: eine Fehlerhuelle als
|
|
67
|
-
* [KasseneckApiError]
|
|
68
|
-
*
|
|
68
|
+
* [KasseneckApiError], `'rejected'` nur mit einem der Ablehnungscodes
|
|
69
|
+
* (README `outcome`); ohne Code oder mit anderem Code, bei Netz,
|
|
70
|
+
* Zeitlimit, HTTP 5xx und unlesbarer Erfolgsantwort `outcome: 'unknown'`
|
|
71
|
+
* (die Erstattung kann gelaufen sein).
|
|
69
72
|
* Ein `false` an dieser Stelle luede zum zweiten Versuch ein, also zur
|
|
70
73
|
* doppelten Erstattung. **Nie wiederholen**, sondern den Stand nachlesen.
|
|
71
74
|
*
|
|
@@ -59,6 +59,8 @@ export declare function createStripeLink(transport: InternerTransport, options:
|
|
|
59
59
|
* Pflichtparameter.
|
|
60
60
|
*
|
|
61
61
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
62
|
-
* unlesbare Antwort
|
|
62
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
63
|
+
* der Ablehnungscodes, README `outcome`) kann der Einzug gelaufen sein: den Stand
|
|
64
|
+
* nachlesen.
|
|
63
65
|
*/
|
|
64
66
|
export declare function stripeCaptureIntent(transport: InternerTransport, stripeSessionId: string): Promise<StripeCaptureResult>;
|
|
@@ -70,7 +70,9 @@ export async function createStripeLink(transport, options) {
|
|
|
70
70
|
* Pflichtparameter.
|
|
71
71
|
*
|
|
72
72
|
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
73
|
-
* unlesbare Antwort
|
|
73
|
+
* unlesbare Antwort, Fehlerhuelle ohne Code oder mit einem Code ausserhalb
|
|
74
|
+
* der Ablehnungscodes, README `outcome`) kann der Einzug gelaufen sein: den Stand
|
|
75
|
+
* nachlesen.
|
|
74
76
|
*/
|
|
75
77
|
export async function stripeCaptureIntent(transport, stripeSessionId) {
|
|
76
78
|
if (typeof stripeSessionId !== 'string' || !stripeSessionId.trim()) {
|
package/dist/esm/pos/client.d.ts
CHANGED
|
@@ -11,7 +11,12 @@ export declare function posSettingsFromWire(data: {
|
|
|
11
11
|
} | null | undefined): PosSettings;
|
|
12
12
|
/** Betriebsweite Einstellungen schreiben (Recht `layout`); liefert den gemischten Stand. */
|
|
13
13
|
export declare function setMyPosSettings(transport: InternerTransport, business: Partial<PosBusinessSettings>): Promise<PosBusinessSettings>;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Geraete-Einstellungen schreiben (Recht `layout`); liefert den gemischten
|
|
16
|
+
* Stand. `shortcuts` nur als ganze Karte aller bekannten Aktionen
|
|
17
|
+
* ([posSettingsChanges] liefert sie bei jeder Tastenaenderung), sonst wirft
|
|
18
|
+
* der Aufruf vor dem Senden.
|
|
19
|
+
*/
|
|
15
20
|
export declare function setMyRegisterDeviceSettings(transport: InternerTransport, deviceId: string, device: Partial<PosDeviceSettings>): Promise<PosDeviceSettings>;
|
|
16
21
|
/** Eingabe von [setMyPosLogo]: ein Bild als Data-URL (PNG, JPEG, SVG) oder das Entfernen. */
|
|
17
22
|
export type SetMyPosLogoOptions = {
|
package/dist/esm/pos/client.js
CHANGED
|
@@ -50,7 +50,12 @@ export async function setMyPosSettings(transport, business) {
|
|
|
50
50
|
const daten = await transport(name, { business });
|
|
51
51
|
return mergePosSettings(POS_BUSINESS_DEFAULTS, objekt(daten?.business));
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Geraete-Einstellungen schreiben (Recht `layout`); liefert den gemischten
|
|
55
|
+
* Stand. `shortcuts` nur als ganze Karte aller bekannten Aktionen
|
|
56
|
+
* ([posSettingsChanges] liefert sie bei jeder Tastenaenderung), sonst wirft
|
|
57
|
+
* der Aufruf vor dem Senden.
|
|
58
|
+
*/
|
|
54
59
|
export async function setMyRegisterDeviceSettings(transport, deviceId, device) {
|
|
55
60
|
const name = 'setMyRegisterDeviceSettings';
|
|
56
61
|
if (typeof deviceId !== 'string' || deviceId.trim() === '') {
|
|
@@ -70,8 +75,15 @@ export async function setMyRegisterDeviceSettings(transport, deviceId, device) {
|
|
|
70
75
|
throw new KasseneckValidationError(name, `device.shortcuts.${aktion}: unbekannte Aktion`, 'request');
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
|
-
//
|
|
74
|
-
//
|
|
78
|
+
// Nur die ganze Karte: der Server prueft Doppelbelegungen nur in der
|
|
79
|
+
// gesendeten Karte, eine halbe (`{cash: ['Mod+K']}`) liesse eine
|
|
80
|
+
// Doppelbelegung mit einer gespeicherten Taste durch. posSettingsChanges
|
|
81
|
+
// liefert bei jeder Tastenaenderung die ganze Karte.
|
|
82
|
+
const fehlt = POS_SHORTCUT_ACTIONS.filter((a) => karte[a] === undefined);
|
|
83
|
+
if (fehlt.length > 0) {
|
|
84
|
+
throw new KasseneckValidationError(name, `device.shortcuts: ganze Karte senden (posSettingsChanges), es fehlen ${fehlt.join(', ')}`, 'request');
|
|
85
|
+
}
|
|
86
|
+
// Doppelbelegung in der ganzen Karte, also in der ganzen Belegung.
|
|
75
87
|
const konflikt = posShortcutConflict(karte);
|
|
76
88
|
if (konflikt) {
|
|
77
89
|
throw new KasseneckValidationError(name, `device.shortcuts.${konflikt.action}: Taste ${konflikt.key} schon belegt (${konflikt.heldBy})`, 'request');
|
package/dist/esm/pos/drucker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KasseneckValidationError } from '../client/errors.js';
|
|
1
2
|
import { rasterRowsBase64 } from '../printing/index.js';
|
|
2
3
|
/** Stand eines Druckjobs (Katalog `DRUCKJOB`). */
|
|
3
4
|
export const PRINT_JOB_STATUSES = ['pending', 'sent', 'printed', 'failed', 'expired'];
|
|
@@ -22,9 +23,27 @@ const STATUS = new Set(PRINT_JOB_STATUSES);
|
|
|
22
23
|
function status(v) {
|
|
23
24
|
return typeof v === 'string' && STATUS.has(v) ? v : 'unknown';
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Die Kennung eines Jobs aus der Antwort. Ohne sie kann niemand den Job
|
|
28
|
+
* abfragen; ein leerer Text waere ein angeblich angelegter Job, und der
|
|
29
|
+
* Kassier druckte ein zweites Mal.
|
|
30
|
+
*/
|
|
31
|
+
function jobKennung(name, d) {
|
|
32
|
+
const id = d?.jobId;
|
|
33
|
+
if (typeof id !== 'string' || id === '') {
|
|
34
|
+
throw new KasseneckValidationError(name, 'Antwort enthaelt keine Kennung (data.jobId fehlt)', 'response');
|
|
35
|
+
}
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
25
38
|
export async function listMyPrinters(transport) {
|
|
26
39
|
const daten = await transport('listMyPrinters', {});
|
|
27
|
-
|
|
40
|
+
const liste = daten?.printers;
|
|
41
|
+
if (!Array.isArray(liste)) {
|
|
42
|
+
// Keine Liste ist etwas anderes als eine leere Liste: „noch kein Drucker“
|
|
43
|
+
// darf nicht aussehen wie „Antwort kaputt“.
|
|
44
|
+
throw new KasseneckValidationError('listMyPrinters', 'Antwort enthaelt keine Liste (data.printers fehlt)', 'response');
|
|
45
|
+
}
|
|
46
|
+
return liste.map((r) => {
|
|
28
47
|
const d = (r ?? {});
|
|
29
48
|
const e = d.lastResult && typeof d.lastResult === 'object' ? d.lastResult : null;
|
|
30
49
|
return {
|
|
@@ -53,7 +72,7 @@ export async function createPrintJob(transport, o) {
|
|
|
53
72
|
if (o.brandMark === true)
|
|
54
73
|
params.brand = true;
|
|
55
74
|
const daten = await transport('createPrintJob', params);
|
|
56
|
-
return { jobId:
|
|
75
|
+
return { jobId: jobKennung('createPrintJob', daten), status: status(daten?.status), result: null };
|
|
57
76
|
}
|
|
58
77
|
/**
|
|
59
78
|
* Stand eines Druckjobs abfragen. Der Aufrufer fragt, bis [isPrintJobFinished]
|
|
@@ -64,7 +83,7 @@ export async function getPrintJob(transport, o) {
|
|
|
64
83
|
const d = await transport('getPrintJob', { printerId: o.printerId, jobId: o.jobId });
|
|
65
84
|
const e = d?.result && typeof d.result === 'object' ? d.result : null;
|
|
66
85
|
return {
|
|
67
|
-
jobId:
|
|
86
|
+
jobId: jobKennung('getPrintJob', d), status: status(d?.status),
|
|
68
87
|
createdAt: zahl(d?.createdAt), sentAt: zahl(d?.sentAt),
|
|
69
88
|
result: e ? { success: e.success === true, code: text(e.code), status: text(e.status), at: zahl(e.at) ?? undefined } : null,
|
|
70
89
|
};
|
package/dist/esm/version.d.ts
CHANGED
package/dist/esm/version.js
CHANGED
package/fixtures/pos-texts.json
CHANGED
package/fixtures/surface.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kreiseck/kasseneck-api",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.4",
|
|
4
4
|
"description": "Austrian fiscal cash register (RKSV) client for JavaScript and TypeScript: signed receipts, cancellations, card payments, receipt printing (ESC/POS), invoices. RKSV-Registrierkasse für JavaScript und TypeScript.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Kreiseck",
|