@kreiseck/kasseneck-api 1.0.0-rc.3 → 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 CHANGED
@@ -147,6 +147,21 @@ an upgrade as long as `/v1` is served.
147
147
  again. `setMyRegisterDeviceSettings` takes `shortcuts` only as the whole map
148
148
  of known actions (as `posSettingsChanges` produces it): the server checks
149
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`.
150
165
  - **Strict validation before sending.** The 0.x payment fields
151
166
  (`paymentMethod`, `paymentMethodFromServer`, `creditCardProvider`,
152
167
  `cardPaymentId`, `cardPaymentData`) throw, also from plain JavaScript.
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 no operation with an effect is left open: no
431
- receipt was signed, nothing went to FinanzOnline and no money moved, so
432
- retrying the same request does not create a second receipt or charge (whether
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
- rejection of `hobexRefund` throws a `KasseneckApiError` with its `code`; it
456
- never returns `false`, because a `false` on an unclear outcome invites a
457
- second refund.
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
@@ -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'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
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
- function ausgangAusCode(code, details) {
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'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
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;
@@ -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) kann die Karte belastet sein: den Stand ueber die
51
- * Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
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] mit Code, Netz, Zeitlimit, HTTP 5xx und eine unlesbare
66
- * Erfolgsantwort mit `outcome: 'unknown'` (die Erstattung kann gelaufen sein).
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) kann die Karte belastet sein: den Stand ueber die
47
- * Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
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] mit Code, Netz, Zeitlimit, HTTP 5xx und eine unlesbare
73
- * Erfolgsantwort mit `outcome: 'unknown'` (die Erstattung kann gelaufen sein).
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) kann der Einzug gelaufen sein: den Stand nachlesen.
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) kann der Einzug gelaufen sein: den Stand nachlesen.
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()) {
@@ -4,4 +4,4 @@
4
4
  * Client-Version, Nachtrag §6). Ein Test haelt sie gleich mit `package.json`;
5
5
  * beim Anheben der Version beide Stellen aendern.
6
6
  */
7
- export declare const PACKAGE_VERSION = "1.0.0-rc.3";
7
+ export declare const PACKAGE_VERSION = "1.0.0-rc.4";
@@ -7,4 +7,4 @@ exports.PACKAGE_VERSION = void 0;
7
7
  * Client-Version, Nachtrag §6). Ein Test haelt sie gleich mit `package.json`;
8
8
  * beim Anheben der Version beide Stellen aendern.
9
9
  */
10
- exports.PACKAGE_VERSION = '1.0.0-rc.3';
10
+ exports.PACKAGE_VERSION = '1.0.0-rc.4';
@@ -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'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
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
- function ausgangAusCode(code, details) {
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'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
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 = {
@@ -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) kann die Karte belastet sein: den Stand ueber die
51
- * Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
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] mit Code, Netz, Zeitlimit, HTTP 5xx und eine unlesbare
66
- * Erfolgsantwort mit `outcome: 'unknown'` (die Erstattung kann gelaufen sein).
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) kann die Karte belastet sein: den Stand ueber die
42
- * Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
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] mit Code, Netz, Zeitlimit, HTTP 5xx und eine unlesbare
68
- * Erfolgsantwort mit `outcome: 'unknown'` (die Erstattung kann gelaufen sein).
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) kann der Einzug gelaufen sein: den Stand nachlesen.
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) kann der Einzug gelaufen sein: den Stand nachlesen.
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()) {
@@ -4,4 +4,4 @@
4
4
  * Client-Version, Nachtrag §6). Ein Test haelt sie gleich mit `package.json`;
5
5
  * beim Anheben der Version beide Stellen aendern.
6
6
  */
7
- export declare const PACKAGE_VERSION = "1.0.0-rc.3";
7
+ export declare const PACKAGE_VERSION = "1.0.0-rc.4";
@@ -4,4 +4,4 @@
4
4
  * Client-Version, Nachtrag §6). Ein Test haelt sie gleich mit `package.json`;
5
5
  * beim Anheben der Version beide Stellen aendern.
6
6
  */
7
- export const PACKAGE_VERSION = '1.0.0-rc.3';
7
+ export const PACKAGE_VERSION = '1.0.0-rc.4';
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0-rc.3",
2
+ "version": "1.0.0-rc.4",
3
3
  "measuredOn": {
4
4
  "tid": "3600335",
5
5
  "hpsVersion": "1.10.0",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "title": "Kasseneck Rechnungs-API",
4
4
  "version": 2,
5
- "package": "1.0.0-rc.3",
5
+ "package": "1.0.0-rc.4",
6
6
  "endpoints": {
7
7
  "createCustomer": {
8
8
  "request": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0-rc.3",
2
+ "version": "1.0.0-rc.4",
3
3
  "languages": [
4
4
  "de",
5
5
  "en"
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0-rc.3",
2
+ "version": "1.0.0-rc.4",
3
3
  "messages": {
4
4
  "network.no_connection": {
5
5
  "text": "Keine Verbindung zum Server. Bitte die Internetverbindung prüfen und erneut versuchen."
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0-rc.3",
2
+ "version": "1.0.0-rc.4",
3
3
  "baseUrls": {
4
4
  "public": "https://api.kasseneck.at/v3",
5
5
  "pos": "https://kasse.kasseneck.at/api/v3"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kreiseck/kasseneck-api",
3
- "version": "1.0.0-rc.3",
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",