@fire-spark/payments-clisitef-pinpad-rn 0.8.1 → 0.8.3
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/dist/index.d.ts +11 -3
- package/dist/index.js +165 -41
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ type ClisitefPinPadOptions = {
|
|
|
17
17
|
timeZone?: string;
|
|
18
18
|
now?: () => Date;
|
|
19
19
|
confirmationTimeoutMs?: number;
|
|
20
|
+
/** Wait for the `stage 1` of a released leftover routine; defaults to 15 s. */
|
|
21
|
+
releaseTimeoutMs?: number;
|
|
20
22
|
/**
|
|
21
23
|
* Payment deadline (`policy.timeout_ms`); defaults to 180 s. A
|
|
22
24
|
* `runtime.timeout_ms` in the payment or method config overrides it.
|
|
@@ -66,6 +68,11 @@ declare const CLISITEF_COMMAND: {
|
|
|
66
68
|
/** `continueTransaction` answers to `CONFIRM` (20). */
|
|
67
69
|
declare const CLISITEF_YES = "0";
|
|
68
70
|
declare const CLISITEF_NO = "1";
|
|
71
|
+
/**
|
|
72
|
+
* Wait for the `stage 1` of a routine a previous charge left open, after
|
|
73
|
+
* answering it with `abortTransaction(-1)`; then the charge starts anyway.
|
|
74
|
+
*/
|
|
75
|
+
declare const CLISITEF_RELEASE_TIMEOUT_MS = 15000;
|
|
69
76
|
/** `TipoCampo` ids stored with `STORE_FIELD` (Fiserv "Tabela TipoCampo"). */
|
|
70
77
|
declare const CLISITEF_FIELD: {
|
|
71
78
|
readonly TRANSACTION_DATE: 105;
|
|
@@ -305,9 +312,10 @@ declare const isOpen: (record: ClisitefTransactionRecord) => boolean;
|
|
|
305
312
|
/**
|
|
306
313
|
* `confirmed`: `stage 2` closed with 0. `declined`: failed before the card
|
|
307
314
|
* was charged. `undone`: `finishTransaction(0)` sent after a capture or an
|
|
308
|
-
* approval. `unresolved`: the undo itself failed; needs review.
|
|
315
|
+
* approval. `unresolved`: the undo itself failed; needs review. `open`: given
|
|
316
|
+
* up, but the routine never returned `stage 1`; the next charge recovers it.
|
|
309
317
|
*/
|
|
310
|
-
type ClisitefOutcomeStatus = "confirmed" | "declined" | "undone" | "unresolved";
|
|
318
|
+
type ClisitefOutcomeStatus = "confirmed" | "declined" | "undone" | "unresolved" | "open";
|
|
311
319
|
type ClisitefOutcome = {
|
|
312
320
|
status: ClisitefOutcomeStatus;
|
|
313
321
|
/** Provider code: `resultCode`, a client error code, or `null`. */
|
|
@@ -342,4 +350,4 @@ type ClisitefTransactionInput = {
|
|
|
342
350
|
*/
|
|
343
351
|
declare const runClisitefTransaction: (input: ClisitefTransactionInput) => Promise<ClisitefOutcome>;
|
|
344
352
|
//#endregion
|
|
345
|
-
export { CLISITEF_CANCEL_CODES, CLISITEF_CAPTURE_FIELDS, CLISITEF_COMMAND, CLISITEF_CONFIRMATION_TIMEOUT_MS, CLISITEF_DEFAULT_OPERATOR, CLISITEF_FIELD, CLISITEF_INCONCLUSIVE_CODES, CLISITEF_MODALITIES, CLISITEF_NO, CLISITEF_PAYMENT_TIMEOUT_MS, CLISITEF_PINPAD_TYPES, CLISITEF_REFUNDS_KEY, CLISITEF_REFUND_STORAGE_KEY, CLISITEF_REVERSALS_KEY, CLISITEF_STORAGE_KEY, CLISITEF_VOID_FIELD, CLISITEF_YES, ClisitefFields, ClisitefMenuDecision, ClisitefMenuPurpose, ClisitefModality, ClisitefOutcome, ClisitefOutcomeStatus, ClisitefPendingReversal, ClisitefPinPadOptions, ClisitefPinPadSettings, ClisitefProviderMetadata, ClisitefTransactionInput, ClisitefTransactionRecord, ClisitefTransactionState, ClisitefVoidContext, ClisitefVoidDeps, clisitefDetails, clisitefLast4, clisitefMetadata, clisitefModalityOf, clisitefPinPadCapabilities, clisitefPinPadDeviceFields, clisitefPinPadErrors, clisitefPinPadPayments, clisitefVoidMetadata, dropReversal, isClisitefModality, isOpen, queueReversal, readClisitefPinPadSettings, readPendingReversals, readRefund, readTransaction, redactSecrets, resolveClisitefMenu, runClisitefTransaction, runClisitefVoid, writeRefund, writeTransaction };
|
|
353
|
+
export { CLISITEF_CANCEL_CODES, CLISITEF_CAPTURE_FIELDS, CLISITEF_COMMAND, CLISITEF_CONFIRMATION_TIMEOUT_MS, CLISITEF_DEFAULT_OPERATOR, CLISITEF_FIELD, CLISITEF_INCONCLUSIVE_CODES, CLISITEF_MODALITIES, CLISITEF_NO, CLISITEF_PAYMENT_TIMEOUT_MS, CLISITEF_PINPAD_TYPES, CLISITEF_REFUNDS_KEY, CLISITEF_REFUND_STORAGE_KEY, CLISITEF_RELEASE_TIMEOUT_MS, CLISITEF_REVERSALS_KEY, CLISITEF_STORAGE_KEY, CLISITEF_VOID_FIELD, CLISITEF_YES, ClisitefFields, ClisitefMenuDecision, ClisitefMenuPurpose, ClisitefModality, ClisitefOutcome, ClisitefOutcomeStatus, ClisitefPendingReversal, ClisitefPinPadOptions, ClisitefPinPadSettings, ClisitefProviderMetadata, ClisitefTransactionInput, ClisitefTransactionRecord, ClisitefTransactionState, ClisitefVoidContext, ClisitefVoidDeps, clisitefDetails, clisitefLast4, clisitefMetadata, clisitefModalityOf, clisitefPinPadCapabilities, clisitefPinPadDeviceFields, clisitefPinPadErrors, clisitefPinPadPayments, clisitefVoidMetadata, dropReversal, isClisitefModality, isOpen, queueReversal, readClisitefPinPadSettings, readPendingReversals, readRefund, readTransaction, redactSecrets, resolveClisitefMenu, runClisitefTransaction, runClisitefVoid, writeRefund, writeTransaction };
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,11 @@ const CLISITEF_COMMAND = {
|
|
|
34
34
|
/** `continueTransaction` answers to `CONFIRM` (20). */
|
|
35
35
|
const CLISITEF_YES = "0";
|
|
36
36
|
const CLISITEF_NO = "1";
|
|
37
|
+
/**
|
|
38
|
+
* Wait for the `stage 1` of a routine a previous charge left open, after
|
|
39
|
+
* answering it with `abortTransaction(-1)`; then the charge starts anyway.
|
|
40
|
+
*/
|
|
41
|
+
const CLISITEF_RELEASE_TIMEOUT_MS = 15e3;
|
|
37
42
|
/** `TipoCampo` ids stored with `STORE_FIELD` (Fiserv "Tabela TipoCampo"). */
|
|
38
43
|
const CLISITEF_FIELD = {
|
|
39
44
|
TRANSACTION_DATE: 105,
|
|
@@ -459,6 +464,8 @@ const runClisitefTransaction = (input) => {
|
|
|
459
464
|
let approved = false;
|
|
460
465
|
let closing = false;
|
|
461
466
|
let aborted = false;
|
|
467
|
+
/** Why the adapter asked to interrupt; `stage 1` has not arrived yet. */
|
|
468
|
+
let givingUp = null;
|
|
462
469
|
let watchdog;
|
|
463
470
|
let queue = Promise.resolve();
|
|
464
471
|
let resolve = () => void 0;
|
|
@@ -507,18 +514,38 @@ const runClisitefTransaction = (input) => {
|
|
|
507
514
|
});
|
|
508
515
|
}
|
|
509
516
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Stops waiting: `stage 1` never came. The routine may still be open in
|
|
519
|
+
* CliSiTef; the next charge releases it (`open` outcome).
|
|
520
|
+
*/
|
|
521
|
+
const abandon = () => {
|
|
522
|
+
log("abandon", { skipped: closing });
|
|
523
|
+
if (closing || !givingUp) return;
|
|
513
524
|
closing = true;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
525
|
+
settle("open", givingUp);
|
|
526
|
+
};
|
|
527
|
+
/**
|
|
528
|
+
* Marks the transaction to interrupt. CliSiTef only takes `Continua = -1`
|
|
529
|
+
* as the answer to an `onData` (Interface Simplificada §5.3), so nothing is
|
|
530
|
+
* sent here: the pending or next event is answered with
|
|
531
|
+
* `abortTransaction(-1)` until `stage 1`. An approval already confirmed
|
|
532
|
+
* with `finishTransaction(1)` is undone instead.
|
|
533
|
+
*/
|
|
534
|
+
const giveUp = (reason) => {
|
|
535
|
+
log("give up", reason);
|
|
536
|
+
if (closing || givingUp) return;
|
|
537
|
+
if (approved) {
|
|
538
|
+
closing = true;
|
|
539
|
+
undo(reason);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
givingUp = reason;
|
|
543
|
+
watchdog = setTimeout(abandon, input.confirmationTimeoutMs);
|
|
517
544
|
};
|
|
518
545
|
function onAbort() {
|
|
519
546
|
log("signal aborted (payment deadline)");
|
|
520
547
|
aborted = true;
|
|
521
|
-
|
|
548
|
+
giveUp({
|
|
522
549
|
code: null,
|
|
523
550
|
message: "Aborted by the payment deadline"
|
|
524
551
|
});
|
|
@@ -532,6 +559,38 @@ const runClisitefTransaction = (input) => {
|
|
|
532
559
|
if (closing) return;
|
|
533
560
|
await client.continueTransaction(data);
|
|
534
561
|
};
|
|
562
|
+
const store = (event) => {
|
|
563
|
+
fields.set(event.fieldId, event.input);
|
|
564
|
+
if (CLISITEF_CAPTURE_FIELDS.has(event.fieldId)) captured = true;
|
|
565
|
+
};
|
|
566
|
+
const clearQr = () => {
|
|
567
|
+
ui.action({
|
|
568
|
+
type: "qr",
|
|
569
|
+
payload: "",
|
|
570
|
+
format: "text",
|
|
571
|
+
expiresAt: null
|
|
572
|
+
});
|
|
573
|
+
};
|
|
574
|
+
/**
|
|
575
|
+
* Answers every event after giving up with `Continua = -1`, without the UI
|
|
576
|
+
* and without a count: a `continueTransaction` here would tell CliSiTef to
|
|
577
|
+
* keep going (PIX keeps polling the server for minutes).
|
|
578
|
+
*/
|
|
579
|
+
const drain = async (event) => {
|
|
580
|
+
log("drain", {
|
|
581
|
+
command: event.command,
|
|
582
|
+
skipped: closing
|
|
583
|
+
});
|
|
584
|
+
const { command } = event;
|
|
585
|
+
if (command === CLISITEF_COMMAND.STORE_FIELD) store(event);
|
|
586
|
+
if (command === CLISITEF_COMMAND.QR_HIDE) clearQr();
|
|
587
|
+
if (closing) return;
|
|
588
|
+
await client.abortTransaction(CLISITEF_ABORT_FLAG);
|
|
589
|
+
};
|
|
590
|
+
/** A UI answer that arrives after giving up is not sent. */
|
|
591
|
+
const respond = (event, data) => {
|
|
592
|
+
return givingUp ? drain(event) : reply(data);
|
|
593
|
+
};
|
|
535
594
|
const showMessage = (text) => {
|
|
536
595
|
const collapsed = collapseCounter(text.trim());
|
|
537
596
|
if (!collapsed || collapsed === lastMessage) return;
|
|
@@ -541,8 +600,8 @@ const runClisitefTransaction = (input) => {
|
|
|
541
600
|
kind: "waiting"
|
|
542
601
|
});
|
|
543
602
|
};
|
|
544
|
-
const onMenu = async (
|
|
545
|
-
const options = parseClisitefMenu(
|
|
603
|
+
const onMenu = async (event) => {
|
|
604
|
+
const options = parseClisitefMenu(event.input);
|
|
546
605
|
const label = title || "Selecione uma opção";
|
|
547
606
|
const decision = resolveClisitefMenu(...[
|
|
548
607
|
options,
|
|
@@ -555,11 +614,11 @@ const runClisitefTransaction = (input) => {
|
|
|
555
614
|
decision
|
|
556
615
|
});
|
|
557
616
|
if (decision.kind === "abort") {
|
|
558
|
-
|
|
617
|
+
giveUp({
|
|
559
618
|
code: "FINANCING_ONLY",
|
|
560
619
|
message: decision.message
|
|
561
|
-
};
|
|
562
|
-
return
|
|
620
|
+
});
|
|
621
|
+
return drain(event);
|
|
563
622
|
}
|
|
564
623
|
if (decision.kind === "pick") return reply(decision.value);
|
|
565
624
|
const p2 = {
|
|
@@ -568,7 +627,7 @@ const runClisitefTransaction = (input) => {
|
|
|
568
627
|
};
|
|
569
628
|
const picked = await ui.select(p2);
|
|
570
629
|
log("ui.select ← out", { picked });
|
|
571
|
-
return
|
|
630
|
+
return respond(event, picked);
|
|
572
631
|
};
|
|
573
632
|
const onQr = (event) => {
|
|
574
633
|
const payload = event.input.trim();
|
|
@@ -592,20 +651,15 @@ const runClisitefTransaction = (input) => {
|
|
|
592
651
|
...redact(event),
|
|
593
652
|
title
|
|
594
653
|
});
|
|
654
|
+
if (givingUp) return drain(event);
|
|
595
655
|
const { command, input: text } = event;
|
|
596
656
|
if (command === CLISITEF_COMMAND.STORE_FIELD) {
|
|
597
|
-
|
|
598
|
-
if (CLISITEF_CAPTURE_FIELDS.has(event.fieldId)) captured = true;
|
|
657
|
+
store(event);
|
|
599
658
|
return reply("");
|
|
600
659
|
}
|
|
601
660
|
if (command === CLISITEF_COMMAND.QR_SHOW) return onQr(event);
|
|
602
661
|
if (command === CLISITEF_COMMAND.QR_HIDE) {
|
|
603
|
-
|
|
604
|
-
type: "qr",
|
|
605
|
-
payload: "",
|
|
606
|
-
format: "text",
|
|
607
|
-
expiresAt: null
|
|
608
|
-
});
|
|
662
|
+
clearQr();
|
|
609
663
|
return reply("");
|
|
610
664
|
}
|
|
611
665
|
if (MESSAGES.has(command)) {
|
|
@@ -625,9 +679,9 @@ const runClisitefTransaction = (input) => {
|
|
|
625
679
|
log("ui.confirm → in", { text: text.trim() || title });
|
|
626
680
|
const yes = await ui.confirm({ text: text.trim() || title });
|
|
627
681
|
log("ui.confirm ← out", { yes });
|
|
628
|
-
return
|
|
682
|
+
return respond(event, yes ? "0" : "1");
|
|
629
683
|
}
|
|
630
|
-
if (command === CLISITEF_COMMAND.MENU) return onMenu(
|
|
684
|
+
if (command === CLISITEF_COMMAND.MENU) return onMenu(event);
|
|
631
685
|
if (READS.has(command)) {
|
|
632
686
|
const known = input.answerField?.(event.fieldId) ?? null;
|
|
633
687
|
if (known !== null) return reply(known, true);
|
|
@@ -640,13 +694,14 @@ const runClisitefTransaction = (input) => {
|
|
|
640
694
|
};
|
|
641
695
|
log("ui.input → in", p1);
|
|
642
696
|
const value = await ui.input(p1);
|
|
643
|
-
return
|
|
697
|
+
return respond(event, value);
|
|
644
698
|
}
|
|
645
699
|
const message = `Unsupported CliSiTef command ${command}`;
|
|
646
|
-
|
|
700
|
+
giveUp({
|
|
647
701
|
code: `UNSUPPORTED_COMMAND_${command}`,
|
|
648
702
|
message
|
|
649
703
|
});
|
|
704
|
+
return drain(event);
|
|
650
705
|
};
|
|
651
706
|
const confirm = () => {
|
|
652
707
|
const message = "CliSiTef did not close the confirmation";
|
|
@@ -678,6 +733,21 @@ const runClisitefTransaction = (input) => {
|
|
|
678
733
|
});
|
|
679
734
|
});
|
|
680
735
|
};
|
|
736
|
+
/**
|
|
737
|
+
* `stage 1` after giving up. A late approval is undone, never confirmed; a
|
|
738
|
+
* cancel (`-2`, `-6`, `-15`) needs no `finishTransaction`: CliSiTef sends
|
|
739
|
+
* the undo itself.
|
|
740
|
+
*/
|
|
741
|
+
const windDown = (reason, code) => {
|
|
742
|
+
closing = true;
|
|
743
|
+
const closed = {
|
|
744
|
+
...reason,
|
|
745
|
+
code: reason.code ?? code
|
|
746
|
+
};
|
|
747
|
+
const inconclusive = captured && CLISITEF_INCONCLUSIVE_CODES.has(code);
|
|
748
|
+
if (code === CLISITEF_RESULT.OK || inconclusive) return undo(closed);
|
|
749
|
+
settle("declined", closed);
|
|
750
|
+
};
|
|
681
751
|
const onResult = async (event) => {
|
|
682
752
|
const { stage, resultCode: code } = event;
|
|
683
753
|
log("onTransactionResult", {
|
|
@@ -686,6 +756,7 @@ const runClisitefTransaction = (input) => {
|
|
|
686
756
|
skipped: closing
|
|
687
757
|
});
|
|
688
758
|
if (closing) return;
|
|
759
|
+
if (givingUp && stage === 1) return windDown(givingUp, code);
|
|
689
760
|
if (stage === 1 && code === CLISITEF_RESULT.OK) {
|
|
690
761
|
approved = true;
|
|
691
762
|
await input.onApproved(fields);
|
|
@@ -715,7 +786,8 @@ const runClisitefTransaction = (input) => {
|
|
|
715
786
|
message: null
|
|
716
787
|
});
|
|
717
788
|
};
|
|
718
|
-
|
|
789
|
+
/** A failed `onData` handler still owes CliSiTef an answer. */
|
|
790
|
+
const enqueue = (handle, event) => {
|
|
719
791
|
const run = async () => {
|
|
720
792
|
await handle();
|
|
721
793
|
};
|
|
@@ -729,10 +801,11 @@ const runClisitefTransaction = (input) => {
|
|
|
729
801
|
error
|
|
730
802
|
});
|
|
731
803
|
if (closing) return;
|
|
732
|
-
|
|
804
|
+
giveUp({
|
|
733
805
|
...reason,
|
|
734
806
|
error
|
|
735
807
|
});
|
|
808
|
+
if (event && givingUp) await drain(event).catch(abandon);
|
|
736
809
|
});
|
|
737
810
|
};
|
|
738
811
|
const begin = async () => {
|
|
@@ -748,7 +821,7 @@ const runClisitefTransaction = (input) => {
|
|
|
748
821
|
});
|
|
749
822
|
}
|
|
750
823
|
signal.addEventListener("abort", onAbort);
|
|
751
|
-
const handleData = (e) => enqueue(() => onData(e));
|
|
824
|
+
const handleData = (e) => enqueue(() => onData(e), e);
|
|
752
825
|
const handleResult = (e) => {
|
|
753
826
|
enqueue(() => onResult(e));
|
|
754
827
|
};
|
|
@@ -1210,6 +1283,7 @@ const paid = (record) => {
|
|
|
1210
1283
|
*/
|
|
1211
1284
|
const isAmbiguous = (outcome) => {
|
|
1212
1285
|
if (outcome.status === "unresolved") return true;
|
|
1286
|
+
if (outcome.status === "open") return outcome.evidence;
|
|
1213
1287
|
if (!outcome.evidence || outcome.status !== "undone") return false;
|
|
1214
1288
|
const { code } = outcome;
|
|
1215
1289
|
const inconclusive = typeof code === "number" && CLISITEF_INCONCLUSIVE_CODES.has(code);
|
|
@@ -1226,7 +1300,8 @@ const outcomeError = (outcome, attempt) => {
|
|
|
1226
1300
|
provider_code: code
|
|
1227
1301
|
};
|
|
1228
1302
|
if (isAmbiguous(outcome)) return createPaymentError("AMBIGUOUS", extra);
|
|
1229
|
-
|
|
1303
|
+
const cancelled = typeof outcome.code === "number" && CLISITEF_CANCEL_CODES.has(outcome.code);
|
|
1304
|
+
if (outcome.aborted && !cancelled) return createPaymentError("TIMEOUT", extra);
|
|
1230
1305
|
if (outcome.code === null) {
|
|
1231
1306
|
const message = outcome.message ?? void 0;
|
|
1232
1307
|
return createPaymentError("PROVIDER_UNAVAILABLE", {
|
|
@@ -1274,9 +1349,13 @@ const debugContext = (ctx) => {
|
|
|
1274
1349
|
deviceSettings: redactSecrets(ctx.deviceSettings)
|
|
1275
1350
|
};
|
|
1276
1351
|
};
|
|
1277
|
-
/**
|
|
1352
|
+
/**
|
|
1353
|
+
* `review` when the customer may have paid; `approved` when the undo failed;
|
|
1354
|
+
* `started` when the routine never closed, so the next charge recovers it.
|
|
1355
|
+
*/
|
|
1278
1356
|
const closingState = (outcome) => {
|
|
1279
1357
|
if (outcome.status === "unresolved") return "approved";
|
|
1358
|
+
if (outcome.status === "open") return "started";
|
|
1280
1359
|
return isAmbiguous(outcome) ? "review" : "undone";
|
|
1281
1360
|
};
|
|
1282
1361
|
const terminalError = (error) => {
|
|
@@ -1299,7 +1378,10 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1299
1378
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1300
1379
|
const confirmationMs = options.confirmationTimeoutMs ?? 6e4;
|
|
1301
1380
|
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
1381
|
+
const releaseMs = options.releaseTimeoutMs ?? 15e3;
|
|
1302
1382
|
const lock = createLock();
|
|
1383
|
+
/** A charge of this process gave up without `stage 1` (`open`). */
|
|
1384
|
+
let leftOpen = false;
|
|
1303
1385
|
const storageOf = (ctx) => {
|
|
1304
1386
|
return options.storage ?? ctx.storage;
|
|
1305
1387
|
};
|
|
@@ -1316,7 +1398,10 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1316
1398
|
fallback: "TERMINAL_NOT_CONFIGURED"
|
|
1317
1399
|
});
|
|
1318
1400
|
};
|
|
1319
|
-
/**
|
|
1401
|
+
/**
|
|
1402
|
+
* Undoes a transaction a crash, a lost result or an interrupted abort left
|
|
1403
|
+
* pending in CliSiTef. A `review` record stays as is when nothing is pending.
|
|
1404
|
+
*/
|
|
1320
1405
|
const recover = async (storage, record, key = CLISITEF_STORAGE_KEY) => {
|
|
1321
1406
|
const { fiscalCoupon, fiscalDate, time } = record;
|
|
1322
1407
|
const count = client.getQttPendingTransactions;
|
|
@@ -1326,6 +1411,7 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1326
1411
|
record
|
|
1327
1412
|
});
|
|
1328
1413
|
if (pending === null) return record;
|
|
1414
|
+
if (pending === 0 && record.state === "review") return record;
|
|
1329
1415
|
let next;
|
|
1330
1416
|
if (pending > 0) {
|
|
1331
1417
|
const p1 = {
|
|
@@ -1371,26 +1457,58 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1371
1457
|
const isConfigureBusy = (error) => {
|
|
1372
1458
|
return error?.provider_code === "-12";
|
|
1373
1459
|
};
|
|
1374
|
-
/**
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1460
|
+
/**
|
|
1461
|
+
* Ends an iterative routine left open in this process (`-12`). CliSiTef
|
|
1462
|
+
* takes `Continua = -1` only as the answer to an `onData`, so the pending
|
|
1463
|
+
* event and every new one get `abortTransaction(-1)` until the routine's
|
|
1464
|
+
* `stage 1`, which is dropped. No `submitPendingMessages` or `closePinPad`
|
|
1465
|
+
* here: right after an abort they left the native thread hung in production.
|
|
1466
|
+
*/
|
|
1467
|
+
const releaseRoutine = () => {
|
|
1468
|
+
clisitefDebug("pinpad.adapter.release", "routine", { releaseMs });
|
|
1469
|
+
leftOpen = false;
|
|
1470
|
+
const subscriptions = [];
|
|
1471
|
+
let timer;
|
|
1472
|
+
const abort = () => {
|
|
1473
|
+
client.abortTransaction(CLISITEF_ABORT_FLAG).catch(() => 0);
|
|
1474
|
+
};
|
|
1475
|
+
return new Promise((resolve) => {
|
|
1476
|
+
const done = () => {
|
|
1477
|
+
clearTimeout(timer);
|
|
1478
|
+
for (const subscription of subscriptions) subscription.remove();
|
|
1479
|
+
client.invalidate();
|
|
1480
|
+
resolve();
|
|
1481
|
+
};
|
|
1482
|
+
subscriptions.push(client.onData(abort));
|
|
1483
|
+
subscriptions.push(client.onTransactionResult((event) => {
|
|
1484
|
+
if (event.stage === 1) done();
|
|
1485
|
+
}));
|
|
1486
|
+
timer = setTimeout(done, releaseMs);
|
|
1487
|
+
abort();
|
|
1488
|
+
});
|
|
1489
|
+
};
|
|
1490
|
+
/** `startTransaction` refused: a previous routine is still open. */
|
|
1491
|
+
const isStartBusy = (outcome) => {
|
|
1492
|
+
return outcome.status === "declined" && outcome.code === -12 && !outcome.aborted && outcome.fields.size === 0;
|
|
1493
|
+
};
|
|
1494
|
+
/** Open, or flagged for review with the coupon maybe still pending. */
|
|
1495
|
+
const recoverable = (record) => {
|
|
1496
|
+
return isOpen(record) || record.state === "review";
|
|
1380
1497
|
};
|
|
1381
1498
|
/** Configures and resolves the open records of the device, if any. */
|
|
1382
1499
|
const prepare = async (ctx) => {
|
|
1383
1500
|
const settings = readClisitefPinPadSettings(ctx.config, ctx.deviceSettings);
|
|
1501
|
+
if (leftOpen) await releaseRoutine();
|
|
1384
1502
|
let error = await configure(settings);
|
|
1385
1503
|
if (isConfigureBusy(error)) {
|
|
1386
1504
|
clisitefDebug("pinpad.adapter.release", "busy", {});
|
|
1387
|
-
await
|
|
1505
|
+
await releaseRoutine();
|
|
1388
1506
|
error = await configure(settings);
|
|
1389
1507
|
}
|
|
1390
1508
|
const storage = storageOf(ctx);
|
|
1391
1509
|
const resolve = async (key) => {
|
|
1392
1510
|
const found = await readTransaction(storage, key);
|
|
1393
|
-
if (error || !found || !
|
|
1511
|
+
if (error || !found || !recoverable(found)) return found;
|
|
1394
1512
|
return recover(storage, found, key);
|
|
1395
1513
|
};
|
|
1396
1514
|
const record = await resolve(CLISITEF_STORAGE_KEY);
|
|
@@ -1510,11 +1628,17 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1510
1628
|
modality: chosen,
|
|
1511
1629
|
purpose: "charge"
|
|
1512
1630
|
};
|
|
1513
|
-
const
|
|
1631
|
+
const run = () => runClisitefTransaction({
|
|
1514
1632
|
...t1,
|
|
1515
1633
|
...t2,
|
|
1516
1634
|
...t3
|
|
1517
1635
|
});
|
|
1636
|
+
let outcome = await run();
|
|
1637
|
+
if (isStartBusy(outcome)) {
|
|
1638
|
+
await releaseRoutine();
|
|
1639
|
+
if (!await configure(settings)) outcome = await run();
|
|
1640
|
+
}
|
|
1641
|
+
if (outcome.status === "open") leftOpen = true;
|
|
1518
1642
|
const fields = Object.fromEntries(outcome.fields);
|
|
1519
1643
|
clisitefDebug("pinpad.adapter.charge", "outcome", {
|
|
1520
1644
|
...outcome,
|
|
@@ -1734,4 +1858,4 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1734
1858
|
};
|
|
1735
1859
|
};
|
|
1736
1860
|
//#endregion
|
|
1737
|
-
export { CLISITEF_CANCEL_CODES, CLISITEF_CAPTURE_FIELDS, CLISITEF_COMMAND, CLISITEF_CONFIRMATION_TIMEOUT_MS, CLISITEF_DEFAULT_OPERATOR, CLISITEF_FIELD, CLISITEF_INCONCLUSIVE_CODES, CLISITEF_MODALITIES, CLISITEF_NO, CLISITEF_PAYMENT_TIMEOUT_MS, CLISITEF_PINPAD_TYPES, CLISITEF_REFUNDS_KEY, CLISITEF_REFUND_STORAGE_KEY, CLISITEF_REVERSALS_KEY, CLISITEF_STORAGE_KEY, CLISITEF_VOID_FIELD, CLISITEF_YES, clisitefDetails, clisitefLast4, clisitefMetadata, clisitefModalityOf, clisitefPinPadCapabilities, clisitefPinPadDeviceFields, clisitefPinPadErrors, clisitefPinPadPayments, clisitefVoidMetadata, dropReversal, isClisitefModality, isOpen, queueReversal, readClisitefPinPadSettings, readPendingReversals, readRefund, readTransaction, redactSecrets, resolveClisitefMenu, runClisitefTransaction, runClisitefVoid, writeRefund, writeTransaction };
|
|
1861
|
+
export { CLISITEF_CANCEL_CODES, CLISITEF_CAPTURE_FIELDS, CLISITEF_COMMAND, CLISITEF_CONFIRMATION_TIMEOUT_MS, CLISITEF_DEFAULT_OPERATOR, CLISITEF_FIELD, CLISITEF_INCONCLUSIVE_CODES, CLISITEF_MODALITIES, CLISITEF_NO, CLISITEF_PAYMENT_TIMEOUT_MS, CLISITEF_PINPAD_TYPES, CLISITEF_REFUNDS_KEY, CLISITEF_REFUND_STORAGE_KEY, CLISITEF_RELEASE_TIMEOUT_MS, CLISITEF_REVERSALS_KEY, CLISITEF_STORAGE_KEY, CLISITEF_VOID_FIELD, CLISITEF_YES, clisitefDetails, clisitefLast4, clisitefMetadata, clisitefModalityOf, clisitefPinPadCapabilities, clisitefPinPadDeviceFields, clisitefPinPadErrors, clisitefPinPadPayments, clisitefVoidMetadata, dropReversal, isClisitefModality, isOpen, queueReversal, readClisitefPinPadSettings, readPendingReversals, readRefund, readTransaction, redactSecrets, resolveClisitefMenu, runClisitefTransaction, runClisitefVoid, writeRefund, writeTransaction };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fire-spark/payments-clisitef-pinpad-rn",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Fire Spark CliSiTef (Software Express) pinpad adapter for Brazil: debit, credit, voucher and Pix with automatic voids on Android kiosks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@fire-spark/payments": "^0.8.
|
|
21
|
-
"@fire-spark/payments-clisitef-client": "^0.8.
|
|
22
|
-
"@fire-spark/types": "^0.8.
|
|
20
|
+
"@fire-spark/payments": "^0.8.3",
|
|
21
|
+
"@fire-spark/payments-clisitef-client": "^0.8.3",
|
|
22
|
+
"@fire-spark/types": "^0.8.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"tsdown": "^0.22.14",
|