Projetando um Sistema de Pagamentos em Escala: Guia Completo de System Design
Resumo: Um guia de nível produção para desenhar sistemas de pagamento robustos, cobrindo autorização, captura, ledger de dupla entrada, idempotência, antifraude, conciliação, multi-moeda, subscriptions, compliance e confiabilidade operacional.
Publicado: Fevereiro 2026
Tempo de leitura: 110 minutos
Palavras-chave: #SystemDesign #PaymentSystem #Ledger #Idempotency #FraudDetection #Reconciliation #PCI #DistributedSystems #TechInterview
Pagamentos são o ponto onde software encontra dinheiro real. Quando um sistema de feed falha, o usuário vê conteúdo errado. Quando um sistema de pagamento falha, você perde dinheiro, gera disputa, cria risco regulatório e destrói confiança.
Um checkout simples parece um clique. Por trás, você precisa coordenar:
- validação de pedido,
- tokenização de meio de pagamento,
- autorização,
- captura,
- registro contábil,
- webhooks assíncronos,
- conciliação com provedores,
- tratamento de reembolso e chargeback.
Tudo isso sob retries, timeouts, duplicação de mensagens e falhas parciais.
Este guia organiza essa complexidade em um design defensável e operável.
Sumário
- Análise de Requisitos
- Cálculos de Envelope
- Arquitetura de Alto Nível
- Design de API
- Modelagem de Dados
- Fluxo de Processamento de Pagamento (Core 1)
- Ledger de Dupla Entrada (Core 2)
- Idempotência e Exactly-Once de Negócio (Core 3)
- Integração com Gateways e Adquirentes
- Detecção de Fraude
- Assinaturas e Recorrência
- Suporte a Multi-Moeda
- Conciliação Financeira
- Segurança e Compliance
- Arquitetura de Banco de Dados
- Arquitetura Event-Driven
- Confiabilidade e Tolerância a Falhas
- Observabilidade e SLOs
- Dicas para Entrevista
- Anti-Patterns
- Conclusão
- Referências
- Referência Rápida
Análise de Requisitos
Requisitos Funcionais
- Criar intents de pagamento.
- Autorizar pagamento em gateway externo.
- Capturar pagamento (imediata ou tardia).
- Registrar todas as transações em ledger de dupla entrada.
- Reembolsar total/parcial.
- Tratar chargebacks/disputas.
- Suportar webhooks assíncronos idempotentes.
- Gerar extratos e relatórios de settlement.
- Suportar pagamentos recorrentes.
- Suportar várias moedas.
Requisitos Não-Funcionais
| Requisito | Meta | Motivo |
|---|---|---|
| Disponibilidade API de pagamento | 99,99% | impacto direto em receita |
| Latência auth | < 500ms p99 (fora lat. gateway) | UX de checkout |
| Durabilidade transacional | perda 0 após ack | confiança e auditoria |
| Integridade contábil | soma débitos = soma créditos | exigência financeira |
| Idempotência | retries seguros | rede e provedores são imperfeitos |
| Auditabilidade | trilha completa e imutável | compliance e disputas |
Perguntas de Clarificação
- Escopo inclui apenas cartão ou métodos alternativos também?
- Captura imediata ou delayed capture?
- Subscription está em escopo?
- Qual volume de transações por dia?
- Precisamos de ledger completo ou apenas registro simplificado?
Premissas do guia:
- integração com gateways externos,
- ledger completo,
- captura flexível,
- webhooks assíncronos,
- antifraude e conciliação obrigatórios.
Cálculos de Envelope
Escala Assumida
Transações/dia: 250 milhões
Valor médio: USD 32
Pico de transações/s (eventos): 25.000
Webhooks/dia: 500 milhões
Reembolsos/dia: 5 milhões
Chargebacks/dia: 300 mil
Throughput
QPS médio tx = 250.000.000 / 86.400 ~= 2.893
Pico (8x) ~= 23.144
Webhooks/s médio ~= 5.787
Pico (6x) ~= 34.722
Armazenamento
Assumindo:
- payment txn row: 1KB
- ledger entry row: 500B
- 4 lançamentos por transação em média
payment rows/dia ~= 250GB
ledger rows/dia ~= 250M * 4 * 500B ~= 500GB
com réplicas e índices => vários TB/dia
Insight
No sistema de pagamento, o gargalo principal não é CPU de API. É integridade de estado entre:
- sistema interno,
- gateway externo,
- ledger,
- settlement.
Arquitetura de Alto Nível
flowchart TB
subgraph Clientes
APP["Checkout Clients"]
MER["Merchant Backoffice"]
end
subgraph Borda
API["Payment API Gateway"]
AUTH["Auth + Rate Limit"]
end
subgraph Serviços Core
ORCH["Payment Orchestrator"]
RISK["Risk/Fraud Engine"]
TOKEN["Token Service"]
GW["Gateway Connector"]
LEDGER["Ledger Service"]
REFUND["Refund Service"]
SUBS["Subscription Service"]
RECON["Reconciliation Service"]
WEBHOOK["Webhook Handler"]
NOTI["Notification Service"]
end
subgraph Dados e Plataforma
SQL[("Transactional SQL")]
LDB[("Ledger Store")]
REDIS[("Redis")]
KAFKA[("Kafka")]
OLAP[("Analytics/Warehouse")]
end
APP --> API --> AUTH --> ORCH
MER --> API
ORCH --> RISK
ORCH --> TOKEN
ORCH --> GW
ORCH --> LEDGER
GW --> WEBHOOK
WEBHOOK --> ORCH
WEBHOOK --> LEDGER
ORCH --> REFUND
ORCH --> SUBS
ORCH --> NOTI
ORCH --> RECON
ORCH --> SQL
LEDGER --> LDB
RISK --> REDIS
ORCH --> KAFKA
LEDGER --> KAFKA
WEBHOOK --> KAFKA
KAFKA --> OLAP
Princípios
- Ledger separado de estado de orquestração.
- Idempotência de ponta a ponta.
- Eventos para desacoplamento e auditoria.
- Reconciliação contínua com fontes externas.
Design de API
Endpoints
POST /v1/payment-intents
POST /v1/payment-intents/{id}/confirm
POST /v1/payment-intents/{id}/capture
POST /v1/payment-intents/{id}/cancel
POST /v1/refunds
GET /v1/payments/{payment_id}
GET /v1/ledger/accounts/{account_id}/entries
POST /v1/webhooks/{provider}
Criar Payment Intent
{
"idempotency_key": "9b2f8d8a-78ae-4722-a8c5-88e8ed8f2ca5",
"merchant_id": "m_123",
"order_id": "ord_901",
"amount": {
"currency": "USD",
"value": 129.99
},
"payment_method": {
"type": "CARD_TOKEN",
"token": "tok_abc"
},
"capture_mode": "MANUAL",
"metadata": {
"channel": "web"
}
}
Response:
{
"payment_intent_id": "pi_887",
"status": "REQUIRES_CONFIRMATION",
"created_at": "2026-02-22T22:10:00Z"
}
Confirmar Payment Intent
{
"idempotency_key": "f9d02bc1-61be-4288-a8cd-6d9c8b4a4df0",
"payment_intent_id": "pi_887"
}
Response:
{
"payment_intent_id": "pi_887",
"payment_id": "pay_001",
"status": "AUTHORIZED",
"authorized_amount": {
"currency": "USD",
"value": 129.99
}
}
Modelagem de Dados
Entidades Principais
- PaymentIntent
- Payment
- PaymentAttempt
- LedgerAccount
- LedgerEntry
- Refund
- Dispute
- SettlementBatch
- ReconciliationRun
Tabelas Core
CREATE TABLE payment_intents (
payment_intent_id VARCHAR(64) PRIMARY KEY,
merchant_id BIGINT NOT NULL,
order_id VARCHAR(64) NOT NULL,
amount_value NUMERIC(20,6) NOT NULL,
amount_currency CHAR(3) NOT NULL,
status VARCHAR(32) NOT NULL,
capture_mode VARCHAR(16) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (merchant_id, order_id)
);
CREATE TABLE payment_attempts (
payment_attempt_id VARCHAR(64) PRIMARY KEY,
payment_intent_id VARCHAR(64) NOT NULL,
provider VARCHAR(32) NOT NULL,
provider_txn_id VARCHAR(128),
status VARCHAR(32) NOT NULL,
response_code VARCHAR(32),
response_payload JSONB,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (provider, provider_txn_id)
);
Ledger Schema
CREATE TABLE ledger_accounts (
account_id BIGSERIAL PRIMARY KEY,
account_type VARCHAR(32) NOT NULL,
currency CHAR(3) NOT NULL,
owner_ref VARCHAR(64),
status VARCHAR(16) NOT NULL,
created_at TIMESTAMP NOT NULL
);
CREATE TABLE ledger_entries (
entry_id BIGSERIAL PRIMARY KEY,
tx_id VARCHAR(64) NOT NULL,
account_id BIGINT NOT NULL REFERENCES ledger_accounts(account_id),
direction VARCHAR(6) NOT NULL CHECK (direction IN ('DEBIT','CREDIT')),
amount NUMERIC(20,6) NOT NULL,
currency CHAR(3) NOT NULL,
created_at TIMESTAMP NOT NULL
);
Regra de Integridade
Para cada tx_id:
SUM(DEBIT) == SUM(CREDIT)
Fluxo de Processamento de Pagamento (Core 1)
Etapas
- Validar request e idempotência.
- Avaliar risco.
- Enviar auth ao gateway.
- Persistir resultado local.
- Escrever lançamentos no ledger.
- Retornar status ao cliente.
Sequência Auth
sequenceDiagram
participant C as Client
participant P as Payment API
participant O as Orchestrator
participant R as Risk Engine
participant G as Gateway
participant L as Ledger
C->>P: confirm payment intent
P->>O: process request
O->>R: risk score
R-->>O: allow
O->>G: authorize
G-->>O: auth approved
O->>L: post ledger entries
L-->>O: committed
O-->>P: AUTHORIZED
P-->>C: response
Estados Típicos
REQUIRES_CONFIRMATIONPROCESSINGAUTHORIZEDCAPTUREDFAILEDCANCELEDREFUNDED
Ledger de Dupla Entrada (Core 2)
Ledger é o coração de confiabilidade financeira.
Exemplo de Transação de Captura
Para captura de USD 100 com fee USD 3:
- Débito: clearing account +100
- Crédito: merchant payable +97
- Crédito: platform fee revenue +3
Características do Ledger
- Append-only.
- Imutável (sem update/delete de entry).
- Reversão por lançamentos compensatórios.
- Índice por tx_id e account_id.
Exemplo de Postagem
{
"tx_id": "tx_445",
"entries": [
{
"account": "clearing_usd",
"direction": "DEBIT",
"amount": "100.00",
"currency": "USD"
},
{
"account": "merchant_payable_m123_usd",
"direction": "CREDIT",
"amount": "97.00",
"currency": "USD"
},
{
"account": "platform_fee_revenue_usd",
"direction": "CREDIT",
"amount": "3.00",
"currency": "USD"
}
]
}
Reversão
Reembolso não apaga transação anterior. Cria nova transação de sinal oposto.
Idempotência e Exactly-Once de Negócio (Core 3)
Rede e provedores vão duplicar requests. Idempotência é obrigatória.
Tabela
CREATE TABLE idempotency_records (
idempotency_key VARCHAR(64) NOT NULL,
merchant_id BIGINT NOT NULL,
endpoint VARCHAR(128) NOT NULL,
request_hash CHAR(64) NOT NULL,
response_code INT NOT NULL,
response_body JSONB NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
PRIMARY KEY (idempotency_key, merchant_id, endpoint)
);
Regras
- Mesmo key + mesmo hash => replay response.
- Mesmo key + hash diferente => 409.
- TTL adequado por endpoint (24h–72h).
Webhook Dedupe
async function processWebhook(provider: string, eventId: string, payload: unknown) {
const key = `${provider}:${eventId}`;
if (await dedupe.exists(key)) return;
await db.transaction(async (tx) => {
await tx.webhook_events.insert({ provider, event_id: eventId, payload });
await tx.dedupe.insert({ key, ttlHours: 72 });
});
}
Integração com Gateways e Adquirentes
Adapter Pattern
Cada provedor tem contrato próprio. Crie camada adaptadora uniforme.
Interface Interna
type GatewayAuthorizeRequest = {
amountValue: string;
currency: string;
token: string;
merchantRef: string;
idempotencyKey: string;
};
type GatewayAuthorizeResponse = {
approved: boolean;
providerTxnId?: string;
responseCode: string;
requiresAction?: boolean;
};
Routing de Gateway
- roteamento por país/moeda,
- fallback para segundo gateway,
- smart retry com limites.
Timeout Policy
- request timeout estrito,
- retries idempotentes,
- classificação de erro (transiente x definitivo).
Detecção de Fraude
Sinais
- device fingerprint,
- velocity por cartão/conta/IP,
- anomalia geográfica,
- histórico de chargeback,
- score de comerciante.
Decisão
- allow,
- challenge,
- decline,
- manual review.
Arquitetura
flowchart LR
P["Payment Request"] --> F["Feature Fetch"]
F --> S["Fraud Scorer"]
S --> D["Decision Engine"]
D --> A["ALLOW"]
D --> C["CHALLENGE"]
D --> X["DECLINE"]
Latência
Fraud no caminho crítico precisa budget de milissegundos baixos.
Assinaturas e Recorrência
Requisitos
- billing cycles,
- retry strategy para falhas,
- grace period,
- dunning workflow,
- cancelamento e prorata.
Ciclo
- scheduler gera renewal intent,
- tenta charge,
- falhou → retry policy,
- sucesso → ledger + invoice,
- expirou retries → suspende.
Retry Plan Exemplo
T+0, T+1d, T+3d, T+7d
Dados
CREATE TABLE subscriptions (
subscription_id VARCHAR(64) PRIMARY KEY,
customer_id BIGINT NOT NULL,
plan_id VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
next_billing_at TIMESTAMP NOT NULL,
retry_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
Suporte a Multi-Moeda
Desafios
- arredondamento,
- taxa de câmbio,
- ledger por moeda,
- settlement cross-border.
Regras
- Nunca misturar moedas no mesmo lançamento.
- Armazenar valores em decimal exato.
- Guardar FX rate usado na conversão.
- Separar contas contábeis por moeda.
Exemplo de Conversão
{
"source": { "currency": "EUR", "value": "10.00" },
"target": { "currency": "USD", "value": "10.83" },
"fx_rate": "1.0830",
"fx_timestamp": "2026-02-22T21:00:00Z"
}
Conciliação Financeira
Conciliação é o mecanismo que detecta divergência entre seu estado e o do provedor.
Tipos
- intraday automática,
- diária completa,
- conciliação de chargeback/refund,
- conciliação de payout.
Pipeline
- importar arquivo/API de settlement,
- normalizar para schema interno,
- comparar com pagamentos/ledger,
- classificar discrepâncias,
- abrir casos de remediação.
Resultado de Conciliação
{
"run_id": "recon_2026_02_22",
"provider": "gateway_x",
"matched": 24900123,
"missing_internal": 123,
"missing_provider": 77,
"amount_mismatch": 44,
"status": "COMPLETED"
}
SLA
Sem conciliação, incidente financeiro pode ficar oculto por dias.
Segurança e Compliance
Segurança
- TLS e mTLS interno em domínios sensíveis.
- tokenização de cartão.
- HSM/KMS para chaves criptográficas.
- segregação de ambientes e segredos.
- auditoria de acesso privilegiado.
PCI DSS
Objetivo prático:
- minimizar escopo PCI,
- evitar armazenar PAN bruto,
- usar provedores/token vault certificados.
Privacidade
- minimização de PII,
- retenção por requisito legal,
- anonimização quando possível,
- trilha de consentimento.
Arquitetura de Banco de Dados
Mapeamento por Domínios
| Domínio | Store |
|---|---|
| intents/payments | SQL transacional |
| ledger | SQL append-only ou store contábil dedicado |
| idempotency/dedupe | SQL ou KV forte |
| risk cache | Redis |
| analytics | OLAP/Warehouse |
Sharding
- por merchant_id para isolamento,
- ou hash(payment_id) para distribuição uniforme,
- com índices secundários para consultas operacionais.
Evolução de Schema
- adicionar campo opcional,
- backfill,
- dual-read,
- switch writer,
- remover legado.
Arquitetura Event-Driven
Eventos Core
PaymentIntentCreatedPaymentAuthorizedPaymentCapturedPaymentFailedRefundIssuedChargebackOpenedChargebackResolvedSettlementImportedReconciliationCompleted
Diagrama
flowchart TB
ORCH["Orchestrator"] --> BUS["Kafka"]
LED["Ledger"] --> BUS
WEB["Webhook Handler"] --> BUS
REF["Refund Service"] --> BUS
BUS --> REC["Reconciliation"]
BUS --> NOTI["Notifications"]
BUS --> DWH["Analytics"]
BUS --> AUD["Audit Pipeline"]
Outbox
Garantir publish confiável junto da transação local.
CREATE TABLE outbox_events (
event_id UUID PRIMARY KEY,
aggregate_type VARCHAR(32) NOT NULL,
aggregate_id VARCHAR(64) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP NOT NULL,
published_at TIMESTAMP
);
Confiabilidade e Tolerância a Falhas
Falhas Clássicas
- timeout no gateway com status desconhecido,
- webhook duplicado/atrasado,
- ledger indisponível,
- fila com lag,
- erro de conciliação.
Estratégias
- idempotência em todas as mutações,
- timeouts + retries com jitter,
- circuit breakers,
- fallback para estado
PENDING_EXTERNAL_CONFIRMATION, - replay de eventos.
Estado "Desconhecido"
Quando timeout no gateway sem resposta conclusiva:
- marcar tentativa como
UNKNOWN, - consultar status assíncrono,
- bloquear duplicidade via idempotency,
- atualizar estado final após confirmação.
SLA de Recovery
- caminhos de autorização/captura devem recuperar rápido,
- processos de conciliação podem ter janelas maiores,
- sem perder audit trail.
Observabilidade e SLOs
SLOs
| Serviço | SLI | SLO |
|---|---|---|
| Payment confirm | p99 latência | < 500ms (sem rede externa) |
| Auth success | taxa de sucesso | dentro da baseline por método |
| Webhook processing | atraso p95 | < 30s |
| Ledger posting | durabilidade | perda 0 |
| Reconciliation | execução diária | 100% runs concluídas |
Golden Signals
Orchestrator:
- success/failure rate,
- timeout externo,
- idempotency replay,
- latency p95/p99.
Ledger:
- post failures,
- imbalance detector,
- write latency.
Webhook:
- dedupe hit rate,
- processing lag,
- invalid signature rate.
Correlação
x-request-id
x-payment-intent-id
x-payment-attempt-id
x-ledger-tx-id
x-provider-txn-id
Dicas para Entrevista
Estrutura de Resposta
- Defina escopo (auth/capture/refund/ledger).
- Faça estimativa de volume.
- Desenhe arquitetura macro.
- Deep dive em idempotência e ledger.
- Explique webhooks e conciliação.
- Feche com compliance e SLOs.
Erros Comuns
- ignorar ledger de dupla entrada,
- tratar retry sem idempotência,
- sem plano para status desconhecido,
- sem conciliação,
- sem separação entre estado de negócio e estado externo.
Tabela de Trade-offs
| Decisão | Opção A | Opção B | Recomendado |
|---|---|---|---|
| registro financeiro | saldo mutável | ledger append-only | append-only |
| retries | sem chave | idempotência forte | idempotência forte |
| webhook | best effort | dedupe + assinatura + replay | dedupe + replay |
| gateway | único provedor | multi-provider | multi-provider (complexidade controlada) |
| conciliação | manual eventual | pipeline automatizada | automatizada |
Anti-Patterns
1) Atualizar saldo direto sem ledger
Problema: sem trilha auditável e alta chance de inconsistência.
Correção: dupla entrada append-only.
2) Retry sem idempotency key
Problema: cobrança duplicada.
Correção: chave obrigatória por mutação.
3) Confiar cegamente no status síncrono
Problema: timeout gera incerteza e duplicidade.
Correção: estado pendente + confirmação assíncrona.
4) Ignorar webhooks duplicados
Problema: transições repetidas e estado corrompido.
Correção: dedupe por provider event id.