tempest-react-sdk 0.83.0 → 0.85.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 (70) hide show
  1. package/README.md +1 -0
  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 +294 -178
  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/http/resumable-upload.cjs +1 -1
  15. package/dist/http/resumable-upload.cjs.map +1 -1
  16. package/dist/http/resumable-upload.js +1 -1
  17. package/dist/http/resumable-upload.js.map +1 -1
  18. package/dist/http/upload-with-progress.cjs +1 -1
  19. package/dist/http/upload-with-progress.cjs.map +1 -1
  20. package/dist/http/upload-with-progress.js +47 -39
  21. package/dist/http/upload-with-progress.js.map +1 -1
  22. package/dist/payments/PixCheckout.cjs +2 -0
  23. package/dist/payments/PixCheckout.cjs.map +1 -0
  24. package/dist/payments/PixCheckout.js +171 -0
  25. package/dist/payments/PixCheckout.js.map +1 -0
  26. package/dist/payments/PixCheckout.module.cjs +3 -0
  27. package/dist/payments/PixCheckout.module.cjs.map +1 -0
  28. package/dist/payments/PixCheckout.module.js +22 -0
  29. package/dist/payments/PixCheckout.module.js.map +1 -0
  30. package/dist/payments/format.cjs +2 -0
  31. package/dist/payments/format.cjs.map +1 -0
  32. package/dist/payments/format.js +9 -0
  33. package/dist/payments/format.js.map +1 -0
  34. package/dist/payments/mercado-pago.cjs +2 -0
  35. package/dist/payments/mercado-pago.cjs.map +1 -0
  36. package/dist/payments/mercado-pago.js +27 -0
  37. package/dist/payments/mercado-pago.js.map +1 -0
  38. package/dist/payments/outcome.cjs +2 -0
  39. package/dist/payments/outcome.cjs.map +1 -0
  40. package/dist/payments/outcome.js +111 -0
  41. package/dist/payments/outcome.js.map +1 -0
  42. package/dist/payments/types.cjs +2 -0
  43. package/dist/payments/types.cjs.map +1 -0
  44. package/dist/payments/types.js +16 -0
  45. package/dist/payments/types.js.map +1 -0
  46. package/dist/payments/use-mercado-pago-card.cjs +2 -0
  47. package/dist/payments/use-mercado-pago-card.cjs.map +1 -0
  48. package/dist/payments/use-mercado-pago-card.js +128 -0
  49. package/dist/payments/use-mercado-pago-card.js.map +1 -0
  50. package/dist/payments/use-pix-charge.cjs +2 -0
  51. package/dist/payments/use-pix-charge.cjs.map +1 -0
  52. package/dist/payments/use-pix-charge.js +71 -0
  53. package/dist/payments/use-pix-charge.js.map +1 -0
  54. package/dist/query/offline-persistence.cjs +1 -1
  55. package/dist/query/offline-persistence.cjs.map +1 -1
  56. package/dist/query/offline-persistence.js +100 -54
  57. package/dist/query/offline-persistence.js.map +1 -1
  58. package/dist/styles/PixCheckout.css +2 -0
  59. package/dist/styles/component/PixCheckout.css +18 -0
  60. package/dist/styles/manifest.json +6 -0
  61. package/dist/styles/payments.css +18 -0
  62. package/dist/styles.css +1 -1
  63. package/dist/sw/cache.cjs +1 -1
  64. package/dist/sw/cache.cjs.map +1 -1
  65. package/dist/sw/cache.js +1 -1
  66. package/dist/sw/cache.js.map +1 -1
  67. package/dist/tempest-react-sdk.cjs +1 -1
  68. package/dist/tempest-react-sdk.d.ts +913 -29
  69. package/dist/tempest-react-sdk.js +104 -97
  70. package/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ //#region src/payments/outcome.ts
