<!-- Source: https://docs.infraio.xyz/pt-BR/concepts/sessions -->
<!-- Last updated: 2026-10-04 -->

# Sessões

Uma **CheckoutSession** é com o que o comprador interage: um objeto
com tempo limitado, de uso único, que é dono da URL do checkout
hospedado. É a mais leve das três entidades centrais. A maior parte da
sua lógica trabalha com [Orders](https://docs.infraio.xyz/pt-BR/concepts/orders) e
**Payment Intents** (veja abaixo).

## O modelo de dados de três entidades

```
CheckoutSession  ←  1:1  →  Order  ←  1:N  →  PaymentIntent
   (comprador)              (catálogo)        (cada tentativa de pagamento)
```

| Entidade | Propósito | Tempo de vida |
| --- | --- | --- |
| **CheckoutSession** | Voltado ao comprador — tem `session_key`, `checkout_url`, TTL | Minutos (padrão 30) |
| **Order** | Seu estado de catálogo — itens, totais, reembolsos | Registro permanente |
| **PaymentIntent** | Uma tentativa de pagamento em uma rede/ativo | Horas; liquida ou expira |

Você cria uma sessão e um pedido juntos (via `POST /b2b/v1/checkout-sessions/quick`).
Cada vez que o comprador escolhe um ativo na página de checkout, um
PaymentIntent novo é aberto na rede certa. Se ele trocar de ativo no
meio do checkout, o intent anterior vai para `EXPIRED` e um novo começa.

## Identificador

Uma sessão é identificada pelo seu `session_key`:

```
cst_G-SO92J7HNWkwMHEHjD4oO1Z
```

URL-safe, com cerca de 24 caracteres depois do prefixo. A página de checkout hospedada
é `https://checkout.infraio.xyz/<session_key>` — trate o session key
como uma credencial bearer para aquele único checkout.

## Ciclo de vida — CheckoutSession

```mermaid
stateDiagram-v2
    [*] --> ACTIVE
    ACTIVE --> COMPLETED: um PaymentIntent liquida
    ACTIVE --> EXPIRED:   expires_at venceu
    ACTIVE --> CANCELED:  comprador ou lojista cancela
    COMPLETED --> [*]
    EXPIRED --> [*]
    CANCELED --> [*]
```

| Estado | Significa |
| --- | --- |
| `ACTIVE` | Sessão criada e aberta. A URL do checkout está utilizável. |
| `COMPLETED` | Um PaymentIntent dessa sessão foi liquidado. O Order agora está `PAID` (ou `PARTIAL_PAID` se foi pagamento insuficiente). |
| `EXPIRED` | `expires_at` passou sem liquidação. Qualquer Order aberto é cancelado. |
| `CANCELED` | Cancelamento explícito — o comprador clicou em "cancelar" ou você chamou o endpoint de cancel. |

Os três estados terminais são mutuamente exclusivos e finais. Uma nova
CheckoutSession contra o mesmo Order pode ser criada se você quiser
tentar de novo (por exemplo, depois de um pagamento insuficiente).

## TTL

- **Padrão:** 30 minutos (configurável pelo campo `expires_in` na
  criação, em segundos).
- **Limites:** Não há mínimo nem máximo aplicado. Escolha um valor que
  combine com a janela de decisão esperada do seu comprador: menos de
  60 segundos arrisca expirar compradores legítimos, e uma sessão que
  fica aberta por mais de 7 dias provavelmente foi abandonada.
- **Padrão por lojista:** Você pode definir um padrão no dashboard, mas
  só `POST /b2b/v1/checkout-sessions` (em dois passos) o honra.
  `POST /b2b/v1/checkout-sessions/quick` usa **30 minutos** quando
  `expires_in` é omitido, independentemente da configuração do
  dashboard. Para usar um padrão diferente com `/quick`, envie
  `expires_in` em toda chamada.
- **Aplicação:** Uma sessão cujo `expires_at` já passou é tratada como
  `EXPIRED` mesmo que o estado ainda não tenha sido atualizado, então
  não dependa do valor do estado no instante exato da expiração.

## Pagamento insuficiente

Se o comprador envia menos do que o valor da sessão, o PaymentIntent
ainda liquida pelo valor parcial e o Order transita para
`PARTIAL_PAID`. A CheckoutSession vai para `COMPLETED` (um
PaymentIntent liquidou), então deixa de ser reaproveitável.

Para aceitar o déficit como pagamento total, o pedido pode ser
resolvido para `PAID`. Veja [Conceitos → Pedidos](https://docs.infraio.xyz/pt-BR/concepts/orders)
(a resolução não está disponível pela API; para continuar self-service,
cobre o restante). Para cobrar o restante, crie uma **nova**
CheckoutSession contra o mesmo Order com o valor residual.

## Pagamento excedente

Se o comprador envia mais do que o valor da sessão (raro, mas
acontece com transferências manuais), a InfraIO Pay detecta o
excedente dentro de 24 horas e avisa você. Ele não é reembolsado
automaticamente. Emita o reembolso pela API de reembolsos ou pelo
dashboard.

## Pagamentos com ativo errado

O endereço de depósito é gerado para uma sessão, uma rede e um ativo.
Se um comprador envia um ativo diferente para ele, o pagamento não é
reconhecido e o PaymentIntent fica aberto até a sessão expirar. O
suporte pode ajudar a recuperar os fundos, mas isso não é automático.
Oriente os compradores a enviar exatamente
o ativo mostrado na página de checkout.

Em TRON, Solana e TON não há endereço de depósito, então isto vale apenas para redes EVM: o comprador paga diretamente a sua carteira. Veja [Redes com pagamento direto na carteira](https://docs.infraio.xyz/pt-BR/concepts/chains#redes-com-pagamento-direto-na-carteira).

## Idempotência na criação

`POST /b2b/v1/checkout-sessions/quick` aceita um campo
`idempotency_key` no body da requisição (atenção: **campo do body, não
header HTTP**). Gere um UUID se você não tiver uma chave natural.

A chave deduplica o **Order**, não a CheckoutSession. Em uma
retentativa com a mesma chave, `/quick` retorna o *pedido original*
(`order_id` é estável), mas cria uma **CheckoutSession nova** — um
`session_key` e uma `checkout_url` novos a cada vez. Isso é
intencional: um Order pode dar suporte a várias tentativas de
checkout (veja [Pedidos](https://docs.infraio.xyz/pt-BR/concepts/orders)), então um `/quick`
em retry entrega ao comprador uma sessão limpa sem duplicar o pedido.

Dois comportamentos para ter em mente:

- **O body não é hasheado nem comparado.** Reusar uma chave com um
  body *diferente* **não** retorna `409` — o servidor silenciosamente
  retorna o pedido já armazenado sob aquela chave e ignora o body
  novo. Então trate uma `idempotency_key` como um token de uso único
  para um único pedido lógico; nunca reaproveite uma entre carrinhos
  diferentes.
- **Só o Order é deduplicado, não a sessão.** Se você precisa da
  *mesma* URL de checkout de volta, persista `session_key` /
  `checkout_url` da primeira resposta — chamar `/quick` de novo não
  vai retornar a antiga. Para enumerar cada sessão criada contra um
  pedido, use `GET /b2b/v1/checkout-sessions/by-order/:order_id`.

## Endpoints da API

| Método | Path | Notas |
| --- | --- | --- |
| `POST` | `/b2b/v1/checkout-sessions/quick` | Cria order + sessão em uma chamada |
| `POST` | `/b2b/v1/checkout-sessions` | Cria sessão contra um order existente |
| `GET` | `/b2b/v1/checkout-sessions/by-order/:order_id` | Lista todas as sessões de um pedido (para histórico de retries) |

Veja o [Início rápido](https://docs.infraio.xyz/pt-BR/get-started/quickstart) para o body
completo de criação e a assinatura.

## Próximos passos

- [Conceitos → Pedidos](https://docs.infraio.xyz/pt-BR/concepts/orders) — a entidade Order (a
  que você trata como fonte da verdade para a entrega).
- [Conceitos → Redes e ativos](https://docs.infraio.xyz/pt-BR/concepts/chains) — redes
  suportadas e premissas de finalidade por rede.
- [Webhooks → Visão geral](https://docs.infraio.xyz/pt-BR/webhooks/overview) — quais eventos
  disparam em cada transição de estado da sessão.
