tempest-react-sdk 0.84.0 → 0.86.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.
Files changed (38) hide show
  1. package/bin/create-tempest-app.mjs +10 -1
  2. package/dist/auth/create-tempest-auth.cjs +1 -1
  3. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  4. package/dist/auth/create-tempest-auth.js +258 -187
  5. package/dist/auth/create-tempest-auth.js.map +1 -1
  6. package/dist/components/Markdown/Markdown.cjs +1 -1
  7. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  8. package/dist/components/Markdown/Markdown.js +29 -28
  9. package/dist/components/Markdown/Markdown.js.map +1 -1
  10. package/dist/components/Markdown/markdown-parse.cjs +6 -5
  11. package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
  12. package/dist/components/Markdown/markdown-parse.js +195 -160
  13. package/dist/components/Markdown/markdown-parse.js.map +1 -1
  14. package/dist/payments/PixCheckout.cjs +1 -1
  15. package/dist/payments/PixCheckout.cjs.map +1 -1
  16. package/dist/payments/PixCheckout.js +1 -1
  17. package/dist/payments/PixCheckout.js.map +1 -1
  18. package/dist/payments/mercado-pago.cjs.map +1 -1
  19. package/dist/payments/mercado-pago.js.map +1 -1
  20. package/dist/payments/outcome.cjs +1 -1
  21. package/dist/payments/outcome.cjs.map +1 -1
  22. package/dist/payments/outcome.js +8 -5
  23. package/dist/payments/outcome.js.map +1 -1
  24. package/dist/payments/use-mercado-pago-card.cjs +1 -1
  25. package/dist/payments/use-mercado-pago-card.cjs.map +1 -1
  26. package/dist/payments/use-mercado-pago-card.js +69 -61
  27. package/dist/payments/use-mercado-pago-card.js.map +1 -1
  28. package/dist/payments/use-pix-charge.cjs +1 -1
  29. package/dist/payments/use-pix-charge.cjs.map +1 -1
  30. package/dist/payments/use-pix-charge.js +36 -28
  31. package/dist/payments/use-pix-charge.js.map +1 -1
  32. package/dist/query/offline-persistence.cjs +1 -1
  33. package/dist/query/offline-persistence.cjs.map +1 -1
  34. package/dist/query/offline-persistence.js +100 -54
  35. package/dist/query/offline-persistence.js.map +1 -1
  36. package/dist/tempest-react-sdk.d.ts +245 -15
  37. package/package.json +7 -1
  38. package/template/package.json +6 -1
@@ -2,19 +2,19 @@ import { useLatestRef as e } from "../hooks/use-latest-ref.js";
2
2
  import { loadMercadoPago as t } from "./mercado-pago.js";
3
3
  import { useCallback as n, useEffect as r, useRef as i, useState as a } from "react";
4
4
  //#region src/payments/use-mercado-pago-card.ts
