Skip to main content
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

function mountPaymentWidget(
  target: string | HTMLElement,
  options: MountPaymentWidgetOptions
): { destroy(): void; refresh(): void };

Parámetros

ParámetroTipoDescripción
targetstring | HTMLElementid del elemento DOM o nodo directo. Pasa ref.current desde React.
options.intentPaymentIntentEl 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.
options.onSuccess(info: StatusInfo) => voidDisparado cuando el estado pasa a completed.
options.onError(info: StatusInfo) => voidDisparado en estados de fallo del intent.
options.onExpire() => voidDisparado cuando el polling supera timeoutMs o cuando el intent expira.
options.theme"light" | "dark"Tema visual. Default: "light".
options.pollIntervalMsnumberIntervalo entre polls (ms). Default razonable.
options.timeoutMsnumberTiempo 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

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

Ejemplo

<div id="pay"></div>
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

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 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.