## Objetivo

Operar el ciclo completo de facturación recurrente: iniciar checkout, confirmar estado post-pago y habilitar autoservicio con trazabilidad por organización.

## Endpoints y contrato operativo

| Endpoint                                                               | Cuándo usar                                     | Requisitos clave                                              |
| ---------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------- |
| `POST /api/billing/subscription/checkout?org={slug}&mode={test\|live}` | iniciar suscripción o upgrade                   | bearer token Beacon, `OWNER`/`ADMIN`, `plan` y `billingCycle` |
| `POST /api/billing/subscription/confirm?org={slug}&mode={test\|live}`  | reconciliar estado tras el retorno del checkout | contexto de organización; body opcional con `sessionId`       |
| `POST /api/billing/subscription/portal?org={slug}&mode={test\|live}`   | abrir gestión self-service                      | cliente Stripe asociado y suscripción elegible                |

## Flujo recomendado

<Steps>
  <Step title="1) Crear checkout">
    Genera sesión con plan, ciclo y contexto de organización. Guarda
    `checkoutSessionId`.
  </Step>
  <Step title="2) Redirigir usuario">
    Envía al usuario a `redirectUrl` y evita submits concurrentes en frontend.
  </Step>
  <Step title="3) Confirmar estado">
    Tras el retorno del checkout, llama `subscription/confirm`. Si tienes el
    `session_id` de la success URL, envíalo como `sessionId`.
  </Step>
  <Step title="4) Exponer portal">
    Genera sesiones de portal solo cuando ya existe un cliente/suscripción
    Stripe gestionable.
  </Step>
</Steps>

## Ejemplo de creación de checkout

```bash
curl -X POST "https://test.beacon.pt/api/billing/subscription/checkout?org=acme&mode=test" \
  -H "Authorization: Bearer <beacon_session_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "plus",
    "billingCycle": "monthly"
  }'
```

```json
{
  "ok": true,
  "checkoutSessionId": "cs_123",
  "redirectUrl": "https://checkout.stripe.com/c/pay/cs_123",
  "plan": "PLUS",
  "billingCycle": "monthly",
  "context": {
    "mode": "test",
    "organizationSlug": "acme"
  }
}
```

## Confirmación y consistencia

- El checkout no expone un `Idempotency-Key` público; la clave Stripe se genera en servidor.
- Si ya existe un checkout pendiente para el mismo plan y ciclo, la API puede reutilizarlo y devolver `pending: true`.
- Si existe un checkout pendiente para otro plan o la organización ya tiene una suscripción activa, la API responde con `409`.
- No asumas éxito solo por el redirect; confirma el estado final por `subscription/confirm` y eventos Stripe.

## Ejemplo de confirmación

```bash
curl -X POST "https://test.beacon.pt/api/billing/subscription/confirm?org=acme&mode=test" \
  -H "Authorization: Bearer <beacon_session_token>" \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"cs_test_a1b2c3"}'
```

```json
{
  "ok": true,
  "subscription": {
    "plan": "PLUS",
    "status": "ACTIVE"
  },
  "context": {
    "mode": "test",
    "organizationSlug": "acme"
  }
}
```

## Errores frecuentes

| Código | Causa típica                                                 | Acción recomendada                                     |
| ------ | ------------------------------------------------------------ | ------------------------------------------------------ |
| `400`  | `org`, `plan` o `billingCycle` inválidos                     | corregir payload/query y reintentar                    |
| `401`  | token inválido/expirado                                      | renovar la sesión Beacon y reintentar                  |
| `403`  | el usuario no es `OWNER`/`ADMIN`                             | revisar permisos de la organización                    |
| `409`  | suscripción activa, checkout pendiente o sesión aún no lista | reutilizar el estado existente o esperar unos segundos |
| `404`  | organización inexistente o inactiva                          | confirmar slug y contexto activo                       |

## Checklist de go-live

- Flujo completo validado en `mode=test` sin estados pendientes.
- Alertas activas para fallos de confirmación y picos de retries.
- Portal limitado por perfil con trazabilidad de acceso.

<Note title="Buena práctica">Valida siempre el flujo completo `checkout -> confirm -> webhook` antes de activar `live`.</Note>
