@fire-spark/payments-clisitef-pinpad-rn 0.11.0 → 0.12.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.
- package/dist/index.d.ts +24 -0
- package/dist/index.js +335 -22
- package/package.json +6 -4
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ declare const clisitefPinPadCapabilities: PaymentAdapterCapabilities;
|
|
|
7
7
|
type ClisitefPinPadOptions = {
|
|
8
8
|
/** Defaults to the shared client bound to the native module. */
|
|
9
9
|
client?: ClisitefClient;
|
|
10
|
+
/**
|
|
11
|
+
* Prints `[CLISITEF_DEBUG]` traces in the console and Android logcat.
|
|
12
|
+
* Off unless set. These lines are not sent to the backend.
|
|
13
|
+
*/
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Sends the `info` logs of a pinpad decline or cancel. Errors are always
|
|
17
|
+
* sent. On unless set to `false`.
|
|
18
|
+
*/
|
|
19
|
+
traces?: boolean;
|
|
10
20
|
/**
|
|
11
21
|
* Device-wide storage for the pending-transaction record. Defaults to
|
|
12
22
|
* `ctx.storage`, which is scoped per payment: a transaction left open by
|
|
@@ -245,6 +255,8 @@ type ClisitefVoidDeps = {
|
|
|
245
255
|
now: () => Date;
|
|
246
256
|
timeZone?: string;
|
|
247
257
|
confirmationTimeoutMs: number;
|
|
258
|
+
/** Gates the `info` decline. Errors are always sent. */
|
|
259
|
+
traces: boolean;
|
|
248
260
|
};
|
|
249
261
|
type ClisitefVoidContext = {
|
|
250
262
|
ui: PaymentUI;
|
|
@@ -308,6 +320,14 @@ declare const readTransaction: (storage: PaymentStorage, key?: string) => Promis
|
|
|
308
320
|
declare const writeTransaction: (storage: PaymentStorage, record: ClisitefTransactionRecord, key?: string) => Promise<void>;
|
|
309
321
|
declare const isOpen: (record: ClisitefTransactionRecord) => boolean;
|
|
310
322
|
//#endregion
|
|
323
|
+
//#region src/trace.d.ts
|
|
324
|
+
type PinpadTrace = {
|
|
325
|
+
call: (body: string) => void;
|
|
326
|
+
result: (body: string) => void;
|
|
327
|
+
note: (body: string) => void;
|
|
328
|
+
detail: () => string;
|
|
329
|
+
};
|
|
330
|
+
//#endregion
|
|
311
331
|
//#region src/transaction.d.ts
|
|
312
332
|
/**
|
|
313
333
|
* `confirmed`: `stage 2` closed with 0. `declined`: failed before the card
|
|
@@ -327,6 +347,8 @@ type ClisitefOutcome = {
|
|
|
327
347
|
evidence: boolean;
|
|
328
348
|
/** Error thrown by `ctx.ui` (e.g. the customer did not answer). */
|
|
329
349
|
error?: unknown;
|
|
350
|
+
/** Native failure attached to the backend log. The charge does not rethrow it. */
|
|
351
|
+
cause?: unknown;
|
|
330
352
|
};
|
|
331
353
|
type ClisitefTransactionInput = {
|
|
332
354
|
client: ClisitefClient;
|
|
@@ -342,6 +364,8 @@ type ClisitefTransactionInput = {
|
|
|
342
364
|
onApproved: (fields: ClisitefFields) => Promise<void>;
|
|
343
365
|
/** Answer for a `READ_*` field without the UI; `null` asks `ui.input`. */
|
|
344
366
|
answerField?: (fieldId: number) => string | null;
|
|
367
|
+
/** Steps of this call. The adapter sends them once, when the call ends. */
|
|
368
|
+
trace?: PinpadTrace;
|
|
345
369
|
};
|
|
346
370
|
/**
|
|
347
371
|
* One CliSiTef transaction: `startTransaction`, the `onData` loop answered
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PaymentError, createPaymentError, definePaymentErrors, toPaymentError } from "@fire-spark/payments";
|
|
2
|
-
import { CLISITEF_ABORT_FLAG, CLISITEF_CONFIRM, CLISITEF_MODALITY, CLISITEF_RESULT, CLISITEF_TIMEOUT_CODE, CLISITEF_VOID_FUNCTION, clisitef, clisitefAdditionalParams, clisitefDebug, clisitefDebugCall, clisitefFiscalDateTime, formatClisitefAmount, parseClisitefMenu } from "@fire-spark/payments-clisitef-client";
|
|
2
|
+
import { CLISITEF_ABORT_FLAG, CLISITEF_CONFIRM, CLISITEF_MODALITY, CLISITEF_RESULT, CLISITEF_TIMEOUT_CODE, CLISITEF_VOID_FUNCTION, clisitef, clisitefAdditionalParams, clisitefDebug, clisitefDebugCall, clisitefFiscalDateTime, formatClisitefAmount, parseClisitefMenu, setClisitefLogs } from "@fire-spark/payments-clisitef-client";
|
|
3
|
+
import { log, logError } from "@fire-spark/core";
|
|
3
4
|
//#region src/constants.ts
|
|
4
5
|
/**
|
|
5
6
|
* `onData` commands handled by the adapter (Fiserv "Tabela de códigos de
|
|
@@ -289,6 +290,103 @@ const clisitefDetails = (metadata) => {
|
|
|
289
290
|
};
|
|
290
291
|
};
|
|
291
292
|
//#endregion
|
|
293
|
+
//#region src/report.ts
|
|
294
|
+
/** Same `log` / `logError` the templates import from `@fire-spark/storefront`. */
|
|
295
|
+
const CHARGE_DID_NOT_START = "CliSiTef charge did not start";
|
|
296
|
+
const CHARGE_DECLINED = "CliSiTef charge declined";
|
|
297
|
+
const CHARGE_DID_NOT_FINISH = "CliSiTef charge did not finish";
|
|
298
|
+
const VOID_DECLINED = "CliSiTef void declined";
|
|
299
|
+
const VOID_DID_NOT_FINISH = "CliSiTef void did not finish";
|
|
300
|
+
const PENDING_RECOVERED = "CliSiTef pending transaction recovered";
|
|
301
|
+
const ROUTINE_RELEASED = "CliSiTef open routine released";
|
|
302
|
+
/** Fixed phrase as the log message; the thrown value keeps its name and stack. */
|
|
303
|
+
const reportedError = (message, error) => {
|
|
304
|
+
const wrapped = new Error(message);
|
|
305
|
+
if (error instanceof Error) {
|
|
306
|
+
wrapped.name = error.name;
|
|
307
|
+
if (error.stack) wrapped.stack = error.stack;
|
|
308
|
+
wrapped.cause = error;
|
|
309
|
+
return wrapped;
|
|
310
|
+
}
|
|
311
|
+
wrapped.cause = error;
|
|
312
|
+
return wrapped;
|
|
313
|
+
};
|
|
314
|
+
/** One backend log. `info` is skipped when `traces` is off. Never throws. */
|
|
315
|
+
const reportPinpad = (input) => {
|
|
316
|
+
if (input.level === "info" && !input.traces) return;
|
|
317
|
+
const options = {
|
|
318
|
+
alias: input.alias,
|
|
319
|
+
status: input.status,
|
|
320
|
+
detail: input.detail
|
|
321
|
+
};
|
|
322
|
+
if (!(input.level === "error" && input.error != null)) {
|
|
323
|
+
log(input.level, input.message, options);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
logError(reportedError(input.message, input.error), options);
|
|
327
|
+
};
|
|
328
|
+
const isTimeout = (outcome) => {
|
|
329
|
+
if (outcome.code === CLISITEF_TIMEOUT_CODE) return true;
|
|
330
|
+
const { error } = outcome;
|
|
331
|
+
if (!(error instanceof Error)) return false;
|
|
332
|
+
return error.name === "ClisitefTimeoutError";
|
|
333
|
+
};
|
|
334
|
+
/** SiTef or the customer answered with a numeric code. */
|
|
335
|
+
const pinpadDecline = (outcome) => {
|
|
336
|
+
return outcome.status === "declined" && typeof outcome.code === "number";
|
|
337
|
+
};
|
|
338
|
+
const finishStatus = (outcome) => {
|
|
339
|
+
const { status } = outcome;
|
|
340
|
+
if (status === "open" || status === "unresolved") return status;
|
|
341
|
+
if (status === "undone") return status;
|
|
342
|
+
return "open";
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* Case 2 (`info`) when the pinpad or the customer closed the charge.
|
|
346
|
+
* Case 3 (`error`) when it stayed open, the undo failed, or a timeout cut it.
|
|
347
|
+
* A confirmed charge sends nothing.
|
|
348
|
+
*/
|
|
349
|
+
const reportChargeOutcome = (traces, trace, outcome) => {
|
|
350
|
+
if (outcome.status === "confirmed") return;
|
|
351
|
+
const answered = pinpadDecline(outcome) && !isTimeout(outcome);
|
|
352
|
+
const quiet = outcome.status === "declined" && !outcome.aborted;
|
|
353
|
+
if (answered || quiet && !isTimeout(outcome)) {
|
|
354
|
+
const body = {
|
|
355
|
+
level: "info",
|
|
356
|
+
alias: "clisitef.charge"
|
|
357
|
+
};
|
|
358
|
+
const text = {
|
|
359
|
+
status: "declined",
|
|
360
|
+
message: CHARGE_DECLINED
|
|
361
|
+
};
|
|
362
|
+
reportPinpad({
|
|
363
|
+
...body,
|
|
364
|
+
...text,
|
|
365
|
+
detail: trace.detail(),
|
|
366
|
+
traces
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (outcome.status === "undone" && !outcome.evidence && !outcome.aborted && !isTimeout(outcome)) return;
|
|
371
|
+
const body = {
|
|
372
|
+
level: "error",
|
|
373
|
+
alias: "clisitef.charge"
|
|
374
|
+
};
|
|
375
|
+
const status = finishStatus(outcome);
|
|
376
|
+
const message = CHARGE_DID_NOT_FINISH;
|
|
377
|
+
const detail = trace.detail();
|
|
378
|
+
const failure = outcome.cause ?? outcome.error;
|
|
379
|
+
const sent = {
|
|
380
|
+
...body,
|
|
381
|
+
status,
|
|
382
|
+
message,
|
|
383
|
+
detail,
|
|
384
|
+
traces,
|
|
385
|
+
error: failure
|
|
386
|
+
};
|
|
387
|
+
reportPinpad(sent);
|
|
388
|
+
};
|
|
389
|
+
//#endregion
|
|
292
390
|
//#region src/reversals.ts
|
|
293
391
|
const readJson = async (storage, key) => {
|
|
294
392
|
const raw = await storage.get(key);
|
|
@@ -338,6 +436,47 @@ const writeRefund = async (storage, attemptReference, result) => {
|
|
|
338
436
|
await storage.set(CLISITEF_REFUNDS_KEY, JSON.stringify(next));
|
|
339
437
|
};
|
|
340
438
|
//#endregion
|
|
439
|
+
//#region src/trace.ts
|
|
440
|
+
/** One in-call trace. Discarded on success; it is not stored on the device. */
|
|
441
|
+
const DETAIL_MAX = 2e3;
|
|
442
|
+
const MARK = "\n…\n";
|
|
443
|
+
/** 14–19 digits. A 13-digit fiscal coupon (`Date.now()`) stays as-is. */
|
|
444
|
+
const PAN = /\d{14,19}/g;
|
|
445
|
+
/** Track 1/2 sentinels. The payload is not a field we record on purpose. */
|
|
446
|
+
const TRACK = /[%;][\dA-Za-z=^]{12,}/g;
|
|
447
|
+
const pad = (value, width) => {
|
|
448
|
+
return String(value).padStart(width, "0");
|
|
449
|
+
};
|
|
450
|
+
/** Local kiosk time of the step, not the time the log is posted. */
|
|
451
|
+
const pinpadStamp = (date) => {
|
|
452
|
+
return `${pad(date.getHours(), 2)}:${pad(date.getMinutes(), 2)}:${pad(date.getSeconds(), 2)}.${pad(date.getMilliseconds(), 3)}`;
|
|
453
|
+
};
|
|
454
|
+
/** PAN and track data only. Amounts, dates and the fiscal coupon stay. */
|
|
455
|
+
const maskCardData = (value) => {
|
|
456
|
+
return value.replace(TRACK, "<track>").replace(PAN, (digits) => {
|
|
457
|
+
return `************${digits.slice(-4)}`;
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
/** Keeps the start and the end when `detail` would pass 2000 characters. */
|
|
461
|
+
const fitDetail = (text) => {
|
|
462
|
+
if (text.length <= DETAIL_MAX) return text;
|
|
463
|
+
const head = Math.ceil((DETAIL_MAX - 3) / 2);
|
|
464
|
+
return `${text.slice(0, head)}${MARK}${text.slice(-998)}`;
|
|
465
|
+
};
|
|
466
|
+
const createPinpadTrace = (now = () => /* @__PURE__ */ new Date()) => {
|
|
467
|
+
const lines = [];
|
|
468
|
+
const push = (body) => {
|
|
469
|
+
const stamp = pinpadStamp(now());
|
|
470
|
+
lines.push(`${stamp} ${maskCardData(body)}`);
|
|
471
|
+
};
|
|
472
|
+
return {
|
|
473
|
+
call: (body) => push(`→ ${body}`),
|
|
474
|
+
result: (body) => push(`← ${body}`),
|
|
475
|
+
note: (body) => push(body),
|
|
476
|
+
detail: () => fitDetail(lines.join("\n"))
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
//#endregion
|
|
341
480
|
//#region src/menu.ts
|
|
342
481
|
const normalize = (label) => {
|
|
343
482
|
return label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase();
|
|
@@ -455,7 +594,27 @@ const redact = (event) => {
|
|
|
455
594
|
* success only when `stage 2` closes with 0. Never rejects.
|
|
456
595
|
*/
|
|
457
596
|
const runClisitefTransaction = (input) => {
|
|
458
|
-
const { client, ui, signal } = input;
|
|
597
|
+
const { client, ui, signal, trace } = input;
|
|
598
|
+
const yesNo = (value) => {
|
|
599
|
+
if (value) return "yes";
|
|
600
|
+
return "no";
|
|
601
|
+
};
|
|
602
|
+
const startLine = () => {
|
|
603
|
+
const { start } = input;
|
|
604
|
+
const modality = `modality=${input.modality}`;
|
|
605
|
+
const fn = `function=${start.modality}`;
|
|
606
|
+
const value = `value=${start.value}`;
|
|
607
|
+
const coupon = `coupon=${start.fiscalCoupon}`;
|
|
608
|
+
const date = `date=${start.fiscalDate}`;
|
|
609
|
+
const time = `time=${start.time}`;
|
|
610
|
+
const head = `start ${modality} ${fn} ${value}`;
|
|
611
|
+
const when = `${coupon} ${date} ${time}`;
|
|
612
|
+
if (!start.restrictions) return `${head} ${when}`;
|
|
613
|
+
return `${head} ${when} restrictions=${start.restrictions}`;
|
|
614
|
+
};
|
|
615
|
+
const finishLine = (confirm) => {
|
|
616
|
+
return `finish confirm=${confirm} coupon=${input.finish.fiscalCoupon}`;
|
|
617
|
+
};
|
|
459
618
|
const fields = /* @__PURE__ */ new Map();
|
|
460
619
|
const subscriptions = [];
|
|
461
620
|
let title = "";
|
|
@@ -487,6 +646,8 @@ const runClisitefTransaction = (input) => {
|
|
|
487
646
|
...reason,
|
|
488
647
|
fieldIds: [...fields.keys()]
|
|
489
648
|
});
|
|
649
|
+
const evidence = `evidence=${yesNo(captured)}`;
|
|
650
|
+
trace?.note(`${evidence} aborted=${yesNo(aborted)}`);
|
|
490
651
|
clearTimeout(watchdog);
|
|
491
652
|
for (const subscription of subscriptions) subscription.remove();
|
|
492
653
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -500,17 +661,22 @@ const runClisitefTransaction = (input) => {
|
|
|
500
661
|
};
|
|
501
662
|
const undo = async (reason) => {
|
|
502
663
|
log("undo (finishTransaction 0)", reason);
|
|
664
|
+
trace?.call(finishLine(CLISITEF_CONFIRM.UNDO));
|
|
503
665
|
try {
|
|
504
666
|
const p1 = {
|
|
505
667
|
confirm: CLISITEF_CONFIRM.UNDO,
|
|
506
668
|
...input.finish
|
|
507
669
|
};
|
|
508
|
-
await client.finishTransaction(p1);
|
|
670
|
+
const code = await client.finishTransaction(p1);
|
|
671
|
+
trace?.result(`finish ${code}`);
|
|
509
672
|
settle("undone", reason);
|
|
510
673
|
} catch (error) {
|
|
674
|
+
trace?.result("finish error");
|
|
675
|
+
const message = errorMessage(error);
|
|
511
676
|
settle("unresolved", {
|
|
512
677
|
...reason,
|
|
513
|
-
message
|
|
678
|
+
message,
|
|
679
|
+
cause: error
|
|
514
680
|
});
|
|
515
681
|
}
|
|
516
682
|
};
|
|
@@ -723,13 +889,19 @@ const runClisitefTransaction = (input) => {
|
|
|
723
889
|
...p1,
|
|
724
890
|
watchdogMs: input.confirmationTimeoutMs
|
|
725
891
|
});
|
|
726
|
-
|
|
892
|
+
trace?.call(finishLine(CLISITEF_CONFIRM.CONFIRM));
|
|
893
|
+
client.finishTransaction(p1).then((code) => trace?.result(`finish ${code}`)).catch((error) => {
|
|
894
|
+
trace?.result("finish error");
|
|
727
895
|
if (closing) return;
|
|
728
896
|
closing = true;
|
|
729
897
|
clearTimeout(watchdog);
|
|
730
|
-
|
|
898
|
+
const reason = {
|
|
731
899
|
code: errorCode(error),
|
|
732
900
|
message: errorMessage(error)
|
|
901
|
+
};
|
|
902
|
+
return undo({
|
|
903
|
+
...reason,
|
|
904
|
+
cause: error
|
|
733
905
|
});
|
|
734
906
|
});
|
|
735
907
|
};
|
|
@@ -755,6 +927,7 @@ const runClisitefTransaction = (input) => {
|
|
|
755
927
|
code,
|
|
756
928
|
skipped: closing
|
|
757
929
|
});
|
|
930
|
+
if (!closing) trace?.result(`stage ${stage} ${code}`);
|
|
758
931
|
if (closing) return;
|
|
759
932
|
if (givingUp && stage === 1) return windDown(givingUp, code);
|
|
760
933
|
if (stage === 1 && code === CLISITEF_RESULT.OK) {
|
|
@@ -827,10 +1000,12 @@ const runClisitefTransaction = (input) => {
|
|
|
827
1000
|
};
|
|
828
1001
|
subscriptions.push(client.onData(handleData));
|
|
829
1002
|
subscriptions.push(client.onTransactionResult(handleResult));
|
|
1003
|
+
trace?.call(startLine());
|
|
830
1004
|
try {
|
|
831
1005
|
const code = await client.startTransaction(input.start);
|
|
832
1006
|
if (code === CLISITEF_RESULT.CONTINUE || closing) return;
|
|
833
1007
|
closing = true;
|
|
1008
|
+
trace?.result(`start ${code}`);
|
|
834
1009
|
settle("declined", {
|
|
835
1010
|
code,
|
|
836
1011
|
message: null
|
|
@@ -838,11 +1013,15 @@ const runClisitefTransaction = (input) => {
|
|
|
838
1013
|
} catch (error) {
|
|
839
1014
|
if (closing) return;
|
|
840
1015
|
closing = true;
|
|
1016
|
+
trace?.result("start error");
|
|
841
1017
|
const reason = {
|
|
842
1018
|
code: errorCode(error),
|
|
843
1019
|
message: errorMessage(error)
|
|
844
1020
|
};
|
|
845
|
-
settle("declined",
|
|
1021
|
+
settle("declined", {
|
|
1022
|
+
...reason,
|
|
1023
|
+
cause: error
|
|
1024
|
+
});
|
|
846
1025
|
}
|
|
847
1026
|
};
|
|
848
1027
|
begin();
|
|
@@ -944,8 +1123,26 @@ const runClisitefVoid = async (deps, ctx, result) => {
|
|
|
944
1123
|
const reference = result.metadata.attempt_reference;
|
|
945
1124
|
const done = await readRefund(ctx.storage, reference);
|
|
946
1125
|
if (done) return done;
|
|
1126
|
+
const trace = createPinpadTrace(deps.now);
|
|
1127
|
+
const voidLog = (level, status, error) => {
|
|
1128
|
+
const body = {
|
|
1129
|
+
level,
|
|
1130
|
+
alias: "clisitef.void",
|
|
1131
|
+
status,
|
|
1132
|
+
message: level === "info" ? VOID_DECLINED : VOID_DID_NOT_FINISH
|
|
1133
|
+
};
|
|
1134
|
+
const detail = trace.detail();
|
|
1135
|
+
reportPinpad({
|
|
1136
|
+
...body,
|
|
1137
|
+
detail,
|
|
1138
|
+
traces: deps.traces,
|
|
1139
|
+
error
|
|
1140
|
+
});
|
|
1141
|
+
};
|
|
947
1142
|
const target = targetOf(result);
|
|
948
1143
|
if (!target) {
|
|
1144
|
+
trace.note(`missing reference=${reference}`);
|
|
1145
|
+
voidLog("error", "unavailable");
|
|
949
1146
|
const error = createPaymentError("MANUAL_REVERSAL_REQUIRED", {
|
|
950
1147
|
message: "Missing CliSiTef void context (host NSU, date, modality)",
|
|
951
1148
|
retryable: false,
|
|
@@ -976,6 +1173,8 @@ const runClisitefVoid = async (deps, ctx, result) => {
|
|
|
976
1173
|
return paid;
|
|
977
1174
|
};
|
|
978
1175
|
if (ctx.unavailable) {
|
|
1176
|
+
trace.note(`unavailable reference=${reference}`);
|
|
1177
|
+
voidLog("error", "unavailable");
|
|
979
1178
|
const error = {
|
|
980
1179
|
...ctx.unavailable,
|
|
981
1180
|
retryable: true
|
|
@@ -1002,6 +1201,9 @@ const runClisitefVoid = async (deps, ctx, result) => {
|
|
|
1002
1201
|
[CLISITEF_VOID_FIELD.ORIGINAL_HOST_NSU]: target.hostNsu
|
|
1003
1202
|
};
|
|
1004
1203
|
if (settings.supervisorPassword) answers[CLISITEF_VOID_FIELD.SUPERVISOR_PASSWORD] = settings.supervisorPassword;
|
|
1204
|
+
const nsu = `nsu=${target.hostNsu}`;
|
|
1205
|
+
const original = `original=${target.transactionDate}`;
|
|
1206
|
+
trace.call(`void ${nsu} ${original}`);
|
|
1005
1207
|
const outcome = await runClisitefTransaction({
|
|
1006
1208
|
client: deps.client,
|
|
1007
1209
|
ui: ctx.ui,
|
|
@@ -1015,7 +1217,8 @@ const runClisitefVoid = async (deps, ctx, result) => {
|
|
|
1015
1217
|
modality: target.modality,
|
|
1016
1218
|
purpose: "void",
|
|
1017
1219
|
onApproved: async () => void 0,
|
|
1018
|
-
answerField: (fieldId) => answers[fieldId] ?? null
|
|
1220
|
+
answerField: (fieldId) => answers[fieldId] ?? null,
|
|
1221
|
+
trace
|
|
1019
1222
|
});
|
|
1020
1223
|
if (outcome.code === -1) deps.client.invalidate();
|
|
1021
1224
|
const voided = clisitefVoidMetadata({
|
|
@@ -1045,6 +1248,8 @@ const runClisitefVoid = async (deps, ctx, result) => {
|
|
|
1045
1248
|
await dropReversal(ctx.storage, reference);
|
|
1046
1249
|
return refunded;
|
|
1047
1250
|
}
|
|
1251
|
+
if (isDefinitive(outcome)) voidLog("info", "declined");
|
|
1252
|
+
else voidLog("error", "queued", outcome.cause ?? outcome.error);
|
|
1048
1253
|
const error = voidError(outcome, ctx.attempt);
|
|
1049
1254
|
const withVoid = {
|
|
1050
1255
|
...result,
|
|
@@ -1374,6 +1579,8 @@ const terminalError = (error) => {
|
|
|
1374
1579
|
* default.
|
|
1375
1580
|
*/
|
|
1376
1581
|
const clisitefPinPadPayments = (options = {}) => {
|
|
1582
|
+
setClisitefLogs(options.debug === true);
|
|
1583
|
+
const traces = options.traces !== false;
|
|
1377
1584
|
const client = options.client ?? clisitef;
|
|
1378
1585
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1379
1586
|
const confirmationMs = options.confirmationTimeoutMs ?? 6e4;
|
|
@@ -1385,11 +1592,41 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1385
1592
|
const storageOf = (ctx) => {
|
|
1386
1593
|
return options.storage ?? ctx.storage;
|
|
1387
1594
|
};
|
|
1388
|
-
const
|
|
1389
|
-
const
|
|
1595
|
+
const chargeStart = (trace, status) => {
|
|
1596
|
+
const body = {
|
|
1597
|
+
level: "error",
|
|
1598
|
+
alias: "clisitef.charge"
|
|
1599
|
+
};
|
|
1600
|
+
const text = {
|
|
1601
|
+
status,
|
|
1602
|
+
message: CHARGE_DID_NOT_START,
|
|
1603
|
+
traces
|
|
1604
|
+
};
|
|
1605
|
+
reportPinpad({
|
|
1606
|
+
...body,
|
|
1607
|
+
...text,
|
|
1608
|
+
detail: trace.detail()
|
|
1609
|
+
});
|
|
1610
|
+
};
|
|
1611
|
+
const configure = async (settings, trace) => {
|
|
1612
|
+
const { terminalNumber } = settings;
|
|
1613
|
+
const { storeCode, serverIp } = settings.configure;
|
|
1614
|
+
const terminal = `terminal=${terminalNumber}`;
|
|
1615
|
+
const store = `store=${storeCode}`;
|
|
1616
|
+
const ip = `ip=${serverIp}`;
|
|
1617
|
+
trace?.call(`configure ${terminal} ${store} ${ip}`);
|
|
1618
|
+
let code;
|
|
1619
|
+
try {
|
|
1620
|
+
code = await client.configure(settings.configure);
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
trace?.result("configure error");
|
|
1623
|
+
throw error;
|
|
1624
|
+
}
|
|
1625
|
+
trace?.result(`configure ${code}`);
|
|
1626
|
+
const shown = redactSecrets(settings);
|
|
1390
1627
|
clisitefDebug("pinpad.adapter.configure", "result", {
|
|
1391
1628
|
code,
|
|
1392
|
-
settings:
|
|
1629
|
+
settings: shown
|
|
1393
1630
|
});
|
|
1394
1631
|
if (code === 0) return null;
|
|
1395
1632
|
client.invalidate();
|
|
@@ -1405,12 +1642,16 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1405
1642
|
const recover = async (storage, record, key = CLISITEF_STORAGE_KEY) => {
|
|
1406
1643
|
const { fiscalCoupon, fiscalDate, time } = record;
|
|
1407
1644
|
const count = client.getQttPendingTransactions;
|
|
1645
|
+
const trace = createPinpadTrace(now);
|
|
1646
|
+
const coupon = `coupon=${fiscalCoupon}`;
|
|
1647
|
+
trace.call(`pending ${coupon} date=${fiscalDate}`);
|
|
1408
1648
|
const pending = await count(fiscalDate, fiscalCoupon).catch(() => null);
|
|
1409
1649
|
clisitefDebug("pinpad.adapter.recover", "pending", {
|
|
1410
1650
|
pending,
|
|
1411
1651
|
record
|
|
1412
1652
|
});
|
|
1413
1653
|
if (pending === null) return record;
|
|
1654
|
+
trace.result(`pending ${pending}`);
|
|
1414
1655
|
if (pending === 0 && record.state === "review") return record;
|
|
1415
1656
|
let next;
|
|
1416
1657
|
if (pending > 0) {
|
|
@@ -1419,10 +1660,13 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1419
1660
|
fiscalCoupon,
|
|
1420
1661
|
fiscalDate
|
|
1421
1662
|
};
|
|
1422
|
-
|
|
1663
|
+
trace.call(`finish confirm=0 ${coupon}`);
|
|
1664
|
+
const code = await client.finishTransaction({
|
|
1423
1665
|
...p1,
|
|
1424
1666
|
time
|
|
1425
|
-
}).catch(() => -1)
|
|
1667
|
+
}).catch(() => -1);
|
|
1668
|
+
trace.result(`finish ${code}`);
|
|
1669
|
+
if (code !== 0) return record;
|
|
1426
1670
|
const error = createPaymentError("PROVIDER_UNAVAILABLE", {
|
|
1427
1671
|
retryable: false,
|
|
1428
1672
|
message: "Pending CliSiTef transaction undone"
|
|
@@ -1451,6 +1695,25 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1451
1695
|
};
|
|
1452
1696
|
}
|
|
1453
1697
|
clisitefDebug("pinpad.adapter.recover", `→ ${next.state}`, next);
|
|
1698
|
+
if (next.state === "undone") {
|
|
1699
|
+
const previous = `previous=${record.state}`;
|
|
1700
|
+
const reference = `reference=${record.attemptReference}`;
|
|
1701
|
+
trace.note(`${previous} ${reference} ${coupon} state=undone`);
|
|
1702
|
+
const body = {
|
|
1703
|
+
level: "error",
|
|
1704
|
+
alias: "clisitef.pending"
|
|
1705
|
+
};
|
|
1706
|
+
const text = {
|
|
1707
|
+
status: "undone",
|
|
1708
|
+
message: PENDING_RECOVERED,
|
|
1709
|
+
traces
|
|
1710
|
+
};
|
|
1711
|
+
reportPinpad({
|
|
1712
|
+
...body,
|
|
1713
|
+
...text,
|
|
1714
|
+
detail: trace.detail()
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1454
1717
|
await writeTransaction(storage, next, key);
|
|
1455
1718
|
return next;
|
|
1456
1719
|
};
|
|
@@ -1467,23 +1730,42 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1467
1730
|
const releaseRoutine = () => {
|
|
1468
1731
|
clisitefDebug("pinpad.adapter.release", "routine", { releaseMs });
|
|
1469
1732
|
leftOpen = false;
|
|
1733
|
+
const trace = createPinpadTrace(now);
|
|
1470
1734
|
const subscriptions = [];
|
|
1471
1735
|
let timer;
|
|
1736
|
+
let closed = false;
|
|
1737
|
+
let stage1 = false;
|
|
1472
1738
|
const abort = () => {
|
|
1473
1739
|
client.abortTransaction(CLISITEF_ABORT_FLAG).catch(() => 0);
|
|
1474
1740
|
};
|
|
1475
1741
|
return new Promise((resolve) => {
|
|
1476
1742
|
const done = () => {
|
|
1743
|
+
if (closed) return;
|
|
1744
|
+
closed = true;
|
|
1477
1745
|
clearTimeout(timer);
|
|
1478
1746
|
for (const subscription of subscriptions) subscription.remove();
|
|
1479
1747
|
client.invalidate();
|
|
1748
|
+
const status = stage1 ? "released" : "release_timeout";
|
|
1749
|
+
if (stage1) trace.result("stage 1");
|
|
1750
|
+
else trace.result("timeout");
|
|
1751
|
+
reportPinpad({
|
|
1752
|
+
level: "error",
|
|
1753
|
+
alias: "clisitef.pending",
|
|
1754
|
+
status,
|
|
1755
|
+
message: ROUTINE_RELEASED,
|
|
1756
|
+
traces,
|
|
1757
|
+
detail: trace.detail()
|
|
1758
|
+
});
|
|
1480
1759
|
resolve();
|
|
1481
1760
|
};
|
|
1482
1761
|
subscriptions.push(client.onData(abort));
|
|
1483
1762
|
subscriptions.push(client.onTransactionResult((event) => {
|
|
1484
|
-
if (event.stage
|
|
1763
|
+
if (event.stage !== 1) return;
|
|
1764
|
+
stage1 = true;
|
|
1765
|
+
done();
|
|
1485
1766
|
}));
|
|
1486
1767
|
timer = setTimeout(done, releaseMs);
|
|
1768
|
+
trace.call("abortTransaction -1");
|
|
1487
1769
|
abort();
|
|
1488
1770
|
});
|
|
1489
1771
|
};
|
|
@@ -1496,14 +1778,14 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1496
1778
|
return isOpen(record) || record.state === "review";
|
|
1497
1779
|
};
|
|
1498
1780
|
/** Configures and resolves the open records of the device, if any. */
|
|
1499
|
-
const prepare = async (ctx) => {
|
|
1781
|
+
const prepare = async (ctx, trace) => {
|
|
1500
1782
|
const settings = readClisitefPinPadSettings(ctx.config, ctx.deviceSettings);
|
|
1501
1783
|
if (leftOpen) await releaseRoutine();
|
|
1502
|
-
let error = await configure(settings);
|
|
1784
|
+
let error = await configure(settings, trace);
|
|
1503
1785
|
if (isConfigureBusy(error)) {
|
|
1504
1786
|
clisitefDebug("pinpad.adapter.release", "busy", {});
|
|
1505
1787
|
await releaseRoutine();
|
|
1506
|
-
error = await configure(settings);
|
|
1788
|
+
error = await configure(settings, trace);
|
|
1507
1789
|
}
|
|
1508
1790
|
const storage = storageOf(ctx);
|
|
1509
1791
|
const resolve = async (key) => {
|
|
@@ -1547,15 +1829,43 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1547
1829
|
const amount = ctx.amount.value;
|
|
1548
1830
|
const reference = ctx.attemptReference;
|
|
1549
1831
|
const empty = { amount };
|
|
1832
|
+
const trace = createPinpadTrace(now);
|
|
1550
1833
|
if (!client.available) {
|
|
1834
|
+
trace.note(`unavailable reference=${reference}`);
|
|
1835
|
+
chargeStart(trace, "unavailable");
|
|
1551
1836
|
const error = createPaymentError("TERMINAL_UNAVAILABLE");
|
|
1552
1837
|
return failed(empty, reference, error);
|
|
1553
1838
|
}
|
|
1554
|
-
|
|
1839
|
+
let prepared;
|
|
1840
|
+
try {
|
|
1841
|
+
prepared = await prepare(ctx, trace);
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
const body = {
|
|
1844
|
+
level: "error",
|
|
1845
|
+
alias: "clisitef.charge"
|
|
1846
|
+
};
|
|
1847
|
+
const text = {
|
|
1848
|
+
status: "configure_failed",
|
|
1849
|
+
message: CHARGE_DID_NOT_START
|
|
1850
|
+
};
|
|
1851
|
+
reportPinpad({
|
|
1852
|
+
...body,
|
|
1853
|
+
...text,
|
|
1854
|
+
detail: trace.detail(),
|
|
1855
|
+
traces,
|
|
1856
|
+
error
|
|
1857
|
+
});
|
|
1858
|
+
throw error;
|
|
1859
|
+
}
|
|
1555
1860
|
const { settings, storage, record: previous } = prepared;
|
|
1556
|
-
if (prepared.error)
|
|
1861
|
+
if (prepared.error) {
|
|
1862
|
+
chargeStart(trace, "configure_failed");
|
|
1863
|
+
return failed(empty, reference, prepared.error);
|
|
1864
|
+
}
|
|
1557
1865
|
if (previous?.attemptReference === reference && previous?.state === "confirmed") return paid(previous);
|
|
1558
1866
|
if (busy([previous, prepared.refund])) {
|
|
1867
|
+
trace.note(`busy reference=${reference}`);
|
|
1868
|
+
chargeStart(trace, "busy");
|
|
1559
1869
|
const error = createPaymentError("TERMINAL_BUSY", { attempt: ctx.attempt });
|
|
1560
1870
|
return failed(empty, reference, error);
|
|
1561
1871
|
}
|
|
@@ -1622,7 +1932,8 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1622
1932
|
};
|
|
1623
1933
|
const t2 = {
|
|
1624
1934
|
confirmationTimeoutMs: confirmationMs,
|
|
1625
|
-
onApproved
|
|
1935
|
+
onApproved,
|
|
1936
|
+
trace
|
|
1626
1937
|
};
|
|
1627
1938
|
const t3 = {
|
|
1628
1939
|
modality: chosen,
|
|
@@ -1636,9 +1947,10 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1636
1947
|
let outcome = await run();
|
|
1637
1948
|
if (isStartBusy(outcome)) {
|
|
1638
1949
|
await releaseRoutine();
|
|
1639
|
-
if (!await configure(settings)) outcome = await run();
|
|
1950
|
+
if (!await configure(settings, trace)) outcome = await run();
|
|
1640
1951
|
}
|
|
1641
1952
|
if (outcome.status === "open") leftOpen = true;
|
|
1953
|
+
reportChargeOutcome(traces, trace, outcome);
|
|
1642
1954
|
const fields = Object.fromEntries(outcome.fields);
|
|
1643
1955
|
clisitefDebug("pinpad.adapter.charge", "outcome", {
|
|
1644
1956
|
...outcome,
|
|
@@ -1673,7 +1985,8 @@ const clisitefPinPadPayments = (options = {}) => {
|
|
|
1673
1985
|
client,
|
|
1674
1986
|
now,
|
|
1675
1987
|
timeZone: options.timeZone,
|
|
1676
|
-
confirmationTimeoutMs: confirmationMs
|
|
1988
|
+
confirmationTimeoutMs: confirmationMs,
|
|
1989
|
+
traces
|
|
1677
1990
|
};
|
|
1678
1991
|
/** Prepares the pinpad (recovering an open charge) and voids `result`. */
|
|
1679
1992
|
const refund = async (ctx, result) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fire-spark/payments-clisitef-pinpad-rn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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,10 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@fire-spark/
|
|
21
|
-
"@fire-spark/payments
|
|
22
|
-
"@fire-spark/
|
|
20
|
+
"@fire-spark/core": "^0.12.0",
|
|
21
|
+
"@fire-spark/payments": "^0.12.0",
|
|
22
|
+
"@fire-spark/payments-clisitef-client": "^0.12.0",
|
|
23
|
+
"@fire-spark/types": "^0.12.0"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"tsdown": "^0.22.14",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"clean": true,
|
|
44
45
|
"deps": {
|
|
45
46
|
"neverBundle": [
|
|
47
|
+
"@fire-spark/core",
|
|
46
48
|
"@fire-spark/payments",
|
|
47
49
|
"@fire-spark/payments-clisitef-client",
|
|
48
50
|
"@fire-spark/types"
|