@kreiseck/kasseneck-api 0.7.1 → 0.7.2

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
@@ -4,6 +4,38 @@ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
4
4
  es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
5
5
  nur der Grund überlebt den nächsten Umbau.
6
6
 
7
+ ## 0.7.2
8
+
9
+ ### `55` („PIN falsch") ist eine gemessene Host-Ablehnung → `declined`
10
+
11
+ **Anlass:** Vorfall vom 02.09.2026 am Produktivterminal 3556988 (HPS 1.11.4,
12
+ Firmware 2.3.9), vom Dart-Zwilling `kasseneck_api` 6.4.0 übernommen — die
13
+ erste echte Host-Ablehnung, die je beobachtet wurde. Die Zahlung antwortete
14
+ direkt mit `55`, die Statusabfrage danach elfmal in Folge ebenfalls. Als
15
+ Wissenslücke kostete der Code 90 s Klärung ins Budget, `unresolved`, einen
16
+ stehenden Merker und eine Rückfrage an den Bediener — für eine falsch
17
+ getippte PIN.
18
+
19
+ - Neu: `WRONG_PIN_CODE`, in `HPS_MEASURED_CODES` als schlüssig geführt.
20
+ - Bewusst **keine** Familienregel für zweistellige Host-Codes: ISO 8583 führt
21
+ dort auch Genehmigungen (`08`, `10`, `11`, `85`). Die Zwei-`9027`-Regel
22
+ greift bei Host-Codes nicht (der Status antwortet mit dem Code selbst);
23
+ ungemessene Host-Codes enden weiterhin bei `unresolved`.
24
+ - `fixtures/hobex-hps-codes.json` neu erzeugt; die Vertragsdatei nennt jetzt
25
+ unter `ergaenztAn`, welcher Code von welchem Gerät stammt.
26
+
27
+ ### Neu: `HpsPaymentResult.lastResponse` und Terminal-Klartext im Nachweis
28
+
29
+ Am 02.09.2026 sah der Bediener bei einer Antwort `55` „PIN falsch" nur
30
+ „Ausgang unklar", musste raten und buchte die abgelehnte Zahlung als bezahlt;
31
+ 75 EUR Umsatz waren weg. Der Klartext hätte die Entscheidung getragen.
32
+
33
+ - `lastResponse`: die letzte Terminal-Antwort bei `unresolved`, auch wenn sie
34
+ nichts entschied — Material für Anzeige und Katalog, nie ein Beleg.
35
+ `response` bleibt bei `unresolved` weiterhin ungesetzt.
36
+ - Der Nachweis nennt bei einem unbekannten Code den Klartext des Terminals:
37
+ `Terminal nennt einen unbekannten Code (55) "PIN falsch"`.
38
+
7
39
  ## 0.7.0
8
40
 
9
41
  ### Neu: Unterpfad `./partner` — die Partner-API
