tempest-react-sdk 0.84.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/bin/create-tempest-app.mjs +10 -1
  2. package/dist/auth/create-tempest-auth.cjs +1 -1
  3. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  4. package/dist/auth/create-tempest-auth.js +258 -187
  5. package/dist/auth/create-tempest-auth.js.map +1 -1
  6. package/dist/components/Markdown/Markdown.cjs +1 -1
  7. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  8. package/dist/components/Markdown/Markdown.js +29 -28
  9. package/dist/components/Markdown/Markdown.js.map +1 -1
  10. package/dist/components/Markdown/markdown-parse.cjs +6 -5
  11. package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
  12. package/dist/components/Markdown/markdown-parse.js +195 -160
  13. package/dist/components/Markdown/markdown-parse.js.map +1 -1
  14. package/dist/payments/PixCheckout.cjs +1 -1
  15. package/dist/payments/PixCheckout.cjs.map +1 -1
  16. package/dist/payments/PixCheckout.js +1 -1
  17. package/dist/payments/PixCheckout.js.map +1 -1
  18. package/dist/payments/mercado-pago.cjs.map +1 -1
  19. package/dist/payments/mercado-pago.js.map +1 -1
  20. package/dist/payments/outcome.cjs +1 -1
  21. package/dist/payments/outcome.cjs.map +1 -1
  22. package/dist/payments/outcome.js +8 -5
  23. package/dist/payments/outcome.js.map +1 -1
  24. package/dist/payments/use-mercado-pago-card.cjs +1 -1
  25. package/dist/payments/use-mercado-pago-card.cjs.map +1 -1
  26. package/dist/payments/use-mercado-pago-card.js +69 -61
  27. package/dist/payments/use-mercado-pago-card.js.map +1 -1
  28. package/dist/payments/use-pix-charge.cjs +1 -1
  29. package/dist/payments/use-pix-charge.cjs.map +1 -1
  30. package/dist/payments/use-pix-charge.js +36 -28
  31. package/dist/payments/use-pix-charge.js.map +1 -1
  32. package/dist/query/offline-persistence.cjs +1 -1
  33. package/dist/query/offline-persistence.cjs.map +1 -1
  34. package/dist/query/offline-persistence.js +100 -54
  35. package/dist/query/offline-persistence.js.map +1 -1
  36. package/dist/tempest-react-sdk.d.ts +245 -15
  37. package/package.json +7 -1
  38. package/template/package.json +6 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PixCheckout.cjs","names":["formatPixRemaining","useCountdown","cn","styles","Money","Alert","_Fragment","QRCode","CopyButton","require_cn","require_Alert","require_use_countdown","require_QRCode","require_CopyButton","require_Money","require_format","require_PixCheckout_module","react_jsx_runtime","DEFAULT_LABELS","STATE_VARIANT","BASE64","PNG_PREFIX","qrImageSource","base64","compact","PixCheckout","rest","text","labels","expiresAt","charge","hasDeadline","remaining","status","pastDeadline","payable","image","state","className","formatRemaining","qrSize","footer"],"sources":["../../src/payments/PixCheckout.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — `charge` is the data, `qrSize` the symbol,\n * `labels`/`formatRemaining` the copy an app translates, and `onCopied` the one\n * intent the screen emits.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { Alert } from \"@/components/Alert\";\nimport type { AlertVariant } from \"@/components/Alert\";\nimport { CopyButton } from \"@/components/CopyButton\";\nimport { Money } from \"@/components/Money\";\nimport { QRCode } from \"@/components/QRCode\";\nimport { useCountdown } from \"@/hooks/use-countdown\";\nimport { cn } from \"@/utils/cn\";\n\nimport { formatPixRemaining } from \"./format\";\nimport type { PaymentStatus, PixCharge } from \"./types\";\nimport styles from \"./PixCheckout.module.css\";\n\n/** Strings the screen renders, so an app can translate them. */\nexport interface PixCheckoutLabels {\n /** Accessible name of the QR. */\n qr: string;\n /** Heading over the copia-e-cola line. */\n copyHint: string;\n /** Copy button, idle. */\n copy: string;\n /** Copy button, just clicked. */\n copied: string;\n /** Prefix of the countdown, e.g. \"Expira em\". */\n expiresIn: string;\n /** Shown when the deadline passed but the backend has not said \"expired\" yet. */\n confirming: string;\n /** Title and description per terminal or waiting state. */\n states: Record<PaymentStatus, { title: string; description: string }>;\n}\n\nconst DEFAULT_LABELS: PixCheckoutLabels = {\n qr: \"QR code do Pix\",\n copyHint: \"Ou copie o código Pix e cole no app do seu banco\",\n copy: \"Copiar código\",\n copied: \"Copiado\",\n expiresIn: \"Expira em\",\n confirming: \"O prazo acabou. Estamos confirmando com o banco se o pagamento chegou.\",\n states: {\n pending: {\n title: \"Aguardando pagamento\",\n description: \"Abra o app do seu banco, escolha Pix e escaneie o QR code.\",\n },\n authorized: {\n title: \"Pagamento autorizado\",\n description: \"Estamos concluindo o pagamento.\",\n },\n in_analysis: {\n title: \"Pagamento em análise\",\n description: \"Avisaremos assim que for concluído.\",\n },\n paid: { title: \"Pagamento confirmado\", description: \"Recebemos o seu Pix. Obrigado!\" },\n expired: {\n title: \"Pix expirado\",\n description: \"O prazo para pagar acabou. Gere um novo Pix.\",\n },\n cancelled: {\n title: \"Pix cancelado\",\n description: \"Esta cobrança foi cancelada e não pode mais ser paga.\",\n },\n refunded: {\n title: \"Pix devolvido\",\n description: \"O valor foi devolvido para a sua conta.\",\n },\n charged_back: {\n title: \"Pix contestado\",\n description: \"O pagamento foi contestado junto ao banco.\",\n },\n failed: {\n title: \"Pix não concluído\",\n description: \"Não foi possível concluir o pagamento. Gere um novo Pix.\",\n },\n unknown: {\n title: \"Verificando o pagamento\",\n description: \"Ainda estamos confirmando este Pix.\",\n },\n },\n};\n\nconst STATE_VARIANT: Record<PaymentStatus, AlertVariant> = {\n pending: \"info\",\n authorized: \"info\",\n in_analysis: \"info\",\n paid: \"success\",\n expired: \"warning\",\n cancelled: \"neutral\",\n refunded: \"neutral\",\n charged_back: \"warning\",\n failed: \"danger\",\n unknown: \"neutral\",\n};\n\nconst BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;\nconst PNG_PREFIX = \"data:image/png;base64,\";\n\n/**\n * The QR image source, or `null` to draw it locally.\n *\n * Only a well-formed Base64 payload becomes a `data:image/png` URL: the field\n * arrives from the network, and an image `src` built from an unchecked string\n * is a place where something other than a PNG can end up. A value that already\n * carries the `data:image/png;base64,` prefix is accepted once.\n */\nfunction qrImageSource(base64: string | null): string | null {\n if (base64 === null) return null;\n const body = base64.startsWith(PNG_PREFIX) ? base64.slice(PNG_PREFIX.length) : base64;\n const compact = body.replace(/\\s+/g, \"\");\n return compact.length > 0 && compact.length % 4 === 0 && BASE64.test(compact)\n ? PNG_PREFIX + compact\n : null;\n}\n\nexport interface PixCheckoutProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The charge as the backend reports it — pair with {@link usePixCharge} to follow it. */\n charge: PixCharge;\n /** Rendered side of the QR in px. Default `220`. */\n qrSize?: number;\n labels?: Partial<Omit<PixCheckoutLabels, \"states\">> & {\n states?: Partial<PixCheckoutLabels[\"states\"]>;\n };\n /** Formats the countdown. Default {@link formatPixRemaining}. */\n formatRemaining?: (ms: number) => string;\n /** Called after the copia-e-cola reaches the clipboard. */\n onCopied?: () => void;\n /** Rendered under the status, e.g. a \"gerar novo Pix\" button for a final state. */\n footer?: ReactNode;\n}\n\n/**\n * The payer's Pix screen: amount, QR, copia-e-cola, countdown and the state the\n * backend reports.\n *\n * Presentational and controlled. It renders `charge` and never changes it, so\n * the decision that a Pix was paid stays where it can be made — on the backend,\n * re-reading the order. Feed it from {@link usePixCharge} or from your own\n * stream.\n *\n * The QR comes from `qr_code_base64` when the provider sent one (Mercado Pago),\n * and is otherwise drawn locally from `br_code` (OpenPix sends a URL instead,\n * and fetching it would hand the payload to an image host for nothing). The\n * copia-e-cola line is always next to it: a QR cannot be scanned by the phone\n * that is showing it, which is where a mobile checkout puts it.\n *\n * When the countdown reaches zero while the backend still says `pending`, the\n * screen hides the QR and says it is **confirming** — not \"expired\". A payment\n * sent in the last second can still settle, and only the backend knows.\n *\n * @example\n * const { charge } = usePixCharge(created, { refresh: (signal) => readCharge(created.provider_charge_id, signal) });\n * <PixCheckout charge={charge} />\n */\nexport function PixCheckout({\n charge,\n qrSize = 220,\n labels,\n formatRemaining = formatPixRemaining,\n onCopied,\n footer,\n className,\n ...rest\n}: PixCheckoutProps) {\n const text: PixCheckoutLabels = {\n ...DEFAULT_LABELS,\n ...labels,\n states: { ...DEFAULT_LABELS.states, ...labels?.states },\n };\n const expiresAt = charge.expires_at === null ? Number.NaN : Date.parse(charge.expires_at);\n const hasDeadline = Number.isFinite(expiresAt);\n const remaining = useCountdown(hasDeadline ? expiresAt : 0, 0);\n const status: PaymentStatus = charge.status in STATE_VARIANT ? charge.status : \"unknown\";\n const pastDeadline = hasDeadline && remaining <= 0;\n const payable = status === \"pending\" && !pastDeadline && charge.br_code !== null;\n const image = qrImageSource(charge.qr_code_base64);\n const state = text.states[status];\n\n return (\n <div className={cn(styles.checkout, className)} data-status={status} {...rest}>\n <Money\n className={styles.amount}\n cents={charge.amount_cents}\n currency={charge.currency}\n />\n {status === \"pending\" ? (\n <div role=\"status\" className={styles.state}>\n <p className={styles.title}>{state.title}</p>\n <p className={styles.description}>\n {pastDeadline ? text.confirming : state.description}\n </p>\n </div>\n ) : (\n <Alert\n className={styles.state}\n variant={STATE_VARIANT[status]}\n title={state.title}\n description={state.description}\n />\n )}\n {payable && hasDeadline && (\n <p className={styles.timer} role=\"timer\" aria-live=\"off\">\n {text.expiresIn} <strong>{formatRemaining(remaining)}</strong>\n </p>\n )}\n {payable && charge.br_code !== null && (\n <>\n <div className={styles.symbol}>\n {image !== null ? (\n <img\n src={image}\n alt={text.qr}\n width={qrSize}\n height={qrSize}\n className={styles.image}\n />\n ) : (\n <QRCode\n value={charge.br_code}\n size={qrSize}\n label={text.qr}\n margin={2}\n />\n )}\n </div>\n <p className={styles.hint}>{text.copyHint}</p>\n <div className={styles.copy}>\n <code className={styles.code} data-testid=\"pix-checkout-code\">\n {charge.br_code}\n </code>\n <CopyButton value={charge.br_code} onCopied={onCopied}>\n <span className={styles.copyIdle}>{text.copy}</span>\n <span className={styles.copyDone}>{text.copied}</span>\n </CopyButton>\n </div>\n </>\n )}\n {footer !== undefined && <div className={styles.footer}>{footer}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PixCheckout.cjs","names":["formatPixRemaining","useCountdown","cn","styles","Money","Alert","_Fragment","QRCode","CopyButton","require_cn","require_Alert","require_use_countdown","require_QRCode","require_CopyButton","require_Money","require_format","require_PixCheckout_module","react_jsx_runtime","DEFAULT_LABELS","STATE_VARIANT","BASE64","PNG_PREFIX","qrImageSource","base64","compact","PixCheckout","rest","text","labels","expiresAt","charge","hasDeadline","remaining","status","pastDeadline","payable","image","state","className","formatRemaining","qrSize","footer"],"sources":["../../src/payments/PixCheckout.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — `charge` is the data, `qrSize` the symbol,\n * `labels`/`formatRemaining` the copy an app translates, and `onCopied` the one\n * intent the screen emits.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { Alert } from \"@/components/Alert\";\nimport type { AlertVariant } from \"@/components/Alert\";\nimport { CopyButton } from \"@/components/CopyButton\";\nimport { Money } from \"@/components/Money\";\nimport { QRCode } from \"@/components/QRCode\";\nimport { useCountdown } from \"@/hooks/use-countdown\";\nimport { cn } from \"@/utils/cn\";\n\nimport { formatPixRemaining } from \"./format\";\nimport type { PaymentStatus, PixCharge } from \"./types\";\nimport styles from \"./PixCheckout.module.css\";\n\n/** Strings the screen renders, so an app can translate them. */\nexport interface PixCheckoutLabels {\n /** Accessible name of the QR. */\n qr: string;\n /** Heading over the copia-e-cola line. */\n copyHint: string;\n /** Copy button, idle. */\n copy: string;\n /** Copy button, just clicked. */\n copied: string;\n /** Prefix of the countdown, e.g. \"Expira em\". */\n expiresIn: string;\n /** Shown when the deadline passed but the backend has not said \"expired\" yet. */\n confirming: string;\n /** Title and description per terminal or waiting state. */\n states: Record<PaymentStatus, { title: string; description: string }>;\n}\n\nconst DEFAULT_LABELS: PixCheckoutLabels = {\n qr: \"QR code do Pix\",\n copyHint: \"Ou copie o código Pix e cole no app do seu banco\",\n copy: \"Copiar código\",\n copied: \"Copiado\",\n expiresIn: \"Expira em\",\n confirming: \"O prazo acabou. Estamos confirmando com o banco se o pagamento chegou.\",\n states: {\n pending: {\n title: \"Aguardando pagamento\",\n description: \"Abra o app do seu banco, escolha Pix e escaneie o QR code.\",\n },\n authorized: {\n title: \"Pagamento autorizado\",\n description: \"Estamos concluindo o pagamento.\",\n },\n in_analysis: {\n title: \"Pagamento em análise\",\n description: \"Avisaremos assim que for concluído.\",\n },\n paid: { title: \"Pagamento confirmado\", description: \"Recebemos o seu Pix. Obrigado!\" },\n expired: {\n title: \"Pix expirado\",\n description: \"O prazo para pagar acabou. Gere um novo Pix.\",\n },\n cancelled: {\n title: \"Pix cancelado\",\n description: \"Esta cobrança foi cancelada e não pode mais ser paga.\",\n },\n refunded: {\n title: \"Pix devolvido\",\n description: \"O valor foi devolvido para a sua conta.\",\n },\n charged_back: {\n title: \"Pix contestado\",\n description: \"O pagamento foi contestado junto ao banco.\",\n },\n failed: {\n title: \"Pix não concluído\",\n description: \"Não foi possível concluir o pagamento. Gere um novo Pix.\",\n },\n unknown: {\n title: \"Verificando o pagamento\",\n description: \"Ainda estamos confirmando este Pix.\",\n },\n },\n};\n\nconst STATE_VARIANT: Record<PaymentStatus, AlertVariant> = {\n pending: \"info\",\n authorized: \"info\",\n in_analysis: \"info\",\n paid: \"success\",\n expired: \"warning\",\n cancelled: \"neutral\",\n refunded: \"neutral\",\n charged_back: \"warning\",\n failed: \"danger\",\n unknown: \"neutral\",\n};\n\nconst BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;\nconst PNG_PREFIX = \"data:image/png;base64,\";\n\n/**\n * The QR image source, or `null` to draw it locally.\n *\n * Only a well-formed Base64 payload becomes a `data:image/png` URL: the field\n * arrives from the network, and an image `src` built from an unchecked string\n * is a place where something other than a PNG can end up. A value that already\n * carries the `data:image/png;base64,` prefix is accepted once.\n */\nfunction qrImageSource(base64: string | null): string | null {\n if (base64 === null) return null;\n const body = base64.startsWith(PNG_PREFIX) ? base64.slice(PNG_PREFIX.length) : base64;\n const compact = body.replace(/\\s+/g, \"\");\n return compact.length > 0 && compact.length % 4 === 0 && BASE64.test(compact)\n ? PNG_PREFIX + compact\n : null;\n}\n\nexport interface PixCheckoutProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The charge as the backend reports it — pair with {@link usePixCharge} to follow it. */\n charge: PixCharge;\n /** Rendered side of the QR in px. Default `220`. */\n qrSize?: number;\n labels?: Partial<Omit<PixCheckoutLabels, \"states\">> & {\n states?: Partial<PixCheckoutLabels[\"states\"]>;\n };\n /** Formats the countdown. Default {@link formatPixRemaining}. */\n formatRemaining?: (ms: number) => string;\n /** Called after the copia-e-cola reaches the clipboard. */\n onCopied?: () => void;\n /** Rendered under the status, e.g. a \"gerar novo Pix\" button for a final state. */\n footer?: ReactNode;\n}\n\n/**\n * The payer's Pix screen: amount, QR, copia-e-cola, countdown and the state the\n * backend reports.\n *\n * Presentational and controlled. It renders `charge` and never changes it, so\n * the decision that a Pix was paid stays where it can be made — on the backend,\n * re-reading the order. Feed it from {@link usePixCharge} or from your own\n * stream.\n *\n * The QR comes from `qr_code_base64` when the provider sent one (Mercado Pago),\n * and is otherwise drawn locally from `br_code` (OpenPix sends a URL instead,\n * and fetching it would hand the payload to an image host for nothing). The\n * copia-e-cola line is always next to it: a QR cannot be scanned by the phone\n * that is showing it, which is where a mobile checkout puts it.\n *\n * A status this version does not know renders as `unknown` (\"verificando\"). Known\n * means an own key of the state table, so a status named after an\n * `Object.prototype` member (`toString`, `constructor`) is unknown too, instead\n * of an empty state block.\n *\n * When the countdown reaches zero while the backend still says `pending`, the\n * screen hides the QR and says it is **confirming** — not \"expired\". A payment\n * sent in the last second can still settle, and only the backend knows.\n *\n * @example\n * const { charge } = usePixCharge(created, { refresh: (signal) => readCharge(created.provider_charge_id, signal) });\n * <PixCheckout charge={charge} />\n */\nexport function PixCheckout({\n charge,\n qrSize = 220,\n labels,\n formatRemaining = formatPixRemaining,\n onCopied,\n footer,\n className,\n ...rest\n}: PixCheckoutProps) {\n const text: PixCheckoutLabels = {\n ...DEFAULT_LABELS,\n ...labels,\n states: { ...DEFAULT_LABELS.states, ...labels?.states },\n };\n const expiresAt = charge.expires_at === null ? Number.NaN : Date.parse(charge.expires_at);\n const hasDeadline = Number.isFinite(expiresAt);\n const remaining = useCountdown(hasDeadline ? expiresAt : 0, 0);\n const status: PaymentStatus = Object.hasOwn(STATE_VARIANT, charge.status)\n ? charge.status\n : \"unknown\";\n const pastDeadline = hasDeadline && remaining <= 0;\n const payable = status === \"pending\" && !pastDeadline && charge.br_code !== null;\n const image = qrImageSource(charge.qr_code_base64);\n const state = text.states[status];\n\n return (\n <div className={cn(styles.checkout, className)} data-status={status} {...rest}>\n <Money\n className={styles.amount}\n cents={charge.amount_cents}\n currency={charge.currency}\n />\n {status === \"pending\" ? (\n <div role=\"status\" className={styles.state}>\n <p className={styles.title}>{state.title}</p>\n <p className={styles.description}>\n {pastDeadline ? text.confirming : state.description}\n </p>\n </div>\n ) : (\n <Alert\n className={styles.state}\n variant={STATE_VARIANT[status]}\n title={state.title}\n description={state.description}\n />\n )}\n {payable && hasDeadline && (\n <p className={styles.timer} role=\"timer\" aria-live=\"off\">\n {text.expiresIn} <strong>{formatRemaining(remaining)}</strong>\n </p>\n )}\n {payable && charge.br_code !== null && (\n <>\n <div className={styles.symbol}>\n {image !== null ? (\n <img\n src={image}\n alt={text.qr}\n width={qrSize}\n height={qrSize}\n className={styles.image}\n />\n ) : (\n <QRCode\n value={charge.br_code}\n size={qrSize}\n label={text.qr}\n margin={2}\n />\n )}\n </div>\n <p className={styles.hint}>{text.copyHint}</p>\n <div className={styles.copy}>\n <code className={styles.code} data-testid=\"pix-checkout-code\">\n {charge.br_code}\n </code>\n <CopyButton value={charge.br_code} onCopied={onCopied}>\n <span className={styles.copyIdle}>{text.copy}</span>\n <span className={styles.copyDone}>{text.copied}</span>\n </CopyButton>\n </div>\n </>\n )}\n {footer !== undefined && <div className={styles.footer}>{footer}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -82,7 +82,7 @@ function g({ charge: p, qrSize: m = 220, labels: g, formatRemaining: _ = o, onCo
82
82
  ...d.states,
83
83
  ...g?.states
84
84
  }
