@kreiseck/kasseneck-api 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/cjs/kasse/settings.d.ts +10 -2
  3. package/dist/cjs/kasse/settings.js +10 -2
  4. package/dist/cjs/payments/hobex-hps/index.d.ts +2 -2
  5. package/dist/cjs/payments/hobex-hps/index.js +36 -1
  6. package/dist/cjs/payments/hobex-hps/outcome.d.ts +20 -1
  7. package/dist/cjs/payments/hobex-hps/outcome.js +11 -0
  8. package/dist/cjs/payments/hobex-hps/payments.d.ts +19 -0
  9. package/dist/cjs/payments/hobex-hps/payments.js +160 -33
  10. package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +180 -31
  11. package/dist/cjs/payments/hobex-hps/transaction-response.js +308 -88
  12. package/dist/cjs/payments/index.d.ts +1 -1
  13. package/dist/cjs/payments/index.js +8 -1
  14. package/dist/esm/kasse/settings.d.ts +10 -2
  15. package/dist/esm/kasse/settings.js +10 -2
  16. package/dist/esm/payments/hobex-hps/index.d.ts +2 -2
  17. package/dist/esm/payments/hobex-hps/index.js +2 -2
  18. package/dist/esm/payments/hobex-hps/outcome.d.ts +20 -1
  19. package/dist/esm/payments/hobex-hps/outcome.js +10 -0
  20. package/dist/esm/payments/hobex-hps/payments.d.ts +19 -0
  21. package/dist/esm/payments/hobex-hps/payments.js +161 -34
  22. package/dist/esm/payments/hobex-hps/transaction-response.d.ts +180 -31
  23. package/dist/esm/payments/hobex-hps/transaction-response.js +302 -87
  24. package/dist/esm/payments/index.d.ts +1 -1
  25. package/dist/esm/payments/index.js +1 -1
  26. package/fixtures/hobex-hps-codes.json +365 -15
  27. package/fixtures/kasse-settings-standard.json +1 -1
  28. package/fixtures/kasse-texte.json +1 -1
  29. package/fixtures/oberflaeche.json +2 -1
  30. package/package.json +1 -1
@@ -59,7 +59,34 @@ function createHpsPayments(client, target, options = {}) {
59
59
  return null;
60
60
  steps.push('Terminal beschaeftigt (HTTP 409) -- die Anfrage wurde nicht angenommen, es ist nichts geschehen');
61
61
  emit('resolved', steps[steps.length - 1], id);
62
- return { outcome: 'declined', transactionId: id, steps: [...steps] };
62
+ return { outcome: 'declined', transactionId: id, reason: 'terminalBusy', steps: [...steps] };
63
+ }
64
+ /**
65
+ * Benennt einen Code mit `effect: 'hostUncertain'` fuer den Nachweis: was
66
+ * das Terminal meldet, mit Code und hobex-Titel. Die Folgerung setzt der
67
+ * Aufrufer dazu.
68
+ */
69
+ function stoerung(res) {
70
+ const info = (0, transaction_response_js_1.hpsCodeInfo)(res.responseCode);
71
+ const was = info.reason === 'internalError'
72
+ ? 'interner Fehler des Terminals'
73
+ : 'Stoerung zwischen Terminal und hobex-Host, das Terminal storniert nicht selbst';
74
+ return `Terminal meldet ${was} (${info.code} "${info.title}")`;
75
+ }
76
+ /**
77
+ * Der Grund eines offenen Ausgangs. [antwort] ist die Antwort auf die
78
+ * ERZEUGENDE Anfrage (oder die zuerst gemeldete Stoerung), [letzte] die
79
+ * zuletzt gelesene Statusabfrage. Eine Stoerung beim Host geht vor; sonst
80
+ * erklaert der Code der erzeugenden Anfrage mehr als ein `9027` danach.
81
+ */
82
+ function offenerGrund(antwort, letzte) {
83
+ if (antwort && (0, transaction_response_js_1.isHostUncertain)(antwort))
84
+ return (0, transaction_response_js_1.hpsCodeReason)(antwort.responseCode);
85
+ if (letzte && (0, transaction_response_js_1.isHostUncertain)(letzte))
86
+ return (0, transaction_response_js_1.hpsCodeReason)(letzte.responseCode);
87
+ if (antwort?.responseCode !== undefined)
88
+ return (0, transaction_response_js_1.hpsCodeReason)(antwort.responseCode);
89
+ return (0, transaction_response_js_1.hpsCodeReason)(letzte?.responseCode);
63
90
  }
64
91
  /** Verlaufseintrag fuer eine direkte Antwort, die den Ausgang NICHT festschreibt. */
