2026-01-16//LOG
Construindo Sistemas de Pagamento Que Nao Perdem Dinheiro
Faz um tempo que construo infra de processamento de pagamento na Yooga. Sistema POS pra restaurante. Dinheiro real, transacao real, consequencia real quando da merda. Isso aqui eh TUDO que aprendi sobre nao perder dinheiro em producao.
REGRA UM: IDEMPOTENCIA NAO EH OPCIONAL
Toda operacao de pagamento precisa de chave de idempotencia. TODA. Eu nao ligo se tu acha que teu sistema nunca vai retentar request. Vai sim. A rede vai solucar. O cara vai dar double tap. O load balancer vai dar timeout e retentar. Se teu endpoint de pagamento nao for idempotente, tu VAI processar cobranca duplicada.
A gente usa chave composta: ID do merchant + UUID gerado pelo client + tipo de operacao. Isso eh gravado ANTES de falar com o processador de pagamento. Se a gente ve a mesma chave duas vezes, retorna o resultado cacheado da primeira tentativa. Sem segunda cobranca. Sem drama.
REGRA DOIS: RACE CONDITION VAI TE ACHAR
Historia boa. A gente tinha um race condition onde duas requests concorrentes podiam ler o mesmo saldo, as duas verificar fundos suficientes, e as duas debitar. Resultado? O merchant conseguia gastar mais do que tinha. Descobrimos porque um restaurante de teste conseguiu processar saldo negativo no rush do almoco.
Fix: locking pessimista no check de saldo. SELECT FOR UPDATE na row da conta, verifica fundos, debita, commit. Sim, serializa pagamento concorrente pra mesma conta. Sim, eh um pouco mais lento. Nao, eu nao ligo. Corretude ganha de performance em sistema de pagamento TODA VEZ.
Mas esse era o race condition facil. O dificil foi com transacao concorrente no terminal de cartao. Dois POS, mesmo merchant, os dois batendo no processador ao mesmo tempo. O processador retorna sucesso pros dois, mas nosso webhook handler processa fora de ordem e a segunda transacao sobrescreve o status da primeira. A gente perdeu visibilidade de pagamento concluido.
Solucao: event sourcing pra mudanca de estado de transacao. Cada atualizacao de status eh evento append-only com numero de sequencia. A gente reconstroi estado atual a partir do log de evento. Sem sobrescrita. Trilha de auditoria completa. Se evento chega fora de ordem, resequencia baseado no timestamp do processador.
REGRA TRES: O INCIDENTE DOS R$47.000
Preciso falar sobre isso porque eh o bug mais caro que ja shipei pra producao.
A gente tinha mecanismo de retry de webhook. Quando o processador mandava confirmacao de pagamento e a gente nao confirmava (HTTP 200), eles retentavam. Normal. So que nosso handler nao checava se a transacao ja tinha sido registrada. Entao cada retry criava um NOVO registro de transacao no nosso sistema. PQP.
Numa sexta a noite lotada, nosso endpoint de webhook caiu por uns 90 segundos por causa de um deploy. O processador enfileirou retry. Quando voltou, tomamos uma rajada de webhook de retry. Cada um criou transacao duplicada. Total duplicado somando todos os merchants afetados: R$47.000.
Pegamos em duas horas por causa dos alertas de reconciliacao. Mas aquelas duas horas foram as mais longas da minha carreira. Tivemos que reverter manualmente cada duplicata, ligar pra cada merchant afetado, e explicar o que aconteceu.
O fix era CONSTRANGEDOR de simples. Checar o ID de transacao do processador contra nossos registros antes de criar entrada nova. Se existe, confirma e pula. Fix de cinco linhas pra um erro de R$47.000.
REGRA QUATRO: RECONCILIACAO EH TEU COLCHAO DE SEGURANCA
Toda noite 3h da manha a gente roda job de reconciliacao. Puxa toda transacao do nosso sistema e toda transacao do processador das ultimas 24 horas. Compara. Qualquer divergencia dispara alerta.
Isso pegou bug que mais nada ia achar. Falha silenciosa onde a gente registrou pagamento mas o processador na real recusou. Edge case onde reembolso parcial se perdeu. Bug de timezone onde transacao aparecia em dia diferente no nosso sistema vs no processador.
Reconciliacao nao eh glamourosa. Ninguem bota no LinkedIn. Mas eh a peca de infra mais importante de sistema de pagamento. Builda antes de processar tua primeira transacao real.
REGRA CINCO: NUNCA CONFIA NO CLIENT
O terminal POS manda o valor a cobrar. Nao confia. Recalcula no servidor a partir dos itens do pedido. A gente teve um incidente onde um APK modificado num terminal comprometido mandava valor menor que o total real do pedido. O merchant tava dando desconto que nao pretendia.
Calculo de valor server-side a partir da fonte de verdade (o pedido no nosso banco) eh inegociavel. Valor do terminal eh so pra exibicao.
PRA FECHAR
Sistema de pagamento nao eh dificil no sentido algoritmico. O codigo eh direto. O que torna dificil eh que cada edge case custa dinheiro real. Race condition numa plataforma de blog gera post duplicado. Race condition num sistema de pagamento faz alguem PERDER DINHEIRO. O que ta em jogo muda como tu pensa sobre cada linha de codigo.
Builda idempotencia primeiro. Locka agressivamente. Reconcilia tudo. Nao confia em nada que vem do client. E pelo amor de deus, testa teus webhook handlers com entrega duplicada antes de deployar pra producao.