85
- }, C = p.expires_at === null ? NaN : Date.parse(p.expires_at), w = Number.isFinite(C), T = n(w ? C : 0, 0), E = p.status in f ? p.status : "unknown", D = w && T <= 0, O = E === "pending" && !D && p.br_code !== null, k = h(p.qr_code_base64), A = S.states[E];
85
+ }, C = p.expires_at === null ? NaN : Date.parse(p.expires_at), w = Number.isFinite(C), T = n(w ? C : 0, 0), E = Object.hasOwn(f, p.status) ? p.status : "unknown", D = w && T <= 0, O = E === "pending" && !D && p.br_code !== null, k = h(p.qr_code_base64), A = S.states[E];
86
86
  return /* @__PURE__ */ u("div", {
87
87
  className: e(s.checkout, b),
88
88
  "data-status": E,
@@ -1 +1 @@
1
- {"version":3,"file":"PixCheckout.js","names":["styles","_Fragment","PixCheckout_module_default","DEFAULT_LABELS","STATE_VARIANT","BASE64","PNG_PREFIX","qrImageSource","base64","compact","PixCheckout","formatPixRemaining","rest","text","labels","expiresAt","charge","hasDeadline","remaining","useCountdown","status","pastDeadline","payable","image","state","cn","className","Money","Alert","formatRemaining","Fragment","QRCode","qrSize","CopyButton","footer"],"sources":["../../src/payments/PixCheckout.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — `charge` is the data, `qrSize` the symbol,\n * `labels`/`formatRemaining` the copy an app translates, and `onCopied` the one\n * intent the screen emits.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { Alert } from \"@/components/Alert\";\nimport type { AlertVariant } from \"@/components/Alert\";\nimport { CopyButton } from \"@/components/CopyButton\";\nimport { Money } from \"@/components/Money\";\nimport { QRCode } from \"@/components/QRCode\";\nimport { useCountdown } from \"@/hooks/use-countdown\";\nimport { cn } from \"@/utils/cn\";\n\nimport { formatPixRemaining } from \"./format\";\nimport type { PaymentStatus, PixCharge } from \"./types\";\nimport styles from \"./PixCheckout.module.css\";\n\n/** Strings the screen renders, so an app can translate them. */\nexport interface PixCheckoutLabels {\n /** Accessible name of the QR. */\n qr: string;\n /** Heading over the copia-e-cola line. */\n copyHint: string;\n /** Copy button, idle. */\n copy: string;\n /** Copy button, just clicked. */\n copied: string;\n /** Prefix of the countdown, e.g. \"Expira em\". */\n expiresIn: string;\n /** Shown when the deadline passed but the backend has not said \"expired\" yet. */\n confirming: string;\n /** Title and description per terminal or waiting state. */\n states: Record<PaymentStatus, { title: string; description: string }>;\n}\n\nconst DEFAULT_LABELS: PixCheckoutLabels = {\n qr: \"QR code do Pix\",\n copyHint: \"Ou copie o código Pix e cole no app do seu banco\",\n copy: \"Copiar código\",\n copied: \"Copiado\",\n expiresIn: \"Expira em\",\n confirming: \"O prazo acabou. Estamos confirmando com o banco se o pagamento chegou.\",\n states: {\n pending: {\n title: \"Aguardando pagamento\",\n description: \"Abra o app do seu banco, escolha Pix e escaneie o QR code.\",\n },\n authorized: {\n title: \"Pagamento autorizado\",\n description: \"Estamos concluindo o pagamento.\",\n },\n in_analysis: {\n title: \"Pagamento em análise\",\n description: \"Avisaremos assim que for concluído.\",\n },\n paid: { title: \"Pagamento confirmado\", description: \"Recebemos o seu Pix. Obrigado!\" },\n expired: {\n title: \"Pix expirado\",\n description: \"O prazo para pagar acabou. Gere um novo Pix.\",\n },\n cancelled: {\n title: \"Pix cancelado\",\n description: \"Esta cobrança foi cancelada e não pode mais ser paga.\",\n },\n refunded: {\n title: \"Pix devolvido\",\n description: \"O valor foi devolvido para a sua conta.\",\n },\n charged_back: {\n title: \"Pix contestado\",\n description: \"O pagamento foi contestado junto ao banco.\",\n },\n failed: {\n title: \"Pix não concluído\",\n description: \"Não foi possível concluir o pagamento. Gere um novo Pix.\",\n },\n unknown: {\n title: \"Verificando o pagamento\",\n description: \"Ainda estamos confirmando este Pix.\",\n },\n },\n};\n\nconst STATE_VARIANT: Record<PaymentStatus, AlertVariant> = {\n pending: \"info\",\n authorized: \"info\",\n in_analysis: \"info\",\n paid: \"success\",\n expired: \"warning\",\n cancelled: \"neutral\",\n refunded: \"neutral\",\n charged_back: \"warning\",\n failed: \"danger\",\n unknown: \"neutral\",\n};\n\nconst BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;\nconst PNG_PREFIX = \"data:image/png;base64,\";\n\n/**\n * The QR image source, or `null` to draw it locally.\n *\n * Only a well-formed Base64 payload becomes a `data:image/png` URL: the field\n * arrives from the network, and an image `src` built from an unchecked string\n * is a place where something other than a PNG can end up. A value that already\n * carries the `data:image/png;base64,` prefix is accepted once.\n */\nfunction qrImageSource(base64: string | null): string | null {\n if (base64 === null) return null;\n const body = base64.startsWith(PNG_PREFIX) ? base64.slice(PNG_PREFIX.length) : base64;\n const compact = body.replace(/\\s+/g, \"\");\n return compact.length > 0 && compact.length % 4 === 0 && BASE64.test(compact)\n ? PNG_PREFIX + compact\n : null;\n}\n\nexport interface PixCheckoutProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The charge as the backend reports it — pair with {@link usePixCharge} to follow it. */\n charge: PixCharge;\n /** Rendered side of the QR in px. Default `220`. */\n qrSize?: number;\n labels?: Partial<Omit<PixCheckoutLabels, \"states\">> & {\n states?: Partial<PixCheckoutLabels[\"states\"]>;\n };\n /** Formats the countdown. Default {@link formatPixRemaining}. */\n formatRemaining?: (ms: number) => string;\n /** Called after the copia-e-cola reaches the clipboard. */\n onCopied?: () => void;\n /** Rendered under the status, e.g. a \"gerar novo Pix\" button for a final state. */\n footer?: ReactNode;\n}\n\n/**\n * The payer's Pix screen: amount, QR, copia-e-cola, countdown and the state the\n * backend reports.\n *\n * Presentational and controlled. It renders `charge` and never changes it, so\n * the decision that a Pix was paid stays where it can be made — on the backend,\n * re-reading the order. Feed it from {@link usePixCharge} or from your own\n * stream.\n *\n * The QR comes from `qr_code_base64` when the provider sent one (Mercado Pago),\n * and is otherwise drawn locally from `br_code` (OpenPix sends a URL instead,\n * and fetching it would hand the payload to an image host for nothing). The\n * copia-e-cola line is always next to it: a QR cannot be scanned by the phone\n * that is showing it, which is where a mobile checkout puts it.\n *\n * When the countdown reaches zero while the backend still says `pending`, the\n * screen hides the QR and says it is **confirming** — not \"expired\". A payment\n * sent in the last second can still settle, and only the backend knows.\n *\n * @example\n * const { charge } = usePixCharge(created, { refresh: (signal) => readCharge(created.provider_charge_id, signal) });\n * <PixCheckout charge={charge} />\n */\nexport function PixCheckout({\n charge,\n qrSize = 220,\n labels,\n formatRemaining = formatPixRemaining,\n onCopied,\n footer,\n className,\n ...rest\n}: PixCheckoutProps) {\n const text: PixCheckoutLabels = {\n ...DEFAULT_LABELS,\n ...labels,\n states: { ...DEFAULT_LABELS.states, ...labels?.states },\n };\n const expiresAt = charge.expires_at === null ? Number.NaN : Date.parse(charge.expires_at);\n const hasDeadline = Number.isFinite(expiresAt);\n const remaining = useCountdown(hasDeadline ? expiresAt : 0, 0);\n const status: PaymentStatus = charge.status in STATE_VARIANT ? charge.status : \"unknown\";\n const pastDeadline = hasDeadline && remaining <= 0;\n const payable = status === \"pending\" && !pastDeadline && charge.br_code !== null;\n const image = qrImageSource(charge.qr_code_base64);\n const state = text.states[status];\n\n return (\n <div className={cn(styles.checkout, className)} data-status={status} {...rest}>\n <Money\n className={styles.amount}\n cents={charge.amount_cents}\n currency={charge.currency}\n />\n {status === \"pending\" ? (\n <div role=\"status\" className={styles.state}>\n <p className={styles.title}>{state.title}</p>\n <p className={styles.description}>\n {pastDeadline ? text.confirming : state.description}\n </p>\n </div>\n ) : (\n <Alert\n className={styles.state}\n variant={STATE_VARIANT[status]}\n title={state.title}\n description={state.description}\n />\n )}\n {payable && hasDeadline && (\n <p className={styles.timer} role=\"timer\" aria-live=\"off\">\n {text.expiresIn} <strong>{formatRemaining(remaining)}</strong>\n </p>\n )}\n {payable && charge.br_code !== null && (\n <>\n <div className={styles.symbol}>\n {image !== null ? (\n <img\n src={image}\n alt={text.qr}\n width={qrSize}\n height={qrSize}\n className={styles.image}\n />\n ) : (\n <QRCode\n value={charge.br_code}\n size={qrSize}\n label={text.qr}\n margin={2}\n />\n )}\n </div>\n <p className={styles.hint}>{text.copyHint}</p>\n <div className={styles.copy}>\n <code className={styles.code} data-testid=\"pix-checkout-code\">\n {charge.br_code}\n </code>\n <CopyButton value={charge.br_code} onCopied={onCopied}>\n <span className={styles.copyIdle}>{text.copy}</span>\n <span className={styles.copyDone}>{text.copied}</span>\n </CopyButton>\n </div>\n </>\n )}\n {footer !== undefined && <div className={styles.footer}>{footer}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PixCheckout.js","names":["styles","_Fragment","PixCheckout_module_default","DEFAULT_LABELS","STATE_VARIANT","BASE64","PNG_PREFIX","qrImageSource","base64","compact","PixCheckout","formatPixRemaining","rest","text","labels","expiresAt","charge","hasDeadline","remaining","useCountdown","status","pastDeadline","payable","image","state","cn","className","Money","Alert","formatRemaining","Fragment","QRCode","qrSize","CopyButton","footer"],"sources":["../../src/payments/PixCheckout.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — `charge` is the data, `qrSize` the symbol,\n * `labels`/`formatRemaining` the copy an app translates, and `onCopied` the one\n * intent the screen emits.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { Alert } from \"@/components/Alert\";\nimport type { AlertVariant } from \"@/components/Alert\";\nimport { CopyButton } from \"@/components/CopyButton\";\nimport { Money } from \"@/components/Money\";\nimport { QRCode } from \"@/components/QRCode\";\nimport { useCountdown } from \"@/hooks/use-countdown\";\nimport { cn } from \"@/utils/cn\";\n\nimport { formatPixRemaining } from \"./format\";\nimport type { PaymentStatus, PixCharge } from \"./types\";\nimport styles from \"./PixCheckout.module.css\";\n\n/** Strings the screen renders, so an app can translate them. */\nexport interface PixCheckoutLabels {\n /** Accessible name of the QR. */\n qr: string;\n /** Heading over the copia-e-cola line. */\n copyHint: string;\n /** Copy button, idle. */\n copy: string;\n /** Copy button, just clicked. */\n copied: string;\n /** Prefix of the countdown, e.g. \"Expira em\". */\n expiresIn: string;\n /** Shown when the deadline passed but the backend has not said \"expired\" yet. */\n confirming: string;\n /** Title and description per terminal or waiting state. */\n states: Record<PaymentStatus, { title: string; description: string }>;\n}\n\nconst DEFAULT_LABELS: PixCheckoutLabels = {\n qr: \"QR code do Pix\",\n copyHint: \"Ou copie o código Pix e cole no app do seu banco\",\n copy: \"Copiar código\",\n copied: \"Copiado\",\n expiresIn: \"Expira em\",\n confirming: \"O prazo acabou. Estamos confirmando com o banco se o pagamento chegou.\",\n states: {\n pending: {\n title: \"Aguardando pagamento\",\n description: \"Abra o app do seu banco, escolha Pix e escaneie o QR code.\",\n },\n authorized: {\n title: \"Pagamento autorizado\",\n description: \"Estamos concluindo o pagamento.\",\n },\n in_analysis: {\n title: \"Pagamento em análise\",\n description: \"Avisaremos assim que for concluído.\",\n },\n paid: { title: \"Pagamento confirmado\", description: \"Recebemos o seu Pix. Obrigado!\" },\n expired: {\n title: \"Pix expirado\",\n description: \"O prazo para pagar acabou. Gere um novo Pix.\",\n },\n cancelled: {\n title: \"Pix cancelado\",\n description: \"Esta cobrança foi cancelada e não pode mais ser paga.\",\n },\n refunded: {\n title: \"Pix devolvido\",\n description: \"O valor foi devolvido para a sua conta.\",\n },\n charged_back: {\n title: \"Pix contestado\",\n description: \"O pagamento foi contestado junto ao banco.\",\n },\n failed: {\n title: \"Pix não concluído\",\n description: \"Não foi possível concluir o pagamento. Gere um novo Pix.\",\n },\n unknown: {\n title: \"Verificando o pagamento\",\n description: \"Ainda estamos confirmando este Pix.\",\n },\n },\n};\n\nconst STATE_VARIANT: Record<PaymentStatus, AlertVariant> = {\n pending: \"info\",\n authorized: \"info\",\n in_analysis: \"info\",\n paid: \"success\",\n expired: \"warning\",\n cancelled: \"neutral\",\n refunded: \"neutral\",\n charged_back: \"warning\",\n failed: \"danger\",\n unknown: \"neutral\",\n};\n\nconst BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;\nconst PNG_PREFIX = \"data:image/png;base64,\";\n\n/**\n * The QR image source, or `null` to draw it locally.\n *\n * Only a well-formed Base64 payload becomes a `data:image/png` URL: the field\n * arrives from the network, and an image `src` built from an unchecked string\n * is a place where something other than a PNG can end up. A value that already\n * carries the `data:image/png;base64,` prefix is accepted once.\n */\nfunction qrImageSource(base64: string | null): string | null {\n if (base64 === null) return null;\n const body = base64.startsWith(PNG_PREFIX) ? base64.slice(PNG_PREFIX.length) : base64;\n const compact = body.replace(/\\s+/g, \"\");\n return compact.length > 0 && compact.length % 4 === 0 && BASE64.test(compact)\n ? PNG_PREFIX + compact\n : null;\n}\n\nexport interface PixCheckoutProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The charge as the backend reports it — pair with {@link usePixCharge} to follow it. */\n charge: PixCharge;\n /** Rendered side of the QR in px. Default `220`. */\n qrSize?: number;\n labels?: Partial<Omit<PixCheckoutLabels, \"states\">> & {\n states?: Partial<PixCheckoutLabels[\"states\"]>;\n };\n /** Formats the countdown. Default {@link formatPixRemaining}. */\n formatRemaining?: (ms: number) => string;\n /** Called after the copia-e-cola reaches the clipboard. */\n onCopied?: () => void;\n /** Rendered under the status, e.g. a \"gerar novo Pix\" button for a final state. */\n footer?: ReactNode;\n}\n\n/**\n * The payer's Pix screen: amount, QR, copia-e-cola, countdown and the state the\n * backend reports.\n *\n * Presentational and controlled. It renders `charge` and never changes it, so\n * the decision that a Pix was paid stays where it can be made — on the backend,\n * re-reading the order. Feed it from {@link usePixCharge} or from your own\n * stream.\n *\n * The QR comes from `qr_code_base64` when the provider sent one (Mercado Pago),\n * and is otherwise drawn locally from `br_code` (OpenPix sends a URL instead,\n * and fetching it would hand the payload to an image host for nothing). The\n * copia-e-cola line is always next to it: a QR cannot be scanned by the phone\n * that is showing it, which is where a mobile checkout puts it.\n *\n * A status this version does not know renders as `unknown` (\"verificando\"). Known\n * means an own key of the state table, so a status named after an\n * `Object.prototype` member (`toString`, `constructor`) is unknown too, instead\n * of an empty state block.\n *\n * When the countdown reaches zero while the backend still says `pending`, the\n * screen hides the QR and says it is **confirming** — not \"expired\". A payment\n * sent in the last second can still settle, and only the backend knows.\n *\n * @example\n * const { charge } = usePixCharge(created, { refresh: (signal) => readCharge(created.provider_charge_id, signal) });\n * <PixCheckout charge={charge} />\n */\nexport function PixCheckout({\n charge,\n qrSize = 220,\n labels,\n formatRemaining = formatPixRemaining,\n onCopied,\n footer,\n className,\n ...rest\n}: PixCheckoutProps) {\n const text: PixCheckoutLabels = {\n ...DEFAULT_LABELS,\n ...labels,\n states: { ...DEFAULT_LABELS.states, ...labels?.states },\n };\n const expiresAt = charge.expires_at === null ? Number.NaN : Date.parse(charge.expires_at);\n const hasDeadline = Number.isFinite(expiresAt);\n const remaining = useCountdown(hasDeadline ? expiresAt : 0, 0);\n const status: PaymentStatus = Object.hasOwn(STATE_VARIANT, charge.status)\n ? charge.status\n : \"unknown\";\n const pastDeadline = hasDeadline && remaining <= 0;\n const payable = status === \"pending\" && !pastDeadline && charge.br_code !== null;\n const image = qrImageSource(charge.qr_code_base64);\n const state = text.states[status];\n\n return (\n <div className={cn(styles.checkout, className)} data-status={status} {...rest}>\n <Money\n className={styles.amount}\n cents={charge.amount_cents}\n currency={charge.currency}\n />\n {status === \"pending\" ? (\n <div role=\"status\" className={styles.state}>\n <p className={styles.title}>{state.title}</p>\n <p className={styles.description}>\n {pastDeadline ? text.confirming : state.description}\n </p>\n </div>\n ) : (\n <Alert\n className={styles.state}\n variant={STATE_VARIANT[status]}\n title={state.title}\n description={state.description}\n />\n )}\n {payable && hasDeadline && (\n <p className={styles.timer} role=\"timer\" aria-live=\"off\">\n {text.expiresIn} <strong>{formatRemaining(remaining)}</strong>\n </p>\n )}\n {payable && charge.br_code !== null && (\n <>\n <div className={styles.symbol}>\n {image !== null ? (\n <img\n src={image}\n alt={text.qr}\n width={qrSize}\n height={qrSize}\n className={styles.image}\n />\n ) : (\n <QRCode\n value={charge.br_code}\n size={qrSize}\n label={text.qr}\n margin={2}\n />\n )}\n </div>\n <p className={styles.hint}>{text.copyHint}</p>\n <div className={styles.copy}>\n <code className={styles.code} data-testid=\"pix-checkout-code\">\n {charge.br_code}\n </code>\n <CopyButton value={charge.br_code} onCopied={onCopied}>\n <span className={styles.copyIdle}>{text.copy}</span>\n <span className={styles.copyDone}>{text.copied}</span>\n </CopyButton>\n </div>\n </>\n )}\n {footer !== undefined && <div className={styles.footer}>{footer}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mercado-pago.cjs","names":["MERCADO_PAGO_SCRIPT_URL","pending","readGlobal","doc","loadMercadoPago","options","ready","inflight","promise","script","fail","reject","message","loaded","resolve"],"sources":["../../src/payments/mercado-pago.ts"],"sourcesContent":["/**\n * The official MercadoPago.js v2 script.\n *\n * Loaded from Mercado Pago's own host, never bundled or vendored: the PCI DSS\n * self-assessment that keeps a merchant in the lightest scope (SAQ A) requires\n * the card fields to come straight from the processor, and Mercado Pago ships\n * the script unversioned so fixes reach every merchant without a rebuild. It is\n * also why this is not an npm dependency — there is no package to pin.\n *\n * A Content-Security-Policy needs `script-src https://sdk.mercadopago.com` and,\n * for the Secure Fields iframes and the API calls the script makes,\n * `frame-src https://secure-fields.mercadopago.com https://api-static.mercadopago.com`\n * plus `connect-src https://api.mercadopago.com https://api.mercadolibre.com`\n * (hosts read from the script itself, `js_version` 2.83.6, on 2026-11-14).\n */\nexport const MERCADO_PAGO_SCRIPT_URL = \"https://sdk.mercadopago.com/js/v2\";\n\n/** The Secure Fields this module mounts. */\nexport type MercadoPagoFieldType = \"cardNumber\" | \"expirationDate\" | \"securityCode\";\n\n/** Options for one Secure Field, as MercadoPago.js accepts them. */\nexport interface MercadoPagoFieldOptions {\n /** Placeholder shown inside the iframe. */\n placeholder?: string;\n /** Inline style applied inside the iframe (font, colour, size). */\n style?: Record<string, string>;\n}\n\n/** One mounted Secure Field — the subset of MercadoPago.js this module calls. */\nexport interface MercadoPagoFieldLike {\n /** Mounts the iframe into the element with this id. */\n mount(containerId: string): unknown;\n /** Removes the iframe. */\n unmount(): unknown;\n /** Subscribes to a field event (`binChange`, `validityChange`, `error`, …). */\n on(event: string, listener: (payload: unknown) => void): unknown;\n}\n\n/** One installment option, as `getInstallments` returns it. */\nexport interface MercadoPagoPayerCost {\n installments: number;\n installment_rate: number;\n installment_amount: number;\n total_amount: number;\n /** Ready-made text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. */\n recommended_message: string;\n}\n\n/** One entry of the `getInstallments` answer. */\nexport interface MercadoPagoInstallmentsEntry {\n payment_method_id: string;\n payment_type_id?: string;\n issuer?: { id?: string | number | null } | null;\n payer_costs: MercadoPagoPayerCost[];\n}\n\n/** Non-card data the token is bound to. */\nexport interface MercadoPagoCardTokenData {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n}\n\n/**\n * The subset of a `new MercadoPago(publicKey)` instance this module calls.\n *\n * Declared here instead of depending on `@mercadopago/sdk-js`, matching how the\n * SDK treats every injected provider: tests pass a fake with this shape, and an\n * app never pays for typings it does not use.\n */\nexport interface MercadoPagoLike {\n fields: {\n create(type: MercadoPagoFieldType, options?: MercadoPagoFieldOptions): MercadoPagoFieldLike;\n createCardToken(data: MercadoPagoCardTokenData): Promise<{ id: string }>;\n };\n getInstallments(params: {\n amount: string;\n bin: string;\n paymentTypeId?: string;\n }): Promise<MercadoPagoInstallmentsEntry[]>;\n}\n\n/** The `MercadoPago` global the script defines. */\nexport type MercadoPagoConstructor = new (\n publicKey: string,\n options?: { locale?: string },\n) => MercadoPagoLike;\n\n/** Options for {@link loadMercadoPago}. */\nexport interface LoadMercadoPagoOptions {\n /** Script URL. Default {@link MERCADO_PAGO_SCRIPT_URL}; override only for a proxy you control. */\n src?: string;\n /** Document to inject into. Default the global `document`. */\n document?: Document;\n}\n\nconst pending = new WeakMap<Document, Promise<MercadoPagoConstructor>>();\n\nfunction readGlobal(doc: Document): MercadoPagoConstructor | undefined {\n const view = doc.defaultView as (Window & { MercadoPago?: MercadoPagoConstructor }) | null;\n return view?.MercadoPago;\n}\n\n/**\n * Load MercadoPago.js once per document and resolve its constructor.\n *\n * Concurrent and repeated calls share one `<script>` tag and one promise, so a\n * checkout that remounts never injects the script twice. A failed load is\n * forgotten instead of cached — the next call tries again, which is what a\n * payer on a flaky connection needs — and the dead tag is removed.\n *\n * @param options - Script URL and target document.\n * @returns The `MercadoPago` constructor.\n * @throws {Error} When the script fails to load, or loads without defining\n * `window.MercadoPago`.\n *\n * @example\n * const MercadoPago = await loadMercadoPago();\n * const mp = new MercadoPago(import.meta.env.VITE_MP_PUBLIC_KEY, { locale: \"pt-BR\" });\n */\nexport function loadMercadoPago(\n options: LoadMercadoPagoOptions = {},\n): Promise<MercadoPagoConstructor> {\n const doc = options.document ?? document;\n const ready = readGlobal(doc);\n if (ready !== undefined) return Promise.resolve(ready);\n const inflight = pending.get(doc);\n if (inflight !== undefined) return inflight;\n\n const promise = new Promise<MercadoPagoConstructor>((resolve, reject) => {\n const script = doc.createElement(\"script\");\n script.src = options.src ?? MERCADO_PAGO_SCRIPT_URL;\n script.async = true;\n const fail = (message: string): void => {\n pending.delete(doc);\n script.remove();\n reject(new Error(message));\n };\n script.addEventListener(\"load\", () => {\n const loaded = readGlobal(doc);\n if (loaded === undefined) {\n fail(\"MercadoPago.js loaded but did not define window.MercadoPago.\");\n return;\n }\n pending.delete(doc);\n resolve(loaded);\n });\n script.addEventListener(\"error\", () =>\n fail(`MercadoPago.js failed to load from ${script.src}.`),\n );\n doc.head.appendChild(script);\n });\n pending.set(doc, promise);\n return promise;\n}\n"],"mappings":"AAeA,IAAaA,EAA0B,oCAiFjCC,EAAU,IAAI,QAEpB,SAASC,EAAW,EAAmD,CAEnE,OADaC,EAAI,aACJ,WACjB,CAmBA,SAAgBC,EACZ,EAAkC,CAAC,EACJ,CAC/B,IAAMD,EAAME,EAAQ,UAAY,SAC1BC,EAAQJ,EAAWC,CAAG,EAC5B,GAAIG,IAAU,IAAA,GAAW,OAAO,QAAQ,QAAQA,CAAK,EACrD,IAAMC,EAAWN,EAAQ,IAAIE,CAAG,EAChC,GAAII,IAAa,IAAA,GAAW,OAAOA,EAEnC,IAAMC,EAAU,IAAI,SAAiC,EAAS,IAAW,CACrE,IAAMC,EAASN,EAAI,cAAc,QAAQ,EACzC,EAAO,IAAME,EAAQ,KAAA,oCACrB,EAAO,MAAQ,GACf,IAAMK,EAAQ,GAA0B,CACpCT,EAAQ,OAAOE,CAAG,EAClBM,EAAO,OAAO,EACdE,EAAW,MAAMC,CAAO,CAAC,CAC7B,EACAH,EAAO,iBAAiB,WAAc,CAClC,IAAMI,EAASX,EAAWC,CAAG,EACzBU,IAAW,IAAA,GACXH,EAAK,8DAA8D,GAGvET,EAAQ,OAAOE,CAAG,EAClBW,EAAQD,CAAM,EAClB,CAAC,EACDJ,EAAO,iBAAiB,YACpBC,EAAK,sCAAsCD,EAAO,IAAI,EAAE,CAC5D,EACAN,EAAI,KAAK,YAAYM,CAAM,CAC/B,CAAC,EAED,OADAR,EAAQ,IAAIE,EAAKK,CAAO,EACjBA,CACX,C"}
1
+ {"version":3,"file":"mercado-pago.cjs","names":["MERCADO_PAGO_SCRIPT_URL","pending","readGlobal","doc","loadMercadoPago","options","ready","inflight","promise","script","fail","reject","message","loaded","resolve"],"sources":["../../src/payments/mercado-pago.ts"],"sourcesContent":["/**\n * The official MercadoPago.js v2 script.\n *\n * Loaded from Mercado Pago's own host, never bundled or vendored: the PCI DSS\n * self-assessment that keeps a merchant in the lightest scope (SAQ A) requires\n * the card fields to come straight from the processor, and Mercado Pago ships\n * the script unversioned so fixes reach every merchant without a rebuild. It is\n * also why this is not an npm dependency — there is no package to pin.\n *\n * A Content-Security-Policy needs `script-src https://sdk.mercadopago.com` and,\n * for the Secure Fields iframes and the API calls the script makes,\n * `frame-src https://secure-fields.mercadopago.com https://api-static.mercadopago.com`\n * plus `connect-src https://api.mercadopago.com https://api.mercadolibre.com`\n * (hosts read from the script itself, `js_version` 2.83.6, on 2026-11-14).\n */\nexport const MERCADO_PAGO_SCRIPT_URL = \"https://sdk.mercadopago.com/js/v2\";\n\n/** The Secure Fields this module mounts. */\nexport type MercadoPagoFieldType = \"cardNumber\" | \"expirationDate\" | \"securityCode\";\n\n/** Options for one Secure Field, as MercadoPago.js accepts them. */\nexport interface MercadoPagoFieldOptions {\n /** Placeholder shown inside the iframe. */\n placeholder?: string;\n /** Inline style applied inside the iframe (font, colour, size). */\n style?: Record<string, string>;\n}\n\n/** One mounted Secure Field — the subset of MercadoPago.js this module calls. */\nexport interface MercadoPagoFieldLike {\n /** Mounts the iframe into the element with this id. */\n mount(containerId: string): unknown;\n /** Removes the iframe. */\n unmount(): unknown;\n /** Subscribes to a field event (`binChange`, `validityChange`, `error`, …). */\n on(event: string, listener: (payload: unknown) => void): unknown;\n}\n\n/** One installment option, as `getInstallments` returns it. */\nexport interface MercadoPagoPayerCost {\n installments: number;\n installment_rate: number;\n installment_amount: number;\n total_amount: number;\n /** Ready-made text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. */\n recommended_message: string;\n}\n\n/** One entry of the `getInstallments` answer. */\nexport interface MercadoPagoInstallmentsEntry {\n payment_method_id: string;\n payment_type_id?: string;\n issuer?: { id?: string | number | null } | null;\n payer_costs: MercadoPagoPayerCost[];\n}\n\n/** Non-card data the token is bound to. */\nexport interface MercadoPagoCardTokenData {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n}\n\n/**\n * What `fields.createCardToken` resolves with — the subset this module reads.\n *\n * The Secure Fields iframe posts the card to `POST /v1/card_tokens` and answers\n * with that endpoint's JSON as is (read from the iframe served by\n * `secure-fields.mercadopago.com` on 2026-11-14), so the shape is the card token\n * resource: `CardTokenResponse` in Mercado Pago's own Node SDK (`mercadopago`\n * 3.6.2), where every field is optional. `first_six_digits` is the BIN of the\n * card that was actually tokenized, which is how {@link useMercadoPagoCard}\n * binds the token to the brand it quoted.\n */\nexport interface MercadoPagoCardToken {\n /** Single-use token id. */\n id: string;\n /** First six digits of the tokenized card. */\n first_six_digits?: string;\n}\n\n/**\n * The subset of a `new MercadoPago(publicKey)` instance this module calls.\n *\n * Declared here instead of depending on `@mercadopago/sdk-js`, matching how the\n * SDK treats every injected provider: tests pass a fake with this shape, and an\n * app never pays for typings it does not use.\n */\nexport interface MercadoPagoLike {\n fields: {\n create(type: MercadoPagoFieldType, options?: MercadoPagoFieldOptions): MercadoPagoFieldLike;\n createCardToken(data: MercadoPagoCardTokenData): Promise<MercadoPagoCardToken>;\n };\n getInstallments(params: {\n amount: string;\n bin: string;\n paymentTypeId?: string;\n }): Promise<MercadoPagoInstallmentsEntry[]>;\n}\n\n/** The `MercadoPago` global the script defines. */\nexport type MercadoPagoConstructor = new (\n publicKey: string,\n options?: { locale?: string },\n) => MercadoPagoLike;\n\n/** Options for {@link loadMercadoPago}. */\nexport interface LoadMercadoPagoOptions {\n /**\n * Script URL. Default {@link MERCADO_PAGO_SCRIPT_URL}; override only for a\n * proxy you control that serves the **identical** MercadoPago.js — SAQ A\n * depends on the fields coming from the processor unaltered. Not checked:\n * the script is unversioned, so there is no stable integrity hash to pin.\n */\n src?: string;\n /** Document to inject into. Default the global `document`. */\n document?: Document;\n}\n\nconst pending = new WeakMap<Document, Promise<MercadoPagoConstructor>>();\n\nfunction readGlobal(doc: Document): MercadoPagoConstructor | undefined {\n const view = doc.defaultView as (Window & { MercadoPago?: MercadoPagoConstructor }) | null;\n return view?.MercadoPago;\n}\n\n/**\n * Load MercadoPago.js once per document and resolve its constructor.\n *\n * Concurrent and repeated calls share one `<script>` tag and one promise, so a\n * checkout that remounts never injects the script twice. A failed load is\n * forgotten instead of cached — the next call tries again, which is what a\n * payer on a flaky connection needs — and the dead tag is removed.\n *\n * Trust: an existing `window.MercadoPago` is used as is, and `src` is not\n * checked. Neither is a boundary the page can enforce — whoever can define that\n * global already runs script here, and an unversioned script has no integrity\n * hash to pin — so the control is the page's CSP `script-src`, restricted to\n * `https://sdk.mercadopago.com` or the proxy `src` points at.\n *\n * @param options - Script URL and target document.\n * @returns The `MercadoPago` constructor.\n * @throws {Error} When the script fails to load, or loads without defining\n * `window.MercadoPago`.\n *\n * @example\n * const MercadoPago = await loadMercadoPago();\n * const mp = new MercadoPago(import.meta.env.VITE_MP_PUBLIC_KEY, { locale: \"pt-BR\" });\n */\nexport function loadMercadoPago(\n options: LoadMercadoPagoOptions = {},\n): Promise<MercadoPagoConstructor> {\n const doc = options.document ?? document;\n const ready = readGlobal(doc);\n if (ready !== undefined) return Promise.resolve(ready);\n const inflight = pending.get(doc);\n if (inflight !== undefined) return inflight;\n\n const promise = new Promise<MercadoPagoConstructor>((resolve, reject) => {\n const script = doc.createElement(\"script\");\n script.src = options.src ?? MERCADO_PAGO_SCRIPT_URL;\n script.async = true;\n const fail = (message: string): void => {\n pending.delete(doc);\n script.remove();\n reject(new Error(message));\n };\n script.addEventListener(\"load\", () => {\n const loaded = readGlobal(doc);\n if (loaded === undefined) {\n fail(\"MercadoPago.js loaded but did not define window.MercadoPago.\");\n return;\n }\n pending.delete(doc);\n resolve(loaded);\n });\n script.addEventListener(\"error\", () =>\n fail(`MercadoPago.js failed to load from ${script.src}.`),\n );\n doc.head.appendChild(script);\n });\n pending.set(doc, promise);\n return promise;\n}\n"],"mappings":"AAeA,IAAaA,EAA0B,oCAwGjCC,EAAU,IAAI,QAEpB,SAASC,EAAW,EAAmD,CAEnE,OADaC,EAAI,aACJ,WACjB,CAyBA,SAAgBC,EACZ,EAAkC,CAAC,EACJ,CAC/B,IAAMD,EAAME,EAAQ,UAAY,SAC1BC,EAAQJ,EAAWC,CAAG,EAC5B,GAAIG,IAAU,IAAA,GAAW,OAAO,QAAQ,QAAQA,CAAK,EACrD,IAAMC,EAAWN,EAAQ,IAAIE,CAAG,EAChC,GAAII,IAAa,IAAA,GAAW,OAAOA,EAEnC,IAAMC,EAAU,IAAI,SAAiC,EAAS,IAAW,CACrE,IAAMC,EAASN,EAAI,cAAc,QAAQ,EACzC,EAAO,IAAME,EAAQ,KAAA,oCACrB,EAAO,MAAQ,GACf,IAAMK,EAAQ,GAA0B,CACpCT,EAAQ,OAAOE,CAAG,EAClBM,EAAO,OAAO,EACdE,EAAW,MAAMC,CAAO,CAAC,CAC7B,EACAH,EAAO,iBAAiB,WAAc,CAClC,IAAMI,EAASX,EAAWC,CAAG,EACzBU,IAAW,IAAA,GACXH,EAAK,8DAA8D,GAGvET,EAAQ,OAAOE,CAAG,EAClBW,EAAQD,CAAM,EAClB,CAAC,EACDJ,EAAO,iBAAiB,YACpBC,EAAK,sCAAsCD,EAAO,IAAI,EAAE,CAC5D,EACAN,EAAI,KAAK,YAAYM,CAAM,CAC/B,CAAC,EAED,OADAR,EAAQ,IAAIE,EAAKK,CAAO,EACjBA,CACX,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"mercado-pago.js","names":["MERCADO_PAGO_SCRIPT_URL","pending","readGlobal","doc","loadMercadoPago","options","ready","inflight","promise","script","fail","reject","message","loaded","resolve"],"sources":["../../src/payments/mercado-pago.ts"],"sourcesContent":["/**\n * The official MercadoPago.js v2 script.\n *\n * Loaded from Mercado Pago's own host, never bundled or vendored: the PCI DSS\n * self-assessment that keeps a merchant in the lightest scope (SAQ A) requires\n * the card fields to come straight from the processor, and Mercado Pago ships\n * the script unversioned so fixes reach every merchant without a rebuild. It is\n * also why this is not an npm dependency — there is no package to pin.\n *\n * A Content-Security-Policy needs `script-src https://sdk.mercadopago.com` and,\n * for the Secure Fields iframes and the API calls the script makes,\n * `frame-src https://secure-fields.mercadopago.com https://api-static.mercadopago.com`\n * plus `connect-src https://api.mercadopago.com https://api.mercadolibre.com`\n * (hosts read from the script itself, `js_version` 2.83.6, on 2026-11-14).\n */\nexport const MERCADO_PAGO_SCRIPT_URL = \"https://sdk.mercadopago.com/js/v2\";\n\n/** The Secure Fields this module mounts. */\nexport type MercadoPagoFieldType = \"cardNumber\" | \"expirationDate\" | \"securityCode\";\n\n/** Options for one Secure Field, as MercadoPago.js accepts them. */\nexport interface MercadoPagoFieldOptions {\n /** Placeholder shown inside the iframe. */\n placeholder?: string;\n /** Inline style applied inside the iframe (font, colour, size). */\n style?: Record<string, string>;\n}\n\n/** One mounted Secure Field — the subset of MercadoPago.js this module calls. */\nexport interface MercadoPagoFieldLike {\n /** Mounts the iframe into the element with this id. */\n mount(containerId: string): unknown;\n /** Removes the iframe. */\n unmount(): unknown;\n /** Subscribes to a field event (`binChange`, `validityChange`, `error`, …). */\n on(event: string, listener: (payload: unknown) => void): unknown;\n}\n\n/** One installment option, as `getInstallments` returns it. */\nexport interface MercadoPagoPayerCost {\n installments: number;\n installment_rate: number;\n installment_amount: number;\n total_amount: number;\n /** Ready-made text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. */\n recommended_message: string;\n}\n\n/** One entry of the `getInstallments` answer. */\nexport interface MercadoPagoInstallmentsEntry {\n payment_method_id: string;\n payment_type_id?: string;\n issuer?: { id?: string | number | null } | null;\n payer_costs: MercadoPagoPayerCost[];\n}\n\n/** Non-card data the token is bound to. */\nexport interface MercadoPagoCardTokenData {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n}\n\n/**\n * The subset of a `new MercadoPago(publicKey)` instance this module calls.\n *\n * Declared here instead of depending on `@mercadopago/sdk-js`, matching how the\n * SDK treats every injected provider: tests pass a fake with this shape, and an\n * app never pays for typings it does not use.\n */\nexport interface MercadoPagoLike {\n fields: {\n create(type: MercadoPagoFieldType, options?: MercadoPagoFieldOptions): MercadoPagoFieldLike;\n createCardToken(data: MercadoPagoCardTokenData): Promise<{ id: string }>;\n };\n getInstallments(params: {\n amount: string;\n bin: string;\n paymentTypeId?: string;\n }): Promise<MercadoPagoInstallmentsEntry[]>;\n}\n\n/** The `MercadoPago` global the script defines. */\nexport type MercadoPagoConstructor = new (\n publicKey: string,\n options?: { locale?: string },\n) => MercadoPagoLike;\n\n/** Options for {@link loadMercadoPago}. */\nexport interface LoadMercadoPagoOptions {\n /** Script URL. Default {@link MERCADO_PAGO_SCRIPT_URL}; override only for a proxy you control. */\n src?: string;\n /** Document to inject into. Default the global `document`. */\n document?: Document;\n}\n\nconst pending = new WeakMap<Document, Promise<MercadoPagoConstructor>>();\n\nfunction readGlobal(doc: Document): MercadoPagoConstructor | undefined {\n const view = doc.defaultView as (Window & { MercadoPago?: MercadoPagoConstructor }) | null;\n return view?.MercadoPago;\n}\n\n/**\n * Load MercadoPago.js once per document and resolve its constructor.\n *\n * Concurrent and repeated calls share one `<script>` tag and one promise, so a\n * checkout that remounts never injects the script twice. A failed load is\n * forgotten instead of cached — the next call tries again, which is what a\n * payer on a flaky connection needs — and the dead tag is removed.\n *\n * @param options - Script URL and target document.\n * @returns The `MercadoPago` constructor.\n * @throws {Error} When the script fails to load, or loads without defining\n * `window.MercadoPago`.\n *\n * @example\n * const MercadoPago = await loadMercadoPago();\n * const mp = new MercadoPago(import.meta.env.VITE_MP_PUBLIC_KEY, { locale: \"pt-BR\" });\n */\nexport function loadMercadoPago(\n options: LoadMercadoPagoOptions = {},\n): Promise<MercadoPagoConstructor> {\n const doc = options.document ?? document;\n const ready = readGlobal(doc);\n if (ready !== undefined) return Promise.resolve(ready);\n const inflight = pending.get(doc);\n if (inflight !== undefined) return inflight;\n\n const promise = new Promise<MercadoPagoConstructor>((resolve, reject) => {\n const script = doc.createElement(\"script\");\n script.src = options.src ?? MERCADO_PAGO_SCRIPT_URL;\n script.async = true;\n const fail = (message: string): void => {\n pending.delete(doc);\n script.remove();\n reject(new Error(message));\n };\n script.addEventListener(\"load\", () => {\n const loaded = readGlobal(doc);\n if (loaded === undefined) {\n fail(\"MercadoPago.js loaded but did not define window.MercadoPago.\");\n return;\n }\n pending.delete(doc);\n resolve(loaded);\n });\n script.addEventListener(\"error\", () =>\n fail(`MercadoPago.js failed to load from ${script.src}.`),\n );\n doc.head.appendChild(script);\n });\n pending.set(doc, promise);\n return promise;\n}\n"],"mappings":";AAeA,IAAaA,IAA0B,qCAiFjCC,oBAAU,IAAI,QAAmD;AAEvE,SAASC,EAAW,GAAmD;CAEnE,OADaC,EAAI,aACJ;AACjB;AAmBA,SAAgBC,EACZ,IAAkC,CAAC,GACJ;CAC/B,IAAMD,IAAME,EAAQ,YAAY,UAC1BC,IAAQJ,EAAWC,CAAG;CAC5B,IAAIG,MAAU,KAAA,GAAW,OAAO,QAAQ,QAAQA,CAAK;CACrD,IAAMC,IAAWN,EAAQ,IAAIE,CAAG;CAChC,IAAII,MAAa,KAAA,GAAW,OAAOA;CAEnC,IAAMC,IAAU,IAAI,SAAiC,GAAS,MAAW;EACrE,IAAMC,IAASN,EAAI,cAAc,QAAQ;EAEzC,AADA,EAAO,MAAME,EAAQ,OAAA,qCACrB,EAAO,QAAQ;EACf,IAAMK,KAAQ,MAA0B;GAGpC,AAFAT,EAAQ,OAAOE,CAAG,GAClBM,EAAO,OAAO,GACdE,EAAW,MAAMC,CAAO,CAAC;EAC7B;EAaA,AAZAH,EAAO,iBAAiB,cAAc;GAClC,IAAMI,IAASX,EAAWC,CAAG;GAC7B,AAAIU,MAAW,KAAA,IACXH,EAAK,8DAA8D,KAGvET,EAAQ,OAAOE,CAAG,GAClBW,EAAQD,CAAM;EAClB,CAAC,GACDJ,EAAO,iBAAiB,eACpBC,EAAK,sCAAsCD,EAAO,IAAI,EAAE,CAC5D,GACAN,EAAI,KAAK,YAAYM,CAAM;CAC/B,CAAC;CAED,OADAR,EAAQ,IAAIE,GAAKK,CAAO,GACjBA;AACX"}
1
+ {"version":3,"file":"mercado-pago.js","names":["MERCADO_PAGO_SCRIPT_URL","pending","readGlobal","doc","loadMercadoPago","options","ready","inflight","promise","script","fail","reject","message","loaded","resolve"],"sources":["../../src/payments/mercado-pago.ts"],"sourcesContent":["/**\n * The official MercadoPago.js v2 script.\n *\n * Loaded from Mercado Pago's own host, never bundled or vendored: the PCI DSS\n * self-assessment that keeps a merchant in the lightest scope (SAQ A) requires\n * the card fields to come straight from the processor, and Mercado Pago ships\n * the script unversioned so fixes reach every merchant without a rebuild. It is\n * also why this is not an npm dependency — there is no package to pin.\n *\n * A Content-Security-Policy needs `script-src https://sdk.mercadopago.com` and,\n * for the Secure Fields iframes and the API calls the script makes,\n * `frame-src https://secure-fields.mercadopago.com https://api-static.mercadopago.com`\n * plus `connect-src https://api.mercadopago.com https://api.mercadolibre.com`\n * (hosts read from the script itself, `js_version` 2.83.6, on 2026-11-14).\n */\nexport const MERCADO_PAGO_SCRIPT_URL = \"https://sdk.mercadopago.com/js/v2\";\n\n/** The Secure Fields this module mounts. */\nexport type MercadoPagoFieldType = \"cardNumber\" | \"expirationDate\" | \"securityCode\";\n\n/** Options for one Secure Field, as MercadoPago.js accepts them. */\nexport interface MercadoPagoFieldOptions {\n /** Placeholder shown inside the iframe. */\n placeholder?: string;\n /** Inline style applied inside the iframe (font, colour, size). */\n style?: Record<string, string>;\n}\n\n/** One mounted Secure Field — the subset of MercadoPago.js this module calls. */\nexport interface MercadoPagoFieldLike {\n /** Mounts the iframe into the element with this id. */\n mount(containerId: string): unknown;\n /** Removes the iframe. */\n unmount(): unknown;\n /** Subscribes to a field event (`binChange`, `validityChange`, `error`, …). */\n on(event: string, listener: (payload: unknown) => void): unknown;\n}\n\n/** One installment option, as `getInstallments` returns it. */\nexport interface MercadoPagoPayerCost {\n installments: number;\n installment_rate: number;\n installment_amount: number;\n total_amount: number;\n /** Ready-made text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. */\n recommended_message: string;\n}\n\n/** One entry of the `getInstallments` answer. */\nexport interface MercadoPagoInstallmentsEntry {\n payment_method_id: string;\n payment_type_id?: string;\n issuer?: { id?: string | number | null } | null;\n payer_costs: MercadoPagoPayerCost[];\n}\n\n/** Non-card data the token is bound to. */\nexport interface MercadoPagoCardTokenData {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n}\n\n/**\n * What `fields.createCardToken` resolves with — the subset this module reads.\n *\n * The Secure Fields iframe posts the card to `POST /v1/card_tokens` and answers\n * with that endpoint's JSON as is (read from the iframe served by\n * `secure-fields.mercadopago.com` on 2026-11-14), so the shape is the card token\n * resource: `CardTokenResponse` in Mercado Pago's own Node SDK (`mercadopago`\n * 3.6.2), where every field is optional. `first_six_digits` is the BIN of the\n * card that was actually tokenized, which is how {@link useMercadoPagoCard}\n * binds the token to the brand it quoted.\n */\nexport interface MercadoPagoCardToken {\n /** Single-use token id. */\n id: string;\n /** First six digits of the tokenized card. */\n first_six_digits?: string;\n}\n\n/**\n * The subset of a `new MercadoPago(publicKey)` instance this module calls.\n *\n * Declared here instead of depending on `@mercadopago/sdk-js`, matching how the\n * SDK treats every injected provider: tests pass a fake with this shape, and an\n * app never pays for typings it does not use.\n */\nexport interface MercadoPagoLike {\n fields: {\n create(type: MercadoPagoFieldType, options?: MercadoPagoFieldOptions): MercadoPagoFieldLike;\n createCardToken(data: MercadoPagoCardTokenData): Promise<MercadoPagoCardToken>;\n };\n getInstallments(params: {\n amount: string;\n bin: string;\n paymentTypeId?: string;\n }): Promise<MercadoPagoInstallmentsEntry[]>;\n}\n\n/** The `MercadoPago` global the script defines. */\nexport type MercadoPagoConstructor = new (\n publicKey: string,\n options?: { locale?: string },\n) => MercadoPagoLike;\n\n/** Options for {@link loadMercadoPago}. */\nexport interface LoadMercadoPagoOptions {\n /**\n * Script URL. Default {@link MERCADO_PAGO_SCRIPT_URL}; override only for a\n * proxy you control that serves the **identical** MercadoPago.js — SAQ A\n * depends on the fields coming from the processor unaltered. Not checked:\n * the script is unversioned, so there is no stable integrity hash to pin.\n */\n src?: string;\n /** Document to inject into. Default the global `document`. */\n document?: Document;\n}\n\nconst pending = new WeakMap<Document, Promise<MercadoPagoConstructor>>();\n\nfunction readGlobal(doc: Document): MercadoPagoConstructor | undefined {\n const view = doc.defaultView as (Window & { MercadoPago?: MercadoPagoConstructor }) | null;\n return view?.MercadoPago;\n}\n\n/**\n * Load MercadoPago.js once per document and resolve its constructor.\n *\n * Concurrent and repeated calls share one `<script>` tag and one promise, so a\n * checkout that remounts never injects the script twice. A failed load is\n * forgotten instead of cached — the next call tries again, which is what a\n * payer on a flaky connection needs — and the dead tag is removed.\n *\n * Trust: an existing `window.MercadoPago` is used as is, and `src` is not\n * checked. Neither is a boundary the page can enforce — whoever can define that\n * global already runs script here, and an unversioned script has no integrity\n * hash to pin — so the control is the page's CSP `script-src`, restricted to\n * `https://sdk.mercadopago.com` or the proxy `src` points at.\n *\n * @param options - Script URL and target document.\n * @returns The `MercadoPago` constructor.\n * @throws {Error} When the script fails to load, or loads without defining\n * `window.MercadoPago`.\n *\n * @example\n * const MercadoPago = await loadMercadoPago();\n * const mp = new MercadoPago(import.meta.env.VITE_MP_PUBLIC_KEY, { locale: \"pt-BR\" });\n */\nexport function loadMercadoPago(\n options: LoadMercadoPagoOptions = {},\n): Promise<MercadoPagoConstructor> {\n const doc = options.document ?? document;\n const ready = readGlobal(doc);\n if (ready !== undefined) return Promise.resolve(ready);\n const inflight = pending.get(doc);\n if (inflight !== undefined) return inflight;\n\n const promise = new Promise<MercadoPagoConstructor>((resolve, reject) => {\n const script = doc.createElement(\"script\");\n script.src = options.src ?? MERCADO_PAGO_SCRIPT_URL;\n script.async = true;\n const fail = (message: string): void => {\n pending.delete(doc);\n script.remove();\n reject(new Error(message));\n };\n script.addEventListener(\"load\", () => {\n const loaded = readGlobal(doc);\n if (loaded === undefined) {\n fail(\"MercadoPago.js loaded but did not define window.MercadoPago.\");\n return;\n }\n pending.delete(doc);\n resolve(loaded);\n });\n script.addEventListener(\"error\", () =>\n fail(`MercadoPago.js failed to load from ${script.src}.`),\n );\n doc.head.appendChild(script);\n });\n pending.set(doc, promise);\n return promise;\n}\n"],"mappings":";AAeA,IAAaA,IAA0B,qCAwGjCC,oBAAU,IAAI,QAAmD;AAEvE,SAASC,EAAW,GAAmD;CAEnE,OADaC,EAAI,aACJ;AACjB;AAyBA,SAAgBC,EACZ,IAAkC,CAAC,GACJ;CAC/B,IAAMD,IAAME,EAAQ,YAAY,UAC1BC,IAAQJ,EAAWC,CAAG;CAC5B,IAAIG,MAAU,KAAA,GAAW,OAAO,QAAQ,QAAQA,CAAK;CACrD,IAAMC,IAAWN,EAAQ,IAAIE,CAAG;CAChC,IAAII,MAAa,KAAA,GAAW,OAAOA;CAEnC,IAAMC,IAAU,IAAI,SAAiC,GAAS,MAAW;EACrE,IAAMC,IAASN,EAAI,cAAc,QAAQ;EAEzC,AADA,EAAO,MAAME,EAAQ,OAAA,qCACrB,EAAO,QAAQ;EACf,IAAMK,KAAQ,MAA0B;GAGpC,AAFAT,EAAQ,OAAOE,CAAG,GAClBM,EAAO,OAAO,GACdE,EAAW,MAAMC,CAAO,CAAC;EAC7B;EAaA,AAZAH,EAAO,iBAAiB,cAAc;GAClC,IAAMI,IAASX,EAAWC,CAAG;GAC7B,AAAIU,MAAW,KAAA,IACXH,EAAK,8DAA8D,KAGvET,EAAQ,OAAOE,CAAG,GAClBW,EAAQD,CAAM;EAClB,CAAC,GACDJ,EAAO,iBAAiB,eACpBC,EAAK,sCAAsCD,EAAO,IAAI,EAAE,CAC5D,GACAN,EAAI,KAAK,YAAYM,CAAM;CAC/B,CAAC;CAED,OADAR,EAAQ,IAAIE,GAAKK,CAAO,GACjBA;AACX"}
@@ -1,2 +1,2 @@
1
- var e=`O valor foi reservado no seu cartão e só será cobrado quando o pedido for confirmado.`,t={bad_filled_card_data:`Confira os dados do cartão: número, validade e código de segurança.`,invalid_card_token:`Não conseguimos validar o cartão. Preencha os dados de novo.`,high_risk:`O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.`,rejected_by_issuer:`O banco emissor recusou o pagamento. Use outro cartão ou fale com o banco.`,required_call_for_authorize:`O banco pede que você autorize este pagamento. Fale com o banco e tente de novo.`,max_attempts_exceeded:`Você atingiu o limite de tentativas com este cartão. Use outro cartão ou outro meio de pagamento.`,card_disabled:`O cartão está bloqueado ou inativo. Ative-o com o banco ou use outro cartão.`,insufficient_amount:`O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.`,card_insufficient_amount:`O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.`,amount_limit_exceeded:`O valor passa do limite permitido para este cartão. Use outro cartão.`,processing_error:`Não conseguimos processar o pagamento. Tente de novo em instantes.`,invalid_installments:`O cartão não aceita esse número de parcelas. Escolha outra opção.`,"3ds_challenge_expired":`O tempo para confirmar a compra no app do banco acabou. Tente de novo.`,waiting_capture:e,pending_challenge:`Confirme a compra no app do seu banco para concluir o pagamento.`,pending_review_manual:`O pagamento está em análise. Avisaremos assim que for concluído.`,in_review:`O pagamento está em análise. Avisaremos assim que for concluído.`,in_process:`O pagamento está sendo processado. Avisaremos assim que for concluído.`},n=new Set([`bad_filled_card_data`,`invalid_card_token`,`high_risk`,`rejected_by_issuer`,`required_call_for_authorize`,`max_attempts_exceeded`,`card_disabled`,`insufficient_amount`,`card_insufficient_amount`,`amount_limit_exceeded`,`processing_error`,`invalid_installments`,`3ds_challenge_expired`]),r={paid:{tone:`success`,title:`Pagamento aprovado`,message:`Recebemos o seu pagamento.`,canRetry:!1},authorized:{tone:`info`,title:`Pagamento autorizado`,message:e,canRetry:!1},pending:{tone:`info`,title:`Aguardando pagamento`,message:`O pagamento ainda não foi concluído.`,canRetry:!1},in_analysis:{tone:`info`,title:`Pagamento em análise`,message:`O pagamento está em análise. Avisaremos assim que for concluído.`,canRetry:!1},failed:{tone:`danger`,title:`Pagamento recusado`,message:`O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.`,canRetry:!0},expired:{tone:`warning`,title:`Pagamento expirado`,message:`O prazo para pagar acabou. Comece um novo pagamento.`,canRetry:!0},cancelled:{tone:`neutral`,title:`Pagamento cancelado`,message:`Esta cobrança foi cancelada e não será cobrada.`,canRetry:!1},refunded:{tone:`neutral`,title:`Pagamento estornado`,message:`O valor foi devolvido a você.`,canRetry:!1},charged_back:{tone:`warning`,title:`Pagamento contestado`,message:`O pagamento foi contestado junto ao banco.`,canRetry:!1},unknown:{tone:`neutral`,title:`Verificando o pagamento`,message:`Ainda estamos confirmando o estado deste pagamento.`,canRetry:!1}};function i(e,i={}){let a=r[e.status]??r.unknown,o=e.status in r?e.status:`unknown`,s=e.status_detail??``,c=s!==``&&o!==`paid`&&n.has(s)===(o===`failed`)?i[s]??t[s]:void 0;return{status:o,...a,message:c??a.message}}exports.CARD_STATUS_DETAIL_MESSAGES=t,exports.describeCardCharge=i;
1
+ var e=`O valor foi reservado no seu cartão e só será cobrado quando o pedido for confirmado.`,t={bad_filled_card_data:`Confira os dados do cartão: número, validade e código de segurança.`,invalid_card_token:`Não conseguimos validar o cartão. Preencha os dados de novo.`,high_risk:`O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.`,rejected_by_issuer:`O banco emissor recusou o pagamento. Use outro cartão ou fale com o banco.`,required_call_for_authorize:`O banco pede que você autorize este pagamento. Fale com o banco e tente de novo.`,max_attempts_exceeded:`Você atingiu o limite de tentativas com este cartão. Use outro cartão ou outro meio de pagamento.`,card_disabled:`O cartão está bloqueado ou inativo. Ative-o com o banco ou use outro cartão.`,insufficient_amount:`O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.`,card_insufficient_amount:`O cartão não tem limite suficiente. Use outro cartão ou outro meio de pagamento.`,amount_limit_exceeded:`O valor passa do limite permitido para este cartão. Use outro cartão.`,processing_error:`Não conseguimos processar o pagamento. Tente de novo em instantes.`,invalid_installments:`O cartão não aceita esse número de parcelas. Escolha outra opção.`,"3ds_challenge_expired":`O tempo para confirmar a compra no app do banco acabou. Tente de novo.`,waiting_capture:e,pending_challenge:`Confirme a compra no app do seu banco para concluir o pagamento.`,pending_review_manual:`O pagamento está em análise. Avisaremos assim que for concluído.`,in_review:`O pagamento está em análise. Avisaremos assim que for concluído.`,in_process:`O pagamento está sendo processado. Avisaremos assim que for concluído.`},n=new Set([`bad_filled_card_data`,`invalid_card_token`,`high_risk`,`rejected_by_issuer`,`required_call_for_authorize`,`max_attempts_exceeded`,`card_disabled`,`insufficient_amount`,`card_insufficient_amount`,`amount_limit_exceeded`,`processing_error`,`invalid_installments`,`3ds_challenge_expired`]),r={paid:{tone:`success`,title:`Pagamento aprovado`,message:`Recebemos o seu pagamento.`,canRetry:!1},authorized:{tone:`info`,title:`Pagamento autorizado`,message:e,canRetry:!1},pending:{tone:`info`,title:`Aguardando pagamento`,message:`O pagamento ainda não foi concluído.`,canRetry:!1},in_analysis:{tone:`info`,title:`Pagamento em análise`,message:`O pagamento está em análise. Avisaremos assim que for concluído.`,canRetry:!1},failed:{tone:`danger`,title:`Pagamento recusado`,message:`O pagamento não foi aprovado. Tente outro cartão ou outro meio de pagamento.`,canRetry:!0},expired:{tone:`warning`,title:`Pagamento expirado`,message:`O prazo para pagar acabou. Comece um novo pagamento.`,canRetry:!0},cancelled:{tone:`neutral`,title:`Pagamento cancelado`,message:`Esta cobrança foi cancelada e não será cobrada.`,canRetry:!1},refunded:{tone:`neutral`,title:`Pagamento estornado`,message:`O valor foi devolvido a você.`,canRetry:!1},charged_back:{tone:`warning`,title:`Pagamento contestado`,message:`O pagamento foi contestado junto ao banco.`,canRetry:!1},unknown:{tone:`neutral`,title:`Verificando o pagamento`,message:`Ainda estamos confirmando o estado deste pagamento.`,canRetry:!1}};function i(e,t){return Object.hasOwn(e,t)?e[t]:void 0}function a(e,a={}){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;return{status:o,...s,message:l??s.message}}exports.CARD_STATUS_DETAIL_MESSAGES=t,exports.describeCardCharge=a;
2
2
  //# sourceMappingURL=outcome.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"outcome.cjs","names":["WAITING_CAPTURE_MESSAGE","CARD_STATUS_DETAIL_MESSAGES","DECLINE_DETAILS","STATUS_OUTCOMES","describeCardCharge","base","charge","status","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 * 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. 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 base = STATUS_OUTCOMES[charge.status] ?? STATUS_OUTCOMES.unknown;\n const status: PaymentStatus = charge.status in STATUS_OUTCOMES ? charge.status : \"unknown\";\n const detail = charge.status_detail ?? \"\";\n const matches =\n detail !== \"\" && status !== \"paid\" && DECLINE_DETAILS.has(detail) === (status === \"failed\");\n const refined = matches ? (messages[detail] ?? CARD_STATUS_DETAIL_MESSAGES[detail]) : undefined;\n return { status, ...base, message: refined ?? base.message };\n}\n"],"mappings":"AAmBA,IAAMA,EACF,wFAgBSC,EAAgE,CACzE,qBAAsB,sEACtB,mBAAoB,+DACpB,UAAW,+EACX,mBACI,6EACJ,4BACI,mFACJ,sBACI,oGACJ,cAAe,+EACf,oBACI,mFACJ,yBACI,mFACJ,sBAAuB,wEACvB,iBAAkB,qEAClB,qBAAsB,oEACtB,wBACI,yEACJ,gBAAiBD,EACjB,kBAAmB,mEACnB,sBAAuB,mEACvB,UAAW,mEACX,WAAY,wEAChB,EAEME,EAAuC,IAAI,IAAI,CACjD,uBACA,qBACA,YACA,qBACA,8BACA,wBACA,gBACA,sBACA,2BACA,wBACA,mBACA,uBACA,uBACJ,CAAC,EAEKC,EAAmF,CACrF,KAAM,CACF,KAAM,UACN,MAAO,qBACP,QAAS,6BACT,SAAU,EACd,EACA,WAAY,CACR,KAAM,OACN,MAAO,uBACP,QAASH,EACT,SAAU,EACd,EACA,QAAS,CACL,KAAM,OACN,MAAO,uBACP,QAAS,uCACT,SAAU,EACd,EACA,YAAa,CACT,KAAM,OACN,MAAO,uBACP,QAAS,mEACT,SAAU,EACd,EACA,OAAQ,CACJ,KAAM,SACN,MAAO,qBACP,QAAS,+EACT,SAAU,EACd,EACA,QAAS,CACL,KAAM,UACN,MAAO,qBACP,QAAS,uDACT,SAAU,EACd,EACA,UAAW,CACP,KAAM,UACN,MAAO,sBACP,QAAS,kDACT,SAAU,EACd,EACA,SAAU,CACN,KAAM,UACN,MAAO,sBACP,QAAS,gCACT,SAAU,EACd,EACA,aAAc,CACV,KAAM,UACN,MAAO,uBACP,QAAS,6CACT,SAAU,EACd,EACA,QAAS,CACL,KAAM,UACN,MAAO,0BACP,QAAS,sDACT,SAAU,EACd,CACJ,EA6BA,SAAgBI,EACZ,EACA,EAA6C,CAAC,EAChC,CACd,IAAMC,EAAOF,EAAgBG,EAAO,SAAWH,EAAgB,QACzDI,EAAwBD,EAAO,UAAUH,EAAkBG,EAAO,OAAS,UAC3EE,EAASF,EAAO,eAAiB,GAGjCG,EADFD,IAAW,IAAMD,IAAW,QAAUL,EAAgB,IAAIM,CAAM,KAAOD,IAAW,UAC3DG,EAASF,IAAWP,EAA4BO,GAAW,IAAA,GACtF,MAAO,CAAE,SAAQ,GAAGH,EAAM,QAASI,GAAWJ,EAAK,OAAQ,CAC/D,C"}
1
+ {"version":3,"file":"outcome.cjs","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,EACF,wFAgBSC,EAAgE,CACzE,qBAAsB,sEACtB,mBAAoB,+DACpB,UAAW,+EACX,mBACI,6EACJ,4BACI,mFACJ,sBACI,oGACJ,cAAe,+EACf,oBACI,mFACJ,yBACI,mFACJ,sBAAuB,wEACvB,iBAAkB,qEAClB,qBAAsB,oEACtB,wBACI,yEACJ,gBAAiBD,EACjB,kBAAmB,mEACnB,sBAAuB,mEACvB,UAAW,mEACX,WAAY,wEAChB,EAEME,EAAuC,IAAI,IAAI,CACjD,uBACA,qBACA,YACA,qBACA,8BACA,wBACA,gBACA,sBACA,2BACA,wBACA,mBACA,uBACA,uBACJ,CAAC,EAEKC,EAAmF,CACrF,KAAM,CACF,KAAM,UACN,MAAO,qBACP,QAAS,6BACT,SAAU,EACd,EACA,WAAY,CACR,KAAM,OACN,MAAO,uBACP,QAASH,EACT,SAAU,EACd,EACA,QAAS,CACL,KAAM,OACN,MAAO,uBACP,QAAS,uCACT,SAAU,EACd,EACA,YAAa,CACT,KAAM,OACN,MAAO,uBACP,QAAS,mEACT,SAAU,EACd,EACA,OAAQ,CACJ,KAAM,SACN,MAAO,qBACP,QAAS,+EACT,SAAU,EACd,EACA,QAAS,CACL,KAAM,UACN,MAAO,qBACP,QAAS,uDACT,SAAU,EACd,EACA,UAAW,CACP,KAAM,UACN,MAAO,sBACP,QAAS,kDACT,SAAU,EACd,EACA,SAAU,CACN,KAAM,UACN,MAAO,sBACP,QAAS,gCACT,SAAU,EACd,EACA,aAAc,CACV,KAAM,UACN,MAAO,uBACP,QAAS,6CACT,SAAU,EACd,EACA,QAAS,CACL,KAAM,UACN,MAAO,0BACP,QAAS,sDACT,SAAU,EACd,CACJ,EAUA,SAASI,EAAQ,EAAyC,EAAiC,CACvF,OAAO,OAAO,OAAOC,EAAOC,CAAG,EAAID,EAAMC,GAAO,IAAA,EACpD,CAgCA,SAAgBC,EACZ,EACA,EAA6C,CAAC,EAChC,CACd,IAAMC,EAAwB,OAAO,OAAOL,EAAiBM,EAAO,MAAM,EACpEA,EAAO,OACP,UACAC,EAAOP,EAAgBK,GACvBG,EAASF,EAAO,eAAiB,GAGjCG,EADFD,IAAW,IAAMH,IAAW,QAAUN,EAAgB,IAAIS,CAAM,KAAOH,IAAW,UAE/EJ,EAAQS,EAAUF,CAAM,GAAKP,EAAQH,EAA6BU,CAAM,EACzE,IAAA,GACN,MAAO,CAAE,SAAQ,GAAGD,EAAM,QAASE,GAAWF,EAAK,OAAQ,CAC/D,C"}
@@ -94,15 +94,18 @@ var e = "O valor foi reservado no seu cartão e só será cobrado quando o pedid
94
94
  canRetry: !1
95
95
  }
96
96
  };
97
- function i(e, i = {}) {
98
- let a = r[e.status] ?? r.unknown, o = e.status in r ? e.status : "unknown", s = e.status_detail ?? "", c = s !== "" && o !== "paid" && n.has(s) === (o === "failed") ? i[s] ?? t[s] : void 0;
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;
99
102
  return {
100
103
  status: o,
101
- ...a,
102
- message: c ?? a.message
104
+ ...s,
105
+ message: l ?? s.message
103
106
  };
104
107
  }
105
108
  //#endregion
106
- export { t as CARD_STATUS_DETAIL_MESSAGES, i as describeCardCharge };
109
+ export { t as CARD_STATUS_DETAIL_MESSAGES, a as describeCardCharge };
107
110
 
108
111
  //# sourceMappingURL=outcome.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"outcome.js","names":["WAITING_CAPTURE_MESSAGE","CARD_STATUS_DETAIL_MESSAGES","DECLINE_DETAILS","STATUS_OUTCOMES","describeCardCharge","base","charge","status","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 * 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. 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 base = STATUS_OUTCOMES[charge.status] ?? STATUS_OUTCOMES.unknown;\n const status: PaymentStatus = charge.status in STATUS_OUTCOMES ? charge.status : \"unknown\";\n const detail = charge.status_detail ?? \"\";\n const matches =\n detail !== \"\" && status !== \"paid\" && DECLINE_DETAILS.has(detail) === (status === \"failed\");\n const refined = matches ? (messages[detail] ?? CARD_STATUS_DETAIL_MESSAGES[detail]) : 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;AA6BA,SAAgBI,EACZ,GACA,IAA6C,CAAC,GAChC;CACd,IAAMC,IAAOF,EAAgBG,EAAO,WAAWH,EAAgB,SACzDI,IAAwBD,EAAO,UAAUH,IAAkBG,EAAO,SAAS,WAC3EE,IAASF,EAAO,iBAAiB,IAGjCG,IADFD,MAAW,MAAMD,MAAW,UAAUL,EAAgB,IAAIM,CAAM,OAAOD,MAAW,YAC3DG,EAASF,MAAWP,EAA4BO,KAAW,KAAA;CACtF,OAAO;EAAE;EAAQ,GAAGH;EAAM,SAASI,KAAWJ,EAAK;CAAQ;AAC/D"}
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"}
@@ -1,2 +1,2 @@
1
- const e=require("../hooks/use-latest-ref.cjs"),t=require("./mercado-pago.cjs");let n=require("react");var r=[`cardNumber`,`expirationDate`,`securityCode`];function i(e){let t=Math.trunc(e/100),n=Math.abs(e%100);return`${t}.${String(n).padStart(2,`0`)}`}function a(e){return Math.round(e*100)}function o(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 s(e){if(typeof e==`object`&&e&&`bin`in e){let t=e.bin;return typeof t==`string`?t:``}return``}function c(c){let{publicKey:l,amountCents:u,containers:d,locale:f=`pt-BR`,fieldOptions:p,load:m=t.loadMercadoPago}=c,[h,g]=(0,n.useState)(`loading`),[_,v]=(0,n.useState)(null),[y,b]=(0,n.useState)(``),[x,S]=(0,n.useState)(null),[C,w]=(0,n.useState)([]),[T,E]=(0,n.useState)(!1),[D,O]=(0,n.useState)(null),k=(0,n.useRef)(null),A=e.useLatestRef({containers:d,locale:f,fieldOptions:p,load:m});return(0,n.useEffect)(()=>{let e=!1,t=[];g(`loading`),v(null);let n=A.current;return n.load().then(i=>{if(e)return;let a=new i(l,{locale:n.locale});for(let e of r){let r=a.fields.create(e,n.fieldOptions?.[e]);r.mount(n.containers[e]),t.push(r),e===`cardNumber`&&r.on(`binChange`,e=>b(s(e)))}k.current=a,g(`ready`)}).catch(t=>{e||(v(o(t)),g(`error`))}),()=>{e=!0,k.current=null;for(let e of t)try{e.unmount()}catch{}}},[l,A]),(0,n.useEffect)(()=>{let e=k.current;if(S(null),w([]),O(null),e===null||y.length<6||h!==`ready`){E(!1);return}let t=!1;return E(!0),e.getInstallments({amount:i(u),bin:y}).then(e=>{if(t)return;let n=e[0];if(n===void 0)throw Error(`Mercado Pago returned no installments for this card.`);S(n.payment_method_id),w(n.payer_costs.map(e=>({installments:Number(e.installments),recommendedMessage:e.recommended_message,installmentAmountCents:a(e.installment_amount),totalAmountCents:a(e.total_amount)})))}).catch(e=>{t||O(o(e))}).finally(()=>{t||E(!1)}),()=>{t=!0}},[y,u,h]),{status:h,error:_,paymentMethodId:x,installmentOptions:C,installmentsLoading:T,installmentsError:D,tokenize:(0,n.useCallback)(async e=>{let t=k.current;if(t===null||h!==`ready`)throw Error(`The card fields are not ready yet.`);if(x===null)throw Error(`The card brand has not been identified yet.`);if(!C.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};return e.identificationType!==void 0&&(n.identificationType=e.identificationType),e.identificationNumber!==void 0&&(n.identificationNumber=e.identificationNumber),{token:(await t.fields.createCardToken(n).catch(e=>{throw o(e)})).id,payment_method_id:x,installments:e.installments}},[h,x,C])}}exports.useMercadoPagoCard=c;
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
2
  //# sourceMappingURL=use-mercado-pago-card.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-mercado-pago-card.cjs","names":["loadMercadoPago","useState","useRef","useLatestRef","useCallback","require_use_latest_ref","require_mercado_pago","react","FIELD_TYPES","centsToDecimal","whole","cents","rest","toCents","amount","toError","reason","parts","item","readBin","payload","bin","useMercadoPagoCard","options","status","setStatus","error","setError","setBin","paymentMethodId","setPaymentMethodId","installmentOptions","setInstallmentOptions","installmentsLoading","setInstallmentsLoading","installmentsError","setInstallmentsError","mpRef","setupRef","cancelled","mounted","setup","mp","MercadoPago","publicKey","type","field","stale","amountCents","entry","entries","cost","option","input","data"],"sources":["../../src/payments/use-mercado-pago-card.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { loadMercadoPago } from \"./mercado-pago\";\nimport type {\n MercadoPagoConstructor,\n MercadoPagoFieldOptions,\n MercadoPagoFieldType,\n MercadoPagoLike,\n} from \"./mercado-pago\";\n\n/** Element ids the three Secure Fields mount into. */\nexport interface MercadoPagoCardContainers {\n cardNumber: string;\n expirationDate: string;\n securityCode: string;\n}\n\n/** Options for {@link useMercadoPagoCard}. */\nexport interface UseMercadoPagoCardOptions {\n /**\n * The application's **Public Key** — never the access token. It is meant to\n * be public: it can only tokenize, and the token is useless without the\n * access token that stays on the backend.\n */\n publicKey: string;\n /** The price in cents, used to quote installments. */\n amountCents: number;\n /** Ids of the empty elements the card number, expiry and CVV iframes mount into. */\n containers: MercadoPagoCardContainers;\n /** MercadoPago.js locale. Default `\"pt-BR\"`. */\n locale?: string;\n /** Per-field options (placeholder, inline style). */\n fieldOptions?: Partial<Record<MercadoPagoFieldType, MercadoPagoFieldOptions>>;\n /** Loads the constructor. Default {@link loadMercadoPago}; tests pass a fake. */\n load?: () => Promise<MercadoPagoConstructor>;\n}\n\n/** One installment choice, in the shape a `<Select>` needs. */\nexport interface CardInstallmentOption {\n /** Number of installments — the value to submit. */\n installments: number;\n /** The provider's own text, e.g. `\"2 parcelas de R$ 54,82 (R$ 109,64)\"`. Show it verbatim. */\n recommendedMessage: string;\n /** Each installment, interest included, in cents. */\n installmentAmountCents: number;\n /** What the payer owes in total, interest included, in cents. */\n totalAmountCents: number;\n}\n\n/** What the non-card part of the form supplies when tokenizing. */\nexport interface CardTokenizeInput {\n /** Name printed on the card. */\n cardholderName: string;\n /** Installments chosen from {@link UseMercadoPagoCardResult.installmentOptions}. */\n installments: number;\n /** Document type (`\"CPF\"`, `\"CNPJ\"`). */\n identificationType?: string;\n /** Document number, digits only. */\n identificationNumber?: string;\n}\n\n/**\n * Everything the backend needs to charge the card — and nothing more.\n *\n * Maps onto the backend's `CardChargeRequest` as `card_token: token` plus the\n * other two fields unchanged. No card number, expiry or CVV: those never left\n * Mercado Pago's iframes.\n */\nexport interface CardTokenResult {\n /** Single-use card token. */\n token: string;\n /** Card brand as Mercado Pago names it (`visa`, `master`, …). */\n payment_method_id: string;\n /** Installments chosen. */\n installments: number;\n}\n\n/** Lifecycle of the Secure Fields. */\nexport type MercadoPagoCardStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Return value of {@link useMercadoPagoCard}. */\nexport interface UseMercadoPagoCardResult {\n /** `\"ready\"` once all three fields are mounted. */\n status: MercadoPagoCardStatus;\n /** Why loading or mounting failed. */\n error: Error | null;\n /** Brand detected from the BIN, or `null` until the payer types enough digits. */\n paymentMethodId: string | null;\n /** Installment choices for the current BIN and amount; empty until known. */\n installmentOptions: CardInstallmentOption[];\n /** `true` while the installments for a new BIN are being fetched. */\n installmentsLoading: boolean;\n /** Why the installment lookup failed, if it did. */\n installmentsError: Error | null;\n /**\n * Ask Mercado Pago for a card token. Throws when the fields are not ready,\n * the BIN has not been identified, or `installments` is not one of the\n * offered options — each of which would otherwise surface later as an\n * opaque `400` from the backend.\n */\n tokenize: (input: CardTokenizeInput) => Promise<CardTokenResult>;\n}\n\nconst FIELD_TYPES: readonly MercadoPagoFieldType[] = [\n \"cardNumber\",\n \"expirationDate\",\n \"securityCode\",\n];\n\n/**\n * The amount as MercadoPago.js wants it: a decimal string, built from integer\n * cents without going through a float.\n */\nfunction centsToDecimal(cents: number): string {\n const whole = Math.trunc(cents / 100);\n const rest = Math.abs(cents % 100);\n return `${whole}.${String(rest).padStart(2, \"0\")}`;\n}\n\nfunction toCents(amount: number): number {\n return Math.round(amount * 100);\n}\n\n/**\n * MercadoPago.js rejects with an array of `{ message }` on a validation error,\n * not an `Error`; normalise so callers can rely on `.message`.\n */\nfunction toError(reason: unknown): Error {\n if (reason instanceof Error) return reason;\n if (Array.isArray(reason)) {\n const parts = reason.map((item: unknown) =>\n typeof item === \"object\" && item !== null && \"message\" in item\n ? String(item.message)\n : String(item),\n );\n return new Error(parts.join(\"; \"));\n }\n return new Error(String(reason));\n}\n\nfunction readBin(payload: unknown): string {\n if (typeof payload === \"object\" && payload !== null && \"bin\" in payload) {\n const bin = payload.bin;\n return typeof bin === \"string\" ? bin : \"\";\n }\n return \"\";\n}\n\n/**\n * Card tokenization with MercadoPago.js **Secure Fields**, plus installments by BIN.\n *\n * The card number, expiry and CVV are typed into iframes served by Mercado Pago,\n * so they never exist in this page's DOM, in React state, in a callback argument\n * or in a log line — not even transiently. That is the property the PCI DSS\n * scoping depends on: a page that renders its own `<input>` for the number and\n * calls `createCardToken` with it has handled cardholder data and is in the\n * heavier SAQ A-EP scope, however quickly it discards the value. The backend\n * never sees the card either: it receives the token, the brand and the\n * installments, which is exactly `CardChargeRequest`.\n *\n * The only digits the hook learns are the BIN (first 6–8), which Mercado Pago's\n * `binChange` event exposes precisely so the page can quote installments; it is\n * used for the lookup and not returned. Installment options come from the\n * provider with its own `recommended_message`, because interest starts at 2x\n * (measured in the sandbox) and the total the payer sees has to be the\n * provider's number, not one recomputed here.\n *\n * The containers must be in the DOM when the hook's effect runs — render the\n * three empty elements in the same component. The fields are created once per\n * `publicKey`; changing the containers, locale or loader afterwards does not\n * remount them.\n *\n * @tempest-limits empty-catch — `unmount()` throws when MercadoPago.js already\n * tore the iframe down (a container removed by React first). The cleanup has\n * nothing left to undo in that case and nowhere useful to report it.\n *\n * @param options - Public key, amount, container ids and an injectable loader.\n * @returns Field status, brand, installment options and `tokenize`.\n *\n * @example\n * const card = useMercadoPagoCard({\n * publicKey: import.meta.env.VITE_MP_PUBLIC_KEY,\n * amountCents: 10964,\n * containers: { cardNumber: \"mp-number\", expirationDate: \"mp-expiry\", securityCode: \"mp-cvv\" },\n * });\n * const { token, payment_method_id, installments } = await card.tokenize({\n * cardholderName: \"APRO\",\n * installments: 1,\n * identificationType: \"CPF\",\n * identificationNumber: \"12345678909\",\n * });\n */\nexport function useMercadoPagoCard(options: UseMercadoPagoCardOptions): UseMercadoPagoCardResult {\n const {\n publicKey,\n amountCents,\n containers,\n locale = \"pt-BR\",\n fieldOptions,\n load = loadMercadoPago,\n } = options;\n const [status, setStatus] = useState<MercadoPagoCardStatus>(\"loading\");\n const [error, setError] = useState<Error | null>(null);\n const [bin, setBin] = useState(\"\");\n const [paymentMethodId, setPaymentMethodId] = useState<string | null>(null);\n const [installmentOptions, setInstallmentOptions] = useState<CardInstallmentOption[]>([]);\n const [installmentsLoading, setInstallmentsLoading] = useState(false);\n const [installmentsError, setInstallmentsError] = useState<Error | null>(null);\n const mpRef = useRef<MercadoPagoLike | null>(null);\n const setupRef = useLatestRef({ containers, locale, fieldOptions, load });\n\n useEffect(() => {\n let cancelled = false;\n const mounted: { unmount(): unknown }[] = [];\n setStatus(\"loading\");\n setError(null);\n const setup = setupRef.current;\n setup\n .load()\n .then((MercadoPago) => {\n if (cancelled) return;\n const mp = new MercadoPago(publicKey, { locale: setup.locale });\n for (const type of FIELD_TYPES) {\n const field = mp.fields.create(type, setup.fieldOptions?.[type]);\n field.mount(setup.containers[type]);\n mounted.push(field);\n if (type === \"cardNumber\")\n field.on(\"binChange\", (payload) => setBin(readBin(payload)));\n }\n mpRef.current = mp;\n setStatus(\"ready\");\n })\n .catch((reason: unknown) => {\n if (cancelled) return;\n setError(toError(reason));\n setStatus(\"error\");\n });\n return () => {\n cancelled = true;\n mpRef.current = null;\n for (const field of mounted) {\n try {\n field.unmount();\n } catch {\n /* empty */\n }\n }\n };\n }, [publicKey, setupRef]);\n\n useEffect(() => {\n const mp = mpRef.current;\n setPaymentMethodId(null);\n setInstallmentOptions([]);\n setInstallmentsError(null);\n if (mp === null || bin.length < 6 || status !== \"ready\") {\n setInstallmentsLoading(false);\n return;\n }\n let stale = false;\n setInstallmentsLoading(true);\n mp.getInstallments({ amount: centsToDecimal(amountCents), bin })\n .then((entries) => {\n if (stale) return;\n const entry = entries[0];\n if (entry === undefined)\n throw new Error(\"Mercado Pago returned no installments for this card.\");\n setPaymentMethodId(entry.payment_method_id);\n setInstallmentOptions(\n entry.payer_costs.map((cost) => ({\n installments: Number(cost.installments),\n recommendedMessage: cost.recommended_message,\n installmentAmountCents: toCents(cost.installment_amount),\n totalAmountCents: toCents(cost.total_amount),\n })),\n );\n })\n .catch((reason: unknown) => {\n if (!stale) setInstallmentsError(toError(reason));\n })\n .finally(() => {\n if (!stale) setInstallmentsLoading(false);\n });\n return () => {\n stale = true;\n };\n }, [bin, amountCents, status]);\n\n const tokenize = useCallback(\n async (input: CardTokenizeInput): Promise<CardTokenResult> => {\n const mp = mpRef.current;\n if (mp === null || status !== \"ready\")\n throw new Error(\"The card fields are not ready yet.\");\n if (paymentMethodId === null)\n throw new Error(\"The card brand has not been identified yet.\");\n if (!installmentOptions.some((option) => option.installments === input.installments)) {\n throw new Error(\n `${input.installments} installments is not an option for this card and amount.`,\n );\n }\n const data: {\n cardholderName: string;\n identificationType?: string;\n identificationNumber?: string;\n } = {\n cardholderName: input.cardholderName,\n };\n if (input.identificationType !== undefined)\n data.identificationType = input.identificationType;\n if (input.identificationNumber !== undefined)\n data.identificationNumber = input.identificationNumber;\n const token = await mp.fields.createCardToken(data).catch((reason: unknown) => {\n throw toError(reason);\n });\n return {\n token: token.id,\n payment_method_id: paymentMethodId,\n installments: input.installments,\n };\n },\n [status, paymentMethodId, installmentOptions],\n );\n\n return {\n status,\n error,\n paymentMethodId,\n installmentOptions,\n installmentsLoading,\n installmentsError,\n tokenize,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-mercado-pago-card.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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"}