Todos os posts
apiintegracaoidempotenciadesenvolvedores

Idempotência em pagamento: por que sua integração cobra o cliente duas vezes

O cliente clicou uma vez e foi cobrado duas. Quase sempre a causa não é bug no seu botão, é uma resposta que se perdeu no caminho. O que é idempotência e como usar a chave que resolve isso.

Equipe Trilho··8 min de leitura

Cobrança duplicada raramente vem de código malfeito. Vem de uma coisa mais chata: você não tem como saber se a requisição que deu timeout chegou ou não.

O problema em três passos

  1. Seu sistema chama a API pra criar uma cobrança.
  2. A cobrança é criada do lado de lá, normalmente.
  3. A resposta se perde. Timeout, rede instável, container reiniciado.

Agora você tem um problema sem saída boa. Se tentar de novo, corre o risco de criar a segunda cobrança. Se não tentar, corre o risco de o cliente ficar sem nada.

Repare que nada aí foi erro seu. É a natureza de qualquer chamada em rede, e é por isso que existe uma solução padronizada.

O que é idempotência

Uma operação é idempotente quando executá-la várias vezes tem o mesmo efeito de executá-la uma vez. Consultar um saldo é idempotente por natureza. Criar uma cobrança não é: chamou duas vezes, criou duas.

A chave de idempotência é o mecanismo que transforma a segunda em idempotente. Você gera um identificador único pra aquela intenção de cobrança e manda junto na requisição. Se a mesma chave chegar de novo, o servidor não cria nada: ele devolve o resultado da primeira.

Como usar direito

Manda um cabeçalho de idempotência com um identificador único por tentativa de compra. Um UUID serve.

POST /v1/transactions
Idempotency-Key: 9f2c1a44-6f5c-4a1e-9a71-2f0a5c8d3b12
Content-Type: application/json

{ "amount_cents": 19700, "method": "PIX" }

Repetiu a chamada com a mesma chave depois de um timeout? Volta a mesma cobrança, com o mesmo identificador. Uma só existe.

Os três erros de quem usa

Gerar a chave no lugar errado. Se você gera a chave dentro da função que faz a chamada, cada retentativa gera uma chave nova e a proteção some. A chave tem que nascer junto com a intenção do cliente, antes da primeira tentativa, e sobreviver às retentativas.

Usar o id do pedido como chave sempre. Funciona pra impedir duplicata do mesmo pedido, mas trava o caso legítimo de o cliente tentar pagar de novo depois de a primeira cobrança expirar. Uma saída é combinar id do pedido com a tentativa.

Achar que idempotência é eterna. A janela costuma ser de horas ou poucos dias. Depois disso, a mesma chave cria uma cobrança nova. Não use como controle de duplicidade de longo prazo.

A outra metade do problema

Idempotência resolve a duplicata na criação. Ela não resolve a duplicata no aviso. Webhook é reenviado por natureza: quem manda repete até ter certeza de que você recebeu, então o mesmo evento de pagamento pode chegar duas ou três vezes.

Se o seu código credita saldo toda vez que recebe o aviso, você vai creditar em dobro em algum momento. A defesa é a mesma ideia, aplicada na entrada: antes de processar, verifica se aquela venda já está no estado que o evento pede. Já está paga? Responde sucesso e não faz nada.

Como o Trilho trata

A API pública aceita chave de idempotência na criação de cobrança. Do lado do recebimento, cada aviso de pagamento é conferido contra o estado atual da venda antes de qualquer crédito, então o mesmo evento chegando três vezes credita uma.

Toda entrega de webhook fica registrada com o que foi recebido e como foi tratado, o que torna possível auditar depois em vez de depender de log de aplicação.

Resumo

Gere a chave junto com a intenção de compra, não dentro da função de chamada. Mande em toda criação de cobrança. E trate o webhook como se ele fosse chegar repetido, porque vai.

Coloque em prática

Testa o Trilho grátis, sem cartão de crédito

Cria a conta em 2 minutos e gera seu primeiro PIX ainda hoje.

Criar conta grátis →

Leia também