Skip to main content
El SDK del navegador no crea payment intents y nunca habla con ZoPay directamente. Tu servidor crea el intent contra Connect API; el navegador monta el widget con el envelope recibido, y el widget polea estado vía un callback que tú implementas (checkStatus) que proxea la lectura a tu backend.

Flujo en tres pasos

  1. Servidor llama a POST /connect/v1/payment-intents con la clave Connect.
  2. Servidor devuelve el envelope PaymentIntent al frontend (nunca la clave).
  3. Frontend monta el widget con mountPaymentWidget(target, { intent, checkStatus, … }).

1. Crear el intent (servidor)

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 completo PaymentIntent ({ id, amount, currency, addresses, … }). Referencia: Crear Payment Intent.

2. Devolver el envelope al cliente

Tu propio endpoint debe devolver el envelope al navegador. No expongas sk_live_… al navegador.
POST /api/zopay/checkout/session  →  { id, amount, currency, addresses, … }

3. Endpoint de lectura de estado (servidor)

El widget no lee estado directo de ZoPay — proxea por tu backend:
// 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

npm install @zopay/js
<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: limpia en unmount.
// useEffect(() => () => handle.destroy(), []);

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 real, llama desde tu backend a POST /test/payment-intents/{id}/simulate-deposit — el widget detectará el cambio de estado en el siguiente poll y disparará onSuccess.

Siguiente paso