> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zopay.cash/llms.txt
> Use this file to discover all available pages before exploring further.

# mountPaymentWidget

> Renderiza el widget completo de ZoPay (QR + selector + polling).

```ts theme={null}
import { mountPaymentWidget } from "@zopay/js";

const handle = mountPaymentWidget(target, options);
// handle => { destroy(): void; refresh(): void }
```

Monta el widget de pago de ZoPay en el DOM. El widget:

* Muestra el QR para cada (asset, red) del intent.
* Renderiza un selector de moneda / red cuando hay varias opciones.
* Polea el estado vía el callback `checkStatus` que tú provees.
* Llama a `onSuccess` / `onError` / `onExpire` según el resultado.

**El widget nunca habla con la API de ZoPay directamente.** Todas las lecturas de estado pasan por tu callback `checkStatus`, que típicamente proxea a un endpoint en tu backend que sí tiene la clave secreta.

## Signature

```ts theme={null}
function mountPaymentWidget(
  target: string | HTMLElement,
  options: MountPaymentWidgetOptions
): { destroy(): void; refresh(): void };
```

## Parámetros

| Parámetro                | Tipo                         | Descripción                                                                                                                                                                                    |
| ------------------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target`                 | `string \| HTMLElement`      | `id` del elemento DOM o nodo directo. Pasa `ref.current` desde React.                                                                                                                          |
| `options.intent`         | `PaymentIntent`              | El envelope que devolvió tu backend tras crear el intent.                                                                                                                                      |
| `options.checkStatus`    | `() => Promise<StatusInfo>`  | Callback que el widget invoca para refrescar estado. Tu implementación debe pegarle a tu backend y devolver el resultado normalizado con [`normalizeIntent`](/sdk-reference/normalize-intent). |
| `options.onSuccess`      | `(info: StatusInfo) => void` | Disparado cuando el estado pasa a `completed`.                                                                                                                                                 |
| `options.onError`        | `(info: StatusInfo) => void` | Disparado en estados de fallo del intent.                                                                                                                                                      |
| `options.onExpire`       | `() => void`                 | Disparado cuando el polling supera `timeoutMs` o cuando el intent expira.                                                                                                                      |
| `options.theme`          | `"light" \| "dark"`          | Tema visual. Default: `"light"`.                                                                                                                                                               |
| `options.pollIntervalMs` | `number`                     | Intervalo entre polls (ms). Default razonable.                                                                                                                                                 |
| `options.timeoutMs`      | `number`                     | Tiempo máximo de polling antes de disparar `onExpire`. Default: 15 minutos.                                                                                                                    |

Cualquier opción adicional documentada en el README del paquete se respeta también.

## Valor de retorno

```ts theme={null}
{
  destroy(): void;   // Desmonta el widget y para el polling. Llámalo en unmount.
  refresh(): void;   // Dispara un checkStatus inmediato fuera del intervalo.
}
```

## Ejemplo

```html theme={null}
<div id="pay"></div>
```

```ts theme={null}
import { mountPaymentWidget, normalizeIntent } from "@zopay/js";
import "@zopay/js/styles.css";

const intent = await fetch("/api/zopay/checkout/session", { method: "POST" })
  .then(r => r.json());

const handle = mountPaymentWidget(document.getElementById("pay")!, {
  intent,
  checkStatus: async () => {
    const res = await fetch(`/api/zopay/status/${intent.id}`);
    return normalizeIntent(await res.json());
  },
  onSuccess: (info) => console.log("paid", info.txHash, info.amountReceived),
  onError:   (info) => console.error("failed", info),
  onExpire:  ()     => console.warn("timed out"),
  theme: "light",
});
```

## React / Vue

```tsx theme={null}
function Checkout({ intent }: { intent: PaymentIntent }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!ref.current) return;
    const handle = mountPaymentWidget(ref.current, {
      intent,
      checkStatus: async () => normalizeIntent(
        await fetch(`/api/zopay/status/${intent.id}`).then(r => r.json())
      ),
      onSuccess: () => router.push("/success"),
    });
    return () => handle.destroy();
  }, [intent.id]);

  return <div ref={ref} />;
}
```

## Errores comunes

* **El widget queda en blanco.** Verifica que importaste `@zopay/js/styles.css` una vez en tu bundle.
* **El `checkStatus` recibe un shape distinto del esperado.** Pasa siempre la respuesta a [`normalizeIntent`](/sdk-reference/normalize-intent) antes de devolverla — el widget consume `StatusInfo`, no el envelope crudo de la API.
* **El polling sigue después de desmontar el componente.** Olvidaste llamar a `handle.destroy()` en el cleanup de tu efecto.
