Skip to main content
@zopay/js no crea payment intents — tu servidor lo hace contra Connect API. El widget consume el envelope del intent y polea estado vía un callback que tú implementas (checkStatus) que proxea la lectura a través de tu backend.

1. Instalar

npm install @zopay/js
@zopay/js no tiene peer dependencies obligatorias. (Hay un peer opcional de React, declarado pero no requerido.)

2. Crear el intent en tu backend

Tu endpoint (p. ej. POST /api/zopay/checkout/session) holds the sk_live_ key, generates a fresh Idempotency-Key per call, and computes the amount server-side from the cart:
curl -X POST "https://api.zopay.cash/connect/v1/payment-intents" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "25.00",
    "currencies": ["USDT"],
    "external_ref": "user_alice"
  }'
Devuelve el envelope PaymentIntent ({ id, amount, currency, addresses, … }). Devuélvelo verbatim a tu frontend desde tu propio endpoint. Referencia: Crear Payment Intent.

3. Tu endpoint de lectura de estado

El widget no llama a ZoPay directo. Cuando necesita refrescar estado, llama a tu callback checkStatus, que a su vez le pide a tu backend que llame a GET /payment-intents/{id} con la clave secreta y devuelva el body verbatim:
// Tu endpoint: GET /api/zopay/status/:id
app.get('/api/zopay/status/:id', async (req, res) => {
  const r = await fetch(
    `https://api.zopay.cash/connect/v1/payment-intents/${req.params.id}`,
    { headers: { Authorization: `Bearer ${process.env.ZOPAY_SECRET}` } }
  );
  res.json(await r.json());
});

4. Montar el widget

<div id="pay"></div>
import { mountPaymentWidget, normalizeIntent } from "@zopay/js";
import "@zopay/js/styles.css";

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

// 2. Montar el widget. El callback `checkStatus` proxea estado a tu backend.
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; revisa manualmente"),
  theme: "light",
});

// React/Vue: limpia en unmount.
// useEffect(() => () => handle.destroy(), []);

// Trigger inmediato de status check.
// handle.refresh();
mountPaymentWidget devuelve { destroy, refresh }. El parámetro target acepta tanto un id (string) como un nodo DOM — pasa ref.current directo desde React.

Probar en sandbox

Con una clave sk_test_…, el intent se crea con livemode: false. Para simular el cobro sin esperar un depósito on-chain, llama a POST /test/payment-intents/{id}/simulate-deposit desde tu backend — el widget detectará el cambio en el siguiente poll y dispará onSuccess.

Sin widget: sólo poller

Si quieres la máquina de estados de polling sin el widget renderizado (UI a medida), usa createPoller. Ver createPoller.

Lecturas obligatorias