API duplicou cobranças? Como garantir idempotência no backend
Descubra como chaves de idempotência em APIs REST evitam cobranças duplicadas em gateways de pagamento, usando bancos relacionais para rastrear requisições.
Resumo
- Requisições duplicadas ocorrem por instabilidades de rede e retentativas automáticas de clientes.
- Idempotência garante que uma mesma operação produza o mesmo efeito mesmo se executada várias vezes.
- Chaves únicas enviadas no cabeçalho HTTP identificam cada transação de forma inequívoca.
- Bancos relacionais com restrições de unicidade bloqueiam processamentos paralelos indesejados.
- O tratamento correto de status HTTP evita confusões visuais e financeiras para o usuário final.
O perigo invisível das cobranças em dobro na sua API
Imagine a cena: um cliente tenta comprar um produto no seu e-commerce, o botão de pagamento trava por dois segundos, ele clica de novo e, no fim do mês, descobre que foi cobrado duas vezes. Na engenharia de software, chamamos isso de anomalia por requisições duplicadas. Quando a rede falha no meio do caminho, o aplicativo do cliente não sabe se o pagamento chegou ao servidor e tenta enviar o pedido novamente. Se o seu backend não estiver preparado para reconhecer que aquela é a mesma compra, o sistema vai processar o pagamento duas vezes. Na prática, isso significa dor de cabeça para o suporte e prejuízo financeiro.
Para piorar, os gateways de pagamento, que são os sistemas externos que processam cartões de crédito, também possuem suas próprias regras de reenvio. Se o seu microsserviço dispara uma requisição de cobrança e sofre uma queda de conexão antes de receber a resposta, ele fica no escuro. Ao religar, se ele tentar cobrar de novo sem uma estratégia de proteção, o estrago está feito. É justamente para blindar o sistema contra essas falhas que utilizamos o conceito de idempotência, uma palavra elegante da computação que resolve um problema muito cotidiano.
O que é idempotência e como ela funciona na prática
Em termos simples, uma operação idempotente é aquela que você pode repetir quantas vezes quiser, mas o resultado final será sempre o mesmo da primeira execução. Pense no botão de um elevador: se você apertar o botão do térreo cinco vezes seguidas, o elevador vai para o térreo e para lá, sem descer mais cinco andares. Na web, o método HTTP GET é naturalmente idempotente, porque buscar uma página dez vezes apenas traz a mesma informação de volta, sem alterar nada no banco de dados. O problema mora nos métodos POST, que criam registros e realizam ações financeiras.
Quando criamos uma API REST para receber pagamentos, precisamos injetar essa mesma lógica de segurança. A ferramenta mais eficiente para isso é a chave de idempotência, conhecida internacionalmente como Idempotency Key. Funciona assim: antes de enviar a ordem de pagamento, o aplicativo do cliente gera um código único, como um identificador universal aleatório (UUID). Esse código viaja no cabeçalho HTTP da requisição. Quando o seu servidor recebe a requisição de pagamento, ele consulta rapidamente o banco de dados para verificar se esse código já foi processado antes.
Desenhando o fluxo de validação no banco relacional
Para implementar essa trava de segurança, precisamos de uma tabela dedicada no banco de dados relacional, como PostgreSQL ou MySQL. Essa tabela armazena a chave de idempotência, o status atual da requisição, que pode ser 'processando', 'sucesso' ou 'falha', e a resposta original que foi devolvida ao cliente. Quando uma nova requisição chega, a primeira coisa que o código faz é tentar inserir essa chave na tabela com o status inicial. Utilizar restrições de unicidade no banco garante que, mesmo que duas requisições idênticas cheguem exatamente no mesmo milissegundo, apenas uma conseguirá gravar o registro.
Se a inserção falhar porque a chave já existe, o backend entra no modo de recuperação. Se a requisição original ainda estiver rodando, o servidor pode responder com um código HTTP adequado informando que a operação está em andamento, evitando uma nova execução paralela e pesada. Se a operação anterior já terminou com sucesso, o servidor simplesmente busca a resposta guardada e a devolve intacta ao cliente, fingindo que executou tudo de novo, mas sem mexer no gateway de pagamento. Essa estratégia protege a integridade dos dados e otimiza o uso do servidor.
CREATE TABLE idempotency_keys (
key VARCHAR(255) PRIMARY KEY,
status VARCHAR(50) NOT NULL,
response_body JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Tratando concorrência e o perigo das condições de corrida
Um dos maiores desafios ao implementar essa arquitetura é lidar com a concorrência em alta escala. Condições de corrida, conhecidas em inglês como race conditions, acontecem quando duas requisições com a mesma chave chegam quase juntas e passam juntas pela verificação de existência antes que qualquer uma delas consiga salvar o registro na tabela. Se isso acontecer, ambas tentarão chamar o gateway de pagamento e a duplicação de cobrança acontecerá do mesmo jeito.
A forma mais segura de blindar o seu código contra essa falha é confiar na restrição de chave primária ou de índice único do próprio banco de dados relacional. Em vez de fazer um SELECT seguido de um INSERT em momentos separados, faça diretamente o comando de inserção. O banco de dados vai rejeitar o segundo comando instantaneamente devido à duplicidade da chave, lançando um erro controlado. O seu código captura esse erro e decide se deve aguardar o término da primeira requisição ou retornar o resultado já cacheado. Assim como ocorre quando o Node.js consome muita memória sob carga excessiva e exige cuidados de resiliência, a arquitetura de pagamentos exige rigor estrutural.
Boas práticas para expirar chaves e manter a saúde da API
Guardar chaves de idempotência para sempre no banco de dados é um desperdício desnecessário de espaço em disco e pode degradar a performance das consultas ao longo dos meses. As empresas costumam adotar uma janela de validade para essas chaves, que costuma variar entre 24 horas e 7 dias, dependendo da regra de negócio do produto financeiro. Passado esse período, as chaves antigas podem ser apagadas com segurança por uma rotina de limpeza automatizada, processo semelhante ao que fazemos quando limpamos o cache do systemd journal no Linux para liberar espaço.
Outro ponto crítico é o tratamento adequado dos códigos de status HTTP nas respostas cacheadas. Se a transação original foi bem-sucedida, a resposta repetida deve retornar exatamente o mesmo código de sucesso, como 200 OK ou 201 Created. Se a operação falhou por falta de saldo, o sistema deve devolver o erro original, garantindo que o cliente entenda o motivo real do problema sem que uma nova tentativa seja gerada à revelia. A transparência nos códigos de status evita confusões tanto para os aplicativos clientes quanto para as equipes de suporte técnico.
Considerações finais
Garantir idempotência em APIs de pagamento não é apenas um detalhe de implementação, mas um requisito fundamental de confiabilidade para qualquer sistema moderno de backend. Ao utilizar chaves exclusivas associadas a restrições rígidas em bancos relacionais, eliminamos o risco de cobranças duplicadas geradas por quedas de rede e reentregas automáticas. Investir tempo nessa arquitetura protege o patrimônio dos clientes, fortalece a reputação da empresa e reduz drasticamente os chamados de suporte operacional.
A adoção desse padrão exige disciplina na modelagem de dados e atenção aos detalhes de concorrência, mas os benefícios superam amplamente o esforço inicial. Com um fluxo seguro, transparente e resiliente, sua aplicação ganha maturidade para escalar com estabilidade e confiança, pronta para absorver picos de tráfego sem sustos financeiros.