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

# Inicio rápido

> Crea una sesión de checkout y confirma el pago con la API REST de Onflay en 5 minutos — sin instalar ningún SDK.

Al final de esta guía tendrás un endpoint en tu backend que redirige al
comprador a un checkout hospedado de Onflay y confirma el pago **sin
webhooks**, usando `fetch` + la API REST.

<Note>
  Onflay es **API-first**. La API REST + OpenAPI es la superficie que
  invertimos activamente. Los SDKs `@onflay/*` están **congelados en
  mantenimiento** mientras la API v1 se estabiliza — siguen publicados e
  instalables, pero no reciben features nuevas. Para integraciones nuevas usa
  REST. Ver [Principios API-first](/developers/escalera-de-integracion).
</Note>

## 1. Crea una API key de sandbox

En el dashboard cambia a **Sandbox** y crea una key. Se ve como `sk_test_…`.
La key determina el entorno — `sk_test_` apunta a `sandbox-api.onflay.com` y
`sk_live_` a `api.onflay.com`. No configuras nada más.

```bash theme={"dark"}
export ONFLAY_API_KEY=sk_test_...
```

<Warning>
  Las `sk_*` son secretas. Solo viven en tu backend. Nunca en código de
  navegador, apps móviles ni repos públicos.
</Warning>

## 2. Descubre el recipe del catálogo

En el panel de desarrollador de la oferta, setea un **catalog key** y **plan
keys**. Luego lee el recipe para obtener claves estables, precios y
entitlements:

```bash theme={"dark"}
curl https://sandbox-api.onflay.com/v1/catalog/listings/main-saas/recipe \
  -H "Authorization: Bearer $ONFLAY_API_KEY"
```

```json theme={"dark"}
{
  "object": "listing_recipe",
  "catalogKey": "main-saas",
  "plans": [
    {
      "key": "pro",
      "name": "Pro",
      "price": { "amountInCents": 2900, "currency": "usd", "interval": "month" },
      "prices": [
        { "interval": "month", "amountInCents": 2900, "currency": "usd" },
        { "interval": "year",  "amountInCents": 29000, "currency": "usd" }
      ],
      "entitlements": [{ "key": "pro_features", "value": true }]
    }
  ],
  "endpoints": {
    "checkoutSessions": "https://sandbox-api.onflay.com/v1/checkout-sessions",
    "receipt": "https://sandbox-api.onflay.com/v1/checkout-sessions/{checkoutId}/receipt",
    "customerAccess": "https://sandbox-api.onflay.com/v1/customer-access/{externalCustomerId}"
  }
}
```

Renderiza tu UI de precios desde `recipe.plans[].prices`. No hardcodees
montos autoritativos.

## 3. Crea una sesión de checkout

Cuando el usuario hace click en "Comprar", tu backend crea una sesión y lo
redirige a la URL que devuelve Onflay. El comprador llega a un checkout
hospedado por Onflay — tú no manejas datos de tarjeta.

```ts theme={"dark"}
// app/api/checkout/route.ts (Next.js App Router)
const ONFLAY_API_BASE = 'https://sandbox-api.onflay.com';

export async function POST(request: Request) {
  const { userId, email } = await request.json();

  const res = await fetch(`${ONFLAY_API_BASE}/v1/checkout-sessions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ONFLAY_API_KEY!}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `checkout:${userId}:main-saas:pro:month`,
    },
    body: JSON.stringify({
      catalog: { listing: 'main-saas', plan: 'pro', interval: 'month' },
      externalCustomerId: userId,
      email,
      successUrl: 'https://tu-app.com/gracias',
      cancelUrl: 'https://tu-app.com/precios',
    }),
  });

  const session = await res.json();
  return Response.redirect(session.checkoutUrl, 303);
}
```

`interval` es opcional (default `month`). Usa `year` cuando el plan expone
precio anual y el comprador lo elige.

### Idempotency-Key

La cabecera `Idempotency-Key` protege contra duplicados: si reintentas la
misma operación, Onflay devuelve la misma sesión en vez de crear otra.
Combina entidades que hacen única la operación:

```text theme={"dark"}
checkout:user_123:main-saas:pro:month
checkout:order_789
```

No uses un valor aleatorio ni uno regenerado por cada retry — la clave debe
ser la misma en todos los intentos de la misma operación.

## 4. Confirma el pago (sin webhook)

La URL `successUrl` solo sirve para mostrar una pantalla de éxito. **No es
confirmación de pago.** Tras el redirect, Onflay siempre añade `checkout_id` y
`return_token` a la query. Léelos en tu success page y envíalos a tu backend,
que hace poll del receipt hasta `paid` | `failed` | `expired`.

```ts theme={"dark"}
// app/api/confirm-payment/route.ts
const ONFLAY_API_BASE = 'https://sandbox-api.onflay.com';

export async function POST(request: Request) {
  const { checkoutId, returnToken } = await request.json();

  for (let i = 0; i < 30; i++) {
    const res = await fetch(
      `${ONFLAY_API_BASE}/v1/checkout-sessions/${checkoutId}/receipt?returnToken=${returnToken}`,
      { headers: { Authorization: `Bearer ${process.env.ONFLAY_API_KEY!}` } },
    );
    const receipt = await res.json();
    if (receipt.status === 'paid') return Response.json({ ok: true, receipt });
    if (receipt.status === 'failed' || receipt.status === 'expired') {
      return Response.json({ ok: false, receipt }, { status: 400 });
    }
    await new Promise((r) => setTimeout(r, 2000));
  }
  return Response.json({ ok: false, error: 'timeout' }, { status: 408 });
}
```

### Alternativa: customer-access

Si ya conoces el `externalCustomerId`, puedes confirmar + provisionar en un
solo paso consultando el snapshot de acceso:

```bash theme={"dark"}
curl https://sandbox-api.onflay.com/v1/customer-access/user_123 \
  -H "Authorization: Bearer $ONFLAY_API_KEY"
```

Si `activePlans` contiene el plan con `status: ACTIVE`, el pago pasó y el
acceso está concedido. Usa `ETag` / `version` para detectar cambios baratos.
Ver [Customer access](/developers/customer-access).

## 5. Pasa a producción

Cambia `sk_test_*` por `sk_live_*` desde la pestaña **Live** del dashboard y
apunta el base URL a `https://api.onflay.com`. No hay otros cambios de código.

<Note>
  El webhook `customer.access_changed` es **opcional** (Nivel 3) si quieres
  push en vez de poll. Los eventos `payment.completed` / `subscription.*`
  existen pero son Avanzado — no los necesitas para el flujo default.
</Note>

***

## Siguiente paso

Con esto tienes el flujo completo Nivel 1 + 2: checkout → confirmación por
receipt o customer-access, sin webhooks.

* [Customer access](/developers/customer-access) — provisioning default + webhook opcional.
* [Escalera de integración](/developers/escalera-de-integracion) — los 4 niveles.
* [Referencia API](/api-reference/openapi) — OpenAPI con copy-as-curl en cada endpoint.