5
- var o = [
5
+ var o = 6, s = [
6
6
  "cardNumber",
7
7
  "expirationDate",
8
8
  "securityCode"
9
9
  ];
10
- function s(e) {
10
+ function c(e) {
11
11
  let t = Math.trunc(e / 100), n = Math.abs(e % 100);
12
12
  return `${t}.${String(n).padStart(2, "0")}`;
13
13
  }
14
- function c(e) {
14
+ function l(e) {
15
15
  return Math.round(e * 100);
16
16
  }
17
- function l(e) {
17
+ function u(e) {
18
18
  if (e instanceof Error) return e;
19
19
  if (Array.isArray(e)) {
20
20
  let t = e.map((e) => typeof e == "object" && e && "message" in e ? String(e.message) : String(e));
@@ -22,99 +22,107 @@ function l(e) {
22
22
  }
23
23
  return Error(String(e));
24
24
  }
25
- function u(e) {
25
+ function d(e) {
26
26
  if (typeof e == "object" && e && "bin" in e) {
27
27
  let t = e.bin;
28
28
  return typeof t == "string" ? t : "";
29
29
  }
30
30
  return "";
31
31
  }
32
- function d(d) {
33
- let { publicKey: f, amountCents: p, containers: m, locale: h = "pt-BR", fieldOptions: g, load: _ = t } = d, [v, y] = a("loading"), [b, x] = a(null), [S, C] = a(""), [w, T] = a(null), [E, D] = a([]), [O, k] = a(!1), [A, j] = a(null), M = i(null), N = e({
34
- containers: m,
35
- locale: h,
36
- fieldOptions: g,
37
- load: _
32
+ function f(f) {
33
+ let { publicKey: p, amountCents: m, containers: h, locale: g = "pt-BR", fieldOptions: _, load: v = t } = f, [y, b] = a("loading"), [x, S] = a(null), [C, w] = a(""), [T, E] = a(null), [D, O] = a([]), [k, A] = a(!1), [j, M] = a(null), N = i(null), P = e({
34
+ containers: h,
35
+ locale: g,
36
+ fieldOptions: _,
37
+ load: v
38
38
  });
39
- return r(() => {
39
+ r(() => {
40
40
  let e = !1, t = [];
41
- y("loading"), x(null);
42
- let n = N.current;
41
+ b("loading"), S(null), w("");
42
+ let n = P.current;
43
43
  return n.load().then((r) => {
44
44
  if (e) return;
45
- let i = new r(f, { locale: n.locale });
46
- for (let e of o) {
45
+ let i = new r(p, { locale: n.locale });
46
+ for (let e of s) {
47
47
  let r = i.fields.create(e, n.fieldOptions?.[e]);
48
- r.mount(n.containers[e]), t.push(r), e === "cardNumber" && r.on("binChange", (e) => C(u(e)));
48
+ r.mount(n.containers[e]), t.push(r), e === "cardNumber" && r.on("binChange", (e) => w(d(e)));
49
49
  }
50
- M.current = i, y("ready");
50
+ N.current = i, b("ready");
51
51
  }).catch((t) => {
52
- e || (x(l(t)), y("error"));
52
+ e || (S(u(t)), b("error"));
53
53
  }), () => {
54
- e = !0, M.current = null;
54
+ e = !0, N.current = null;
55
55
  for (let e of t) try {
56
56
  e.unmount();
57
57
  } catch {}
58
58
  };
59
- }, [f, N]), r(() => {
60
- let e = M.current;
61
- if (T(null), D([]), j(null), e === null || S.length < 6 || v !== "ready") {
62
- k(!1);
59
+ }, [p, P]), r(() => {
60
+ let e = N.current;
61
+ if (E(null), O([]), M(null), e === null || C.length < o || y !== "ready") {
62
+ A(!1);
63
63
  return;
64
64
  }
65
65
  let t = !1;
66
- return k(!0), e.getInstallments({
67
- amount: s(p),
68
- bin: S
66
+ return A(!0), e.getInstallments({
67
+ amount: c(m),
68
+ bin: C
69
69
  }).then((e) => {
70
70
  if (t) return;
71
71
  let n = e[0];
72
72
  if (n === void 0) throw Error("Mercado Pago returned no installments for this card.");
73
- T(n.payment_method_id), D(n.payer_costs.map((e) => ({
73
+ E({
74
+ bin: C,
75
+ paymentMethodId: n.payment_method_id
76
+ }), O(n.payer_costs.map((e) => ({
74
77
  installments: Number(e.installments),
75
78
  recommendedMessage: e.recommended_message,
76
- installmentAmountCents: c(e.installment_amount),
77
- totalAmountCents: c(e.total_amount)
79
+ installmentAmountCents: l(e.installment_amount),
80
+ totalAmountCents: l(e.total_amount)
78
81
  })));
79
82
  }).catch((e) => {
80
- t || j(l(e));
83
+ t || M(u(e));
81
84
  }).finally(() => {
82
- t || k(!1);
85
+ t || A(!1);
83
86
  }), () => {
84
87
  t = !0;
85
88
  };
86
89
  }, [
87
- S,
88
- p,
89
- v
90
- ]), {
91
- status: v,
92
- error: b,
93
- paymentMethodId: w,
94
- installmentOptions: E,
95
- installmentsLoading: O,
96
- installmentsError: A,
97
- tokenize: n(async (e) => {
98
- let t = M.current;
99
- if (t === null || v !== "ready") throw Error("The card fields are not ready yet.");
100
- if (w === null) throw Error("The card brand has not been identified yet.");
101
- if (!E.some((t) => t.installments === e.installments)) throw Error(`${e.installments} installments is not an option for this card and amount.`);
102
- let n = { cardholderName: e.cardholderName };
103
- return e.identificationType !== void 0 && (n.identificationType = e.identificationType), e.identificationNumber !== void 0 && (n.identificationNumber = e.identificationNumber), {
104
- token: (await t.fields.createCardToken(n).catch((e) => {
105
- throw l(e);
106
- })).id,
107
- payment_method_id: w,
108
- installments: e.installments
109
- };
110
- }, [
111
- v,
112
- w,
113
- E
114
- ])
90
+ C,
91
+ m,
92
+ y
93
+ ]);
94
+ let F = n(async (e) => {
95
+ let t = N.current;
96
+ if (t === null || y !== "ready") throw Error("The card fields are not ready yet.");
97
+ if (T === null) throw Error("The card brand has not been identified yet.");
98
+ if (!D.some((t) => t.installments === e.installments)) throw Error(`${e.installments} installments is not an option for this card and amount.`);
99
+ let n = { cardholderName: e.cardholderName };
100
+ e.identificationType !== void 0 && (n.identificationType = e.identificationType), e.identificationNumber !== void 0 && (n.identificationNumber = e.identificationNumber);
101
+ let r = await t.fields.createCardToken(n).catch((e) => {
102
+ throw u(e);
103
+ }), i = r.first_six_digits;
104
+ if (typeof i == "string" && i !== T.bin.slice(0, o)) throw Error("The card changed after the installments were quoted. Try again.");
105
+ return {
106
+ token: r.id,
107
+ payment_method_id: T.paymentMethodId,
108
+ installments: e.installments
109
+ };
110
+ }, [
111
+ y,
112
+ T,
113
+ D
114
+ ]);
115
+ return {
116
+ status: y,
117
+ error: x,
118
+ paymentMethodId: T?.paymentMethodId ?? null,
119
+ installmentOptions: D,
120
+ installmentsLoading: k,
121
+ installmentsError: j,
122
+ tokenize: F
115
123
  };
116
124
  }
117
125
  //#endregion
118
- export { d as useMercadoPagoCard };
126
+ export { f as useMercadoPagoCard };
119
127
 
120
128
  //# sourceMappingURL=use-mercado-pago-card.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-mercado-pago-card.js","names":["FIELD_TYPES","centsToDecimal","whole","cents","rest","toCents","amount","toError","reason","parts","item","readBin","payload","bin","useMercadoPagoCard","loadMercadoPago","options","status","setStatus","useState","error","setError","setBin","paymentMethodId","setPaymentMethodId","installmentOptions","setInstallmentOptions","installmentsLoading","setInstallmentsLoading","installmentsError","setInstallmentsError","mpRef","useRef","setupRef","useLatestRef","useEffect","cancelled","mounted","setup","mp","MercadoPago","publicKey","type","field","stale","amountCents","entry","entries","cost","useCallback","option","input","data"],"sources":["../../src/payments/use-mercado-pago-card.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { loadMercadoPago } from \"./mercado-pago\";\nimport type {\n MercadoPagoConstructor,\n MercadoPagoFieldOptions,\n MercadoPagoFieldType,\n MercadoPagoLike,\n} from \"./mercado-pago\";\n\n/** Element ids the three Secure Fields mount into. */\nexport interface MercadoPagoCardContainers {\n cardNumber: string;\n expirationDate: string;\n securityCode: string;\n}\n\n/** Options for {@link useMercadoPagoCard}. */\nexport interface UseMercadoPagoCardOptions {\n /**\n * The application's **Public Key** — never the access token. It is meant to\n * be public: it can only tokenize, and the token is useless without the\n * access token that stays on the backend.\n */\n publicKey: string;\n /** The price in cents, used to quote installments. */\n amountCents: number;\n /** Ids of the empty elements the card number, expiry and CVV iframes mount into. */\n containers: MercadoPagoCardContainers;\n /** MercadoPago.js locale. Default `\"pt-BR\"`. */\n locale?: string;\n /** Per-field options (placeholder, inline style). */\n fieldOptions?: Partial<Record<MercadoPagoFieldType, MercadoPagoFieldOptions>>;\n /** Loads the constructor. Default {@link loadMercadoPago}; tests pass a fake. */\n load?: () => Promise<MercadoPagoConstructor>;\n}\n\n/** One installment choice, in the shape a `<Select>` needs. */\nexport interface CardInstallmentOption {\n /** Number of installments — the value to submit. */\n installments: number;\n /** The provider's own text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. Show it verbatim. */\n recommendedMessage: string;\n /** Each installment, interest included, in cents. */\n installmentAmountCents: number;\n /** What the payer owes in total, interest included, in cents. */\n totalAmountCents: number;\n}\n\n/** What the non-card part of the form supplies when tokenizing. */\nexport interface CardTokenizeInput {\n /** Name printed on the card. */\n cardholderName: string;\n /** Installments chosen from {@link UseMercadoPagoCardResult.installmentOptions}. */\n installments: number;\n /** Document type (`\"CPF\"`, `\"CNPJ\"`). */\n identificationType?: string;\n /** Document number, digits only. */\n identificationNumber?: string;\n}\n\n/**\n * Everything the backend needs to charge the card — and nothing more.\n *\n * Maps onto the backend's `CardChargeRequest` as `card_token: token` plus the\n * other two fields unchanged. No card number, expiry or CVV: those never left\n * Mercado Pago's iframes.\n */\nexport interface CardTokenResult {\n /** Single-use card token. */\n token: string;\n /** Card brand as Mercado Pago names it (`visa`, `master`, …). */\n payment_method_id: string;\n /** Installments chosen. */\n installments: number;\n}\n\n/** Lifecycle of the Secure Fields. */\nexport type MercadoPagoCardStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Return value of {@link useMercadoPagoCard}. */\nexport interface UseMercadoPagoCardResult {\n /** `\"ready\"` once all three fields are mounted. */\n status: MercadoPagoCardStatus;\n /** Why loading or mounting failed. */\n error: Error | null;\n /** Brand detected from the BIN, or `null` until the payer types enough digits. */\n paymentMethodId: string | null;\n /** Installment choices for the current BIN and amount; empty until known. */\n installmentOptions: CardInstallmentOption[];\n /** `true` while the installments for a new BIN are being fetched. */\n installmentsLoading: boolean;\n /** Why the installment lookup failed, if it did. */\n installmentsError: Error | null;\n /**\n * Ask Mercado Pago for a card token. Throws when the fields are not ready,\n * the BIN has not been identified, or `installments` is not one of the\n * offered options — each of which would otherwise surface later as an\n * opaque `400` from the backend.\n */\n tokenize: (input: CardTokenizeInput) => Promise<CardTokenResult>;\n}\n\nconst FIELD_TYPES: readonly MercadoPagoFieldType[] = [\n \"cardNumber\",\n \"expirationDate\",\n \"securityCode\",\n];\n\n/**\n * The amount as MercadoPago.js wants it: a decimal string, built from integer\n * cents without going through a float.\n */\nfunction centsToDecimal(cents: number): string {\n const whole = Math.trunc(cents / 100);\n const rest = Math.abs(cents % 100);\n return `${whole}.${String(rest).padStart(2, \"0\")}`;\n}\n\nfunction toCents(amount: number): number {\n return Math.round(amount * 100);\n}\n\n/**\n * MercadoPago.js rejects with an array of `{ message }` on a validation error,\n * not an `Error`; normalise so callers can rely on `.message`.\n */\nfunction toError(reason: unknown): Error {\n if (reason instanceof Error) return reason;\n if (Array.isArray(reason)) {\n const parts = reason.map((item: unknown) =>\n typeof item === \"object\" && item !== null && \"message\" in item\n ? String(item.message)\n : String(item),\n );\n return new Error(parts.join(\"; \"));\n }\n return new Error(String(reason));\n}\n\nfunction readBin(payload: unknown): string {\n if (typeof payload === \"object\" && payload !== null && \"bin\" in payload) {\n const bin = payload.bin;\n return typeof bin === \"string\" ? bin : \"\";\n }\n return \"\";\n}\n\n/**\n * Card tokenization with MercadoPago.js **Secure Fields**, plus installments by BIN.\n *\n * The card number, expiry and CVV are typed into iframes served by Mercado Pago,\n * so they never exist in this page's DOM, in React state, in a callback argument\n * or in a log line — not even transiently. That is the property the PCI DSS\n * scoping depends on: a page that renders its own `<input>` for the number and\n * calls `createCardToken` with it has handled cardholder data and is in the\n * heavier SAQ A-EP scope, however quickly it discards the value. The backend\n * never sees the card either: it receives the token, the brand and the\n * installments, which is exactly `CardChargeRequest`.\n *\n * The only digits the hook learns are the BIN (first 6–8), which Mercado Pago's\n * `binChange` event exposes precisely so the page can quote installments; it is\n * used for the lookup and not returned. Installment options come from the\n * provider with its own `recommended_message`, because interest starts at 2x\n * (measured in the sandbox) and the total the payer sees has to be the\n * provider's number, not one recomputed here.\n *\n * The containers must be in the DOM when the hook's effect runs — render the\n * three empty elements in the same component. The fields are created once per\n * `publicKey`; changing the containers, locale or loader afterwards does not\n * remount them.\n *\n * @tempest-limits empty-catch — `unmount()` throws when MercadoPago.js already\n * tore the iframe down (a container removed by React first). The cleanup has\n * nothing left to undo in that case and nowhere useful to report it.\n *\n * @param options - Public key, amount, container ids and an injectable loader.\n * @returns Field status, brand, installment options and `tokenize`.\n *\n * @example\n * const card = useMercadoPagoCard({\n * publicKey: import.meta.env.VITE_MP_PUBLIC_KEY,\n * amountCents: 10964,\n * containers: { cardNumber: \"mp-number\", expirationDate: \"mp-expiry\", securityCode: \"mp-cvv\" },\n * });\n * const { token, payment_method_id, installments } = await card.tokenize({\n * cardholderName: \"APRO\",\n * installments: 1,\n * identificationType: \"CPF\",\n * identificationNumber: \"12345678909\",\n * });\n */\nexport function useMercadoPagoCard(options: UseMercadoPagoCardOptions): UseMercadoPagoCardResult {\n const {\n publicKey,\n amountCents,\n containers,\n locale = \"pt-BR\",\n fieldOptions,\n load = loadMercadoPago,\n } = options;\n const [status, setStatus] = useState<MercadoPagoCardStatus>(\"loading\");\n const [error, setError] = useState<Error | null>(null);\n const [bin, setBin] = useState(\"\");\n const [paymentMethodId, setPaymentMethodId] = useState<string | null>(null);\n const [installmentOptions, setInstallmentOptions] = useState<CardInstallmentOption[]>([]);\n const [installmentsLoading, setInstallmentsLoading] = useState(false);\n const [installmentsError, setInstallmentsError] = useState<Error | null>(null);\n const mpRef = useRef<MercadoPagoLike | null>(null);\n const setupRef = useLatestRef({ containers, locale, fieldOptions, load });\n\n useEffect(() => {\n let cancelled = false;\n const mounted: { unmount(): unknown }[] = [];\n setStatus(\"loading\");\n setError(null);\n const setup = setupRef.current;\n setup\n .load()\n .then((MercadoPago) => {\n if (cancelled) return;\n const mp = new MercadoPago(publicKey, { locale: setup.locale });\n for (const type of FIELD_TYPES) {\n const field = mp.fields.create(type, setup.fieldOptions?.[type]);\n field.mount(setup.containers[type]);\n mounted.push(field);\n if (type === \"cardNumber\")\n field.on(\"binChange\", (payload) => setBin(readBin(payload)));\n }\n mpRef.current = mp;\n setStatus(\"ready\");\n })\n .catch((reason: unknown) => {\n if (cancelled) return;\n setError(toError(reason));\n setStatus(\"error\");\n });\n return () => {\n cancelled = true;\n mpRef.current = null;\n for (const field of mounted) {\n try {\n field.unmount();\n } catch {\n /* empty */\n }\n }\n };\n }, [publicKey, setupRef]);\n\n useEffect(() => {\n const mp = mpRef.current;\n setPaymentMethodId(null);\n setInstallmentOptions([]);\n setInstallmentsError(null);\n if (mp === null || bin.length < 6 || status !== \"ready\") {\n setInstallmentsLoading(false);\n return;\n }\n let stale = false;\n setInstallmentsLoading(true);\n mp.getInstallments({ amount: centsToDecimal(amountCents), bin })\n .then((entries) => {\n if (stale) return;\n const entry = entries[0];\n if (entry === undefined)\n throw new Error(\"Mercado Pago returned no installments for this card.\");\n setPaymentMethodId(entry.payment_method_id);\n setInstallmentOptions(\n entry.payer_costs.map((cost) => ({\n installments: Number(cost.installments),\n recommendedMessage: cost.recommended_message,\n installmentAmountCents: toCents(cost.installment_amount),\n totalAmountCents: toCents(cost.total_amount),\n })),\n );\n })\n .catch((reason: unknown) => {\n if (!stale) setInstallmentsError(toError(reason));\n })\n .finally(() => {\n if (!stale) setInstallmentsLoading(false);\n });\n return () => {\n stale = true;\n };\n }, [bin, amountCents, status]);\n\n const tokenize = useCallback(\n async (input: CardTokenizeInput): Promise<CardTokenResult> => {\n const mp = mpRef.current;\n if (mp === null || status !== \"ready\")\n throw new Error(\"The card fields are not ready yet.\");\n if (paymentMethodId === null)\n throw new Error(\"The card brand has not been identified yet.\");\n if (!installmentOptions.some((option) => option.installments === input.installments)) {\n throw new Error(\n `${input.installments} installments is not an option for this card and amount.`,\n );\n }\n const data: {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n } = {\n cardholderName: input.cardholderName,\n };\n if (input.identificationType !== undefined)\n data.identificationType = input.identificationType;\n if (input.identificationNumber !== undefined)\n data.identificationNumber = input.identificationNumber;\n const token = await mp.fields.createCardToken(data).catch((reason: unknown) => {\n throw toError(reason);\n });\n return {\n token: token.id,\n payment_method_id: paymentMethodId,\n installments: input.installments,\n };\n },\n [status, paymentMethodId, installmentOptions],\n );\n\n return {\n status,\n error,\n paymentMethodId,\n installmentOptions,\n installmentsLoading,\n installmentsError,\n tokenize,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-mercado-pago-card.js","names":["BIN_PREFIX","FIELD_TYPES","centsToDecimal","whole","cents","rest","toCents","amount","toError","reason","parts","item","readBin","payload","bin","useMercadoPagoCard","loadMercadoPago","options","status","setStatus","useState","error","setError","setBin","quote","setQuote","installmentOptions","setInstallmentOptions","installmentsLoading","setInstallmentsLoading","installmentsError","setInstallmentsError","mpRef","useRef","setupRef","useLatestRef","useEffect","cancelled","mounted","setup","mp","MercadoPago","publicKey","type","field","stale","amountCents","entry","entries","cost","tokenize","useCallback","option","input","data","token","firstSix"],"sources":["../../src/payments/use-mercado-pago-card.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { loadMercadoPago } from \"./mercado-pago\";\nimport type {\n MercadoPagoConstructor,\n MercadoPagoFieldOptions,\n MercadoPagoFieldType,\n MercadoPagoLike,\n} from \"./mercado-pago\";\n\n/** Element ids the three Secure Fields mount into. */\nexport interface MercadoPagoCardContainers {\n cardNumber: string;\n expirationDate: string;\n securityCode: string;\n}\n\n/** Options for {@link useMercadoPagoCard}. */\nexport interface UseMercadoPagoCardOptions {\n /**\n * The application's **Public Key** — never the access token. It is meant to\n * be public: it can only tokenize, and the token is useless without the\n * access token that stays on the backend.\n */\n publicKey: string;\n /** The price in cents, used to quote installments. */\n amountCents: number;\n /** Ids of the empty elements the card number, expiry and CVV iframes mount into. */\n containers: MercadoPagoCardContainers;\n /** MercadoPago.js locale. Default `\"pt-BR\"`. */\n locale?: string;\n /** Per-field options (placeholder, inline style). */\n fieldOptions?: Partial<Record<MercadoPagoFieldType, MercadoPagoFieldOptions>>;\n /** Loads the constructor. Default {@link loadMercadoPago}; tests pass a fake. */\n load?: () => Promise<MercadoPagoConstructor>;\n}\n\n/** One installment choice, in the shape a `<Select>` needs. */\nexport interface CardInstallmentOption {\n /** Number of installments — the value to submit. */\n installments: number;\n /** The provider's own text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. Show it verbatim. */\n recommendedMessage: string;\n /** Each installment, interest included, in cents. */\n installmentAmountCents: number;\n /** What the payer owes in total, interest included, in cents. */\n totalAmountCents: number;\n}\n\n/** What the non-card part of the form supplies when tokenizing. */\nexport interface CardTokenizeInput {\n /** Name printed on the card. */\n cardholderName: string;\n /** Installments chosen from {@link UseMercadoPagoCardResult.installmentOptions}. */\n installments: number;\n /** Document type (`\"CPF\"`, `\"CNPJ\"`). */\n identificationType?: string;\n /** Document number, digits only. */\n identificationNumber?: string;\n}\n\n/**\n * Everything the backend needs to charge the card — and nothing more.\n *\n * Maps onto the backend's `CardChargeRequest` as `card_token: token` plus the\n * other two fields unchanged. No card number, expiry or CVV: those never left\n * Mercado Pago's iframes.\n */\nexport interface CardTokenResult {\n /** Single-use card token. */\n token: string;\n /** Card brand as Mercado Pago names it (`visa`, `master`, …). */\n payment_method_id: string;\n /** Installments chosen. */\n installments: number;\n}\n\n/** Lifecycle of the Secure Fields. */\nexport type MercadoPagoCardStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Return value of {@link useMercadoPagoCard}. */\nexport interface UseMercadoPagoCardResult {\n /** `\"ready\"` once all three fields are mounted. */\n status: MercadoPagoCardStatus;\n /** Why loading or mounting failed. */\n error: Error | null;\n /** Brand detected from the BIN, or `null` until the payer types enough digits. */\n paymentMethodId: string | null;\n /** Installment choices for the current BIN and amount; empty until known. */\n installmentOptions: CardInstallmentOption[];\n /** `true` while the installments for a new BIN are being fetched. */\n installmentsLoading: boolean;\n /** Why the installment lookup failed, if it did. */\n installmentsError: Error | null;\n /**\n * Ask Mercado Pago for a card token. Throws when the fields are not ready,\n * the BIN has not been identified, or `installments` is not one of the\n * offered options — each of which would otherwise surface later as an\n * opaque `400` from the backend. Also throws, after the token is created,\n * when the token's `first_six_digits` is not the BIN the brand and\n * installments were quoted for: the payer changed the card number after the\n * quote, and charging it would send one card with another card's brand.\n */\n tokenize: (input: CardTokenizeInput) => Promise<CardTokenResult>;\n}\n\nconst BIN_PREFIX = 6;\n\nconst FIELD_TYPES: readonly MercadoPagoFieldType[] = [\n \"cardNumber\",\n \"expirationDate\",\n \"securityCode\",\n];\n\n/**\n * The amount as MercadoPago.js wants it: a decimal string, built from integer\n * cents without going through a float.\n */\nfunction centsToDecimal(cents: number): string {\n const whole = Math.trunc(cents / 100);\n const rest = Math.abs(cents % 100);\n return `${whole}.${String(rest).padStart(2, \"0\")}`;\n}\n\nfunction toCents(amount: number): number {\n return Math.round(amount * 100);\n}\n\n/**\n * MercadoPago.js rejects with an array of `{ message }` on a validation error,\n * not an `Error`; normalise so callers can rely on `.message`.\n */\nfunction toError(reason: unknown): Error {\n if (reason instanceof Error) return reason;\n if (Array.isArray(reason)) {\n const parts = reason.map((item: unknown) =>\n typeof item === \"object\" && item !== null && \"message\" in item\n ? String(item.message)\n : String(item),\n );\n return new Error(parts.join(\"; \"));\n }\n return new Error(String(reason));\n}\n\nfunction readBin(payload: unknown): string {\n if (typeof payload === \"object\" && payload !== null && \"bin\" in payload) {\n const bin = payload.bin;\n return typeof bin === \"string\" ? bin : \"\";\n }\n return \"\";\n}\n\n/**\n * Card tokenization with MercadoPago.js **Secure Fields**, plus installments by BIN.\n *\n * The card number, expiry and CVV are typed into iframes served by Mercado Pago,\n * so they never exist in this page's DOM, in React state, in a callback argument\n * or in a log line — not even transiently. That is the property the PCI DSS\n * scoping depends on: a page that renders its own `<input>` for the number and\n * calls `createCardToken` with it has handled cardholder data and is in the\n * heavier SAQ A-EP scope, however quickly it discards the value. The backend\n * never sees the card either: it receives the token, the brand and the\n * installments, which is exactly `CardChargeRequest`.\n *\n * The only digits the hook learns are the BIN (first 6–8), which Mercado Pago's\n * `binChange` event exposes precisely so the page can quote installments; it is\n * used for the lookup and not returned. Installment options come from the\n * provider with its own `recommended_message`, because interest starts at 2x\n * (measured in the sandbox) and the total the payer sees has to be the\n * provider's number, not one recomputed here.\n *\n * The containers must be in the DOM when the hook's effect runs — render the\n * three empty elements in the same component. The fields are created once per\n * `publicKey`; changing the containers, locale or loader afterwards does not\n * remount them. A new `publicKey` remounts empty fields and forgets the previous\n * BIN, brand and quote with them.\n *\n * `tokenize` binds the token to the quote: Mercado Pago's token carries\n * `first_six_digits`, and a token whose digits are not the quoted BIN is\n * refused. `binChange` is debounced inside the iframe, so a payer who swaps the\n * card number and submits at once would otherwise send the new card with the\n * old card's brand and installments. A token response without\n * `first_six_digits` is not refused — there is nothing to compare — and the\n * backend and Mercado Pago still validate the brand on the charge.\n *\n * @tempest-limits empty-catch — `unmount()` throws when MercadoPago.js already\n * tore the iframe down (a container removed by React first). The cleanup has\n * nothing left to undo in that case and nowhere useful to report it.\n *\n * @param options - Public key, amount, container ids and an injectable loader.\n * @returns Field status, brand, installment options and `tokenize`.\n *\n * @example\n * const card = useMercadoPagoCard({\n * publicKey: import.meta.env.VITE_MP_PUBLIC_KEY,\n * amountCents: 10964,\n * containers: { cardNumber: \"mp-number\", expirationDate: \"mp-expiry\", securityCode: \"mp-cvv\" },\n * });\n * const { token, payment_method_id, installments } = await card.tokenize({\n * cardholderName: \"APRO\",\n * installments: 1,\n * identificationType: \"CPF\",\n * identificationNumber: \"12345678909\",\n * });\n */\nexport function useMercadoPagoCard(options: UseMercadoPagoCardOptions): UseMercadoPagoCardResult {\n const {\n publicKey,\n amountCents,\n containers,\n locale = \"pt-BR\",\n fieldOptions,\n load = loadMercadoPago,\n } = options;\n const [status, setStatus] = useState<MercadoPagoCardStatus>(\"loading\");\n const [error, setError] = useState<Error | null>(null);\n const [bin, setBin] = useState(\"\");\n const [quote, setQuote] = useState<{ bin: string; paymentMethodId: string } | null>(null);\n const [installmentOptions, setInstallmentOptions] = useState<CardInstallmentOption[]>([]);\n const [installmentsLoading, setInstallmentsLoading] = useState(false);\n const [installmentsError, setInstallmentsError] = useState<Error | null>(null);\n const mpRef = useRef<MercadoPagoLike | null>(null);\n const setupRef = useLatestRef({ containers, locale, fieldOptions, load });\n\n useEffect(() => {\n let cancelled = false;\n const mounted: { unmount(): unknown }[] = [];\n setStatus(\"loading\");\n setError(null);\n setBin(\"\");\n const setup = setupRef.current;\n setup\n .load()\n .then((MercadoPago) => {\n if (cancelled) return;\n const mp = new MercadoPago(publicKey, { locale: setup.locale });\n for (const type of FIELD_TYPES) {\n const field = mp.fields.create(type, setup.fieldOptions?.[type]);\n field.mount(setup.containers[type]);\n mounted.push(field);\n if (type === \"cardNumber\")\n field.on(\"binChange\", (payload) => setBin(readBin(payload)));\n }\n mpRef.current = mp;\n setStatus(\"ready\");\n })\n .catch((reason: unknown) => {\n if (cancelled) return;\n setError(toError(reason));\n setStatus(\"error\");\n });\n return () => {\n cancelled = true;\n mpRef.current = null;\n for (const field of mounted) {\n try {\n field.unmount();\n } catch {\n /* empty */\n }\n }\n };\n }, [publicKey, setupRef]);\n\n useEffect(() => {\n const mp = mpRef.current;\n setQuote(null);\n setInstallmentOptions([]);\n setInstallmentsError(null);\n if (mp === null || bin.length < BIN_PREFIX || status !== \"ready\") {\n setInstallmentsLoading(false);\n return;\n }\n let stale = false;\n setInstallmentsLoading(true);\n mp.getInstallments({ amount: centsToDecimal(amountCents), bin })\n .then((entries) => {\n if (stale) return;\n const entry = entries[0];\n if (entry === undefined)\n throw new Error(\"Mercado Pago returned no installments for this card.\");\n setQuote({ bin, paymentMethodId: entry.payment_method_id });\n setInstallmentOptions(\n entry.payer_costs.map((cost) => ({\n installments: Number(cost.installments),\n recommendedMessage: cost.recommended_message,\n installmentAmountCents: toCents(cost.installment_amount),\n totalAmountCents: toCents(cost.total_amount),\n })),\n );\n })\n .catch((reason: unknown) => {\n if (!stale) setInstallmentsError(toError(reason));\n })\n .finally(() => {\n if (!stale) setInstallmentsLoading(false);\n });\n return () => {\n stale = true;\n };\n }, [bin, amountCents, status]);\n\n const tokenize = useCallback(\n async (input: CardTokenizeInput): Promise<CardTokenResult> => {\n const mp = mpRef.current;\n if (mp === null || status !== \"ready\")\n throw new Error(\"The card fields are not ready yet.\");\n if (quote === null) throw new Error(\"The card brand has not been identified yet.\");\n if (!installmentOptions.some((option) => option.installments === input.installments)) {\n throw new Error(\n `${input.installments} installments is not an option for this card and amount.`,\n );\n }\n const data: {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n } = {\n cardholderName: input.cardholderName,\n };\n if (input.identificationType !== undefined)\n data.identificationType = input.identificationType;\n if (input.identificationNumber !== undefined)\n data.identificationNumber = input.identificationNumber;\n const token = await mp.fields.createCardToken(data).catch((reason: unknown) => {\n throw toError(reason);\n });\n const firstSix = token.first_six_digits;\n if (typeof firstSix === \"string\" && firstSix !== quote.bin.slice(0, BIN_PREFIX)) {\n throw new Error(\"The card changed after the installments were quoted. Try again.\");\n }\n return {\n token: token.id,\n payment_method_id: quote.paymentMethodId,\n installments: input.installments,\n };\n },\n [status, quote, installmentOptions],\n );\n\n return {\n status,\n error,\n paymentMethodId: quote?.paymentMethodId ?? null,\n installmentOptions,\n installmentsLoading,\n installmentsError,\n tokenize,\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../hooks/use-latest-ref.cjs"),t=require("./types.cjs");let n=require("react");function r(e,n){return n.provider_charge_id===e.provider_charge_id&&!(t.isFinalPaymentStatus(e.status)&&!t.isFinalPaymentStatus(n.status))}function i(i,a={}){let{intervalMs:o=5e3}=a,[s,c]=(0,n.useState)({charge:i,error:null}),[l,u]=(0,n.useState)(i.provider_charge_id),d=e.useLatestRef({refresh:a.refresh,subscribe:a.subscribe}),f=l===i.provider_charge_id?s.charge:i;l!==i.provider_charge_id&&(u(i.provider_charge_id),c({charge:i,error:null}));let p=f.provider_charge_id,m=t.isFinalPaymentStatus(f.status);return(0,n.useEffect)(()=>{let{subscribe:e}=d.current;if(e!==void 0)return e(e=>c(t=>r(t.charge,e)?{charge:e,error:null}:t))},[p,d]),(0,n.useEffect)(()=>{if(m||d.current.refresh===void 0)return;let e=new AbortController,t,n=()=>{let i=d.current.refresh;i!==void 0&&i(e.signal).then(t=>{e.signal.aborted||c(e=>r(e.charge,t)?{charge:t,error:null}:e)}).catch(t=>{if(e.signal.aborted)return;let n=t instanceof Error?t:Error(String(t));c(e=>({...e,error:n}))}).finally(()=>{e.signal.aborted||(t=setTimeout(n,o))})};return t=setTimeout(n,o),()=>{e.abort(),t!==void 0&&clearTimeout(t)}},[p,m,o,d]),{charge:f,error:l===i.provider_charge_id?s.error:null}}exports.usePixCharge=i;
1
+ const e=require("../hooks/use-latest-ref.cjs"),t=require("./types.cjs");let n=require("react");var r=new Set([`paid`,`refunded`,`charged_back`]);function i(e){return JSON.stringify([e.provider,e.provider_charge_id])}function a(e,n){return n.provider!==e.provider||n.provider_charge_id!==e.provider_charge_id?!1:e.status===`paid`?r.has(n.status):!(t.isFinalPaymentStatus(e.status)&&!t.isFinalPaymentStatus(n.status))}function o(r,o={}){let{intervalMs:s=5e3}=o,[c,l]=(0,n.useState)({charge:r,error:null}),u=i(r),[d,f]=(0,n.useState)(u),p=e.useLatestRef({refresh:o.refresh,subscribe:o.subscribe}),m=d===u?c.charge:r;d!==u&&(f(u),l({charge:r,error:null}));let h=i(m),g=t.isFinalPaymentStatus(m.status);return(0,n.useEffect)(()=>{let{subscribe:e}=p.current;if(e!==void 0)return e(e=>l(t=>a(t.charge,e)?{charge:e,error:null}:t))},[h,p]),(0,n.useEffect)(()=>{if(g||p.current.refresh===void 0)return;let e=new AbortController,t,n=()=>{let r=p.current.refresh;r!==void 0&&r(e.signal).then(t=>{e.signal.aborted||l(e=>a(e.charge,t)?{charge:t,error:null}:e)}).catch(t=>{if(e.signal.aborted)return;let n=t instanceof Error?t:Error(String(t));l(e=>({...e,error:n}))}).finally(()=>{e.signal.aborted||(t=setTimeout(n,s))})};return t=setTimeout(n,s),()=>{e.abort(),t!==void 0&&clearTimeout(t)}},[h,g,s,p]),{charge:m,error:d===u?c.error:null}}exports.usePixCharge=o;
2
2
  //# sourceMappingURL=use-pix-charge.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-pix-charge.cjs","names":["isFinalPaymentStatus","useState","useLatestRef","require_use_latest_ref","require_types","react","accepts","next","current","usePixCharge","options","state","setState","initial","followed","setFollowed","sourcesRef","charge","id","final","subscribe","controller","timer","tick","refresh","error","reason","intervalMs"],"sources":["../../src/payments/use-pix-charge.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { isFinalPaymentStatus } from \"./types\";\nimport type { PixCharge } from \"./types\";\n\n/** Options for {@link usePixCharge}. */\nexport interface UsePixChargeOptions {\n /**\n * Re-reads the charge from **your backend** — which re-reads it from the\n * provider. Called every `intervalMs` until the charge is final. The signal\n * aborts when the component unmounts or the charge changes.\n */\n refresh?: (signal: AbortSignal) => Promise<PixCharge>;\n /**\n * Pushes updates instead of polling — an SSE stream, a WebSocket. Receives\n * the emitter and returns its own cleanup. Can be combined with `refresh`.\n */\n subscribe?: (emit: (charge: PixCharge) => void) => () => void;\n /** Poll period in ms. Default `5000`. */\n intervalMs?: number;\n}\n\n/** Return value of {@link usePixCharge}. */\nexport interface UsePixChargeResult {\n /** The latest charge the backend reported. */\n charge: PixCharge;\n /** The last `refresh` failure, cleared by the next success. Polling continues after it. */\n error: Error | null;\n}\n\n/**\n * Whether an update may replace the charge on screen.\n *\n * Rejects an update for another charge (a stale response after the app\n * switched charges) and one that moves a final charge back to a non-final\n * state — an out-of-order delivery when polling and a push channel race, which\n * would otherwise flash \"aguardando pagamento\" under a payer who already paid.\n */\nfunction accepts(current: PixCharge, next: PixCharge): boolean {\n if (next.provider_charge_id !== current.provider_charge_id) return false;\n return !(isFinalPaymentStatus(current.status) && !isFinalPaymentStatus(next.status));\n}\n\n/**\n * Follow a Pix charge until the backend says it is final.\n *\n * The browser never decides that a Pix was paid: there is no client-side signal\n * that could prove it, and a screen that infers \"paid\" from a countdown or a\n * copy click is a screen that ships goods for free. This hook only relays what\n * the app's `refresh`/`subscribe` report, and stops polling once the status is\n * final (`paid`, `expired`, `cancelled`, `refunded`, `charged_back`, `failed`).\n *\n * Polling is sequential — the next request starts `intervalMs` after the\n * previous one settled — so a slow backend is never hit by overlapping reads.\n * A failed read keeps the last good charge and is exposed as `error`; the poll\n * goes on, because a dropped request is not a payment state.\n *\n * Passing a charge with a different `provider_charge_id` restarts the follow.\n *\n * @param initial - The charge as created.\n * @param options - `refresh`, `subscribe` and the poll period.\n * @returns The latest charge and the last read error.\n *\n * @example\n * const { charge } = usePixCharge(created, {\n * refresh: (signal) => api.get<PixCharge>(`/api/pix/${created.provider_charge_id}`, { signal }),\n * });\n * return <PixCheckout charge={charge} />;\n */\nexport function usePixCharge(\n initial: PixCharge,\n options: UsePixChargeOptions = {},\n): UsePixChargeResult {\n const { intervalMs = 5000 } = options;\n const [state, setState] = useState<{ charge: PixCharge; error: Error | null }>({\n charge: initial,\n error: null,\n });\n const [followed, setFollowed] = useState(initial.provider_charge_id);\n const sourcesRef = useLatestRef({ refresh: options.refresh, subscribe: options.subscribe });\n const charge = followed === initial.provider_charge_id ? state.charge : initial;\n\n if (followed !== initial.provider_charge_id) {\n setFollowed(initial.provider_charge_id);\n setState({ charge: initial, error: null });\n }\n\n const id = charge.provider_charge_id;\n const final = isFinalPaymentStatus(charge.status);\n\n useEffect(() => {\n const { subscribe } = sourcesRef.current;\n if (subscribe === undefined) return undefined;\n return subscribe((next) =>\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n ),\n );\n }, [id, sourcesRef]);\n\n useEffect(() => {\n if (final || sourcesRef.current.refresh === undefined) return undefined;\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const tick = (): void => {\n const refresh = sourcesRef.current.refresh;\n if (refresh === undefined) return;\n refresh(controller.signal)\n .then((next) => {\n if (controller.signal.aborted) return;\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n );\n })\n .catch((reason: unknown) => {\n if (controller.signal.aborted) return;\n const error = reason instanceof Error ? reason : new Error(String(reason));\n setState((current) => ({ ...current, error }));\n })\n .finally(() => {\n if (!controller.signal.aborted) timer = setTimeout(tick, intervalMs);\n });\n };\n timer = setTimeout(tick, intervalMs);\n return () => {\n controller.abort();\n if (timer !== undefined) clearTimeout(timer);\n };\n }, [id, final, intervalMs, sourcesRef]);\n\n return { charge, error: followed === initial.provider_charge_id ? state.error : null };\n}\n"],"mappings":"A,+FAwCA,SAASM,EAAQ,EAAoB,EAA0B,CAE3D,OADIC,EAAK,qBAAuBC,EAAQ,oBACjC,EAAER,EAAAA,qBAAqBQ,EAAQ,MAAM,GAAK,CAACR,EAAAA,qBAAqBO,EAAK,MAAM,EACtF,CA4BA,SAAgBE,EACZ,EACA,EAA+B,CAAC,EACd,CAClB,GAAM,CAAE,aAAa,KAASC,EACxB,CAACC,EAAOC,IAAA,EAAYX,EAAAA,SAAAA,CAAqD,CAC3E,OAAQY,EACR,MAAO,IACX,CAAC,EACK,CAACC,EAAUC,IAAA,EAAed,EAAAA,SAAAA,CAASY,EAAQ,kBAAkB,EAC7DG,EAAad,EAAAA,aAAa,CAAE,QAASQ,EAAQ,QAAS,UAAWA,EAAQ,SAAU,CAAC,EACpFO,EAASH,IAAaD,EAAQ,mBAAqBF,EAAM,OAASE,EAEpEC,IAAaD,EAAQ,qBACrBE,EAAYF,EAAQ,kBAAkB,EACtCD,EAAS,CAAE,OAAQC,EAAS,MAAO,IAAK,CAAC,GAG7C,IAAMK,EAAKD,EAAO,mBACZE,EAAQnB,EAAAA,qBAAqBiB,EAAO,MAAM,EA0ChD,OAxCA,EAAAZ,EAAA,UAAA,KAAgB,CACZ,GAAM,CAAE,aAAcW,EAAW,QACjC,GAAII,IAAc,IAAA,GAClB,OAAOA,EAAW,GACdR,EAAU,GACNN,EAAQE,EAAQ,OAAQD,CAAI,EAAI,CAAE,OAAQA,EAAM,MAAO,IAAK,EAAIC,CACpE,CACJ,CACJ,EAAG,CAACU,EAAIF,CAAU,CAAC,GAEnB,EAAAX,EAAA,UAAA,KAAgB,CACZ,GAAIc,GAASH,EAAW,QAAQ,UAAY,IAAA,GAAW,OACvD,IAAMK,EAAa,IAAI,gBACnBC,EACEC,MAAmB,CACrB,IAAMC,EAAUR,EAAW,QAAQ,QAC/BQ,IAAY,IAAA,IAChBA,EAAQH,EAAW,MAAM,CAAC,CACrB,KAAM,GAAS,CACRA,EAAW,OAAO,SACtBT,EAAU,GACNN,EAAQE,EAAQ,OAAQD,CAAI,EAAI,CAAE,OAAQA,EAAM,MAAO,IAAK,EAAIC,CACpE,CACJ,CAAC,CAAC,CACD,MAAO,GAAoB,CACxB,GAAIa,EAAW,OAAO,QAAS,OAC/B,IAAMI,EAAQC,aAAkB,MAAQA,EAAa,MAAM,OAAOA,CAAM,CAAC,EACzEd,EAAU,IAAa,CAAE,GAAGJ,EAAS,OAAM,EAAE,CACjD,CAAC,CAAC,CACD,YAAc,CACNa,EAAW,OAAO,UAAS,EAAQ,WAAWE,EAAMI,CAAU,EACvE,CAAC,CACT,EAEA,MADA,GAAQ,WAAWJ,EAAMI,CAAU,MACtB,CACTN,EAAW,MAAM,EACbC,IAAU,IAAA,IAAW,aAAaA,CAAK,CAC/C,CACJ,EAAG,CAACJ,EAAIC,EAAOQ,EAAYX,CAAU,CAAC,EAE/B,CAAE,SAAQ,MAAOF,IAAaD,EAAQ,mBAAqBF,EAAM,MAAQ,IAAK,CACzF,C"}
1
+ {"version":3,"file":"use-pix-charge.cjs","names":["isFinalPaymentStatus","useState","useLatestRef","require_use_latest_ref","require_types","react","AFTER_PAID","chargeKey","charge","accepts","next","current","usePixCharge","options","state","setState","initial","initialKey","followed","setFollowed","sourcesRef","id","final","subscribe","controller","timer","tick","refresh","error","reason","intervalMs"],"sources":["../../src/payments/use-pix-charge.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { isFinalPaymentStatus } from \"./types\";\nimport type { PaymentStatus, PixCharge } from \"./types\";\n\n/** Options for {@link usePixCharge}. */\nexport interface UsePixChargeOptions {\n /**\n * Re-reads the charge from **your backend** — which re-reads it from the\n * provider. Called every `intervalMs` until the charge is final. The signal\n * aborts when the component unmounts or the charge changes.\n */\n refresh?: (signal: AbortSignal) => Promise<PixCharge>;\n /**\n * Pushes updates instead of polling — an SSE stream, a WebSocket. Receives\n * the emitter and returns its own cleanup. Can be combined with `refresh`.\n */\n subscribe?: (emit: (charge: PixCharge) => void) => () => void;\n /** Poll period in ms. Default `5000`. */\n intervalMs?: number;\n}\n\n/** Return value of {@link usePixCharge}. */\nexport interface UsePixChargeResult {\n /** The latest charge the backend reported. */\n charge: PixCharge;\n /** The last `refresh` failure, cleared by the next success. Polling continues after it. */\n error: Error | null;\n}\n\nconst AFTER_PAID: ReadonlySet<PaymentStatus> = new Set<PaymentStatus>([\n \"paid\",\n \"refunded\",\n \"charged_back\",\n]);\n\n/** Identity of a charge: its id is only unique within its provider. */\nfunction chargeKey(charge: PixCharge): string {\n return JSON.stringify([charge.provider, charge.provider_charge_id]);\n}\n\n/**\n * Whether an update may replace the charge on screen.\n *\n * Rejects an update for another charge — a different `provider` or\n * `provider_charge_id`, since ids are only unique within one provider — which is\n * a stale response after the app switched charges. Rejects one that moves a\n * final charge back to a non-final state: an out-of-order delivery when polling\n * and a push channel race, which would otherwise flash \"aguardando pagamento\"\n * under a payer who already paid. And once the charge is `paid`, the only\n * states it may move to are `refunded` and `charged_back`: a late `expired`,\n * `cancelled` or `failed` is the same race, and showing it would tell a payer\n * who already paid to \"gerar um novo Pix\". The reverse stays open — an\n * `expired` charge may still become `paid`, because a Pix sent in the last\n * second can settle after the deadline.\n */\nfunction accepts(current: PixCharge, next: PixCharge): boolean {\n if (next.provider !== current.provider) return false;\n if (next.provider_charge_id !== current.provider_charge_id) return false;\n if (current.status === \"paid\") return AFTER_PAID.has(next.status);\n return !(isFinalPaymentStatus(current.status) && !isFinalPaymentStatus(next.status));\n}\n\n/**\n * Follow a Pix charge until the backend says it is final.\n *\n * The browser never decides that a Pix was paid: there is no client-side signal\n * that could prove it, and a screen that infers \"paid\" from a countdown or a\n * copy click is a screen that ships goods for free. This hook only relays what\n * the app's `refresh`/`subscribe` report, and stops polling once the status is\n * final (`paid`, `expired`, `cancelled`, `refunded`, `charged_back`, `failed`).\n *\n * Polling is sequential — the next request starts `intervalMs` after the\n * previous one settled — so a slow backend is never hit by overlapping reads.\n * A failed read keeps the last good charge and is exposed as `error`; the poll\n * goes on, because a dropped request is not a payment state.\n *\n * An update for another `provider` or `provider_charge_id` is ignored, and a\n * `paid` charge only moves on to `refunded` or `charged_back` — a late\n * `expired` never replaces it. Passing a charge with a different `provider` or\n * `provider_charge_id` restarts the follow.\n *\n * @param initial - The charge as created.\n * @param options - `refresh`, `subscribe` and the poll period.\n * @returns The latest charge and the last read error.\n *\n * @example\n * const { charge } = usePixCharge(created, {\n * refresh: (signal) => api.get<PixCharge>(`/api/pix/${created.provider_charge_id}`, { signal }),\n * });\n * return <PixCheckout charge={charge} />;\n */\nexport function usePixCharge(\n initial: PixCharge,\n options: UsePixChargeOptions = {},\n): UsePixChargeResult {\n const { intervalMs = 5000 } = options;\n const [state, setState] = useState<{ charge: PixCharge; error: Error | null }>({\n charge: initial,\n error: null,\n });\n const initialKey = chargeKey(initial);\n const [followed, setFollowed] = useState(initialKey);\n const sourcesRef = useLatestRef({ refresh: options.refresh, subscribe: options.subscribe });\n const charge = followed === initialKey ? state.charge : initial;\n\n if (followed !== initialKey) {\n setFollowed(initialKey);\n setState({ charge: initial, error: null });\n }\n\n const id = chargeKey(charge);\n const final = isFinalPaymentStatus(charge.status);\n\n useEffect(() => {\n const { subscribe } = sourcesRef.current;\n if (subscribe === undefined) return undefined;\n return subscribe((next) =>\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n ),\n );\n }, [id, sourcesRef]);\n\n useEffect(() => {\n if (final || sourcesRef.current.refresh === undefined) return undefined;\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const tick = (): void => {\n const refresh = sourcesRef.current.refresh;\n if (refresh === undefined) return;\n refresh(controller.signal)\n .then((next) => {\n if (controller.signal.aborted) return;\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n );\n })\n .catch((reason: unknown) => {\n if (controller.signal.aborted) return;\n const error = reason instanceof Error ? reason : new Error(String(reason));\n setState((current) => ({ ...current, error }));\n })\n .finally(() => {\n if (!controller.signal.aborted) timer = setTimeout(tick, intervalMs);\n });\n };\n timer = setTimeout(tick, intervalMs);\n return () => {\n controller.abort();\n if (timer !== undefined) clearTimeout(timer);\n };\n }, [id, final, intervalMs, sourcesRef]);\n\n return { charge, error: followed === initialKey ? state.error : null };\n}\n"],"mappings":"A,+FAgCA,IAAMM,EAAyC,IAAI,IAAmB,CAClE,OACA,WACA,cACJ,CAAC,EAGD,SAASC,EAAU,EAA2B,CAC1C,OAAO,KAAK,UAAU,CAACC,EAAO,SAAUA,EAAO,kBAAkB,CAAC,CACtE,CAiBA,SAASC,EAAQ,EAAoB,EAA0B,CAI3D,OAHIC,EAAK,WAAaC,EAAQ,UAC1BD,EAAK,qBAAuBC,EAAQ,mBAA2B,GAC/DA,EAAQ,SAAW,OAAeL,EAAW,IAAII,EAAK,MAAM,EACzD,EAAEV,EAAAA,qBAAqBW,EAAQ,MAAM,GAAK,CAACX,EAAAA,qBAAqBU,EAAK,MAAM,EACtF,CA+BA,SAAgBE,EACZ,EACA,EAA+B,CAAC,EACd,CAClB,GAAM,CAAE,aAAa,KAASC,EACxB,CAACC,EAAOC,IAAA,EAAYd,EAAAA,SAAAA,CAAqD,CAC3E,OAAQe,EACR,MAAO,IACX,CAAC,EACKC,EAAaV,EAAUS,CAAO,EAC9B,CAACE,EAAUC,IAAA,EAAelB,EAAAA,SAAAA,CAASgB,CAAU,EAC7CG,EAAalB,EAAAA,aAAa,CAAE,QAASW,EAAQ,QAAS,UAAWA,EAAQ,SAAU,CAAC,EACpFL,EAASU,IAAaD,EAAaH,EAAM,OAASE,EAEpDE,IAAaD,IACbE,EAAYF,CAAU,EACtBF,EAAS,CAAE,OAAQC,EAAS,MAAO,IAAK,CAAC,GAG7C,IAAMK,EAAKd,EAAUC,CAAM,EACrBc,EAAQtB,EAAAA,qBAAqBQ,EAAO,MAAM,EA0ChD,OAxCA,EAAAH,EAAA,UAAA,KAAgB,CACZ,GAAM,CAAE,aAAce,EAAW,QACjC,GAAIG,IAAc,IAAA,GAClB,OAAOA,EAAW,GACdR,EAAU,GACNN,EAAQE,EAAQ,OAAQD,CAAI,EAAI,CAAE,OAAQA,EAAM,MAAO,IAAK,EAAIC,CACpE,CACJ,CACJ,EAAG,CAACU,EAAID,CAAU,CAAC,GAEnB,EAAAf,EAAA,UAAA,KAAgB,CACZ,GAAIiB,GAASF,EAAW,QAAQ,UAAY,IAAA,GAAW,OACvD,IAAMI,EAAa,IAAI,gBACnBC,EACEC,MAAmB,CACrB,IAAMC,EAAUP,EAAW,QAAQ,QAC/BO,IAAY,IAAA,IAChBA,EAAQH,EAAW,MAAM,CAAC,CACrB,KAAM,GAAS,CACRA,EAAW,OAAO,SACtBT,EAAU,GACNN,EAAQE,EAAQ,OAAQD,CAAI,EAAI,CAAE,OAAQA,EAAM,MAAO,IAAK,EAAIC,CACpE,CACJ,CAAC,CAAC,CACD,MAAO,GAAoB,CACxB,GAAIa,EAAW,OAAO,QAAS,OAC/B,IAAMI,EAAQC,aAAkB,MAAQA,EAAa,MAAM,OAAOA,CAAM,CAAC,EACzEd,EAAU,IAAa,CAAE,GAAGJ,EAAS,OAAM,EAAE,CACjD,CAAC,CAAC,CACD,YAAc,CACNa,EAAW,OAAO,UAAS,EAAQ,WAAWE,EAAMI,CAAU,EACvE,CAAC,CACT,EAEA,MADA,GAAQ,WAAWJ,EAAMI,CAAU,MACtB,CACTN,EAAW,MAAM,EACbC,IAAU,IAAA,IAAW,aAAaA,CAAK,CAC/C,CACJ,EAAG,CAACJ,EAAIC,EAAOQ,EAAYV,CAAU,CAAC,EAE/B,CAAE,SAAQ,MAAOF,IAAaD,EAAaH,EAAM,MAAQ,IAAK,CACzE,C"}
@@ -2,62 +2,70 @@ import { useLatestRef as e } from "../hooks/use-latest-ref.js";
2
2
  import { isFinalPaymentStatus as t } from "./types.js";
3
3
  import { useEffect as n, useState as r } from "react";
4
4
  //#region src/payments/use-pix-charge.ts
5
- function i(e, n) {
6
- return n.provider_charge_id === e.provider_charge_id && !(t(e.status) && !t(n.status));
5
+ var i = /* @__PURE__ */ new Set([
6
+ "paid",
7
+ "refunded",
8
+ "charged_back"
9
+ ]);
10
+ function a(e) {
11
+ return JSON.stringify([e.provider, e.provider_charge_id]);
7
12
  }
8
- function a(a, o = {}) {
9
- let { intervalMs: s = 5e3 } = o, [c, l] = r({
10
- charge: a,
13
+ function o(e, n) {
14
+ return n.provider !== e.provider || n.provider_charge_id !== e.provider_charge_id ? !1 : e.status === "paid" ? i.has(n.status) : !(t(e.status) && !t(n.status));
15
+ }
16
+ function s(i, s = {}) {
17
+ let { intervalMs: c = 5e3 } = s, [l, u] = r({
18
+ charge: i,
11
19
  error: null
12
- }), [u, d] = r(a.provider_charge_id), f = e({
13
- refresh: o.refresh,
14
- subscribe: o.subscribe
15
- }), p = u === a.provider_charge_id ? c.charge : a;
16
- u !== a.provider_charge_id && (d(a.provider_charge_id), l({
17
- charge: a,
20
+ }), d = a(i), [f, p] = r(d), m = e({
21
+ refresh: s.refresh,
22
+ subscribe: s.subscribe
23
+ }), h = f === d ? l.charge : i;
24
+ f !== d && (p(d), u({
25
+ charge: i,
18
26
  error: null
19
27
  }));
20
- let m = p.provider_charge_id, h = t(p.status);
28
+ let g = a(h), _ = t(h.status);
21
29
  return n(() => {
22
- let { subscribe: e } = f.current;
23
- if (e !== void 0) return e((e) => l((t) => i(t.charge, e) ? {
30
+ let { subscribe: e } = m.current;
31
+ if (e !== void 0) return e((e) => u((t) => o(t.charge, e) ? {
24
32
  charge: e,
25
33
  error: null
26
34
  } : t));
27
- }, [m, f]), n(() => {
28
- if (h || f.current.refresh === void 0) return;
35
+ }, [g, m]), n(() => {
36
+ if (_ || m.current.refresh === void 0) return;
29
37
  let e = new AbortController(), t, n = () => {
30
- let r = f.current.refresh;
38
+ let r = m.current.refresh;
31
39
  r !== void 0 && r(e.signal).then((t) => {
32
- e.signal.aborted || l((e) => i(e.charge, t) ? {
40
+ e.signal.aborted || u((e) => o(e.charge, t) ? {
33
41
  charge: t,
34
42
  error: null
35
43
  } : e);
36
44
  }).catch((t) => {
37
45
  if (e.signal.aborted) return;
38
46
  let n = t instanceof Error ? t : Error(String(t));
39
- l((e) => ({
47
+ u((e) => ({
40
48
  ...e,
41
49
  error: n
42
50
  }));
43
51
  }).finally(() => {
44
- e.signal.aborted || (t = setTimeout(n, s));
52
+ e.signal.aborted || (t = setTimeout(n, c));
45
53
  });
46
54
  };
47
- return t = setTimeout(n, s), () => {
55
+ return t = setTimeout(n, c), () => {
48
56
  e.abort(), t !== void 0 && clearTimeout(t);
49
57
  };
50
58
  }, [
51
- m,
52
- h,
53
- s,
54
- f
59
+ g,
60
+ _,
61
+ c,
62
+ m
55
63
  ]), {
56
- charge: p,
57
- error: u === a.provider_charge_id ? c.error : null
64
+ charge: h,
65
+ error: f === d ? l.error : null
58
66
  };
59
67
  }
60
68
  //#endregion
61
- export { a as usePixCharge };
69
+ export { s as usePixCharge };
62
70
 
63
71
  //# sourceMappingURL=use-pix-charge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-pix-charge.js","names":["accepts","next","current","isFinalPaymentStatus","usePixCharge","options","state","setState","useState","initial","followed","setFollowed","sourcesRef","useLatestRef","charge","id","final","useEffect","subscribe","controller","timer","tick","refresh","error","reason","intervalMs"],"sources":["../../src/payments/use-pix-charge.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { isFinalPaymentStatus } from \"./types\";\nimport type { PixCharge } from \"./types\";\n\n/** Options for {@link usePixCharge}. */\nexport interface UsePixChargeOptions {\n /**\n * Re-reads the charge from **your backend** — which re-reads it from the\n * provider. Called every `intervalMs` until the charge is final. The signal\n * aborts when the component unmounts or the charge changes.\n */\n refresh?: (signal: AbortSignal) => Promise<PixCharge>;\n /**\n * Pushes updates instead of polling — an SSE stream, a WebSocket. Receives\n * the emitter and returns its own cleanup. Can be combined with `refresh`.\n */\n subscribe?: (emit: (charge: PixCharge) => void) => () => void;\n /** Poll period in ms. Default `5000`. */\n intervalMs?: number;\n}\n\n/** Return value of {@link usePixCharge}. */\nexport interface UsePixChargeResult {\n /** The latest charge the backend reported. */\n charge: PixCharge;\n /** The last `refresh` failure, cleared by the next success. Polling continues after it. */\n error: Error | null;\n}\n\n/**\n * Whether an update may replace the charge on screen.\n *\n * Rejects an update for another charge (a stale response after the app\n * switched charges) and one that moves a final charge back to a non-final\n * state — an out-of-order delivery when polling and a push channel race, which\n * would otherwise flash \"aguardando pagamento\" under a payer who already paid.\n */\nfunction accepts(current: PixCharge, next: PixCharge): boolean {\n if (next.provider_charge_id !== current.provider_charge_id) return false;\n return !(isFinalPaymentStatus(current.status) && !isFinalPaymentStatus(next.status));\n}\n\n/**\n * Follow a Pix charge until the backend says it is final.\n *\n * The browser never decides that a Pix was paid: there is no client-side signal\n * that could prove it, and a screen that infers \"paid\" from a countdown or a\n * copy click is a screen that ships goods for free. This hook only relays what\n * the app's `refresh`/`subscribe` report, and stops polling once the status is\n * final (`paid`, `expired`, `cancelled`, `refunded`, `charged_back`, `failed`).\n *\n * Polling is sequential — the next request starts `intervalMs` after the\n * previous one settled — so a slow backend is never hit by overlapping reads.\n * A failed read keeps the last good charge and is exposed as `error`; the poll\n * goes on, because a dropped request is not a payment state.\n *\n * Passing a charge with a different `provider_charge_id` restarts the follow.\n *\n * @param initial - The charge as created.\n * @param options - `refresh`, `subscribe` and the poll period.\n * @returns The latest charge and the last read error.\n *\n * @example\n * const { charge } = usePixCharge(created, {\n * refresh: (signal) => api.get<PixCharge>(`/api/pix/${created.provider_charge_id}`, { signal }),\n * });\n * return <PixCheckout charge={charge} />;\n */\nexport function usePixCharge(\n initial: PixCharge,\n options: UsePixChargeOptions = {},\n): UsePixChargeResult {\n const { intervalMs = 5000 } = options;\n const [state, setState] = useState<{ charge: PixCharge; error: Error | null }>({\n charge: initial,\n error: null,\n });\n const [followed, setFollowed] = useState(initial.provider_charge_id);\n const sourcesRef = useLatestRef({ refresh: options.refresh, subscribe: options.subscribe });\n const charge = followed === initial.provider_charge_id ? state.charge : initial;\n\n if (followed !== initial.provider_charge_id) {\n setFollowed(initial.provider_charge_id);\n setState({ charge: initial, error: null });\n }\n\n const id = charge.provider_charge_id;\n const final = isFinalPaymentStatus(charge.status);\n\n useEffect(() => {\n const { subscribe } = sourcesRef.current;\n if (subscribe === undefined) return undefined;\n return subscribe((next) =>\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n ),\n );\n }, [id, sourcesRef]);\n\n useEffect(() => {\n if (final || sourcesRef.current.refresh === undefined) return undefined;\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const tick = (): void => {\n const refresh = sourcesRef.current.refresh;\n if (refresh === undefined) return;\n refresh(controller.signal)\n .then((next) => {\n if (controller.signal.aborted) return;\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n );\n })\n .catch((reason: unknown) => {\n if (controller.signal.aborted) return;\n const error = reason instanceof Error ? reason : new Error(String(reason));\n setState((current) => ({ ...current, error }));\n })\n .finally(() => {\n if (!controller.signal.aborted) timer = setTimeout(tick, intervalMs);\n });\n };\n timer = setTimeout(tick, intervalMs);\n return () => {\n controller.abort();\n if (timer !== undefined) clearTimeout(timer);\n };\n }, [id, final, intervalMs, sourcesRef]);\n\n return { charge, error: followed === initial.provider_charge_id ? state.error : null };\n}\n"],"mappings":";;;;AAwCA,SAASA,EAAQ,GAAoB,GAA0B;CAE3D,OADIC,EAAK,uBAAuBC,EAAQ,sBACjC,EAAEC,EAAqBD,EAAQ,MAAM,KAAK,CAACC,EAAqBF,EAAK,MAAM;AACtF;AA4BA,SAAgBG,EACZ,GACA,IAA+B,CAAC,GACd;CAClB,IAAM,EAAE,gBAAa,QAASC,GACxB,CAACC,GAAOC,KAAYC,EAAqD;EAC3E,QAAQC;EACR,OAAO;CACX,CAAC,GACK,CAACC,GAAUC,KAAeH,EAASC,EAAQ,kBAAkB,GAC7DG,IAAaC,EAAa;EAAE,SAASR,EAAQ;EAAS,WAAWA,EAAQ;CAAU,CAAC,GACpFS,IAASJ,MAAaD,EAAQ,qBAAqBH,EAAM,SAASG;CAExE,AAAIC,MAAaD,EAAQ,uBACrBE,EAAYF,EAAQ,kBAAkB,GACtCF,EAAS;EAAE,QAAQE;EAAS,OAAO;CAAK,CAAC;CAG7C,IAAMM,IAAKD,EAAO,oBACZE,IAAQb,EAAqBW,EAAO,MAAM;CA0ChD,OAxCAG,QAAgB;EACZ,IAAM,EAAE,iBAAcL,EAAW;EACjC,IAAIM,MAAc,KAAA,GAClB,OAAOA,GAAW,MACdX,GAAU,MACNP,EAAQE,EAAQ,QAAQD,CAAI,IAAI;GAAE,QAAQA;GAAM,OAAO;EAAK,IAAIC,CACpE,CACJ;CACJ,GAAG,CAACa,GAAIH,CAAU,CAAC,GAEnBK,QAAgB;EACZ,IAAID,KAASJ,EAAW,QAAQ,YAAY,KAAA,GAAW;EACvD,IAAMO,IAAa,IAAI,gBAAgB,GACnCC,GACEC,UAAmB;GACrB,IAAMC,IAAUV,EAAW,QAAQ;GACnC,AAAIU,MAAY,KAAA,KAChBA,EAAQH,EAAW,MAAM,CAAC,CACrB,MAAM,MAAS;IACZ,AAAIA,EAAW,OAAO,WACtBZ,GAAU,MACNP,EAAQE,EAAQ,QAAQD,CAAI,IAAI;KAAE,QAAQA;KAAM,OAAO;IAAK,IAAIC,CACpE;GACJ,CAAC,CAAC,CACD,OAAO,MAAoB;IACxB,IAAIiB,EAAW,OAAO,SAAS;IAC/B,IAAMI,IAAQC,aAAkB,QAAQA,IAAa,MAAM,OAAOA,CAAM,CAAC;IACzE,GAAU,OAAa;KAAE,GAAGtB;KAAS;IAAM,EAAE;GACjD,CAAC,CAAC,CACD,cAAc;IACX,AAAKiB,EAAW,OAAO,YAAS,IAAQ,WAAWE,GAAMI,CAAU;GACvE,CAAC;EACT;EAEA,OADA,IAAQ,WAAWJ,GAAMI,CAAU,SACtB;GAET,AADAN,EAAW,MAAM,GACbC,MAAU,KAAA,KAAW,aAAaA,CAAK;EAC/C;CACJ,GAAG;EAACL;EAAIC;EAAOS;EAAYb;CAAU,CAAC,GAE/B;EAAE;EAAQ,OAAOF,MAAaD,EAAQ,qBAAqBH,EAAM,QAAQ;CAAK;AACzF"}
1
+ {"version":3,"file":"use-pix-charge.js","names":["AFTER_PAID","chargeKey","charge","accepts","next","current","isFinalPaymentStatus","usePixCharge","options","state","setState","useState","initial","initialKey","followed","setFollowed","sourcesRef","useLatestRef","id","final","useEffect","subscribe","controller","timer","tick","refresh","error","reason","intervalMs"],"sources":["../../src/payments/use-pix-charge.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { isFinalPaymentStatus } from \"./types\";\nimport type { PaymentStatus, PixCharge } from \"./types\";\n\n/** Options for {@link usePixCharge}. */\nexport interface UsePixChargeOptions {\n /**\n * Re-reads the charge from **your backend** — which re-reads it from the\n * provider. Called every `intervalMs` until the charge is final. The signal\n * aborts when the component unmounts or the charge changes.\n */\n refresh?: (signal: AbortSignal) => Promise<PixCharge>;\n /**\n * Pushes updates instead of polling — an SSE stream, a WebSocket. Receives\n * the emitter and returns its own cleanup. Can be combined with `refresh`.\n */\n subscribe?: (emit: (charge: PixCharge) => void) => () => void;\n /** Poll period in ms. Default `5000`. */\n intervalMs?: number;\n}\n\n/** Return value of {@link usePixCharge}. */\nexport interface UsePixChargeResult {\n /** The latest charge the backend reported. */\n charge: PixCharge;\n /** The last `refresh` failure, cleared by the next success. Polling continues after it. */\n error: Error | null;\n}\n\nconst AFTER_PAID: ReadonlySet<PaymentStatus> = new Set<PaymentStatus>([\n \"paid\",\n \"refunded\",\n \"charged_back\",\n]);\n\n/** Identity of a charge: its id is only unique within its provider. */\nfunction chargeKey(charge: PixCharge): string {\n return JSON.stringify([charge.provider, charge.provider_charge_id]);\n}\n\n/**\n * Whether an update may replace the charge on screen.\n *\n * Rejects an update for another charge — a different `provider` or\n * `provider_charge_id`, since ids are only unique within one provider — which is\n * a stale response after the app switched charges. Rejects one that moves a\n * final charge back to a non-final state: an out-of-order delivery when polling\n * and a push channel race, which would otherwise flash \"aguardando pagamento\"\n * under a payer who already paid. And once the charge is `paid`, the only\n * states it may move to are `refunded` and `charged_back`: a late `expired`,\n * `cancelled` or `failed` is the same race, and showing it would tell a payer\n * who already paid to \"gerar um novo Pix\". The reverse stays open — an\n * `expired` charge may still become `paid`, because a Pix sent in the last\n * second can settle after the deadline.\n */\nfunction accepts(current: PixCharge, next: PixCharge): boolean {\n if (next.provider !== current.provider) return false;\n if (next.provider_charge_id !== current.provider_charge_id) return false;\n if (current.status === \"paid\") return AFTER_PAID.has(next.status);\n return !(isFinalPaymentStatus(current.status) && !isFinalPaymentStatus(next.status));\n}\n\n/**\n * Follow a Pix charge until the backend says it is final.\n *\n * The browser never decides that a Pix was paid: there is no client-side signal\n * that could prove it, and a screen that infers \"paid\" from a countdown or a\n * copy click is a screen that ships goods for free. This hook only relays what\n * the app's `refresh`/`subscribe` report, and stops polling once the status is\n * final (`paid`, `expired`, `cancelled`, `refunded`, `charged_back`, `failed`).\n *\n * Polling is sequential — the next request starts `intervalMs` after the\n * previous one settled — so a slow backend is never hit by overlapping reads.\n * A failed read keeps the last good charge and is exposed as `error`; the poll\n * goes on, because a dropped request is not a payment state.\n *\n * An update for another `provider` or `provider_charge_id` is ignored, and a\n * `paid` charge only moves on to `refunded` or `charged_back` — a late\n * `expired` never replaces it. Passing a charge with a different `provider` or\n * `provider_charge_id` restarts the follow.\n *\n * @param initial - The charge as created.\n * @param options - `refresh`, `subscribe` and the poll period.\n * @returns The latest charge and the last read error.\n *\n * @example\n * const { charge } = usePixCharge(created, {\n * refresh: (signal) => api.get<PixCharge>(`/api/pix/${created.provider_charge_id}`, { signal }),\n * });\n * return <PixCheckout charge={charge} />;\n */\nexport function usePixCharge(\n initial: PixCharge,\n options: UsePixChargeOptions = {},\n): UsePixChargeResult {\n const { intervalMs = 5000 } = options;\n const [state, setState] = useState<{ charge: PixCharge; error: Error | null }>({\n charge: initial,\n error: null,\n });\n const initialKey = chargeKey(initial);\n const [followed, setFollowed] = useState(initialKey);\n const sourcesRef = useLatestRef({ refresh: options.refresh, subscribe: options.subscribe });\n const charge = followed === initialKey ? state.charge : initial;\n\n if (followed !== initialKey) {\n setFollowed(initialKey);\n setState({ charge: initial, error: null });\n }\n\n const id = chargeKey(charge);\n const final = isFinalPaymentStatus(charge.status);\n\n useEffect(() => {\n const { subscribe } = sourcesRef.current;\n if (subscribe === undefined) return undefined;\n return subscribe((next) =>\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n ),\n );\n }, [id, sourcesRef]);\n\n useEffect(() => {\n if (final || sourcesRef.current.refresh === undefined) return undefined;\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const tick = (): void => {\n const refresh = sourcesRef.current.refresh;\n if (refresh === undefined) return;\n refresh(controller.signal)\n .then((next) => {\n if (controller.signal.aborted) return;\n setState((current) =>\n accepts(current.charge, next) ? { charge: next, error: null } : current,\n );\n })\n .catch((reason: unknown) => {\n if (controller.signal.aborted) return;\n const error = reason instanceof Error ? reason : new Error(String(reason));\n setState((current) => ({ ...current, error }));\n })\n .finally(() => {\n if (!controller.signal.aborted) timer = setTimeout(tick, intervalMs);\n });\n };\n timer = setTimeout(tick, intervalMs);\n return () => {\n controller.abort();\n if (timer !== undefined) clearTimeout(timer);\n };\n }, [id, final, intervalMs, sourcesRef]);\n\n return { charge, error: followed === initialKey ? state.error : null };\n}\n"],"mappings":";;;;AAgCA,IAAMA,oBAAyC,IAAI,IAAmB;CAClE;CACA;CACA;AACJ,CAAC;AAGD,SAASC,EAAU,GAA2B;CAC1C,OAAO,KAAK,UAAU,CAACC,EAAO,UAAUA,EAAO,kBAAkB,CAAC;AACtE;AAiBA,SAASC,EAAQ,GAAoB,GAA0B;CAI3D,OAHIC,EAAK,aAAaC,EAAQ,YAC1BD,EAAK,uBAAuBC,EAAQ,qBAA2B,KAC/DA,EAAQ,WAAW,SAAeL,EAAW,IAAII,EAAK,MAAM,IACzD,EAAEE,EAAqBD,EAAQ,MAAM,KAAK,CAACC,EAAqBF,EAAK,MAAM;AACtF;AA+BA,SAAgBG,EACZ,GACA,IAA+B,CAAC,GACd;CAClB,IAAM,EAAE,gBAAa,QAASC,GACxB,CAACC,GAAOC,KAAYC,EAAqD;EAC3E,QAAQC;EACR,OAAO;CACX,CAAC,GACKC,IAAaZ,EAAUW,CAAO,GAC9B,CAACE,GAAUC,KAAeJ,EAASE,CAAU,GAC7CG,IAAaC,EAAa;EAAE,SAAST,EAAQ;EAAS,WAAWA,EAAQ;CAAU,CAAC,GACpFN,IAASY,MAAaD,IAAaJ,EAAM,SAASG;CAExD,AAAIE,MAAaD,MACbE,EAAYF,CAAU,GACtBH,EAAS;EAAE,QAAQE;EAAS,OAAO;CAAK,CAAC;CAG7C,IAAMM,IAAKjB,EAAUC,CAAM,GACrBiB,IAAQb,EAAqBJ,EAAO,MAAM;CA0ChD,OAxCAkB,QAAgB;EACZ,IAAM,EAAE,iBAAcJ,EAAW;EACjC,IAAIK,MAAc,KAAA,GAClB,OAAOA,GAAW,MACdX,GAAU,MACNP,EAAQE,EAAQ,QAAQD,CAAI,IAAI;GAAE,QAAQA;GAAM,OAAO;EAAK,IAAIC,CACpE,CACJ;CACJ,GAAG,CAACa,GAAIF,CAAU,CAAC,GAEnBI,QAAgB;EACZ,IAAID,KAASH,EAAW,QAAQ,YAAY,KAAA,GAAW;EACvD,IAAMM,IAAa,IAAI,gBAAgB,GACnCC,GACEC,UAAmB;GACrB,IAAMC,IAAUT,EAAW,QAAQ;GACnC,AAAIS,MAAY,KAAA,KAChBA,EAAQH,EAAW,MAAM,CAAC,CACrB,MAAM,MAAS;IACZ,AAAIA,EAAW,OAAO,WACtBZ,GAAU,MACNP,EAAQE,EAAQ,QAAQD,CAAI,IAAI;KAAE,QAAQA;KAAM,OAAO;IAAK,IAAIC,CACpE;GACJ,CAAC,CAAC,CACD,OAAO,MAAoB;IACxB,IAAIiB,EAAW,OAAO,SAAS;IAC/B,IAAMI,IAAQC,aAAkB,QAAQA,IAAa,MAAM,OAAOA,CAAM,CAAC;IACzE,GAAU,OAAa;KAAE,GAAGtB;KAAS;IAAM,EAAE;GACjD,CAAC,CAAC,CACD,cAAc;IACX,AAAKiB,EAAW,OAAO,YAAS,IAAQ,WAAWE,GAAMI,CAAU;GACvE,CAAC;EACT;EAEA,OADA,IAAQ,WAAWJ,GAAMI,CAAU,SACtB;GAET,AADAN,EAAW,MAAM,GACbC,MAAU,KAAA,KAAW,aAAaA,CAAK;EAC/C;CACJ,GAAG;EAACL;EAAIC;EAAOS;EAAYZ;CAAU,CAAC,GAE/B;EAAE;EAAQ,OAAOF,MAAaD,IAAaJ,EAAM,QAAQ;CAAK;AACzE"}
@@ -1,2 +1,2 @@
1
- const e=require("../utils/dev-mode.cjs"),t=require("../utils/owner-fingerprint.cjs"),n=require("../offline/create-offline-store.cjs");let r=require("@tanstack/react-query");var i=new Map,a=`p:anonymous`;function o(e){try{return t.ownerFingerprint(`p`,e())}catch{return null}}function s(t){let{queryClient:s,throttleMs:c=1e3,getPrincipal:l}=t,u=t.key??`tanstack-query`,d=t.databaseName??`TempestQueryCache`,f=n.createOfflineStore({databaseName:d,version:1,tableName:`cache`,indexes:`&id`,keyPath:`id`}),p=`${d}\u0000${u}`,m=!1,h=!1,g=!1,_=null,v=null,y=new Set;async function b(){let e=i.get(p);e&&await e}function x(){!h&&e.isDevBuild()&&(h=!0,console.warn(`[tempest-react-sdk] persistQueryClientOffline: handle ended by clear(); create a new one after login to keep persisting.`))}function S(){!g&&e.isDevBuild()&&(g=!0,console.warn(`[tempest-react-sdk] persistQueryClientOffline: another account signed in on a handle bound to the previous one; its snapshot is deleted instead of written. Create a new handle after login to keep persisting.`))}async function C(){if(m||(await b(),m))return;let e={id:u,state:(0,r.dehydrate)(s)},t=!1;if(l){let n=o(l);t=_!==null&&n!==_,t&&n!==null&&S(),_??=n,e.owner=n??a}let n=t?f.delete(u):f.put(e);y.add(n);try{await n}finally{y.delete(n)}}function w(){v&&=(clearTimeout(v),null)}function T(){v||m||(v=setTimeout(()=>{v=null,C()},c))}function E(e){m?(e.type===`added`||e.type===`updated`)&&(D(),x()):(l&&(_??=o(l)),T())}let D=s.getQueryCache().subscribe(E);async function O(e){try{await Promise.allSettled([...y]),await f.delete(u)}finally{e&&s.clear()}}return{async restore(){if(m){x();return}if(await b(),m)return;let e=await f.get(u);if(e&&!m){if(l){let t=o(l);if(e.owner!==(t??a))return;_??=t}(0,r.hydrate)(s,e.state)}},async flush(){m?x():(w(),await C())},unsubscribe(){w(),D()},async clear(e={}){m=!0,w();let t=i.get(p),n=O(e.resetClient??!1),r=Promise.allSettled([t,n]);i.set(p,r),r.then(()=>{i.get(p)===r&&i.delete(p)}),await n}}}exports.persistQueryClientOffline=s;
1
+ const e=require("../utils/dev-mode.cjs"),t=require("../auth/session-identity.cjs"),n=require("../utils/owner-fingerprint.cjs"),r=require("../offline/create-offline-store.cjs");let i=require("@tanstack/react-query");var a=new Map;function o(e){if(!e)return!0;try{return e()===!0}catch{return!1}}var s=new WeakMap;function c(e,t){e.ended||(t===null?e.ended=e.session!==null:e.hydratedAnonymous||e.session!==null&&e.session!==t?e.ended=!0:e.session=t)}function l(e){return e.getQueryCache().getAll().length===0&&e.getMutationCache().getAll().length===0}var u=`p:anonymous`;function d(e){try{return n.ownerFingerprint(`p`,e())}catch{return null}}function f(n){let{queryClient:f,throttleMs:p=1e3,auth:m}=n,h=n.getPrincipal,g=h??m?.sessionKey,_=m?()=>{let e=m.sessionKey();return o(n.isPrincipalKnown)&&m.isSessionKnown()&&(h===void 0||e===null||d(h)!==null)}:n.isPrincipalKnown,v=n.key??`tanstack-query`,y=n.databaseName??`TempestQueryCache`,b=r.createOfflineStore({databaseName:y,version:1,tableName:`cache`,indexes:`&id`,keyPath:`id`}),x=`${y}\u0000${v}`,S=!1,C=!1,w=!1,T=!1,E=null,D=!1,O=null,k=new Set;async function A(){let e=a.get(x);e&&await e}function j(){!w&&e.isDevBuild()&&(w=!0,console.warn(`[tempest-react-sdk] persistQueryClientOffline: handle ended by clear(); create a new one after login to keep persisting.`))}function M(){!T&&e.isDevBuild()&&(T=!0,console.warn(`[tempest-react-sdk] persistQueryClientOffline: an account signed in on a handle holding another session's cache; its snapshot is deleted instead of written. Call clear({ resetClient: true }), then create a new handle.`))}m&&l(f)&&s.delete(f);function N(){let e=s.get(f);return e||(e={session:null,hydratedAnonymous:!1,ended:!1},s.set(f,e)),e}function P(){if(!m||S)return!1;let e=N();try{c(e,m.sessionKey())}catch{return e.ended}return e.ended}let F=m?t.sessionIdentityOf(m.sessionKey)?.subscribe(P)??null:null;P();async function I(){if(S||m&&C||(await A(),S||m&&C))return;if(g&&!o(_)){R();return}let e={id:v,state:(0,i.dehydrate)(f)},t=!1;if(g){let n=d(g);t=P()||E!==null&&n!==E||D&&n!==null,t&&n!==null&&M(),E??=n,e.owner=n??u}let n=t?b.delete(v):b.put(e);k.add(n);try{await n}finally{k.delete(n)}}function L(){O&&=(clearTimeout(O),null)}function R(){O||S||(O=setTimeout(()=>{O=null,I()},p))}function z(e){S?(e.type===`added`||e.type===`updated`)&&(B(),j()):(P(),g&&o(_)&&(E??=d(g)),R())}let B=f.getQueryCache().subscribe(z);async function V(e){try{await Promise.allSettled([...k]),await b.delete(v)}finally{e&&(f.clear(),m&&s.delete(f))}}return{async restore(){if(S){j();return}if(await A(),S)return;let e=await b.get(v);if(e&&!S){if(g){if(!o(_)||P())return;let t=d(g);if(e.owner!==(t??u))return;E??=t,t===null&&(D=!0,m&&(N().hydratedAnonymous=!0))}(0,i.hydrate)(f,e.state)}},async flush(){S?j():(L(),await I())},unsubscribe(){C=!0,L(),B(),F?.()},async clear(e={}){S=!0,L(),F?.(),m&&(N().ended=!0);let t=a.get(x),n=V(e.resetClient??!1),r=Promise.allSettled([t,n]);a.set(x,r),r.then(()=>{a.get(x)===r&&a.delete(x)}),await n}}}exports.persistQueryClientOffline=f;
2
2
  //# sourceMappingURL=offline-persistence.cjs.map