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

# Quickstart

> Crear un payment intent en el servidor y montarlo en el navegador con @zopay/js.

`@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

```bash theme={null}
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:

```bash theme={null}
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](/api-reference/endpoints/post-payment-intents).

## 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:

```ts theme={null}
// 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

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

```ts theme={null}
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`](/api-reference/endpoints/post-test-payment-intents-by-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](/sdk-reference/create-poller).

## Lecturas obligatorias

* [Lo que NO debes hacer](/sdk-reference/index#lo-que-no-debes-hacer) — el modelo de seguridad del SDK.