2
+ var e = "O valor foi reservado no seu cartão e só será cobrado quando o pedido for confirmado.", t = {
3
+ bad_filled_card_data: "Confira os dados do cartão: número, validade e código de segurança.",
4
+ invalid_card_token: "Não conseguimos validar o cartão. Preencha os dados de novo.",
5
+ high_risk: "O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.",
6
+ rejected_by_issuer: "O banco emissor recusou o pagamento. Use outro cartão ou fale com o banco.",
7
+ required_call_for_authorize: "O banco pede que você autorize este pagamento. Fale com o banco e tente de novo.",
8
+ max_attempts_exceeded: "Você atingiu o limite de tentativas com este cartão. Use outro cartão ou outro meio de pagamento.",
9
+ card_disabled: "O cartão está bloqueado ou inativo. Ative-o com o banco ou use outro cartão.",
10
+ insufficient_amount: "O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.",
11
+ card_insufficient_amount: "O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.",
12
+ amount_limit_exceeded: "O valor passa do limite permitido para este cartão. Use outro cartão.",
13
+ processing_error: "Não conseguimos processar o pagamento. Tente de novo em instantes.",
14
+ invalid_installments: "O cartão não aceita esse número de parcelas. Escolha outra opção.",
15
+ "3ds_challenge_expired": "O tempo para confirmar a compra no app do banco acabou. Tente de novo.",
16
+ waiting_capture: e,
17
+ pending_challenge: "Confirme a compra no app do seu banco para concluir o pagamento.",
18
+ pending_review_manual: "O pagamento está em análise. Avisaremos assim que for concluído.",
19
+ in_review: "O pagamento está em análise. Avisaremos assim que for concluído.",
20
+ in_process: "O pagamento está sendo processado. Avisaremos assim que for concluído."
21
+ }, n = /* @__PURE__ */ new Set([
22
+ "bad_filled_card_data",
23
+ "invalid_card_token",
24
+ "high_risk",
25
+ "rejected_by_issuer",
26
+ "required_call_for_authorize",
27
+ "max_attempts_exceeded",
28
+ "card_disabled",
29
+ "insufficient_amount",
30
+ "card_insufficient_amount",
31
+ "amount_limit_exceeded",
32
+ "processing_error",
33
+ "invalid_installments",
34
+ "3ds_challenge_expired"
35
+ ]), r = {
36
+ paid: {
37
+ tone: "success",
38
+ title: "Pagamento aprovado",
39
+ message: "Recebemos o seu pagamento.",
40
+ canRetry: !1
41
+ },
42
+ authorized: {
43
+ tone: "info",
44
+ title: "Pagamento autorizado",
45
+ message: e,
46
+ canRetry: !1
47
+ },
48
+ pending: {
49
+ tone: "info",
50
+ title: "Aguardando pagamento",
51
+ message: "O pagamento ainda não foi concluído.",
52
+ canRetry: !1
53
+ },
54
+ in_analysis: {
55
+ tone: "info",
56
+ title: "Pagamento em análise",
57
+ message: "O pagamento está em análise. Avisaremos assim que for concluído.",
58
+ canRetry: !1
59
+ },
60
+ failed: {
61
+ tone: "danger",
62
+ title: "Pagamento recusado",
63
+ message: "O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.",
64
+ canRetry: !0
65
+ },
66
+ expired: {
67
+ tone: "warning",
68
+ title: "Pagamento expirado",
69
+ message: "O prazo para pagar acabou. Comece um novo pagamento.",
70
+ canRetry: !0
71
+ },
72
+ cancelled: {
73
+ tone: "neutral",
74
+ title: "Pagamento cancelado",
75
+ message: "Esta cobrança foi cancelada e não será cobrada.",
76
+ canRetry: !1
77
+ },
78
+ refunded: {
79
+ tone: "neutral",
80
+ title: "Pagamento estornado",
81
+ message: "O valor foi devolvido a você.",
82
+ canRetry: !1
83
+ },
84
+ charged_back: {
85
+ tone: "warning",
86
+ title: "Pagamento contestado",
87
+ message: "O pagamento foi contestado junto ao banco.",
88
+ canRetry: !1
89
+ },
90
+ unknown: {
91
+ tone: "neutral",
92
+ title: "Verificando o pagamento",
93
+ message: "Ainda estamos confirmando o estado deste pagamento.",
94
+ canRetry: !1
95
+ }
96
+ };
97
+ function i(e, t) {
98
+ return Object.hasOwn(e, t) ? e[t] : void 0;
99
+ }
100
+ function a(e, a = {}) {
101
+ let o = Object.hasOwn(r, e.status) ? e.status : "unknown", s = r[o], c = e.status_detail ?? "", l = c !== "" && o !== "paid" && n.has(c) === (o === "failed") ? i(a, c) ?? i(t, c) : void 0;
102
+ return {
103
+ status: o,
104
+ ...s,
105
+ message: l ?? s.message
106
+ };
107
+ }
108
+ //#endregion
109
+ export { t as CARD_STATUS_DETAIL_MESSAGES, a as describeCardCharge };
110
+
111
+ //# sourceMappingURL=outcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.js","names":["WAITING_CAPTURE_MESSAGE","CARD_STATUS_DETAIL_MESSAGES","DECLINE_DETAILS","STATUS_OUTCOMES","ownText","table","key","describeCardCharge","status","charge","base","detail","refined","messages"],"sources":["../../src/payments/outcome.ts"],"sourcesContent":["import type { CardCharge, PaymentStatus } from \"./types\";\n\n/** Visual tone of an outcome, matching `Alert`'s variants. */\nexport type PaymentOutcomeTone = \"success\" | \"info\" | \"warning\" | \"danger\" | \"neutral\";\n\n/** What a card charge means to the payer, ready to render. */\nexport interface PaymentOutcome {\n /** The canonical state the outcome was derived from. */\n status: PaymentStatus;\n /** Tone for the surrounding `Alert`/`Badge`. */\n tone: PaymentOutcomeTone;\n /** Short headline. */\n title: string;\n /** One sentence telling the payer what happened and what to do. */\n message: string;\n /** Whether the payer can fix it from the checkout (another card, other data). */\n canRetry: boolean;\n}\n\nconst WAITING_CAPTURE_MESSAGE =\n \"O valor foi reservado no seu cartão e só será cobrado quando o pedido for confirmado.\";\n\n/**\n * PT-BR text for every `status_detail` Mercado Pago's Orders API documents.\n *\n * Ported from Mercado Pago's \"Status da transação\" table\n * (`developers/pt/docs/checkout-api-orders/payment-management/status/transaction-status`,\n * read on 2026-11-14), rewritten for the payer: the provider's descriptions are\n * addressed to the integrator. `rejected_by_issuer` is the decline the backend\n * pinned in the sandbox (holder `OTHE`, HTTP 402); the others come from the\n * table. `high_risk` deliberately does not say \"fraud\" — telling the payer the\n * anti-fraud system flagged them helps exactly the person it should not.\n *\n * A test pins every key, so a code dropped or renamed here fails instead of\n * silently falling back to the generic text.\n */\nexport const CARD_STATUS_DETAIL_MESSAGES: Readonly<Record<string, string>> = {\n bad_filled_card_data: \"Confira os dados do cartão: número, validade e código de segurança.\",\n invalid_card_token: \"Não conseguimos validar o cartão. Preencha os dados de novo.\",\n high_risk: \"O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.\",\n rejected_by_issuer:\n \"O banco emissor recusou o pagamento. Use outro cartão ou fale com o banco.\",\n required_call_for_authorize:\n \"O banco pede que você autorize este pagamento. Fale com o banco e tente de novo.\",\n max_attempts_exceeded:\n \"Você atingiu o limite de tentativas com este cartão. Use outro cartão ou outro meio de pagamento.\",\n card_disabled: \"O cartão está bloqueado ou inativo. Ative-o com o banco ou use outro cartão.\",\n insufficient_amount:\n \"O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.\",\n card_insufficient_amount:\n \"O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.\",\n amount_limit_exceeded: \"O valor passa do limite permitido para este cartão. Use outro cartão.\",\n processing_error: \"Não conseguimos processar o pagamento. Tente de novo em instantes.\",\n invalid_installments: \"O cartão não aceita esse número de parcelas. Escolha outra opção.\",\n \"3ds_challenge_expired\":\n \"O tempo para confirmar a compra no app do banco acabou. Tente de novo.\",\n waiting_capture: WAITING_CAPTURE_MESSAGE,\n pending_challenge: \"Confirme a compra no app do seu banco para concluir o pagamento.\",\n pending_review_manual: \"O pagamento está em análise. Avisaremos assim que for concluído.\",\n in_review: \"O pagamento está em análise. Avisaremos assim que for concluído.\",\n in_process: \"O pagamento está sendo processado. Avisaremos assim que for concluído.\",\n};\n\nconst DECLINE_DETAILS: ReadonlySet<string> = new Set([\n \"bad_filled_card_data\",\n \"invalid_card_token\",\n \"high_risk\",\n \"rejected_by_issuer\",\n \"required_call_for_authorize\",\n \"max_attempts_exceeded\",\n \"card_disabled\",\n \"insufficient_amount\",\n \"card_insufficient_amount\",\n \"amount_limit_exceeded\",\n \"processing_error\",\n \"invalid_installments\",\n \"3ds_challenge_expired\",\n]);\n\nconst STATUS_OUTCOMES: Readonly<Record<PaymentStatus, Omit<PaymentOutcome, \"status\">>> = {\n paid: {\n tone: \"success\",\n title: \"Pagamento aprovado\",\n message: \"Recebemos o seu pagamento.\",\n canRetry: false,\n },\n authorized: {\n tone: \"info\",\n title: \"Pagamento autorizado\",\n message: WAITING_CAPTURE_MESSAGE,\n canRetry: false,\n },\n pending: {\n tone: \"info\",\n title: \"Aguardando pagamento\",\n message: \"O pagamento ainda não foi concluído.\",\n canRetry: false,\n },\n in_analysis: {\n tone: \"info\",\n title: \"Pagamento em análise\",\n message: \"O pagamento está em análise. Avisaremos assim que for concluído.\",\n canRetry: false,\n },\n failed: {\n tone: \"danger\",\n title: \"Pagamento recusado\",\n message: \"O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.\",\n canRetry: true,\n },\n expired: {\n tone: \"warning\",\n title: \"Pagamento expirado\",\n message: \"O prazo para pagar acabou. Comece um novo pagamento.\",\n canRetry: true,\n },\n cancelled: {\n tone: \"neutral\",\n title: \"Pagamento cancelado\",\n message: \"Esta cobrança foi cancelada e não será cobrada.\",\n canRetry: false,\n },\n refunded: {\n tone: \"neutral\",\n title: \"Pagamento estornado\",\n message: \"O valor foi devolvido a você.\",\n canRetry: false,\n },\n charged_back: {\n tone: \"warning\",\n title: \"Pagamento contestado\",\n message: \"O pagamento foi contestado junto ao banco.\",\n canRetry: false,\n },\n unknown: {\n tone: \"neutral\",\n title: \"Verificando o pagamento\",\n message: \"Ainda estamos confirmando o estado deste pagamento.\",\n canRetry: false,\n },\n};\n\n/**\n * The text a table holds for `key` itself, or `undefined`.\n *\n * `status` and `status_detail` arrive from the network, and a plain object\n * literal answers `toString`, `constructor` or `__proto__` from its prototype:\n * an `in` check or a bare lookup would read those as known and hand back a\n * function. Only an own key counts.\n */\nfunction ownText(table: Readonly<Record<string, string>>, key: string): string | undefined {\n return Object.hasOwn(table, key) ? table[key] : undefined;\n}\n\n/**\n * Turn a card charge from the backend into what the payer should read.\n *\n * The **status** decides the outcome; `status_detail` only refines the sentence.\n * That ordering is the point: `waiting_capture` arrives as `authorized` and\n * reads as \"reserved, not charged\", never as paid, and a detail this table does\n * not know falls back to the status's generic text instead of guessing — and\n * \"know\" means an own key of the table, so a status or detail named after an\n * `Object.prototype` member (`toString`, `constructor`, `__proto__`) reads as\n * `unknown` / unknown detail instead of a half-empty outcome. A\n * decline reason only refines a `failed` charge, and a pending reason only a\n * charge that is not `failed`, so a detail that contradicts its status cannot\n * put \"recusado\" and \"reservado\" on the same screen.\n *\n * A decline is data here, not an exception. The backend returns a declined card\n * as a `CardCharge` with `status: \"failed\"` — Mercado Pago answers it with HTTP\n * 402 and the backend reads that as a result — so the checkout renders the\n * reason the same way it renders an approval. If your own endpoint chooses to\n * answer a decline with a non-2xx status, parse its body and pass it here; do\n * not show it as a network error, because it is not one.\n *\n * @param charge - The charge, or just its `status` and `status_detail`.\n * @param messages - Overrides per `status_detail`, merged over\n * {@link CARD_STATUS_DETAIL_MESSAGES} — for another language or tone.\n * @returns Tone, title, message and whether the payer can retry.\n *\n * @example\n * const outcome = describeCardCharge(charge);\n * <Alert variant={outcome.tone} title={outcome.title} description={outcome.message} />\n */\nexport function describeCardCharge(\n charge: Pick<CardCharge, \"status\" | \"status_detail\">,\n messages: Readonly<Record<string, string>> = {},\n): PaymentOutcome {\n const status: PaymentStatus = Object.hasOwn(STATUS_OUTCOMES, charge.status)\n ? charge.status\n : \"unknown\";\n const base = STATUS_OUTCOMES[status];\n const detail = charge.status_detail ?? \"\";\n const matches =\n detail !== \"\" && status !== \"paid\" && DECLINE_DETAILS.has(detail) === (status === \"failed\");\n const refined = matches\n ? (ownText(messages, detail) ?? ownText(CARD_STATUS_DETAIL_MESSAGES, detail))\n : undefined;\n return { status, ...base, message: refined ?? base.message };\n}\n"],"mappings":";AAmBA,IAAMA,IACF,yFAgBSC,IAAgE;CACzE,sBAAsB;CACtB,oBAAoB;CACpB,WAAW;CACX,oBACI;CACJ,6BACI;CACJ,uBACI;CACJ,eAAe;CACf,qBACI;CACJ,0BACI;CACJ,uBAAuB;CACvB,kBAAkB;CAClB,sBAAsB;CACtB,yBACI;CACJ,iBAAiBD;CACjB,mBAAmB;CACnB,uBAAuB;CACvB,WAAW;CACX,YAAY;AAChB,GAEME,oBAAuC,IAAI,IAAI;CACjD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC,GAEKC,IAAmF;CACrF,MAAM;EACF,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,YAAY;EACR,MAAM;EACN,OAAO;EACP,SAASH;EACT,UAAU;CACd;CACA,SAAS;EACL,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,aAAa;EACT,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,QAAQ;EACJ,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,SAAS;EACL,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,WAAW;EACP,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,UAAU;EACN,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,cAAc;EACV,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;CACA,SAAS;EACL,MAAM;EACN,OAAO;EACP,SAAS;EACT,UAAU;CACd;AACJ;AAUA,SAASI,EAAQ,GAAyC,GAAiC;CACvF,OAAO,OAAO,OAAOC,GAAOC,CAAG,IAAID,EAAMC,KAAO,KAAA;AACpD;AAgCA,SAAgBC,EACZ,GACA,IAA6C,CAAC,GAChC;CACd,IAAMC,IAAwB,OAAO,OAAOL,GAAiBM,EAAO,MAAM,IACpEA,EAAO,SACP,WACAC,IAAOP,EAAgBK,IACvBG,IAASF,EAAO,iBAAiB,IAGjCG,IADFD,MAAW,MAAMH,MAAW,UAAUN,EAAgB,IAAIS,CAAM,OAAOH,MAAW,YAE/EJ,EAAQS,GAAUF,CAAM,KAAKP,EAAQH,GAA6BU,CAAM,IACzE,KAAA;CACN,OAAO;EAAE;EAAQ,GAAGD;EAAM,SAASE,KAAWF,EAAK;CAAQ;AAC/D"}
@@ -0,0 +1,2 @@
1
+ var e=new Set([`paid`,`expired`,`cancelled`,`refunded`,`charged_back`,`failed`]);function t(t){return e.has(t)}exports.isFinalPaymentStatus=t;
2
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","names":["FINAL_STATUSES","isFinalPaymentStatus","status"],"sources":["../../src/payments/types.ts"],"sourcesContent":["/**\n * The payment contract as the `tempest-fastapi-sdk` backend serialises it.\n *\n * Every shape here is the JSON a service built on that SDK answers with — the\n * Pydantic schemas in `tempest_fastapi_sdk/integrations/payment/base.py`, dumped\n * with `model_dump(mode=\"json\")`. The field names stay in snake_case because\n * `BaseSchema` declares no alias generator, so that is what crosses the wire;\n * renaming them here would make every app write a mapper that can drift.\n *\n * Datetimes arrive as ISO 8601 strings and money as integer cents, never as a\n * float: the backend refuses floats for money on purpose, and so does this side.\n */\n\n/**\n * The canonical state of a charge — `PaymentStatusEnum` in the backend.\n *\n * `\"unknown\"` is a real state, not a fallback: the backend uses it for a\n * provider state its version does not classify, keeping the provider's own\n * string in `provider_status`. A screen should show it as \"we are checking\",\n * never as paid or failed.\n */\nexport type PaymentStatus =\n | \"pending\"\n | \"authorized\"\n | \"paid\"\n | \"expired\"\n | \"cancelled\"\n | \"refunded\"\n | \"charged_back\"\n | \"in_analysis\"\n | \"failed\"\n | \"unknown\";\n\n/** Who is paying — `PixPayer` in the backend, shared by Pix and card requests. */\nexport interface PaymentPayer {\n /** Full name. */\n name?: string | null;\n /** CPF or CNPJ, digits or formatted. */\n tax_id?: string | null;\n /** E-mail. Mercado Pago requires it on a card charge (`payer.email`). */\n email?: string | null;\n /** Phone number. */\n phone?: string | null;\n}\n\n/** A Pix charge — `PixCharge` in the backend. */\nexport interface PixCharge {\n /** Provider that owns the charge (`\"mercado_pago\"`, `\"openpix\"`, …). */\n provider: string;\n /** The charge's id at the provider. */\n provider_charge_id: string;\n /** The service's own identifier for the charge. */\n reference: string;\n /** Amount in cents. */\n amount_cents: number;\n /** ISO 4217 code; `\"BRL\"` for Pix. */\n currency: string;\n /** Canonical state — the only field a screen branches on. */\n status: PaymentStatus;\n /** The provider's raw state, for support and logs. */\n provider_status: string;\n /** EMV copia-e-cola string. */\n br_code: string | null;\n /** QR as a Base64 PNG, without the `data:` prefix (Mercado Pago fills it). */\n qr_code_base64: string | null;\n /** QR as a URL (OpenPix fills it instead of the Base64). */\n qr_code_image_url: string | null;\n /** Pix end-to-end id, once the provider reports one. */\n end_to_end_id: string | null;\n /** When the charge stops being payable, ISO 8601. */\n expires_at: string | null;\n /** When it settled, ISO 8601. */\n paid_at: string | null;\n /** The provider's payload as decoded. A screen should not read it. */\n raw?: Record<string, unknown>;\n}\n\n/**\n * The body a service needs to charge a tokenized card — `CardChargeRequest` in\n * the backend, minus nothing: `card_token`, `payment_method_id` and\n * `installments` are exactly what {@link useMercadoPagoCard}'s `tokenize`\n * returns, and the card number is never part of it.\n */\nexport interface CardChargeRequest {\n /** The price in cents. With installments, still the price: the provider adds the interest. */\n amount_cents: number;\n /** The service's own identifier — also the create's idempotency key. */\n reference: string;\n /** Single-use token produced in the browser. */\n card_token: string;\n /** Card brand as the provider names it (`visa`, `master`, …). */\n payment_method_id: string;\n /** Installments the payer chose. Default `1` on the backend. */\n installments?: number;\n /** `false` only authorizes; the charge comes back `authorized`. Default `true`. */\n capture?: boolean;\n /** Free text, where the provider supports it. */\n description?: string | null;\n /** Who is paying. */\n payer?: PaymentPayer | null;\n}\n\n/** A card charge — `CardCharge` in the backend. */\nexport interface CardCharge {\n /** Provider that owns the charge. */\n provider: string;\n /** The charge's id at the provider. */\n provider_charge_id: string;\n /** The service's own identifier. */\n reference: string;\n /** Amount charged, in cents. */\n amount_cents: number;\n /** How much went back to the payer, in cents. A partial refund keeps `status` at `paid`. */\n refunded_cents: number;\n /** ISO 4217 code. */\n currency: string;\n /**\n * Canonical state. A declined card is `\"failed\"` — returned, not raised: the\n * backend answers the decline as a charge, with the reason in `status_detail`.\n */\n status: PaymentStatus;\n /** The provider's raw state. */\n provider_status: string;\n /** The provider's reason — `rejected_by_issuer`, `waiting_capture`, `accredited`. */\n status_detail: string | null;\n /** Card brand. */\n payment_method_id: string | null;\n /** Installments charged. */\n installments: number | null;\n /** Each installment, interest included, in cents. */\n installment_amount_cents: number | null;\n /** What the payer owes with interest, in cents. Not proof of payment — read `status`. */\n payer_total_cents: number | null;\n /** The provider's payload as decoded. */\n raw?: Record<string, unknown>;\n}\n\nconst FINAL_STATUSES: ReadonlySet<PaymentStatus> = new Set<PaymentStatus>([\n \"paid\",\n \"expired\",\n \"cancelled\",\n \"refunded\",\n \"charged_back\",\n \"failed\",\n]);\n\n/**\n * Whether a checkout has nothing left to wait for.\n *\n * `paid`, `expired`, `cancelled`, `refunded`, `charged_back` and `failed` end the\n * flow the payer is looking at, so a status poll stops there. `pending`,\n * `authorized`, `in_analysis` and `unknown` do not: each is a state the backend\n * will still move. `paid` can later become `refunded`, but that is no longer\n * something the checkout screen waits for.\n *\n * @param status - The charge's canonical state.\n * @returns `true` when following the charge further is pointless.\n *\n * @example\n * if (isFinalPaymentStatus(charge.status)) stopPolling();\n */\nexport function isFinalPaymentStatus(status: PaymentStatus): boolean {\n return FINAL_STATUSES.has(status);\n}\n"],"mappings":"AAyIA,IAAMA,EAA6C,IAAI,IAAmB,CACtE,OACA,UACA,YACA,WACA,eACA,QACJ,CAAC,EAiBD,SAAgBC,EAAqB,EAAgC,CACjE,OAAOD,EAAe,IAAIE,CAAM,CACpC,C"}
@@ -0,0 +1,16 @@
1
+ //#region src/payments/types.ts
2
+ var e = /* @__PURE__ */ new Set([
3
+ "paid",
4
+ "expired",
5
+ "cancelled",
6
+ "refunded",
7
+ "charged_back",
8
+ "failed"
9
+ ]);
10
+ function t(t) {
11
+ return e.has(t);
12
+ }
13
+ //#endregion
14
+ export { t as isFinalPaymentStatus };
15
+
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":["FINAL_STATUSES","isFinalPaymentStatus","status"],"sources":["../../src/payments/types.ts"],"sourcesContent":["/**\n * The payment contract as the `tempest-fastapi-sdk` backend serialises it.\n *\n * Every shape here is the JSON a service built on that SDK answers with — the\n * Pydantic schemas in `tempest_fastapi_sdk/integrations/payment/base.py`, dumped\n * with `model_dump(mode=\"json\")`. The field names stay in snake_case because\n * `BaseSchema` declares no alias generator, so that is what crosses the wire;\n * renaming them here would make every app write a mapper that can drift.\n *\n * Datetimes arrive as ISO 8601 strings and money as integer cents, never as a\n * float: the backend refuses floats for money on purpose, and so does this side.\n */\n\n/**\n * The canonical state of a charge — `PaymentStatusEnum` in the backend.\n *\n * `\"unknown\"` is a real state, not a fallback: the backend uses it for a\n * provider state its version does not classify, keeping the provider's own\n * string in `provider_status`. A screen should show it as \"we are checking\",\n * never as paid or failed.\n */\nexport type PaymentStatus =\n | \"pending\"\n | \"authorized\"\n | \"paid\"\n | \"expired\"\n | \"cancelled\"\n | \"refunded\"\n | \"charged_back\"\n | \"in_analysis\"\n | \"failed\"\n | \"unknown\";\n\n/** Who is paying — `PixPayer` in the backend, shared by Pix and card requests. */\nexport interface PaymentPayer {\n /** Full name. */\n name?: string | null;\n /** CPF or CNPJ, digits or formatted. */\n tax_id?: string | null;\n /** E-mail. Mercado Pago requires it on a card charge (`payer.email`). */\n email?: string | null;\n /** Phone number. */\n phone?: string | null;\n}\n\n/** A Pix charge — `PixCharge` in the backend. */\nexport interface PixCharge {\n /** Provider that owns the charge (`\"mercado_pago\"`, `\"openpix\"`, …). */\n provider: string;\n /** The charge's id at the provider. */\n provider_charge_id: string;\n /** The service's own identifier for the charge. */\n reference: string;\n /** Amount in cents. */\n amount_cents: number;\n /** ISO 4217 code; `\"BRL\"` for Pix. */\n currency: string;\n /** Canonical state — the only field a screen branches on. */\n status: PaymentStatus;\n /** The provider's raw state, for support and logs. */\n provider_status: string;\n /** EMV copia-e-cola string. */\n br_code: string | null;\n /** QR as a Base64 PNG, without the `data:` prefix (Mercado Pago fills it). */\n qr_code_base64: string | null;\n /** QR as a URL (OpenPix fills it instead of the Base64). */\n qr_code_image_url: string | null;\n /** Pix end-to-end id, once the provider reports one. */\n end_to_end_id: string | null;\n /** When the charge stops being payable, ISO 8601. */\n expires_at: string | null;\n /** When it settled, ISO 8601. */\n paid_at: string | null;\n /** The provider's payload as decoded. A screen should not read it. */\n raw?: Record<string, unknown>;\n}\n\n/**\n * The body a service needs to charge a tokenized card — `CardChargeRequest` in\n * the backend, minus nothing: `card_token`, `payment_method_id` and\n * `installments` are exactly what {@link useMercadoPagoCard}'s `tokenize`\n * returns, and the card number is never part of it.\n */\nexport interface CardChargeRequest {\n /** The price in cents. With installments, still the price: the provider adds the interest. */\n amount_cents: number;\n /** The service's own identifier — also the create's idempotency key. */\n reference: string;\n /** Single-use token produced in the browser. */\n card_token: string;\n /** Card brand as the provider names it (`visa`, `master`, …). */\n payment_method_id: string;\n /** Installments the payer chose. Default `1` on the backend. */\n installments?: number;\n /** `false` only authorizes; the charge comes back `authorized`. Default `true`. */\n capture?: boolean;\n /** Free text, where the provider supports it. */\n description?: string | null;\n /** Who is paying. */\n payer?: PaymentPayer | null;\n}\n\n/** A card charge — `CardCharge` in the backend. */\nexport interface CardCharge {\n /** Provider that owns the charge. */\n provider: string;\n /** The charge's id at the provider. */\n provider_charge_id: string;\n /** The service's own identifier. */\n reference: string;\n /** Amount charged, in cents. */\n amount_cents: number;\n /** How much went back to the payer, in cents. A partial refund keeps `status` at `paid`. */\n refunded_cents: number;\n /** ISO 4217 code. */\n currency: string;\n /**\n * Canonical state. A declined card is `\"failed\"` — returned, not raised: the\n * backend answers the decline as a charge, with the reason in `status_detail`.\n */\n status: PaymentStatus;\n /** The provider's raw state. */\n provider_status: string;\n /** The provider's reason — `rejected_by_issuer`, `waiting_capture`, `accredited`. */\n status_detail: string | null;\n /** Card brand. */\n payment_method_id: string | null;\n /** Installments charged. */\n installments: number | null;\n /** Each installment, interest included, in cents. */\n installment_amount_cents: number | null;\n /** What the payer owes with interest, in cents. Not proof of payment — read `status`. */\n payer_total_cents: number | null;\n /** The provider's payload as decoded. */\n raw?: Record<string, unknown>;\n}\n\nconst FINAL_STATUSES: ReadonlySet<PaymentStatus> = new Set<PaymentStatus>([\n \"paid\",\n \"expired\",\n \"cancelled\",\n \"refunded\",\n \"charged_back\",\n \"failed\",\n]);\n\n/**\n * Whether a checkout has nothing left to wait for.\n *\n * `paid`, `expired`, `cancelled`, `refunded`, `charged_back` and `failed` end the\n * flow the payer is looking at, so a status poll stops there. `pending`,\n * `authorized`, `in_analysis` and `unknown` do not: each is a state the backend\n * will still move. `paid` can later become `refunded`, but that is no longer\n * something the checkout screen waits for.\n *\n * @param status - The charge's canonical state.\n * @returns `true` when following the charge further is pointless.\n *\n * @example\n * if (isFinalPaymentStatus(charge.status)) stopPolling();\n */\nexport function isFinalPaymentStatus(status: PaymentStatus): boolean {\n return FINAL_STATUSES.has(status);\n}\n"],"mappings":";AAyIA,IAAMA,oBAA6C,IAAI,IAAmB;CACtE;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;AAiBD,SAAgBC,EAAqB,GAAgC;CACjE,OAAOD,EAAe,IAAIE,CAAM;AACpC"}
@@ -0,0 +1,2 @@
1
+ const e=require("../hooks/use-latest-ref.cjs"),t=require("./mercado-pago.cjs");let n=require("react");var r=6,i=[`cardNumber`,`expirationDate`,`securityCode`];function a(e){let t=Math.trunc(e/100),n=Math.abs(e%100);return`${t}.${String(n).padStart(2,`0`)}`}function o(e){return Math.round(e*100)}function s(e){if(e instanceof Error)return e;if(Array.isArray(e)){let t=e.map(e=>typeof e==`object`&&e&&`message`in e?String(e.message):String(e));return Error(t.join(`; `))}return Error(String(e))}function c(e){if(typeof e==`object`&&e&&`bin`in e){let t=e.bin;return typeof t==`string`?t:``}return``}function l(l){let{publicKey:u,amountCents:d,containers:f,locale:p=`pt-BR`,fieldOptions:m,load:h=t.loadMercadoPago}=l,[g,_]=(0,n.useState)(`loading`),[v,y]=(0,n.useState)(null),[b,x]=(0,n.useState)(``),[S,C]=(0,n.useState)(null),[w,T]=(0,n.useState)([]),[E,D]=(0,n.useState)(!1),[O,k]=(0,n.useState)(null),A=(0,n.useRef)(null),j=e.useLatestRef({containers:f,locale:p,fieldOptions:m,load:h});(0,n.useEffect)(()=>{let e=!1,t=[];_(`loading`),y(null),x(``);let n=j.current;return n.load().then(r=>{if(e)return;let a=new r(u,{locale:n.locale});for(let e of i){let r=a.fields.create(e,n.fieldOptions?.[e]);r.mount(n.containers[e]),t.push(r),e===`cardNumber`&&r.on(`binChange`,e=>x(c(e)))}A.current=a,_(`ready`)}).catch(t=>{e||(y(s(t)),_(`error`))}),()=>{e=!0,A.current=null;for(let e of t)try{e.unmount()}catch{}}},[u,j]),(0,n.useEffect)(()=>{let e=A.current;if(C(null),T([]),k(null),e===null||b.length<r||g!==`ready`){D(!1);return}let t=!1;return D(!0),e.getInstallments({amount:a(d),bin:b}).then(e=>{if(t)return;let n=e[0];if(n===void 0)throw Error(`Mercado Pago returned no installments for this card.`);C({bin:b,paymentMethodId:n.payment_method_id}),T(n.payer_costs.map(e=>({installments:Number(e.installments),recommendedMessage:e.recommended_message,installmentAmountCents:o(e.installment_amount),totalAmountCents:o(e.total_amount)})))}).catch(e=>{t||k(s(e))}).finally(()=>{t||D(!1)}),()=>{t=!0}},[b,d,g]);let M=(0,n.useCallback)(async e=>{let t=A.current;if(t===null||g!==`ready`)throw Error(`The card fields are not ready yet.`);if(S===null)throw Error(`The card brand has not been identified yet.`);if(!w.some(t=>t.installments===e.installments))throw Error(`${e.installments} installments is not an option for this card and amount.`);let n={cardholderName:e.cardholderName};e.identificationType!==void 0&&(n.identificationType=e.identificationType),e.identificationNumber!==void 0&&(n.identificationNumber=e.identificationNumber);let i=await t.fields.createCardToken(n).catch(e=>{throw s(e)}),a=i.first_six_digits;if(typeof a==`string`&&a!==S.bin.slice(0,r))throw Error(`The card changed after the installments were quoted. Try again.`);return{token:i.id,payment_method_id:S.paymentMethodId,installments:e.installments}},[g,S,w]);return{status:g,error:v,paymentMethodId:S?.paymentMethodId??null,installmentOptions:w,installmentsLoading:E,installmentsError:O,tokenize:M}}exports.useMercadoPagoCard=l;
2
+ //# sourceMappingURL=use-mercado-pago-card.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mercado-pago-card.cjs","names":["loadMercadoPago","useState","useRef","useLatestRef","useCallback","require_use_latest_ref","require_mercado_pago","react","BIN_PREFIX","FIELD_TYPES","centsToDecimal","whole","cents","rest","toCents","amount","toError","reason","parts","item","readBin","payload","bin","useMercadoPagoCard","options","status","setStatus","error","setError","setBin","quote","setQuote","installmentOptions","setInstallmentOptions","installmentsLoading","setInstallmentsLoading","installmentsError","setInstallmentsError","mpRef","setupRef","cancelled","mounted","setup","mp","MercadoPago","publicKey","type","field","stale","amountCents","entry","entries","cost","tokenize","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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"}
@@ -0,0 +1,128 @@
1
+ import { useLatestRef as e } from "../hooks/use-latest-ref.js";
2
+ import { loadMercadoPago as t } from "./mercado-pago.js";
3
+ import { useCallback as n, useEffect as r, useRef as i, useState as a } from "react";
4
+ //#region src/payments/use-mercado-pago-card.ts
5
+ var o = 6, s = [
6
+ "cardNumber",
7
+ "expirationDate",
8
+ "securityCode"
9
+ ];
10
+ function c(e) {
11
+ let t = Math.trunc(e / 100), n = Math.abs(e % 100);
12
+ return `${t}.${String(n).padStart(2, "0")}`;
13
+ }
14
+ function l(e) {
15
+ return Math.round(e * 100);
16
+ }
17
+ function u(e) {
18
+ if (e instanceof Error) return e;
19
+ if (Array.isArray(e)) {
20
+ let t = e.map((e) => typeof e == "object" && e && "message" in e ? String(e.message) : String(e));
21
+ return Error(t.join("; "));
22
+ }
23
+ return Error(String(e));
24
+ }
25
+ function d(e) {
26
+ if (typeof e == "object" && e && "bin" in e) {
27
+ let t = e.bin;
28
+ return typeof t == "string" ? t : "";
29
+ }
30
+ return "";
31
+ }
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
+ });
39
+ r(() => {
40
+ let e = !1, t = [];
41
+ b("loading"), S(null), w("");
42
+ let n = P.current;
43
+ return n.load().then((r) => {
44
+ if (e) return;
45
+ let i = new r(p, { locale: n.locale });
46
+ for (let e of s) {
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) => w(d(e)));
49
+ }
50
+ N.current = i, b("ready");
51
+ }).catch((t) => {
52
+ e || (S(u(t)), b("error"));
53
+ }), () => {
54
+ e = !0, N.current = null;
55
+ for (let e of t) try {
56
+ e.unmount();
57
+ } catch {}
58
+ };
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
+ return;
64
+ }
65
+ let t = !1;
66
+ return A(!0), e.getInstallments({
67
+ amount: c(m),
68
+ bin: C
69
+ }).then((e) => {
70
+ if (t) return;
71
+ let n = e[0];
72
+ if (n === void 0) throw Error("Mercado Pago returned no installments for this card.");
73
+ E({
74
+ bin: C,
75
+ paymentMethodId: n.payment_method_id
76
+ }), O(n.payer_costs.map((e) => ({
77
+ installments: Number(e.installments),
78
+ recommendedMessage: e.recommended_message,
79
+ installmentAmountCents: l(e.installment_amount),
80
+ totalAmountCents: l(e.total_amount)
81
+ })));
82
+ }).catch((e) => {
83
+ t || M(u(e));
84
+ }).finally(() => {
85
+ t || A(!1);
86
+ }), () => {
87
+ t = !0;
88
+ };
89
+ }, [
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
123
+ };
124
+ }
125
+ //#endregion
126
+ export { f as useMercadoPagoCard };
127
+
128
+ //# sourceMappingURL=use-mercado-pago-card.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,2 @@
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
+ //# sourceMappingURL=use-pix-charge.cjs.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,71 @@
1
+ import { useLatestRef as e } from "../hooks/use-latest-ref.js";
2
+ import { isFinalPaymentStatus as t } from "./types.js";
3
+ import { useEffect as n, useState as r } from "react";
4
+ //#region src/payments/use-pix-charge.ts
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]);
12
+ }
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,
19
+ error: null
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,
26
+ error: null
27
+ }));
28
+ let g = a(h), _ = t(h.status);
29
+ return n(() => {
30
+ let { subscribe: e } = m.current;
31
+ if (e !== void 0) return e((e) => u((t) => o(t.charge, e) ? {
32
+ charge: e,
33
+ error: null
34
+ } : t));
35
+ }, [g, m]), n(() => {
36
+ if (_ || m.current.refresh === void 0) return;
37
+ let e = new AbortController(), t, n = () => {
38
+ let r = m.current.refresh;
39
+ r !== void 0 && r(e.signal).then((t) => {
40
+ e.signal.aborted || u((e) => o(e.charge, t) ? {
41
+ charge: t,
42
+ error: null
43
+ } : e);
44
+ }).catch((t) => {
45
+ if (e.signal.aborted) return;
46
+ let n = t instanceof Error ? t : Error(String(t));
47
+ u((e) => ({
48
+ ...e,
49
+ error: n
50
+ }));
51
+ }).finally(() => {
52
+ e.signal.aborted || (t = setTimeout(n, c));
53
+ });
54
+ };
55
+ return t = setTimeout(n, c), () => {
56
+ e.abort(), t !== void 0 && clearTimeout(t);
57
+ };
58
+ }, [
59
+ g,
60
+ _,
61
+ c,
62
+ m
63
+ ]), {
64
+ charge: h,
65
+ error: f === d ? l.error : null
66
+ };
67
+ }
68
+ //#endregion
69
+ export { s as usePixCharge };
70
+
71
+ //# sourceMappingURL=use-pix-charge.js.map
@@ -0,0 +1 @@
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!==null&&(_??=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()):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!==null&&(_??=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