Skip to Content
ConceitosSessões

Sessões

Uma CheckoutSession é com o que o comprador interage — um objeto com TTL, de uso único, que é dono da URL do checkout hospedado. É a mais enxuta das três entidades centrais; o trabalho pesado acontece em Orders e em 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)
EntidadePropósitoTempo de vida
CheckoutSessionVoltado ao comprador — tem session_key, checkout_url, TTLMinutos (padrão 30)
OrderSeu estado de catálogo — itens, totais, reembolsosRegistro permanente
PaymentIntentUma tentativa de pagamento em uma rede/ativoHoras; 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 ~24 caracteres depois do prefixo (18 bytes aleatórios codificados em base64url, sem padding). 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

EstadoSignifica
ACTIVESessão criada e aberta. A URL do checkout está utilizável.
COMPLETEDUm PaymentIntent dessa sessão foi liquidado. O Order agora está PAID (ou PARTIAL_PAID se foi pagamento insuficiente).
EXPIREDexpires_at passou sem liquidação. O worker de limpeza mudou o estado e cancelou qualquer Order aberto.
CANCELEDCancelamento 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 existe um min/max rígido aplicado pelo servidor. Use valores sensatos — menos de 60 segundos arrisca expirar compradores legítimos; mais de 7 dias segura capacidade em um token que provavelmente foi abandonado. Escolha um número que combine com a janela de decisão esperada do seu comprador.
  • Padrão por lojista: Configurável via dashboard, mas só o endpoint legado POST /b2b/v1/checkout-sessions (em dois passos) honra. O endpoint POST /b2b/v1/checkout-sessions/quick sempre cai no fallback de 30 minutos se expires_in for omitido, independentemente da configuração por lojista. Se você precisa de um padrão diferente no quick path, envie expires_in explicitamente em toda chamada.
  • Aplicação: Preguiçoso na leitura + um worker periódico de limpeza. Uma sessão cujo expires_at já passou é tratada como EXPIRED mesmo que o campo de estado ainda não tenha sido escrito, então não dependa de ler o estado via API 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 (não há API pública de resolve hoje; para um fluxo self-service, prefira cobrar 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), o scanner on-chain captura o excedente dentro de 24 horas e dispara um alerta para o lojista. Não tem reembolso automático — emita um manualmente pela API de reembolsos ou pelo dashboard.

Pagamentos com ativo errado

O endereço de depósito é gerado por tupla (session, chain, asset). Se um comprador envia o ativo errado para o endereço, o matcher on-chain não reconhece e o PaymentIntent fica aberto até a expiração do TTL. A gente consegue recuperar os fundos, mas é um fluxo de suporte, não automático — oriente os compradores a enviar exatamente o ativo mostrado na página de checkout.

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 automaticamente se o seu cliente não tiver uma chave natural — o SDK faz isso por padrão.

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), então um /quick em retry entrega ao comprador uma sessão limpa sem duplicar o pedido.

Dois comportamentos diferem de uma camada de idempotência estilo Stripe — não se deixe pegar:

  • 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étodoPathNotas
POST/b2b/v1/checkout-sessions/quickCria order + sessão em uma chamada
POST/b2b/v1/checkout-sessionsCria sessão contra um order existente
GET/b2b/v1/checkout-sessions/by-order/:order_idLista todas as sessões de um pedido (para histórico de retries)
GET/checkout/:session_keyPúblico — o que o navegador do comprador acessa

Veja o Início rápido para o body completo de criação e a assinatura.

Próximos passos