@fire-spark/payments-pagoplux-card 0.13.3 → 0.14.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
@@ -73,9 +73,20 @@ type PagopluxOtpSession = {
73
73
  tknky: string;
74
74
  tkniv: string;
75
75
  };
76
+ /** Bank challenge of a code 103 with 3DS (API de Pago 1.3 §E-F). */
77
+ type PagopluxChallenge = {
78
+ url: string;
79
+ method: "GET" | "POST";
80
+ parameters: {
81
+ name: string;
82
+ value: string;
83
+ }[];
84
+ version: string | null;
85
+ transaction_id: string | null;
86
+ };
76
87
  /** Answer of the Spark `charge` operation. */
77
88
  type PagopluxChargeOutcome = {
78
- status: "PAID" | "FAILED" | "PENDING" | "OTP_REQUIRED";
89
+ status: "PAID" | "FAILED" | "PENDING" | "OTP_REQUIRED" | "THREE_DS_REQUIRED";
79
90
  code: number | null;
80
91
  description: string | null;
81
92
  transaction_id: string | null;
@@ -90,6 +101,13 @@ type PagopluxChargeOutcome = {
90
101
  installments: number | null;
91
102
  date: string | null;
92
103
  otp: PagopluxOtpSession | null;
104
+ /** Only with `THREE_DS_REQUIRED` (absent from older APIs). */
105
+ challenge?: PagopluxChallenge | null;
106
+ };
107
+ /** Answer of the Spark `three_ds_result` operation. */
108
+ type PagopluxThreeDsResult = {
109
+ tender_status: string;
110
+ outcome: PagopluxChargeOutcome | null;
93
111
  };
94
112
  //#endregion
95
113
  //#region src/crypto.d.ts
@@ -159,7 +177,7 @@ declare const PAGOPLUX_OTP_TRIES = 3;
159
177
  declare const PAGOPLUX_CARD_POLICY: Partial<PaymentPolicy>;
160
178
  //#endregion
161
179
  //#region src/errors.d.ts
162
- /** API de Pago 1.2 §E, keyed by `code` as returned. */
180
+ /** API de Pago 1.3 §H, keyed by `code` as returned. */
163
181
  declare const pagopluxErrors: Readonly<Record<string, import("@fire-spark/payments").PaymentErrorEntry>> & {
164
182
  "1": {
165
183
  code: "INVALID_CREDENTIALS";
@@ -206,4 +224,4 @@ declare const pagopluxErrors: Readonly<Record<string, import("@fire-spark/paymen
206
224
  /** Spark code for a code 2 decline from its message, else `null`. */
207
225
  declare const pagopluxDeclineCode: (message: string | null) => "AMBIGUOUS" | "BATCH_CLOSE_REQUIRED" | "CANCELLED_BY_USER" | "CARD_DECLINED" | "EXPIRED_CARD" | "FINANCING_EXPIRED" | "FINANCING_REJECTED" | "FRAUD_SUSPECTED" | "INSUFFICIENT_BALANCE" | "INSUFFICIENT_FUNDS" | "INVALID_CARD" | "INVALID_CREDENTIALS" | "INVALID_PIN" | "INVALID_REQUEST" | "LIMIT_EXCEEDED" | "MANUAL_REVERSAL_REQUIRED" | "NETWORK_ERROR" | "PROVIDER_UNAVAILABLE" | "REVERSAL_NOT_ALLOWED" | "TERMINAL_BUSY" | "TERMINAL_NEEDS_INIT" | "TERMINAL_NOT_CONFIGURED" | "TERMINAL_UNAVAILABLE" | "TIMEOUT" | "TRANSFER_ALREADY_USED" | "TRANSFER_AMOUNT_MISMATCH" | "TRANSFER_NOT_FOUND" | "UNKNOWN" | "USER_TIMEOUT" | "WALLET_EXPIRED" | "WALLET_REJECTED" | null;
208
226
  //#endregion
209
- export { PAGOPLUX_CARD_POLICY, PAGOPLUX_METHOD, PAGOPLUX_OTP_TRIES, PAGOPLUX_PROVIDER, type PagopluxAddressEntry, type PagopluxBuyerEntry, type PagopluxCardCollector, type PagopluxCardEntry, type PagopluxCardForm, type PagopluxCardFormInput, PagopluxCardOptions, type PagopluxChargeOutcome, type PagopluxEncryptedCard, type PagopluxOtpSession, PagopluxRandom, defaultRandom, encryptPagopluxCard, fromBase64, pagopluxAes, pagopluxCardPayments, pagopluxDeclineCode, pagopluxErrors, pagopluxPublicKeyOf, pagopluxRsa, pagopluxSymmetricKey, parsePagopluxPublicKey, toBase64 };
227
+ export { PAGOPLUX_CARD_POLICY, PAGOPLUX_METHOD, PAGOPLUX_OTP_TRIES, PAGOPLUX_PROVIDER, type PagopluxAddressEntry, type PagopluxBuyerEntry, type PagopluxCardCollector, type PagopluxCardEntry, type PagopluxCardForm, type PagopluxCardFormInput, PagopluxCardOptions, type PagopluxChallenge, type PagopluxChargeOutcome, type PagopluxEncryptedCard, type PagopluxOtpSession, PagopluxRandom, type PagopluxThreeDsResult, defaultRandom, encryptPagopluxCard, fromBase64, pagopluxAes, pagopluxCardPayments, pagopluxDeclineCode, pagopluxErrors, pagopluxPublicKeyOf, pagopluxRsa, pagopluxSymmetricKey, parsePagopluxPublicKey, toBase64 };
package/dist/index.js CHANGED
@@ -181,7 +181,7 @@ const encryptPagopluxCard = (entry, publicKey, random = defaultRandom) => {
181
181
  //#region src/errors.ts
182
182
  const source = "docs/payments/pagoplux/card.md#11-errores";
183
183
  const since = "1.2";
184
- /** API de Pago 1.2 §E, keyed by `code` as returned. */
184
+ /** API de Pago 1.3 §H, keyed by `code` as returned. */
185
185
  const pagopluxErrors = definePaymentErrors({
186
186
  "1": {
187
187
  code: "INVALID_CREDENTIALS",
@@ -427,6 +427,42 @@ const serverError = (ctx, cause) => {
427
427
  attempt
428
428
  });
429
429
  };
430
+ /** A UI timeout or an aborted attempt: the orchestrator maps it. */
431
+ var Passthrough = class extends Error {
432
+ inner;
433
+ constructor(inner) {
434
+ super("passthrough");
435
+ this.inner = inner;
436
+ }
437
+ };
438
+ const isUserTimeout = (cause) => {
439
+ return cause instanceof Error && cause.name === "PaymentUserTimeout";
440
+ };
441
+ /**
442
+ * A question to the customer. A template that rejects (closed dialog) is
443
+ * a cancel; a timeout or an aborted attempt goes to the orchestrator.
444
+ */
445
+ const askUser = async (ctx, call) => {
446
+ try {
447
+ return await call();
448
+ } catch (cause) {
449
+ if (cause instanceof PaymentError) throw cause;
450
+ if (isUserTimeout(cause) || ctx.signal.aborted) throw new Passthrough(cause);
451
+ throw fail("CANCELLED_BY_USER", "The customer closed the prompt");
452
+ }
453
+ };
454
+ /** Bank pages need a moment to land on the 3DS return after the message. */
455
+ const THREE_DS_READS = 4;
456
+ const THREE_DS_READ_GAP_MS = 1500;
457
+ const wait = (ms, signal) => {
458
+ return new Promise((resolve) => {
459
+ const timer = setTimeout(resolve, ms);
460
+ signal.addEventListener("abort", () => {
461
+ clearTimeout(timer);
462
+ resolve();
463
+ });
464
+ });
465
+ };
430
466
  const failedResult = (ctx, error) => {
431
467
  const m1 = {
432
468
  attempt_reference: ctx.attemptReference,
@@ -488,11 +524,12 @@ const pagopluxCardPayments = (options) => {
488
524
  text: "Código incorrecto, intenta de nuevo",
489
525
  kind: "error"
490
526
  });
491
- const code = await ctx.ui.input({
527
+ const prompt = {
492
528
  label: otpLabel,
493
529
  type: "number",
494
530
  maxLength: 8
495
- });
531
+ };
532
+ const code = await askUser(ctx, () => ctx.ui.input(prompt));
496
533
  const otp = {
497
534
  ...session,
498
535
  code: code.trim()
@@ -514,6 +551,75 @@ const pagopluxCardPayments = (options) => {
514
551
  code: 102
515
552
  } : outcome;
516
553
  };
554
+ /** Outcome the 3DS return recorded on the tender, if it arrived. */
555
+ const readThreeDs = async (ctx, reads) => {
556
+ for (let read = 0; read < reads; read++) {
557
+ if (read > 0) await wait(THREE_DS_READ_GAP_MS, ctx.signal);
558
+ if (ctx.signal.aborted) break;
559
+ const answer = await ctx.server.call("three_ds_result", {});
560
+ if (answer?.outcome) return answer.outcome;
561
+ }
562
+ return null;
563
+ };
564
+ /**
565
+ * Code 103 with a bank page (API de Pago 1.3 §E): the template shows it
566
+ * (`ui.challenge`); the bank sends the customer to the Fire Spark 3DS
567
+ * return, which closes the tender on the server; the device then reads
568
+ * that outcome. Closed before the bank answered: it may have charged.
569
+ */
570
+ const withChallenge = async (ctx, challenge) => {
571
+ const show = ctx.ui.challenge;
572
+ if (!show) throw fail("INVALID_REQUEST", "The channel cannot show the 3-D Secure challenge");
573
+ ctx.ui.message({
574
+ text: "Verifica el pago con tu banco…",
575
+ kind: "info"
576
+ });
577
+ const c1 = {
578
+ url: challenge.url,
579
+ method: challenge.method
580
+ };
581
+ const c2 = {
582
+ parameters: challenge.parameters,
583
+ version: challenge.version
584
+ };
585
+ const input = {
586
+ ...c1,
587
+ ...c2,
588
+ signal: ctx.signal
589
+ };
590
+ const shown = await askUser(ctx, () => show.call(ctx.ui, input)).catch((cause) => {
591
+ if (cause instanceof Passthrough) throw cause;
592
+ return { completed: false };
593
+ });
594
+ ctx.ui.message({
595
+ text: "Confirmando pago…",
596
+ kind: "waiting"
597
+ });
598
+ const reads = shown.completed ? THREE_DS_READS : 1;
599
+ const outcome = await readThreeDs(ctx, reads);
600
+ if (outcome) return outcome;
601
+ const pending = {
602
+ code: 103,
603
+ description: "3DS challenge without bank answer",
604
+ transaction_id: challenge.transaction_id
605
+ };
606
+ const e1 = {
607
+ authorization_token: null,
608
+ amount: null,
609
+ card: null
610
+ };
611
+ const e2 = {
612
+ installments: null,
613
+ date: null,
614
+ otp: null
615
+ };
616
+ return {
617
+ status: "PENDING",
618
+ ...pending,
619
+ ...e1,
620
+ ...e2
621
+ };
622
+ };
517
623
  const start = async (ctx) => {
518
624
  const keyPromise = publicKeyFor(ctx);
519
625
  keyPromise.catch(() => void 0);
@@ -550,9 +656,11 @@ const pagopluxCardPayments = (options) => {
550
656
  };
551
657
  try {
552
658
  const first = await charge(ctx, input);
553
- const outcome = first.status === "OTP_REQUIRED" ? await withOtp(ctx, input, first) : first;
659
+ let outcome = first.status === "OTP_REQUIRED" ? await withOtp(ctx, input, first) : first;
660
+ if (outcome.status === "THREE_DS_REQUIRED" && outcome.challenge) outcome = await withChallenge(ctx, outcome.challenge);
554
661
  return resultOf(ctx, outcome);
555
662
  } catch (cause) {
663
+ if (cause instanceof Passthrough) throw cause.inner;
556
664
  if (cause instanceof PaymentError) return failedResult(ctx, cause.error);
557
665
  return failedResult(ctx, serverError(ctx, cause));
558
666
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fire-spark/payments-pagoplux-card",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "description": "Fire Spark PagoPlux card adapter (API de Pago): card encrypted on the device, charged by Spark, OTP for Diners/Interdin/Discover",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -18,8 +18,8 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@noble/ciphers": "^1.3.0",
21
- "@fire-spark/payments": "^0.13.3",
22
- "@fire-spark/types": "^0.13.3"
21
+ "@fire-spark/payments": "^0.14.0",
22
+ "@fire-spark/types": "^0.14.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "tsdown": "^0.22.14",