## Objetivo

Operar o ciclo completo de faturação recorrente: iniciar checkout, confirmar estado pós-pagamento e abrir portal self-service sem perder rastreabilidade por organização.

## Endpoints e contrato operativo

| Endpoint                                                               | Quando usar                                 | Requisitos-chave                                                    |
| ---------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------- |
| `POST /api/billing/subscription/checkout?org={slug}&mode={test\|live}` | iniciar subscrição ou upgrade               | Bearer token Beacon, papel `OWNER`/`ADMIN`, `plan` e `billingCycle` |
| `POST /api/billing/subscription/confirm?org={slug}&mode={test\|live}`  | reconciliar estado após retorno do checkout | contexto de organização; body opcional com `sessionId`              |
| `POST /api/billing/subscription/portal?org={slug}&mode={test\|live}`   | abrir gestão self-service de plano/método   | cliente Stripe já associado e subscrição elegível                   |

## Fluxo recomendado

<Steps>
  <Step title="1) Criar checkout">
    Gera sessão com plano, ciclo e contexto de organização. Guarda o
    `checkoutSessionId` para rastreio.
  </Step>
  <Step title="2) Redirecionar utilizador">
    Envia o utilizador para `redirectUrl` e evita submits concorrentes no
    frontend.
  </Step>
  <Step title="3) Confirmar resultado">
    Após o retorno do checkout, chama `subscription/confirm`. Se tiveres o
    `session_id` do success URL, envia-o como `sessionId`.
  </Step>
  <Step title="4) Expor portal">
    Disponibiliza `subscription/portal` apenas quando já existe
    cliente/subscrição Stripe gerível.
  </Step>
</Steps>

## Exemplo de criação 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"
  }
}
```

## Confirmação e consistência

- O checkout não expõe `Idempotency-Key` pública; a chave usada na Stripe é gerada no servidor.
- Se já existir checkout pendente para o mesmo plano e ciclo, a API pode reutilizar a sessão pendente e devolver `pending: true`.
- Se existir checkout pendente para outro plano ou se a organização já tiver subscrição activa, a API responde com `409`.
- Não assumes sucesso apenas com o redirect concluído; o estado final deve ser confirmado por `subscription/confirm` e por eventos Stripe.

## Exemplo de confirmação

```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"
  }
}
```

## Erros frequentes

| Código | Causa típica                                                    | Ação recomendada                                    |
| ------ | --------------------------------------------------------------- | --------------------------------------------------- |
| `400`  | `org`, `plan` ou `billingCycle` inválidos                       | corrigir payload/query e repetir                    |
| `401`  | token inválido ou expirado                                      | renovar sessão Beacon e repetir                     |
| `403`  | utilizador sem `OWNER`/`ADMIN`                                  | rever permissões da organização                     |
| `409`  | subscrição activa, checkout pendente ou sessão ainda não pronta | usar o estado existente ou aguardar alguns segundos |
| `404`  | organização inexistente ou inactiva                             | confirmar slug e contexto activo                    |

## Checklist de go-live

- Fluxo testado em `mode=test` com sucesso e sem estados pendentes.
- Alertas ativos para falhas de confirmação e aumento de retries.
- Portal restrito por perfil e com auditoria de acesso.

<Note title="Boas práticas">Valida sempre o fluxo completo `checkout -> confirm -> webhook` antes de activar `live`.</Note>
