Pagamento com cartão parece uma operação: o cliente paga.
São duas, separadas no tempo, e cada uma tem um risco diferente. Sistema que modela como uma coisa só acaba com duas categorias de problema que ninguém consegue explicar depois.
O que cada uma significa
Autorização é uma reserva. O emissor confirma que o limite existe e bloqueia o valor. O dinheiro não saiu de lugar nenhum: ficou comprometido.
Captura é a cobrança de fato. Você diz ao adquirente “pode levar”, e aí o valor entra no fluxo de liquidação.
Entre uma e outra pode passar um segundo ou vários dias. E a autorização expira sozinha, tipicamente numa janela de dias que varia por bandeira e por tipo de transação.
Se você capturar depois da expiração, a captura falha. O cliente já viu o valor bloqueado no cartão dele e você não recebeu nada.
Por que separar
O modelo de captura automática, tudo junto, é o certo pra venda digital com entrega imediata. Não separe se você não precisa.
A separação vale em três situações.
Quando existe risco de não conseguir entregar. Produto físico com estoque incerto: autoriza no checkout, captura no envio. Se o estoque falhar, você cancela a autorização em vez de cobrar e estornar.
Quando o valor final muda. Combustível, hotel, aluguel de carro. Autoriza uma estimativa, captura o valor real.
Quando existe análise antifraude demorada. Autoriza, analisa, captura se aprovar.
Em todas, a lógica é a mesma: reservar o dinheiro é barato e reversível, cobrar não é.
Cancelar não é estornar
Distinção que gera confusão e custa dinheiro.
Cancelamento é desfazer a autorização antes da captura. O bloqueio some, nenhum dinheiro se moveu, e o custo é próximo de zero.
Estorno é devolver depois da captura. O dinheiro já circulou, tem taxa envolvida em muitos arranjos, e aparece no extrato do cliente como duas linhas.
Sistema que só implementa estorno acaba capturando e devolvendo em situações onde bastava cancelar. Isso custa taxa, gera confusão no cliente e polui a conciliação.
O modelo de dados
O erro estrutural mais comum é uma tabela payments com uma coluna status.
-- não representa a realidade
create table payments (
id uuid primary key,
order_id uuid,
amount_minor bigint,
status text -- 'authorized' | 'captured' | 'refunded' ...
);
Isso não representa captura parcial, não representa duas capturas da mesma autorização, e não representa estorno parcial. Todas as três são situações normais.
O que representa:
create table payment_authorization (
id uuid primary key,
order_id uuid not null,
provider text not null,
provider_auth_id text not null,
amount_minor bigint not null,
captured_minor bigint not null default 0,
status text not null, -- 'authorized','expired','cancelled','captured'
authorized_at timestamptz not null,
expires_at timestamptz not null,
unique (provider, provider_auth_id)
);
create table payment_capture (
id uuid primary key,
authorization_id uuid not null references payment_authorization(id),
provider_capture_id text not null,
amount_minor bigint not null,
captured_at timestamptz not null,
unique (provider, provider_capture_id)
);
Uma autorização, N capturas. Cada captura com seu identificador do provedor e sua constraint única, que é o que impede notificação reenviada de virar dinheiro contado duas vezes.
E cada uma dessas operações gera lançamento no ledger, em contas diferentes: autorização não é receita, é compromisso.
A expiração é problema seu
O adquirente não vai te avisar que a autorização venceu. Ela simplesmente para de funcionar.
Um job que roda todo dia procurando autorização perto do vencimento sem captura é obrigatório em qualquer sistema com captura postergada:
select * from payment_authorization
where status = 'authorized'
and captured_minor = 0
and expires_at < now() + interval '24 hours';
Cada linha é uma decisão pendente: capturar, cancelar ou reautorizar. Deixar vencer em silêncio é a forma mais comum de perder venda que já estava aprovada.
A janela em que tudo dá errado
Entre autorizar e capturar, três coisas podem acontecer, e o sistema precisa de resposta pra todas.
O cliente cancela o pedido. Você cancela a autorização, não estorna.
O estoque acabou. Mesma coisa, e é o caso que justifica a separação existir.
O cartão foi bloqueado por fraude entre os dois momentos. A captura falha, e o pedido precisa voltar pra um estado que permita nova tentativa de pagamento, não sumir.
Esse terceiro caso é o mais esquecido, e produz o pedido que fica preso em “aguardando pagamento” pra sempre, até alguém do suporte mexer na mão.
O que registrar
Cada mudança de estado precisa virar evento, com o payload cru do provedor guardado.
Seis meses depois, quando alguém perguntar por que aquele pedido foi entregue sem pagamento, a resposta vai estar na sequência de eventos ou não vai existir. Escrevi sobre esse desenho em log append-only.
E guarde sempre os identificadores do provedor, mesmo os que você não usa hoje. É a única chave que permite conciliar depois, e recuperar isso retroativamente é impossível.