65
92
  function offeneAntwort(res) {
@@ -69,6 +96,12 @@ function createHpsPayments(client, target, options = {}) {
69
96
  if ((0, transaction_response_js_1.isTechnicalError)(res)) {
70
97
  return `Antwort mit technischem Fehler (${transaction_response_js_1.TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang, Ausgang wird geklaert`;
71
98
  }
99
+ if ((0, transaction_response_js_1.isHostUncertain)(res)) {
100
+ return `${stoerung(res)} -- ob belastet wurde, weiss das Terminal nicht, Ausgang wird geklaert`;
101
+ }
102
+ if (res.responseCode === transaction_response_js_1.NOT_FOUND_CODE) {
103
+ return `Terminal kennt den Vorgang nicht (${res.responseCode} "Not Found") -- keine Aussage, Ausgang wird geklaert`;
104
+ }
72
105
  if ((0, transaction_response_js_1.isUnknownCode)(res)) {
73
106
  return `Terminal nennt einen unbekannten Code (${res.responseCode})${klartext(res)} -- Ausgang wird geklaert`;
74
107
  }
@@ -82,6 +115,22 @@ function createHpsPayments(client, target, options = {}) {
82
115
  if ((0, transaction_response_js_1.isTechnicalError)(status)) {
83
116
  return `Status: technischer Fehler (${transaction_response_js_1.TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang`;
84
117
  }
118
+ if ((0, transaction_response_js_1.isHostUncertain)(status)) {
119
+ return `Status: ${stoerung(status)} -- keine Aussage`;
120
+ }
121
+ // Bewusst NICHT "keine Auskunft": das Wort steht fuer das gemessene 9027,
122
+ // und Aufrufer lesen es als solches (sastre, OpenCardPaymentService).
123
+ if (status.responseCode === transaction_response_js_1.NOT_FOUND_CODE) {
124
+ return `Status: Vorgang nicht gefunden (${status.responseCode}) -- keine Aussage`;
125
+ }
126
+ const info = (0, transaction_response_js_1.hpsCodeInfo)(status.responseCode);
127
+ if (info?.rejectsRequest) {
128
+ return `Status: Abfrage abgewiesen (${info.code} "${info.title}") -- keine Aussage ueber den Vorgang`;
129
+ }
130
+ if (info?.conclusive && !(0, transaction_response_js_1.isApproved)(status)) {
131
+ // Nur erreichbar nach einer Stoerung beim Host, siehe [resolve].
132
+ return `Status: abgelehnt (${info.code} "${info.title}") -- nach der Stoerung beim hobex-Host entscheidet nur eine Genehmigung`;
133
+ }
85
134
  if ((0, transaction_response_js_1.isUnknownCode)(status)) {
86
135
  return `Status: unbekannter Code (${status.responseCode})${klartext(status)} -- keine Aussage`;
87
136
  }
@@ -98,14 +147,27 @@ function createHpsPayments(client, target, options = {}) {
98
147
  const text = res.responseText?.trim();
99
148
  return text ? ` "${text}"` : '';
100
149
  }
101
- /** Ordnet eine Terminal-Antwort ein. `null`, wenn sie nichts entscheidet. */
102
- function fromResponse(res, id, steps) {
150
+ /**
151
+ * Ordnet eine Terminal-Antwort ein. `null`, wenn sie nichts entscheidet.
152
+ *
153
+ * [aufhebung]: die Antwort gehoert zu [cancel]. Ein `'0'` heisst dort
154
+ * "aufgehoben", und der Grund ist `'canceled'` statt `'approved'`.
155
+ */
156
+ function fromResponse(res, id, steps, aufhebung = false) {
103
157
  if (!(0, transaction_response_js_1.isConclusive)(res))
104
158
  return null;
105
159
  const approved = res.responseCode === '0';
106
- steps.push(approved ? 'Terminal: genehmigt' : `Terminal: abgelehnt (${res.responseCode})`);
160
+ steps.push(approved
161
+ ? 'Terminal: genehmigt'
162
+ : `Terminal: abgelehnt (${res.responseCode} "${(0, transaction_response_js_1.hpsCodeInfo)(res.responseCode).title}")`);
107
163
  emit('resolved', steps[steps.length - 1], id);
108
- return { outcome: approved ? 'approved' : 'declined', transactionId: id, response: res, steps: [...steps] };
164
+ return {
165
+ outcome: approved ? 'approved' : 'declined',
166
+ transactionId: id,
167
+ response: res,
168
+ reason: approved && aufhebung ? 'canceled' : (0, transaction_response_js_1.hpsCodeReason)(res.responseCode),
169
+ steps: [...steps],
170
+ };
109
171
  }
110
172
  /**
111
173
  * Ordnet die DIREKTE Antwort auf einen Aufhebungs-Request ein. Fast dasselbe
@@ -131,7 +193,7 @@ function createHpsPayments(client, target, options = {}) {
131
193
  steps.push(`Aufhebung mit ${transaction_response_js_1.TRANSACTION_CANCELED_CODE} beantwortet -- mehrdeutig, der Zustand der Originalzahlung wird abgefragt`);
132
194
  return null;
133
195
  }
134
- return fromResponse(res, id, steps);
196
+ return fromResponse(res, id, steps, true);
135
197
  }
136
198
  /**
137
199
  * Ordnet die Statusabfrage einer OFFENEN AUFHEBUNG ein. `null`, wenn sie
@@ -186,7 +248,7 @@ function createHpsPayments(client, target, options = {}) {
186
248
  if (voided) {
187
249
  steps.push(`Terminal: Aufhebung bestaetigt (${status.responseCode ?? status.state})`);
188
250
  emit('resolved', steps[steps.length - 1], id);
189
- return { outcome: 'approved', transactionId: id, response: status, steps: [...steps] };
251
+ return { outcome: 'approved', transactionId: id, response: status, reason: 'canceled', steps: [...steps] };
190
252
  }
191
253
  if ((0, transaction_response_js_1.isApproved)(status)) {
192
254
  if (firstQuery) {
@@ -202,14 +264,15 @@ function createHpsPayments(client, target, options = {}) {
202
264
  /**
203
265
  * [letzteAntwort] ist die letzte Antwort, die das Terminal in dieser
204
266
  * Klaerung gab -- als `lastResponse` fuer Anzeige und Katalog,
205
- * ausdruecklich NICHT als `response`.
267
+ * ausdruecklich NICHT als `response`. [grund] siehe [offenerGrund].
206
268
  */
207
- function open(id, steps, letzteAntwort) {
269
+ function open(id, steps, letzteAntwort, grund) {
208
270
  emit('resolved', steps[steps.length - 1], id);
209
271
  return {
210
272
  outcome: 'unresolved',
211
273
  transactionId: id,
212
274
  ...(letzteAntwort ? { lastResponse: letzteAntwort } : {}),
275
+ ...(grund ? { reason: grund } : {}),
213
276
  steps: [...steps],
214
277
  };
215
278
  }
@@ -274,26 +337,46 @@ function createHpsPayments(client, target, options = {}) {
274
337
  }
275
338
  steps.push('Abbruch bestaetigt -- der Vorgang war noch abbrechbar, es ist nichts belastet');
276
339
  emit('resolved', steps[steps.length - 1], id);
277
- return { outcome: 'declined', transactionId: id, response: res, steps: [...steps] };
340
+ return { outcome: 'declined', transactionId: id, response: res, reason: 'aborted', steps: [...steps] };
278
341
  }
279
- /** Klaert einen offenen Ausgang: erst abbrechen, dann abfragen -- bis das Terminal etwas sagt oder das Budget aufgebraucht ist. */
280
342
  /**
281
- * `antwortMitCode` heisst: das Terminal hat auf die ERZEUGENDE Anfrage eine
282
- * Antwort MIT Ergebniscode geliefert, deren Bedeutung wir nur nicht kennen.
283
- * Dann ist der Vorgang am Geraet abgeschlossen -- und erst dadurch bekommt
284
- * [NO_STATEMENT_CODE] (`9027`) beim Pollen einen Aussagewert, den es sonst
285
- * nicht hat. Siehe [ausGeschlossenerAntwort].
343
+ * Klaert einen offenen Ausgang: erst abbrechen, dann abfragen -- bis das
344
+ * Terminal etwas sagt oder das Budget aufgebraucht ist.
345
+ *
346
+ * [antwort] ist die Antwort auf die ERZEUGENDE Anfrage, sofern eine kam.
347
+ * Traegt sie einen Ergebniscode, dessen Bedeutung wir nur nicht kennen, ist
348
+ * der Vorgang am Geraet abgeschlossen -- und erst dadurch bekommt
349
+ * `9027` beim Pollen einen Aussagewert, den es sonst nicht hat. Siehe
350
+ * [ausGeschlossenerAntwort]. Meldet sie eine Stoerung beim Host
351
+ * ([isHostUncertain]), bekommt `9027` diesen Aussagewert gerade NICHT --
352
+ * siehe Klassendoku, "Stoerung beim Host".
286
353
  */
287
- async function resolve(id, steps, antwortMitCode = false, letzteAntwort) {
354
+ async function resolve(id, steps, antwort) {
288
355
  emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
289
356
  const start = now();
290
357
  const elapsedMs = () => now() - start;
291
- const aborted = await tryAbort(id, steps, elapsedMs);
292
- if (aborted)
293
- return aborted;
358
+ const antwortMitCode = antwort?.responseCode !== undefined;
359
+ // Einmal gemeldet, bleibt die Stoerung stehen -- auch wenn erst die
360
+ // Statusabfrage sie nennt und danach 9027 kommt.
361
+ let stoerungsAntwort = antwort && (0, transaction_response_js_1.isHostUncertain)(antwort) ? antwort : undefined;
362
+ let letzteAntwort = antwort;
363
+ if (stoerungsAntwort === undefined) {
364
+ const aborted = await tryAbort(id, steps, elapsedMs);
365
+ if (aborted)
366
+ return aborted;
367
+ }
368
+ else {
369
+ // Der Vorgang ist am Terminal schon beendet -- mit einer Stoerung beim
370
+ // Host. Ein quittierter Abbruch bewiese nur, dass am Terminal nichts mehr
371
+ // laeuft, nicht, dass der Host nichts belastet hat.
372
+ steps.push('Kein Abbruchversuch -- das Terminal hat den Vorgang mit einer Stoerung beim hobex-Host beendet');
373
+ }
294
374
  let wait = 0;
295
375
  let transportFailures = 0;
296
376
  let ohneAuskunft = 0;
377
+ // Beantwortete Statusabfragen in Folge, die zu einem Vorgang mit
378
+ // Host-Stoerung nichts Neues sagen.
379
+ let stoerungOhneNeues = 0;
297
380
  while (elapsedMs() < resolveBudgetMs) {
298
381
  if (wait > 0) {
299
382
  const left = resolveBudgetMs - elapsedMs();
@@ -318,23 +401,45 @@ function createHpsPayments(client, target, options = {}) {
318
401
  wait = nextWait(wait);
319
402
  continue;
320
403
  }
321
- const settled = fromResponse(status, id, steps);
322
- if (settled)
323
- return settled;
404
+ if ((0, transaction_response_js_1.isHostUncertain)(status))
405
+ stoerungsAntwort ??= status;
406
+ const hostUngewiss = stoerungsAntwort !== undefined;
407
+ // Auf die Statusabfrage entscheidet nur ein Code, der den gesuchten
408
+ // Vorgang beschreibt -- nicht einer, der diese Abfrage abweist
409
+ // ([isConclusiveAsStatus]). Nach einer Stoerung beim Host entscheidet nur
410
+ // noch eine Genehmigung: jede andere Aussage des Terminals betrifft seinen
411
+ // Speicher, nicht den des Hosts.
412
+ if (hostUngewiss ? (0, transaction_response_js_1.isApproved)(status) : (0, transaction_response_js_1.isConclusiveAsStatus)(status)) {
413
+ const settled = fromResponse(status, id, steps);
414
+ if (settled)
415
+ return settled;
416
+ }
324
417
  if ((0, transaction_response_js_1.isNoStatement)(status)) {
325
418
  ohneAuskunft += 1;
326
- const geklaert = ausGeschlossenerAntwort(id, steps, status, antwortMitCode, ohneAuskunft);
327
- if (geklaert)
328
- return geklaert;
419
+ if (!hostUngewiss) {
420
+ const geklaert = ausGeschlossenerAntwort(id, steps, status, antwortMitCode ? antwort : undefined, ohneAuskunft);
421
+ if (geklaert)
422
+ return geklaert;
423
+ }
329
424
  }
330
425
  else {
331
426
  ohneAuskunft = 0;
332
427
  }
428
+ // Nach einer Stoerung ist jede Antwort ausser '0' (die oben schon
429
+ // entschieden hat) "nichts Neues".
430
+ stoerungOhneNeues = hostUngewiss ? stoerungOhneNeues + 1 : 0;
333
431
  steps.push(statusOhneErgebnis(status) ?? 'Status: noch kein Ergebniscode');
432
+ if (stoerungOhneNeues >= 2) {
433
+ // Siehe Klassendoku, "Stoerung beim Host": das Terminal weiss es
434
+ // nicht, und es wird es auch nicht wissen, wenn wir weiterfragen.
435
+ steps.push('Statusabfrage zweimal ohne Neues nach einer Stoerung beim hobex-Host -- '
436
+ + 'das Terminal kann nicht sagen, ob belastet wurde, Ausgang bleibt offen');
437
+ return open(id, steps, letzteAntwort, offenerGrund(stoerungsAntwort ?? antwort, letzteAntwort));
438
+ }
334
439
  wait = nextWait(wait);
335
440
  }
336
441
  steps.push('Ausgang bleibt offen');
337
- return open(id, steps, letzteAntwort);
442
+ return open(id, steps, letzteAntwort, offenerGrund(stoerungsAntwort ?? antwort, letzteAntwort));
338
443
  }
339
444
  /**
340
445
  * Liest `9027` beim Pollen als "nicht genehmigt" -- aber NUR, wenn das
@@ -359,10 +464,15 @@ function createHpsPayments(client, target, options = {}) {
359
464
  * entstuende die Doppelbelastung vom 24.08.2026 an einer neuen Stelle.
360
465
  * Dieselbe Absicherung traegt bereits [fromCancelStatus].
361
466
  *
467
+ * [antwort] ist die Antwort MIT Code auf die erzeugende Anfrage -- oder
468
+ * `undefined`, wenn keine kam; dann greift die Regel nicht. Ihr Code ergibt
469
+ * den `reason`: er erklaert den Ausgang, das `9027` danach nicht. Der
470
+ * Aufrufer ruft diese Regel NICHT fuer eine Stoerung beim Host.
471
+ *
362
472
  * `undefined` heisst: nicht entschieden, weiter pollen.
363
473
  */
364
- function ausGeschlossenerAntwort(id, steps, status, antwortMitCode, ohneAuskunft) {
365
- if (!antwortMitCode)
474
+ function ausGeschlossenerAntwort(id, steps, status, antwort, ohneAuskunft) {
475
+ if (!antwort)
366
476
  return undefined;
367
477
  if (ohneAuskunft < 2)
368
478
  return undefined;
@@ -374,6 +484,7 @@ function createHpsPayments(client, target, options = {}) {
374
484
  outcome: 'declined',
375
485
  transactionId: id,
376
486
  response: status,
487
+ reason: (0, transaction_response_js_1.hpsCodeReason)(antwort.responseCode),
377
488
  steps: [...steps],
378
489
  };
379
490
  }
@@ -393,11 +504,17 @@ function createHpsPayments(client, target, options = {}) {
393
504
  *
394
505
  * Budget, Backoff und Transportfehler-Deckelung sind unveraendert aus
395
506
  * [resolve] uebernommen.
507
+ *
508
+ * [antwort] ist die direkte Antwort auf den Aufhebungs-Request, sofern eine
509
+ * kam. Meldete sie eine Stoerung beim Host, entscheidet ein unveraendertes
510
+ * `'0'` nichts -- siehe Klassendoku, "Stoerung beim Host".
396
511
  */
397
- async function resolveCancel(id, steps, letzteAntwort) {
512
+ async function resolveCancel(id, steps, antwort) {
398
513
  emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
399
514
  const start = now();
400
515
  const elapsedMs = () => now() - start;
516
+ const hostUngewiss = antwort !== undefined && (0, transaction_response_js_1.isHostUncertain)(antwort);
517
+ let letzteAntwort = antwort;
401
518
  let wait = 0;
402
519
  let transportFailures = 0;
403
520
  // Zaehlt nur BEANTWORTETE Statusabfragen -- Grundlage der Karenz fuer den
@@ -428,6 +545,16 @@ function createHpsPayments(client, target, options = {}) {
428
545
  wait = nextWait(wait);
429
546
  continue;
430
547
  }
548
+ if (hostUngewiss && (0, transaction_response_js_1.isApproved)(status) && answeredQueries >= 2) {
549
+ // Siehe Klassendoku, "Stoerung beim Host": das unveraenderte '0'
550
+ // spiegelt nur den Speicher des Terminals. "Hat nicht gegriffen"
551
+ // fuehrte nach dem Tagesabschluss zu einer Rueckerstattung, die der
552
+ // Kunde doppelt bekaeme, falls der Host die Aufhebung doch verbucht hat.
553
+ steps.push('Terminal: Originalzahlung steht unveraendert (0), aber die Aufhebung endete mit einer '
554
+ + 'Stoerung beim hobex-Host -- ob sie dort gewirkt hat, kann das Terminal nicht sagen, '
555
+ + 'Ausgang bleibt offen');
556
+ return open(id, steps, letzteAntwort, offenerGrund(antwort, letzteAntwort));
557
+ }
431
558
  const settled = fromCancelStatus(status, id, steps, answeredQueries === 1);
432
559
  if (settled)
433
560
  return settled;
@@ -439,7 +566,7 @@ function createHpsPayments(client, target, options = {}) {
439
566
  wait = nextWait(wait);
440
567
  }
441
568
  steps.push('Ausgang bleibt offen');
442
- return open(id, steps, letzteAntwort);
569
+ return open(id, steps, letzteAntwort, offenerGrund(antwort, letzteAntwort));
443
570
  }
444
571
  async function pay(paymentOptions) {
445
572
  const id = paymentOptions.transactionId ?? (0, transaction_id_js_1.newHpsTransactionId)();
@@ -478,7 +605,7 @@ function createHpsPayments(client, target, options = {}) {
478
605
  return settled;
479
606
  steps.push(offeneAntwort(res));
480
607
  }
481
- return resolve(id, steps, res?.responseCode !== undefined, res);
608
+ return resolve(id, steps, res);
482
609
  }
483
610
  /**
484
611
  * Gutschrift mit geklaertem Ausgang -- EXAKT derselbe Klaerweg wie [pay]
@@ -522,7 +649,7 @@ function createHpsPayments(client, target, options = {}) {
522
649
  // Dieselbe Klaerfunktion wie [pay]: die Kennung ist die des NEUEN
523
650
  // Vorgangs, eine Statusabfrage darauf liefert also genau dessen Ausgang,
524
651
  // und der Abbruch ist derselbe Diskriminator wie bei einer Zahlung.
525
- return resolve(id, steps, res?.responseCode !== undefined, res);
652
+ return resolve(id, steps, res);
526
653
  }
527
654
  /**
528
655
  * Aufhebung (Storno/Void) einer bestehenden Zahlung mit geklaertem Ausgang.
@@ -5,7 +5,7 @@
5
5
  * ein, es reicht den Terminal-Rumpf roh durch — die Einordnung passiert hier.
6
6
  *
7
7
  * **Zwilling:** `kasseneck_api/lib/src/hobex_hps/transaction_response.dart`.
8
- * Beide Seiten pinnen dieselbe Codetabelle, siehe `HPS_MEASURED_CODES` unten
8
+ * Beide Seiten pinnen dieselbe Codetabelle, siehe `HPS_CODES` unten
9
9
  * und `fixtures/hobex-hps-codes.json`.
10
10
  *
11
11
  * **`responseCode !== '0'` ist NICHT die Pruefung auf eine Ablehnung.** Genau
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * [isConclusive] ist die einzige Stelle, an der ein Code zu einem Ausgang
20
20
  * wird — und sie ist eine ECHTE Positivliste: nur ein Code, dessen Bedeutung
21
- * GEMESSEN und in [HPS_MEASURED_CODES] benannt ist, zaehlt. Jeder andere —
21
+ * feststeht und in [HPS_CODES] benannt ist, zaehlt. Jeder andere —
22
22
  * auch ein neuer, heute noch unbekannter Code — ist eine Wissensluecke, siehe
23
23
  * [isUnknownCode]. Am 27.08.2026 hat der Dart-Zwilling gemessen, warum die
24
24
  * Gegenrichtung ("jeder Code ausser 9027 ist schluessig") gefaehrlich ist: ein
@@ -26,54 +26,121 @@
26
26
  * schluessig und haette eine Zahlung, unter der tatsaechlich Geld geflossen
27
27
  * sein kann, als `declined` gemeldet.
28
28
  */
29
- /** Ein Ergebniscode, dessen Bedeutung GEMESSEN und hier benannt ist. */
29
+ /** Wie ein Ergebniscode den Ausgang eines Vorgangs bestimmt. */
30
+ export type HpsCodeEffect =
31
+ /** Schreibt den Ausgang fest: `'0'` genehmigt, jeder andere abgelehnt. */
32
+ 'conclusive'
33
+ /** Gemessen oder dokumentiert, aber KEINE Aussage (9027, 9900, 100011). */
34
+ | 'noStatement'
35
+ /**
36
+ * Der Host war beteiligt, das Terminal storniert nicht selbst -- ob belastet
37
+ * wurde, weiss das Terminal nicht. Weiterklaeren, aber ohne dass ein
38
+ * spaeteres `9027` daraus "nichts belastet" machen darf (siehe `payments.ts`).
39
+ */
40
+ | 'hostUncertain';
41
+ /** Woher die Bedeutung eines Codes stammt. */
42
+ export type HpsCodeSource =
43
+ /** Am Geraet gemessen (`doc/kartenzahlung.md` im Dart-Zwilling). */
44
+ 'measured'
45
+ /** Aus der Antwortcodeliste von hobex (erhalten 11.09.2026). */
46
+ | 'documented'
47
+ /** Beides. */
48
+ | 'measuredAndDocumented';
49
+ /**
50
+ * Worauf eine Kasse reagiert -- der Grund hinter einem Ergebniscode. Mehrere
51
+ * Codes teilen sich einen Grund, wenn am Tresen dasselbe zu tun ist
52
+ * (`100004`, `100005`, `100012`: "Karte nicht gelesen, noch einmal"). Der Satz
53
+ * fuer den Bediener steht in [HPS_REASON_HINTS]; eine Kasse mit eigener
54
+ * Uebersetzung schluesselt ueber den Grund selbst.
55
+ */
56
+ export type HpsCodeReason = 'approved' | 'aborted' | 'noCard' | 'cardReadFailed' | 'cardDeclined' | 'wrongPin' | 'amountInvalid' | 'tipNotSelected' | 'terminalBusy' | 'terminalBlocked' | 'terminalSetup' | 'terminalFault' | 'requestRejected' | 'invalidTransaction' | 'refundPassword' | 'refundDisabled' | 'hostTimeoutReversed' | 'hostFault' | 'internalError' | 'canceled' | 'notAbortable' | 'noStatement' | 'technicalError' | 'unknown';
57
+ /** Der Satz fuer den Bediener je Grund, deutsch. */
58
+ export declare const HPS_REASON_HINTS: Readonly<Record<HpsCodeReason, string>>;
59
+ /**
60
+ * Die beiden Gruende hinter einem Code mit `effect: 'hostUncertain'`: ob Geld
61
+ * geflossen ist, weiss das Terminal nicht.
62
+ */
63
+ export declare function isHostUncertainReason(reason: HpsCodeReason | undefined): boolean;
64
+ /** Ein Ergebniscode, dessen Bedeutung feststeht (gemessen oder dokumentiert). */
30
65
  export interface HpsMeasuredCode {
31
66
  /** Der Ergebniscode, wie ihn das Terminal im Feld `responseCode` sendet. */
32
67
  readonly code: string;
33
- /** Bedeutung, wie gemessen — deutsch, ohne Umlaute (siehe Vorbild). */
68
+ /** Bedeutung — deutsch, ohne Umlaute (siehe Vorbild). */
34
69
  readonly meaning: string;
35
70
  /**
36
71
  * `true`: der Code schreibt einen Ausgang fest (Teil der Positivliste,
37
- * siehe [isConclusive]). `false`: gemessen und benannt, aber ausdruecklich
38
- * KEINE Aussage ueber den Vorgang (9027, 9900).
72
+ * siehe [isConclusive]). `false`: benannt, aber KEINE Aussage ueber den
73
+ * Vorgang -- oder ein ungewisser Host-Ausgang, siehe [HpsCode.effect].
39
74
  */
40
75
  readonly conclusive: boolean;
41
76
  }
77
+ /** Ein Eintrag der vollstaendigen Codetabelle [HPS_CODES]. */
78
+ export interface HpsCode extends HpsMeasuredCode {
79
+ /** Titel, wie hobex ihn fuehrt bzw. das Terminal als `responseText` sendet. */
80
+ readonly title: string;
81
+ readonly effect: HpsCodeEffect;
82
+ readonly reason: HpsCodeReason;
83
+ readonly source: HpsCodeSource;
84
+ /**
85
+ * Der Code weist die ANFRAGE selbst ab (TID, Form, Geraetezustand). Auf eine
86
+ * Zahlung ist das deren Ablehnung; auf eine STATUSABFRAGE heisst es nur, dass
87
+ * diese Abfrage nicht bedient wurde -- ueber den gesuchten Vorgang sagt es
88
+ * nichts (gemessen fuer `100108`). Siehe [isConclusiveAsStatus].
89
+ */
90
+ readonly rejectsRequest: boolean;
91
+ }
42
92
  /**
43
- * Die gemessene Codetabelle — Vertrag mit dem Dart-Zwilling, siehe
44
- * `fixtures/hobex-hps-codes.json`. Gemessen an einem hobex-HPS (TID 3600335,
45
- * HPS 1.10.0, Firmware 7.3.6, 26.–28.08.2026).
93
+ * Die vollstaendige Codetabelle — Vertrag mit dem Dart-Zwilling
94
+ * (`HpsCodes.all` in `lib/src/hobex_hps/response_codes.dart`), ausgegeben als
95
+ * `fixtures/hobex-hps-codes.json`.
96
+ *
97
+ * Zwei Quellen: GEMESSEN an hobex-HPS-Geraeten (TID 3600335, HPS 1.10.0,
98
+ * Firmware 7.3.6, 26.–28.08.2026; TID 3556988 im Betrieb) und die
99
+ * Antwortcodeliste von hobex (erhalten 11.09.2026). Die Regel bleibt: nur ein
100
+ * Code mit feststehender Bedeutung schreibt einen Ausgang fest -- die Liste des
101
+ * Herstellers ist eine solche Feststellung, kein Raten aus der Codefamilie.
102
+ *
103
+ * Eingeordnet wird danach, WO im Ablauf ein dokumentierter Code entsteht:
104
+ * - vor dem Host (Anfrage, Karte, EMV-Kernel, Eingaben, Geraetezustand) ->
105
+ * `conclusive`, also `declined`;
106
+ * - `100029`, Zeitueberschreitung zum Host MIT auto-reversal -> ebenfalls
107
+ * `declined`, das Terminal storniert laut hobex selbst;
108
+ * - beim oder nach dem Host OHNE auto-reversal, dazu der Sammelcode `100999`
109
+ * -> `hostUncertain`.
46
110
  *
47
- * Reihenfolge ist die im Messprotokoll (`doc/kartenzahlung.md` im
48
- * Dart-Zwilling) — numerisch aufsteigend zu sortieren wuerde beim Diff
49
- * gegen die Vertragsdatei nichts gewinnen und macht Aenderungen schwerer
50
- * nachzuverfolgen.
111
+ * Reihenfolge: zuerst die gemessenen wie im Messprotokoll, dann die
112
+ * dokumentierten aufsteigend -- identisch mit dem Dart-Zwilling.
113
+ */
114
+ export declare const HPS_CODES: readonly HpsCode[];
115
+ /**
116
+ * @deprecated Seit 0.10.0 [HPS_CODES] -- die Tabelle fuehrt nicht mehr nur
117
+ * gemessene Codes. Gleicher Inhalt, bleibt fuer bestehende Aufrufer.
51
118
  */
52
119
  export declare const HPS_MEASURED_CODES: readonly HpsMeasuredCode[];
53
120
  /** `responseCode` einer genehmigten Zahlung. */
54
121
  export declare const APPROVED_CODE = "0";
55
- /** Siehe [HPS_MEASURED_CODES]: ungueltiger Vorgang, nichts passiert. */
122
+ /** Siehe [HPS_CODES]: ungueltiger Vorgang, nichts passiert. */
56
123
  export declare const INVALID_TRANSACTION_CODE = "9002";
57
- /** Siehe [HPS_MEASURED_CODES]: aufgehoben. */
124
+ /** Siehe [HPS_CODES]: aufgehoben. */
58
125
  export declare const TRANSACTION_CANCELED_CODE = "9011";
59
- /** Siehe [HPS_MEASURED_CODES]: keine Aussage. */
126
+ /** Siehe [HPS_CODES]: keine Aussage. */
60
127
  export declare const NO_STATEMENT_CODE = "9027";
61
- /** Siehe [HPS_MEASURED_CODES]: Kennung nicht numerisch, keine Aussage. */
128
+ /** Siehe [HPS_CODES]: Kennung nicht numerisch, keine Aussage. */
62
129
  export declare const TECHNICAL_ERROR_CODE = "9900";
63
- /** Siehe [HPS_MEASURED_CODES]: abgebrochen. */
130
+ /** Siehe [HPS_CODES]: abgebrochen. */
64
131
  export declare const ABORTED_CODE = "100002";
65
- /** Siehe [HPS_MEASURED_CODES]: Karte nicht aufgelegt. */
132
+ /** Siehe [HPS_CODES]: Karte nicht aufgelegt. */
66
133
  export declare const CARD_NOT_PRESENT_CODE = "100003";
67
- /** Siehe [HPS_MEASURED_CODES]: nicht mehr abbrechbar. */
134
+ /** Siehe [HPS_CODES]: nicht mehr abbrechbar. */
68
135
  export declare const NOT_ABORTABLE_CODE = "100010";
69
- /** Siehe [HPS_MEASURED_CODES]: Betrag abgewiesen, vor dem Kartenfluss. */
136
+ /** Siehe [HPS_CODES]: Betrag abgewiesen, vor dem Kartenfluss. */
70
137
  export declare const INVALID_AMOUNT_CODE = "9003";
71
- /** Siehe [HPS_MEASURED_CODES]: Betrag ausserhalb des zulaessigen Bereichs. */
138
+ /** Siehe [HPS_CODES]: Betrag ausserhalb des zulaessigen Bereichs. */
72
139
  export declare const AMOUNT_OUT_OF_RANGE_CODE = "100019";
73
- /** Siehe [HPS_MEASURED_CODES]: Terminal-Kennung unbekannt. */
140
+ /** Siehe [HPS_CODES]: Terminal-Kennung unbekannt. */
74
141
  export declare const INVALID_TID_CODE = "100108";
75
142
  /**
76
- * Siehe [HPS_MEASURED_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet.
143
+ * Siehe [HPS_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet.
77
144
  *
78
145
  * Zweistellig, weil ein Antwortcode des HOSTS (ISO 8583, 55 = "Incorrect
79
146
  * PIN"), kein `9xxx`-Terminalcode und kein `100xxx`-Code der HPS-Anwendung.
@@ -84,6 +151,58 @@ export declare const INVALID_TID_CODE = "100108";
84
151
  * dann nicht 9027 antwortet, sondern mit dem Code selbst.
85
152
  */
86
153
  export declare const WRONG_PIN_CODE = "55";
154
+ /** `100001` "Bad Request" -- nichts belastet. */
155
+ export declare const BAD_REQUEST_CODE = "100001";
156
+ /** `100004` "Card read failed" -- nichts belastet. Im Betrieb am 28.08.2026 gesehen. */
157
+ export declare const CARD_READ_FAILED_CODE = "100004";
158
+ /** `100005` "App select failed" -- nichts belastet. Im Betrieb am 28.08.2026 gesehen. */
159
+ export declare const APP_SELECT_FAILED_CODE = "100005";
160
+ /** `100006` "Communication with TecsXml failed" (No auto-reversal) -- ungewiss. */
161
+ export declare const HOST_COMMUNICATION_FAILED_CODE = "100006";
162
+ /** `100007` "Processing of TecsXml step failed" (No auto-reversal) -- ungewiss. */
163
+ export declare const HOST_STEP_FAILED_CODE = "100007";
164
+ /** `100008` "Invalid TID" laut hobex; gemessen wurde [INVALID_TID_CODE]. */
165
+ export declare const INVALID_TID_DOCUMENTED_CODE = "100008";
166
+ /** `100009` "Invalid Tx Type" -- nichts belastet. */
167
+ export declare const INVALID_TX_TYPE_CODE = "100009";
168
+ /** `100011` "Not Found" -- keine Aussage, aber ohne die Zwei-9027-Regel. */
169
+ export declare const NOT_FOUND_CODE = "100011";
170
+ /** `100012` "Max retries exceeded" -- nichts belastet. */
171
+ export declare const MAX_RETRIES_EXCEEDED_CODE = "100012";
172
+ /** `100013` "Diagnosis failed" -- nichts belastet. */
173
+ export declare const DIAGNOSIS_FAILED_CODE = "100013";
174
+ /** `100014` "Card information wasn't entered" (MOTO) -- nichts belastet. */
175
+ export declare const CARD_INFO_NOT_ENTERED_CODE = "100014";
176
+ /** `100015` "Card declined" (EMV-Kernel) -- nichts belastet. Im Betrieb am 28. und 31.08.2026 gesehen. */
177
+ export declare const CARD_DECLINED_CODE = "100015";
178
+ /** `100017` "Card Not Supported" -- nichts belastet. */
179
+ export declare const CARD_NOT_SUPPORTED_CODE = "100017";
180
+ /** `100018` "Scep enrollment failed" -- nichts belastet. */
181
+ export declare const SCEP_ENROLLMENT_FAILED_CODE = "100018";
182
+ /** `100020` "Refund password is invalid" -- nichts ausgezahlt. */
183
+ export declare const REFUND_PASSWORD_INVALID_CODE = "100020";
184
+ /** `100021` "Failed to enter the password" -- nichts ausgezahlt. */
185
+ export declare const PASSWORD_NOT_ENTERED_CODE = "100021";
186
+ /** `100022` "Terminal is blocked" -- nichts belastet. */
187
+ export declare const TERMINAL_BLOCKED_CODE = "100022";
188
+ /** `100023` "Invalid message type" -- ungewiss. */
189
+ export declare const INVALID_MESSAGE_TYPE_CODE = "100023";
190
+ /** `100024` "Transaction completion has failed" -- ungewiss. */
191
+ export declare const COMPLETION_FAILED_CODE = "100024";
192
+ /** `100025` "Refund transactions are disabled" -- nichts ausgezahlt. */
193
+ export declare const REFUND_DISABLED_CODE = "100025";
194
+ /** `100026` "Transaction was declined." (Chip-Daten fuer eine Karte ohne Chip) -- ungewiss. */
195
+ export declare const CHIP_DATA_MISMATCH_CODE = "100026";
196
+ /** `100027` "Unsupported UserData in TecsXml Response" -- ungewiss. */
197
+ export declare const UNSUPPORTED_USER_DATA_CODE = "100027";
198
+ /** `100028` "Tip selection process has failed." -- nichts belastet. */
199
+ export declare const TIP_SELECTION_FAILED_CODE = "100028";
200
+ /** `100029` "Communication with TecsXml timeout" (auto-reversal) -- nichts belastet. */
201
+ export declare const HOST_TIMEOUT_REVERSED_CODE = "100029";
202
+ /** `100998` "Terminal is busy" -- nichts belastet; gemessen als HTTP [TERMINAL_BUSY_HTTP_STATUS]. */
203
+ export declare const TERMINAL_BUSY_CODE = "100998";
204
+ /** `100999` "Internal Error" -- Sammelcode, ungewiss. */
205
+ export declare const INTERNAL_ERROR_CODE = "100999";
87
206
  /**
88
207
  * HTTP `409` ("Terminal is busy"): das Terminal serialisiert und weist eine
89
208
  * zweite Anfrage ab, waehrend eine erste noch laeuft. Am 27.08.2026 gemessen:
@@ -91,12 +210,19 @@ export declare const WRONG_PIN_CODE = "55";
91
210
  * Spur (die Statusabfrage auf seine Kennung liefert weiterhin
92
211
  * [NO_STATEMENT_CODE]).
93
212
  *
94
- * Bewusst KEIN Eintrag in [HPS_MEASURED_CODES]: es ist ein HTTP-Status, kein
213
+ * Bewusst KEIN Eintrag in [HPS_CODES]: es ist ein HTTP-Status, kein
95
214
  * `responseCode` — er entsteht, bevor ueberhaupt ein Antwortrumpf gelesen
96
215
  * wird. Siehe `errors.ts` (`HpsConnectTerminalError.isTerminalBusy`) fuer die
97
216
  * getrennte Auswertung.
98
217
  */
99
218
  export declare const TERMINAL_BUSY_HTTP_STATUS = 409;
219
+ /** Der Eintrag zu [code] in [HPS_CODES], oder `undefined`, wenn seine Bedeutung nicht feststeht. */
220
+ export declare function hpsCodeInfo(code: string | undefined): HpsCode | undefined;
221
+ /**
222
+ * Der Grund zu [code] -- `'unknown'` fuer einen Code ausserhalb der Tabelle,
223
+ * `undefined` ohne Code.
224
+ */
225
+ export declare function hpsCodeReason(code: string | undefined): HpsCodeReason | undefined;
100
226
  /** Antwort des Terminals — Zahlung, Statusabfrage oder Abbruch. */
101
227
  export interface HpsTransactionResponse {
102
228
  /** Kennung dieser Transaktion, wie vom Terminal bestaetigt bzw. echoed. */
@@ -148,7 +274,13 @@ export declare function isApproved(res: Pick<HpsTransactionResponse, 'responseCo
148
274
  export declare function isInProgress(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
149
275
  /** `true`, wenn ein Abbruch daran scheiterte, dass der Vorgang nicht mehr abbrechbar war. */
150
276
  export declare function isNotAbortable(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
151
- /** `true`, wenn das Terminal zu dieser Kennung keine Auskunft gibt (9027). */
277
+ /**
278
+ * `true`, wenn das Terminal zu dieser Kennung keine Auskunft gibt (9027).
279
+ *
280
+ * Bewusst NUR `9027`, nicht auch [NOT_FOUND_CODE] (`100011`): auf `9027` ruht
281
+ * die Zwei-9027-Regel in `payments.ts`, und die ist fuer genau diesen Code
282
+ * gemessen. `100011` ist dokumentiert, aber nie gesehen.
283
+ */
152
284
  export declare function isNoStatement(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
153
285
  /** `true`, wenn das Terminal einen technischen Fehler meldet (9900). */
154
286
  export declare function isTechnicalError(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
@@ -163,13 +295,30 @@ export declare function isTechnicalError(res: Pick<HpsTransactionResponse, 'resp
163
295
  export declare function isCanceled(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
164
296
  /**
165
297
  * `true`, wenn diese Antwort ueberhaupt eine Aussage ueber den Ausgang
166
- * traegt — ein Ergebniscode, der in [HPS_MEASURED_CODES] als `conclusive`
167
- * gefuehrt wird. Die einzige Stelle, an der ein Code zu einem Ausgang wird.
298
+ * traegt — ein Ergebniscode, der in [HPS_CODES] als `conclusive` gefuehrt
299
+ * wird. Die einzige Stelle, an der ein Code zu einem Ausgang wird.
168
300
  */
169
301
  export declare function isConclusive(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
170
302
  /**
171
- * `true`, wenn ein Ergebniscode VORHANDEN ist, aber weder schluessig noch
172
- * eine der beiden gemessenen Wissensluecken ([isNoStatement],
173
- * [isTechnicalError]) — ein Code, den dieses Modell schlicht nicht kennt.
303
+ * Wie [isConclusive], aber fuer die Antwort auf eine STATUSABFRAGE: ein Code,
304
+ * der die Anfrage selbst abweist (`rejectsRequest`, etwa `100022` "Terminal is
305
+ * blocked" oder `100108` "Invalid TID"), sagt dort nichts ueber den gesuchten
306
+ * Vorgang. Als `declined` gelesen, hiesse ein gesperrtes Terminal "die Zahlung
307
+ * ist nicht belastet".
308
+ */
309
+ export declare function isConclusiveAsStatus(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
310
+ /**
311
+ * `true`, wenn der Code einen Ausgang meldet, den das Terminal selbst nicht
312
+ * kennt: der hobex-Host war beteiligt, und das Terminal storniert nicht von
313
+ * sich aus (`effect: 'hostUncertain'`). Ein spaeteres `9027` auf die
314
+ * Statusabfrage heisst dann NICHT "nichts belastet" -- es spiegelt nur den
315
+ * Speicher des Terminals, nicht den des Hosts.
316
+ */
317
+ export declare function isHostUncertain(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;
318
+ /**
319
+ * `true`, wenn ein Ergebniscode VORHANDEN ist, dessen Bedeutung aber nicht
320
+ * feststeht -- er fehlt in [HPS_CODES]. Ein Code, den dieses Modell schlicht
321
+ * nicht kennt; anders als [isNoStatement], [isTechnicalError] und
322
+ * [isHostUncertain], die eine Wissensluecke ueber den VORGANG benennen.
174
323
  */
175
324
  export declare function isUnknownCode(res: Pick<HpsTransactionResponse, 'responseCode'>): boolean;