@@ -23,5 +23,5 @@ export { type HpsPaymentEvent, type HpsPaymentEventKind, type HpsPaymentObserver
23
23
  export { type CardPaymentOutcome, type HpsPaymentResult, mayRetrySafely, } from './outcome.js';
24
24
  export { type HpsCancelOptions, type HpsPaymentOptions, type HpsPayments, type HpsPaymentsOptions, type HpsRefundOptions, createHpsPayments, } from './payments.js';
25
25
  export { MAX_TRANSACTION_ID_LENGTH, createHpsTransactionIdGenerator, isValidHpsTransactionId, newHpsTransactionId, type HpsTransactionIdGeneratorOptions, } from './transaction-id.js';
26
- export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
26
+ export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
27
27
  export { hobexReceiptFromHps } from './receipt.js';
@@ -19,7 +19,7 @@
19
19
  * des Aufrufers.
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.hobexReceiptFromHps = exports.parseHpsTransactionResponse = exports.isUnknownCode = exports.isTechnicalError = exports.isNotAbortable = exports.isNoStatement = exports.isInProgress = exports.isConclusive = exports.isCanceled = exports.isApproved = exports.TRANSACTION_CANCELED_CODE = exports.TERMINAL_BUSY_HTTP_STATUS = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.INVALID_TRANSACTION_CODE = exports.HPS_MEASURED_CODES = exports.CARD_NOT_PRESENT_CODE = exports.APPROVED_CODE = exports.ABORTED_CODE = exports.newHpsTransactionId = exports.isValidHpsTransactionId = exports.createHpsTransactionIdGenerator = exports.MAX_TRANSACTION_ID_LENGTH = exports.createHpsPayments = exports.mayRetrySafely = exports.PREFLIGHT_CONNECT_CODES = exports.HpsTransactionIdError = exports.HpsPreflightError = exports.HpsConnectTransportError = exports.HpsConnectTerminalError = exports.HpsConnectException = exports.HpsClarifyTimeoutError = exports.createHpsConnectClient = void 0;
22
+ exports.hobexReceiptFromHps = exports.parseHpsTransactionResponse = exports.isUnknownCode = exports.isTechnicalError = exports.isNotAbortable = exports.isNoStatement = exports.isInProgress = exports.isConclusive = exports.isCanceled = exports.isApproved = exports.TRANSACTION_CANCELED_CODE = exports.TERMINAL_BUSY_HTTP_STATUS = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.WRONG_PIN_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.INVALID_TRANSACTION_CODE = exports.HPS_MEASURED_CODES = exports.CARD_NOT_PRESENT_CODE = exports.APPROVED_CODE = exports.ABORTED_CODE = exports.newHpsTransactionId = exports.isValidHpsTransactionId = exports.createHpsTransactionIdGenerator = exports.MAX_TRANSACTION_ID_LENGTH = exports.createHpsPayments = exports.mayRetrySafely = exports.PREFLIGHT_CONNECT_CODES = exports.HpsTransactionIdError = exports.HpsPreflightError = exports.HpsConnectTransportError = exports.HpsConnectTerminalError = exports.HpsConnectException = exports.HpsClarifyTimeoutError = exports.createHpsConnectClient = void 0;
23
23
  var connect_client_js_1 = require("./connect-client.js");
24
24
  Object.defineProperty(exports, "createHpsConnectClient", { enumerable: true, get: function () { return connect_client_js_1.createHpsConnectClient; } });
25
25
  var errors_js_1 = require("./errors.js");
@@ -49,6 +49,7 @@ Object.defineProperty(exports, "NOT_ABORTABLE_CODE", { enumerable: true, get: fu
49
49
  Object.defineProperty(exports, "INVALID_AMOUNT_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_AMOUNT_CODE; } });
50
50
  Object.defineProperty(exports, "AMOUNT_OUT_OF_RANGE_CODE", { enumerable: true, get: function () { return transaction_response_js_1.AMOUNT_OUT_OF_RANGE_CODE; } });
51
51
  Object.defineProperty(exports, "INVALID_TID_CODE", { enumerable: true, get: function () { return transaction_response_js_1.INVALID_TID_CODE; } });
52
+ Object.defineProperty(exports, "WRONG_PIN_CODE", { enumerable: true, get: function () { return transaction_response_js_1.WRONG_PIN_CODE; } });
52
53
  Object.defineProperty(exports, "NO_STATEMENT_CODE", { enumerable: true, get: function () { return transaction_response_js_1.NO_STATEMENT_CODE; } });
53
54
  Object.defineProperty(exports, "TECHNICAL_ERROR_CODE", { enumerable: true, get: function () { return transaction_response_js_1.TECHNICAL_ERROR_CODE; } });
54
55
  Object.defineProperty(exports, "TERMINAL_BUSY_HTTP_STATUS", { enumerable: true, get: function () { return transaction_response_js_1.TERMINAL_BUSY_HTTP_STATUS; } });
@@ -18,8 +18,23 @@ export type CardPaymentOutcome = 'approved' | 'declined' | 'unresolved';
18
18
  export interface HpsPaymentResult {
19
19
  readonly outcome: CardPaymentOutcome;
20
20
  readonly transactionId: string;
21
- /** Die letzte Antwort des Terminals, sofern eine schluessige ankam. */
21
+ /**
22
+ * Die Antwort des Terminals, die den Ausgang FESTGESCHRIEBEN hat -- nur
23
+ * bei `approved` und `declined`. Bei `unresolved` fehlt sie: eine
24
+ * Nicht-Aussage darf nicht als Beleg mitgegeben werden. Was das Terminal
25
+ * zuletzt gesagt hat, steht dann in [lastResponse].
26
+ */
22
27
  readonly response?: HpsTransactionResponse;
28
+ /**
29
+ * Die letzte Antwort des Terminals bei `unresolved`, sofern ueberhaupt
30
+ * eine ankam -- auch wenn sie nichts entschied (9027, 9900, ein
31
+ * unbekannter Code). NIE ein Beleg, sondern Material fuer Anzeige und
32
+ * Katalog: am 02.09.2026 sah der Bediener bei einer Antwort `55`
33
+ * "PIN falsch" nur "Ausgang unklar", musste raten und buchte eine
34
+ * abgelehnte Zahlung als bezahlt. Der Klartext des Terminals haette die
35
+ * Entscheidung getragen. Bei schluessigem Ausgang nicht gesetzt.
36
+ */
37
+ readonly lastResponse?: HpsTransactionResponse;
23
38
  /**
24
39
  * Verlauf der Klaerung, in Reihenfolge — der Nachweis, der im
25
40
  * Belastungsstreit gelesen wird. Behauptet nie eine Ursache, die nicht
@@ -70,7 +70,7 @@ function createHpsPayments(client, target, options = {}) {
70
70
  return `Antwort mit technischem Fehler (${transaction_response_js_1.TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang, Ausgang wird geklaert`;
71
71
  }
72
72
  if ((0, transaction_response_js_1.isUnknownCode)(res)) {
73
- return `Terminal nennt einen unbekannten Code (${res.responseCode}) -- Ausgang wird geklaert`;
73
+ return `Terminal nennt einen unbekannten Code (${res.responseCode})${klartext(res)} -- Ausgang wird geklaert`;
74
74
  }
75
75
  return `Antwort ohne Aussage (${res.responseCode}) -- Ausgang wird geklaert`;
76
76
  }
@@ -83,10 +83,21 @@ function createHpsPayments(client, target, options = {}) {
83
83
  return `Status: technischer Fehler (${transaction_response_js_1.TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang`;
84
84
  }
85
85
  if ((0, transaction_response_js_1.isUnknownCode)(status)) {
86
- return `Status: unbekannter Code (${status.responseCode}) -- keine Aussage`;
86
+ return `Status: unbekannter Code (${status.responseCode})${klartext(status)} -- keine Aussage`;
87
87
  }
88
88
  return null;
89
89
  }
90
+ /**
91
+ * Der Klartext des Terminals zu einem UNBEKANNTEN Code, als Zusatz fuer den
92
+ * Nachweis -- oder leer, wenn keiner mitkam. Nur hier, nicht bei den
93
+ * gemessenen Codes: deren Bedeutung ist benannt. Bei einem unbekannten Code
94
+ * ist er dagegen das Einzige, was ein Mensch lesen kann -- "PIN falsch"
95
+ * neben `55` haette am 02.09.2026 gereicht, um nicht "bezahlt" zu buchen.
96
+ */
97
+ function klartext(res) {
98
+ const text = res.responseText?.trim();
99
+ return text ? ` "${text}"` : '';
100
+ }
90
101
  /** Ordnet eine Terminal-Antwort ein. `null`, wenn sie nichts entscheidet. */
91
102
  function fromResponse(res, id, steps) {
92
103
  if (!(0, transaction_response_js_1.isConclusive)(res))
@@ -188,9 +199,19 @@ function createHpsPayments(client, target, options = {}) {
188
199
  }
189
200
  return null;
190
201
  }
191
- function open(id, steps) {
202
+ /**
203
+ * [letzteAntwort] ist die letzte Antwort, die das Terminal in dieser
204
+ * Klaerung gab -- als `lastResponse` fuer Anzeige und Katalog,
205
+ * ausdruecklich NICHT als `response`.
206
+ */
207
+ function open(id, steps, letzteAntwort) {
192
208
  emit('resolved', steps[steps.length - 1], id);
193
- return { outcome: 'unresolved', transactionId: id, steps: [...steps] };
209
+ return {
210
+ outcome: 'unresolved',
211
+ transactionId: id,
212
+ ...(letzteAntwort ? { lastResponse: letzteAntwort } : {}),
213
+ steps: [...steps],
214
+ };
194
215
  }
195
216
  /**
196
217
  * Fuehrt [call] aus, aber hoechstens so lange, wie vom [resolveBudgetMs]
@@ -263,7 +284,7 @@ function createHpsPayments(client, target, options = {}) {
263
284
  * [NO_STATEMENT_CODE] (`9027`) beim Pollen einen Aussagewert, den es sonst
264
285
  * nicht hat. Siehe [ausGeschlossenerAntwort].
265
286
  */
266
- async function resolve(id, steps, antwortMitCode = false) {
287
+ async function resolve(id, steps, antwortMitCode = false, letzteAntwort) {
267
288
  emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
268
289
  const start = now();
269
290
  const elapsedMs = () => now() - start;
@@ -284,6 +305,7 @@ function createHpsPayments(client, target, options = {}) {
284
305
  try {
285
306
  status = await withinBudget(elapsedMs, () => client.status({ ...target, transactionId: id }));
286
307
  transportFailures = 0;
308
+ letzteAntwort = status;
287
309
  }
288
310
  catch (e) {
289
311
  transportFailures += 1;
@@ -312,7 +334,7 @@ function createHpsPayments(client, target, options = {}) {
312
334
  wait = nextWait(wait);
313
335
  }
314
336
  steps.push('Ausgang bleibt offen');
315
- return open(id, steps);
337
+ return open(id, steps, letzteAntwort);
316
338
  }
317
339
  /**
318
340
  * Liest `9027` beim Pollen als "nicht genehmigt" -- aber NUR, wenn das
@@ -372,7 +394,7 @@ function createHpsPayments(client, target, options = {}) {
372
394
  * Budget, Backoff und Transportfehler-Deckelung sind unveraendert aus
373
395
  * [resolve] uebernommen.
374
396
  */
375
- async function resolveCancel(id, steps) {
397
+ async function resolveCancel(id, steps, letzteAntwort) {
376
398
  emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
377
399
  const start = now();
378
400
  const elapsedMs = () => now() - start;
@@ -393,6 +415,7 @@ function createHpsPayments(client, target, options = {}) {
393
415
  status = await withinBudget(elapsedMs, () => client.status({ ...target, transactionId: id }));
394
416
  transportFailures = 0;
395
417
  answeredQueries += 1;
418
+ letzteAntwort = status;
396
419
  }
397
420
  catch (e) {
398
421
  transportFailures += 1;
@@ -416,7 +439,7 @@ function createHpsPayments(client, target, options = {}) {
416
439
  wait = nextWait(wait);
417
440
  }
418
441
  steps.push('Ausgang bleibt offen');
419
- return open(id, steps);
442
+ return open(id, steps, letzteAntwort);
420
443
  }
421
444
  async function pay(paymentOptions) {
422
445
  const id = paymentOptions.transactionId ?? (0, transaction_id_js_1.newHpsTransactionId)();
@@ -455,7 +478,7 @@ function createHpsPayments(client, target, options = {}) {
455
478
  return settled;
456
479
  steps.push(offeneAntwort(res));
457
480
  }
458
- return resolve(id, steps, res?.responseCode !== undefined);
481
+ return resolve(id, steps, res?.responseCode !== undefined, res);
459
482
  }
460
483
  /**
461
484
  * Gutschrift mit geklaertem Ausgang -- EXAKT derselbe Klaerweg wie [pay]
@@ -499,7 +522,7 @@ function createHpsPayments(client, target, options = {}) {
499
522
  // Dieselbe Klaerfunktion wie [pay]: die Kennung ist die des NEUEN
500
523
  // Vorgangs, eine Statusabfrage darauf liefert also genau dessen Ausgang,
501
524
  // und der Abbruch ist derselbe Diskriminator wie bei einer Zahlung.
502
- return resolve(id, steps, res?.responseCode !== undefined);
525
+ return resolve(id, steps, res?.responseCode !== undefined, res);
503
526
  }
504
527
  /**
505
528
  * Aufhebung (Storno/Void) einer bestehenden Zahlung mit geklaertem Ausgang.
@@ -544,7 +567,7 @@ function createHpsPayments(client, target, options = {}) {
544
567
  if (!(0, transaction_response_js_1.isCanceled)(res))
545
568
  steps.push(offeneAntwort(res));
546
569
  }
547
- return resolveCancel(id, steps);
570
+ return resolveCancel(id, steps, res);
548
571
  }
549
572
  return { pay, refund, cancel };
550
573
  }
@@ -72,6 +72,18 @@ export declare const INVALID_AMOUNT_CODE = "9003";
72
72
  export declare const AMOUNT_OUT_OF_RANGE_CODE = "100019";
73
73
  /** Siehe [HPS_MEASURED_CODES]: Terminal-Kennung unbekannt. */
74
74
  export declare const INVALID_TID_CODE = "100108";
75
+ /**
76
+ * Siehe [HPS_MEASURED_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet.
77
+ *
78
+ * Zweistellig, weil ein Antwortcode des HOSTS (ISO 8583, 55 = "Incorrect
79
+ * PIN"), kein `9xxx`-Terminalcode und kein `100xxx`-Code der HPS-Anwendung.
80
+ * Daraus folgt KEINE Regel fuer andere zweistellige Codes: in derselben
81
+ * Familie stehen Genehmigungen (`08`, `10`, `11`, `85`). Jeder andere
82
+ * Host-Code bleibt eine Wissensluecke, bis er gemessen ist -- und die
83
+ * Zwei-9027-Regel in `payments.ts` faengt ihn nicht, weil die Statusabfrage
84
+ * dann nicht 9027 antwortet, sondern mit dem Code selbst.
85
+ */
86
+ export declare const WRONG_PIN_CODE = "55";
75
87
  /**
76
88
  * HTTP `409` ("Terminal is busy"): das Terminal serialisiert und weist eine
77
89
  * zweite Anfrage ab, waehrend eine erste noch laeuft. Am 27.08.2026 gemessen:
@@ -28,7 +28,7 @@
28
28
  * sein kann, als `declined` gemeldet.
29
29
  */
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.TERMINAL_BUSY_HTTP_STATUS = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.CARD_NOT_PRESENT_CODE = exports.ABORTED_CODE = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.TRANSACTION_CANCELED_CODE = exports.INVALID_TRANSACTION_CODE = exports.APPROVED_CODE = exports.HPS_MEASURED_CODES = void 0;
31
+ exports.TERMINAL_BUSY_HTTP_STATUS = exports.WRONG_PIN_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.CARD_NOT_PRESENT_CODE = exports.ABORTED_CODE = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.TRANSACTION_CANCELED_CODE = exports.INVALID_TRANSACTION_CODE = exports.APPROVED_CODE = exports.HPS_MEASURED_CODES = void 0;
32
32
  exports.parseHpsTransactionResponse = parseHpsTransactionResponse;
33
33
  exports.isApproved = isApproved;
34
34
  exports.isInProgress = isInProgress;
@@ -96,6 +96,15 @@ exports.HPS_MEASURED_CODES = [
96
96
  + 'nicht; der Vorgang wird abgewiesen, bevor etwas geschieht',
97
97
  conclusive: true,
98
98
  },
99
+ {
100
+ code: '55',
101
+ meaning: '"PIN falsch" -- Host-Ablehnung wegen falscher PIN, die erste '
102
+ + 'gemessene Host-Ablehnung ueberhaupt (02.09.2026 im Betrieb, TID '
103
+ + '3556988, HPS 1.11.4, Firmware 2.3.9): die Zahlung antwortete direkt '
104
+ + 'damit, die Statusabfrage danach elfmal in Folge ebenso -- eine '
105
+ + 'Host-Ablehnung bleibt am Terminal abrufbar; nichts belastet',
106
+ conclusive: true,
107
+ },
99
108
  ];
100
109
  /** `responseCode` einer genehmigten Zahlung. */
101
110
  exports.APPROVED_CODE = '0';
@@ -119,6 +128,18 @@ exports.INVALID_AMOUNT_CODE = '9003';
119
128
  exports.AMOUNT_OUT_OF_RANGE_CODE = '100019';
120
129
  /** Siehe [HPS_MEASURED_CODES]: Terminal-Kennung unbekannt. */
121
130
  exports.INVALID_TID_CODE = '100108';
131
+ /**
132
+ * Siehe [HPS_MEASURED_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet.
133
+ *
134
+ * Zweistellig, weil ein Antwortcode des HOSTS (ISO 8583, 55 = "Incorrect
135
+ * PIN"), kein `9xxx`-Terminalcode und kein `100xxx`-Code der HPS-Anwendung.
136
+ * Daraus folgt KEINE Regel fuer andere zweistellige Codes: in derselben
137
+ * Familie stehen Genehmigungen (`08`, `10`, `11`, `85`). Jeder andere
138
+ * Host-Code bleibt eine Wissensluecke, bis er gemessen ist -- und die
139
+ * Zwei-9027-Regel in `payments.ts` faengt ihn nicht, weil die Statusabfrage
140
+ * dann nicht 9027 antwortet, sondern mit dem Code selbst.
141
+ */
142
+ exports.WRONG_PIN_CODE = '55';
122
143
  /**
123
144
  * HTTP `409` ("Terminal is busy"): das Terminal serialisiert und weist eine
124
145
  * zweite Anfrage ab, waehrend eine erste noch laeuft. Am 27.08.2026 gemessen:
@@ -23,5 +23,5 @@ export { type HpsPaymentEvent, type HpsPaymentEventKind, type HpsPaymentObserver
23
23
  export { type CardPaymentOutcome, type HpsPaymentResult, mayRetrySafely, } from './outcome.js';
24
24
  export { type HpsCancelOptions, type HpsPaymentOptions, type HpsPayments, type HpsPaymentsOptions, type HpsRefundOptions, createHpsPayments, } from './payments.js';
25
25
  export { MAX_TRANSACTION_ID_LENGTH, createHpsTransactionIdGenerator, isValidHpsTransactionId, newHpsTransactionId, type HpsTransactionIdGeneratorOptions, } from './transaction-id.js';
26
- export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
26
+ export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
27
27
  export { hobexReceiptFromHps } from './receipt.js';
@@ -22,5 +22,5 @@ export { HpsClarifyTimeoutError, HpsConnectException, HpsConnectTerminalError, H
22
22
  export { mayRetrySafely, } from './outcome.js';
23
23
  export { createHpsPayments, } from './payments.js';
24
24
  export { MAX_TRANSACTION_ID_LENGTH, createHpsTransactionIdGenerator, isValidHpsTransactionId, newHpsTransactionId, } from './transaction-id.js';
25
- export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, } from './transaction-response.js';
25
+ export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, } from './transaction-response.js';
26
26
  export { hobexReceiptFromHps } from './receipt.js';
@@ -18,8 +18,23 @@ export type CardPaymentOutcome = 'approved' | 'declined' | 'unresolved';
18
18
  export interface HpsPaymentResult {
19
19
  readonly outcome: CardPaymentOutcome;
20
20
  readonly transactionId: string;
21
- /** Die letzte Antwort des Terminals, sofern eine schluessige ankam. */
21
+ /**
22
+ * Die Antwort des Terminals, die den Ausgang FESTGESCHRIEBEN hat -- nur
23
+ * bei `approved` und `declined`. Bei `unresolved` fehlt sie: eine
24
+ * Nicht-Aussage darf nicht als Beleg mitgegeben werden. Was das Terminal
25
+ * zuletzt gesagt hat, steht dann in [lastResponse].
26
+ */
22
27
  readonly response?: HpsTransactionResponse;
28
+ /**
29
+ * Die letzte Antwort des Terminals bei `unresolved`, sofern ueberhaupt
30
+ * eine ankam -- auch wenn sie nichts entschied (9027, 9900, ein
31
+ * unbekannter Code). NIE ein Beleg, sondern Material fuer Anzeige und
32
+ * Katalog: am 02.09.2026 sah der Bediener bei einer Antwort `55`
33
+ * "PIN falsch" nur "Ausgang unklar", musste raten und buchte eine
34
+ * abgelehnte Zahlung als bezahlt. Der Klartext des Terminals haette die
35
+ * Entscheidung getragen. Bei schluessigem Ausgang nicht gesetzt.
36
+ */
37
+ readonly lastResponse?: HpsTransactionResponse;
23
38
  /**
24
39
  * Verlauf der Klaerung, in Reihenfolge — der Nachweis, der im
25
40
  * Belastungsstreit gelesen wird. Behauptet nie eine Ursache, die nicht
@@ -67,7 +67,7 @@ export function createHpsPayments(client, target, options = {}) {
67
67
  return `Antwort mit technischem Fehler (${TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang, Ausgang wird geklaert`;
68
68
  }
69
69
  if (isUnknownCode(res)) {
70
- return `Terminal nennt einen unbekannten Code (${res.responseCode}) -- Ausgang wird geklaert`;
70
+ return `Terminal nennt einen unbekannten Code (${res.responseCode})${klartext(res)} -- Ausgang wird geklaert`;
71
71
  }
72
72
  return `Antwort ohne Aussage (${res.responseCode}) -- Ausgang wird geklaert`;
73
73
  }
@@ -80,10 +80,21 @@ export function createHpsPayments(client, target, options = {}) {
80
80
  return `Status: technischer Fehler (${TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang`;
81
81
  }
82
82
  if (isUnknownCode(status)) {
83
- return `Status: unbekannter Code (${status.responseCode}) -- keine Aussage`;
83
+ return `Status: unbekannter Code (${status.responseCode})${klartext(status)} -- keine Aussage`;
84
84
  }
85
85
  return null;
86
86
  }
87
+ /**
88
+ * Der Klartext des Terminals zu einem UNBEKANNTEN Code, als Zusatz fuer den
89
+ * Nachweis -- oder leer, wenn keiner mitkam. Nur hier, nicht bei den
90
+ * gemessenen Codes: deren Bedeutung ist benannt. Bei einem unbekannten Code
91
+ * ist er dagegen das Einzige, was ein Mensch lesen kann -- "PIN falsch"
92
+ * neben `55` haette am 02.09.2026 gereicht, um nicht "bezahlt" zu buchen.
93
+ */
94
+ function klartext(res) {
95
+ const text = res.responseText?.trim();
96
+ return text ? ` "${text}"` : '';
97
+ }
87
98
  /** Ordnet eine Terminal-Antwort ein. `null`, wenn sie nichts entscheidet. */
88
99
  function fromResponse(res, id, steps) {
89
100
  if (!isConclusive(res))
@@ -185,9 +196,19 @@ export function createHpsPayments(client, target, options = {}) {
185
196
  }
186
197
  return null;
187
198
  }
188
- function open(id, steps) {
199
+ /**
200
+ * [letzteAntwort] ist die letzte Antwort, die das Terminal in dieser
201
+ * Klaerung gab -- als `lastResponse` fuer Anzeige und Katalog,
202
+ * ausdruecklich NICHT als `response`.
203
+ */
204
+ function open(id, steps, letzteAntwort) {
189
205
  emit('resolved', steps[steps.length - 1], id);
190
- return { outcome: 'unresolved', transactionId: id, steps: [...steps] };
206
+ return {
207
+ outcome: 'unresolved',
208
+ transactionId: id,
209
+ ...(letzteAntwort ? { lastResponse: letzteAntwort } : {}),
210
+ steps: [...steps],
211
+ };
191
212
  }
192
213
  /**
193
214
  * Fuehrt [call] aus, aber hoechstens so lange, wie vom [resolveBudgetMs]
@@ -260,7 +281,7 @@ export function createHpsPayments(client, target, options = {}) {
260
281
  * [NO_STATEMENT_CODE] (`9027`) beim Pollen einen Aussagewert, den es sonst
261
282
  * nicht hat. Siehe [ausGeschlossenerAntwort].
262
283
  */
263
- async function resolve(id, steps, antwortMitCode = false) {
284
+ async function resolve(id, steps, antwortMitCode = false, letzteAntwort) {
264
285
  emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
265
286
  const start = now();
266
287
  const elapsedMs = () => now() - start;
@@ -281,6 +302,7 @@ export function createHpsPayments(client, target, options = {}) {
281
302
  try {
282
303
  status = await withinBudget(elapsedMs, () => client.status({ ...target, transactionId: id }));
283
304
  transportFailures = 0;
305
+ letzteAntwort = status;
284
306
  }
285
307
  catch (e) {
286
308
  transportFailures += 1;
@@ -309,7 +331,7 @@ export function createHpsPayments(client, target, options = {}) {
309
331
  wait = nextWait(wait);
310
332
  }
311
333
  steps.push('Ausgang bleibt offen');
312
- return open(id, steps);
334
+ return open(id, steps, letzteAntwort);
313
335
  }
314
336
  /**
315
337
  * Liest `9027` beim Pollen als "nicht genehmigt" -- aber NUR, wenn das
@@ -369,7 +391,7 @@ export function createHpsPayments(client, target, options = {}) {
369
391
  * Budget, Backoff und Transportfehler-Deckelung sind unveraendert aus
370
392
  * [resolve] uebernommen.
371
393
  */
372
- async function resolveCancel(id, steps) {
394
+ async function resolveCancel(id, steps, letzteAntwort) {
373
395
  emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
374
396
  const start = now();
375
397
  const elapsedMs = () => now() - start;
@@ -390,6 +412,7 @@ export function createHpsPayments(client, target, options = {}) {
390
412
  status = await withinBudget(elapsedMs, () => client.status({ ...target, transactionId: id }));
391
413
  transportFailures = 0;
392
414
  answeredQueries += 1;
415
+ letzteAntwort = status;
393
416
  }
394
417
  catch (e) {
395
418
  transportFailures += 1;
@@ -413,7 +436,7 @@ export function createHpsPayments(client, target, options = {}) {
413
436
  wait = nextWait(wait);
414
437
  }
415
438
  steps.push('Ausgang bleibt offen');
416
- return open(id, steps);
439
+ return open(id, steps, letzteAntwort);
417
440
  }
418
441
  async function pay(paymentOptions) {
419
442
  const id = paymentOptions.transactionId ?? newHpsTransactionId();
@@ -452,7 +475,7 @@ export function createHpsPayments(client, target, options = {}) {
452
475
  return settled;
453
476
  steps.push(offeneAntwort(res));
454
477
  }
455
- return resolve(id, steps, res?.responseCode !== undefined);
478
+ return resolve(id, steps, res?.responseCode !== undefined, res);
456
479
  }
457
480
  /**
458
481
  * Gutschrift mit geklaertem Ausgang -- EXAKT derselbe Klaerweg wie [pay]
@@ -496,7 +519,7 @@ export function createHpsPayments(client, target, options = {}) {
496
519
  // Dieselbe Klaerfunktion wie [pay]: die Kennung ist die des NEUEN
497
520
  // Vorgangs, eine Statusabfrage darauf liefert also genau dessen Ausgang,
498
521
  // und der Abbruch ist derselbe Diskriminator wie bei einer Zahlung.
499
- return resolve(id, steps, res?.responseCode !== undefined);
522
+ return resolve(id, steps, res?.responseCode !== undefined, res);
500
523
  }
501
524
  /**
502
525
  * Aufhebung (Storno/Void) einer bestehenden Zahlung mit geklaertem Ausgang.
@@ -541,7 +564,7 @@ export function createHpsPayments(client, target, options = {}) {
541
564
  if (!isCanceled(res))
542
565
  steps.push(offeneAntwort(res));
543
566
  }
544
- return resolveCancel(id, steps);
567
+ return resolveCancel(id, steps, res);
545
568
  }
546
569
  return { pay, refund, cancel };
547
570
  }
@@ -72,6 +72,18 @@ export declare const INVALID_AMOUNT_CODE = "9003";
72
72
  export declare const AMOUNT_OUT_OF_RANGE_CODE = "100019";
73
73
  /** Siehe [HPS_MEASURED_CODES]: Terminal-Kennung unbekannt. */
74
74
  export declare const INVALID_TID_CODE = "100108";
75
+ /**
76
+ * Siehe [HPS_MEASURED_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet.
77
+ *
78
+ * Zweistellig, weil ein Antwortcode des HOSTS (ISO 8583, 55 = "Incorrect
79
+ * PIN"), kein `9xxx`-Terminalcode und kein `100xxx`-Code der HPS-Anwendung.
80
+ * Daraus folgt KEINE Regel fuer andere zweistellige Codes: in derselben
81
+ * Familie stehen Genehmigungen (`08`, `10`, `11`, `85`). Jeder andere
82
+ * Host-Code bleibt eine Wissensluecke, bis er gemessen ist -- und die
83
+ * Zwei-9027-Regel in `payments.ts` faengt ihn nicht, weil die Statusabfrage
84
+ * dann nicht 9027 antwortet, sondern mit dem Code selbst.
85
+ */
86
+ export declare const WRONG_PIN_CODE = "55";
75
87
  /**
76
88
  * HTTP `409` ("Terminal is busy"): das Terminal serialisiert und weist eine
77
89
  * zweite Anfrage ab, waehrend eine erste noch laeuft. Am 27.08.2026 gemessen:
@@ -84,6 +84,15 @@ export const HPS_MEASURED_CODES = [
84
84
  + 'nicht; der Vorgang wird abgewiesen, bevor etwas geschieht',
85
85
  conclusive: true,
86
86
  },
87
+ {
88
+ code: '55',
89
+ meaning: '"PIN falsch" -- Host-Ablehnung wegen falscher PIN, die erste '
90
+ + 'gemessene Host-Ablehnung ueberhaupt (02.09.2026 im Betrieb, TID '
91
+ + '3556988, HPS 1.11.4, Firmware 2.3.9): die Zahlung antwortete direkt '
92
+ + 'damit, die Statusabfrage danach elfmal in Folge ebenso -- eine '
93
+ + 'Host-Ablehnung bleibt am Terminal abrufbar; nichts belastet',
94
+ conclusive: true,
95
+ },
87
96
  ];
88
97
  /** `responseCode` einer genehmigten Zahlung. */
89
98
  export const APPROVED_CODE = '0';
@@ -107,6 +116,18 @@ export const INVALID_AMOUNT_CODE = '9003';
107
116
  export const AMOUNT_OUT_OF_RANGE_CODE = '100019';
108
117
  /** Siehe [HPS_MEASURED_CODES]: Terminal-Kennung unbekannt. */
109
118
  export const INVALID_TID_CODE = '100108';
119
+ /**
120
+ * Siehe [HPS_MEASURED_CODES]: Host-Ablehnung, falsche PIN -- nichts belastet.
121
+ *
122
+ * Zweistellig, weil ein Antwortcode des HOSTS (ISO 8583, 55 = "Incorrect
123
+ * PIN"), kein `9xxx`-Terminalcode und kein `100xxx`-Code der HPS-Anwendung.
124
+ * Daraus folgt KEINE Regel fuer andere zweistellige Codes: in derselben
125
+ * Familie stehen Genehmigungen (`08`, `10`, `11`, `85`). Jeder andere
126
+ * Host-Code bleibt eine Wissensluecke, bis er gemessen ist -- und die
127
+ * Zwei-9027-Regel in `payments.ts` faengt ihn nicht, weil die Statusabfrage
128
+ * dann nicht 9027 antwortet, sondern mit dem Code selbst.
129
+ */
130
+ export const WRONG_PIN_CODE = '55';
110
131
  /**
111
132
  * HTTP `409` ("Terminal is busy"): das Terminal serialisiert und weist eine
112
133
  * zweite Anfrage ab, waehrend eine erste noch laeuft. Am 27.08.2026 gemessen:
@@ -1,11 +1,22 @@
1
1
  {
2
- "version": "0.7.1",
2
+ "version": "0.7.2",
3
3
  "gemessenAn": {
4
4
  "tid": "3600335",
5
5
  "hpsVersion": "1.10.0",
6
6
  "firmware": "7.3.6",
7
7
  "zeitraum": "26.-28.08.2026"
8
8
  },
9
+ "ergaenztAn": [
10
+ {
11
+ "tid": "3556988",
12
+ "hpsVersion": "1.11.4",
13
+ "firmware": "2.3.9",
14
+ "zeitraum": "02.09.2026",
15
+ "codes": [
16
+ "55"
17
+ ]
18
+ }
19
+ ],
9
20
  "codes": [
10
21
  {
11
22
  "code": "0",
@@ -61,6 +72,11 @@
61
72
  "code": "100108",
62
73
  "meaning": "\"Invalid TID\" -- die Terminal-Kennung gibt es an diesem Geraet nicht; der Vorgang wird abgewiesen, bevor etwas geschieht",
63
74
  "conclusive": true
75
+ },
76
+ {
77
+ "code": "55",
78
+ "meaning": "\"PIN falsch\" -- Host-Ablehnung wegen falscher PIN, die erste gemessene Host-Ablehnung ueberhaupt (02.09.2026 im Betrieb, TID 3556988, HPS 1.11.4, Firmware 2.3.9): die Zahlung antwortete direkt damit, die Statusabfrage danach elfmal in Folge ebenso -- eine Host-Ablehnung bleibt am Terminal abrufbar; nichts belastet",
79
+ "conclusive": true
64
80
  }
65
81
  ],
66
82
  "terminalBusyHttpStatus": 409
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.1",
2
+ "version": "0.7.2",
3
3
  "aufrufe": [
4
4
  "activateCashregister",
5
5
  "cancelReceipt",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kreiseck/kasseneck-api",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "JavaScript-Zwilling von kasseneck_api (Flutter) fuer das Kasseneck-Backend",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Kreiseck",