@fire-spark/payments-clisitef-pinpad-rn 0.11.1 → 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 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: errorMessage(error)
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
- client.finishTransaction(p1).catch((error) => {
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
- undo({
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", reason);
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 configure = async (settings) => {
1389
- const code = await client.configure(settings.configure);
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: redactSecrets(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
- if (await client.finishTransaction({
1663
+ trace.call(`finish confirm=0 ${coupon}`);
1664
+ const code = await client.finishTransaction({
1423
1665
  ...p1,
1424
1666
  time
1425
- }).catch(() => -1) !== 0) return record;
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 === 1) done();
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
- const prepared = await prepare(ctx);
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) return failed(empty, reference, 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.11.1",
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/payments": "^0.11.1",
21
- "@fire-spark/payments-clisitef-client": "^0.11.1",
22
- "@fire-spark/types": "^0.11.1"
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"