Implementação de referência production-grade para Pix Automático + Open Finance Brasil
Backend Spring Boot 4.0 + Java 21 · Frontend React 18 + TypeScript + Tailwind · Empacotados como artefato único
Hexagonal · Saga · Outbox · Idempotência forte · OpenTelemetry
TL;DR · Esta é a implementação que você procuraria se sua fintech precisasse integrar com o Pix Automático amanhã e não pudesse errar. Hexagonal, com idempotência forte, outbox transacional, saga orquestrada, observabilidade end-to-end, painel operacional React e testes que cobrem do agregado até o webhook. Roda 100% local com
make up && make run.
- Por que existe
- Capacidades
- Painel operacional (frontend)
- Arquitetura
- Stack
- Quickstart
- Fluxo Pix Automático
- Endpoints
- Estrutura do projeto
- Estratégia de testes
- Observabilidade
- ADRs
- Roadmap
- Como contribuir
- Segurança
- Licença
- Autor
O Pix Automático (lançado em out/2025) e a Fase 4 do Open Finance Brasil tornaram obrigatória a adequação de sistemas de cobrança recorrente, assinaturas, utilities e fintechs a um conjunto de contratos técnicos não triviais definidos pelo Banco Central. Integrações mal-feitas resultam em cobranças duplicadas, perda de conciliação, multas regulatórias e churn.
Este repositório demonstra, em código real e executável, os padrões que uma integração correta, auditável e resiliente exige — sem atalhos.
| Categoria | O que está implementado |
|---|---|
| Domínio | Agregados Consent, Subscription, Charge com domain events e invariantes do BCB |
| Idempotência | Idempotency-Key (UUIDv4) + Redis + unique constraint em DB — defesa em profundidade |
| Consistência | Outbox Pattern: transação local atômica + publisher Kafka assíncrono |
| Orquestração | Saga orquestrada: autorizar → agendar → iniciar → conciliar com compensação |
| Resiliência | Resilience4j: circuit breaker, retry, rate limiter, bulkhead — por endpoint |
| Segurança | Spring Security + OAuth2 Client Credentials simulando mTLS / FAPI para BCB |
| Observabilidade | OpenTelemetry traces + Prometheus metrics + Loki logs estruturados, correlacionados por traceId |
| Testes | Unitário (domínio puro), integração (Testcontainers), contrato (Pact), arquitetura (ArchUnit) |
| Migrations | Flyway versionado |
| Contratos | OpenAPI 3 autogerado (SpringDoc) |
| Decisões | 5 ADRs documentando cada escolha arquitetural não trivial |
| Painel operacional | SPA React + TypeScript + shadcn/ui — Dashboard, CRUD de Consents, agendamento de Charges, timeline de saga |
SPA em React 18 + TypeScript empacotada dentro do mesmo JAR do backend — make run sobe API + UI no mesmo :8080. Não tem deploy separado, não tem CORS, não tem dois contextos pra debugar.
| Página | O que mostra |
|---|---|
| Dashboard | KPIs (consents/subscriptions/charges totais, taxa de sucesso), feed das últimas 5 cobranças, indicadores de saúde da plataforma |
| Consents | Tabela paginada, criar consent, autorizar, revogar (com motivo), drill-down no detalhe |
| Subscriptions | Tabela vinculada a consents autorizados, criar subscription |
| Charges | Tabela com auto-refresh a cada 5s (acompanha saga ao vivo), agendar cobrança |
| Charge detail | Timeline visual SCHEDULED → INITIATED → SETTLED/FAILED com erro do BCB se houver |
| Webhooks | Documentação do contrato POST /webhooks/bcb/charge-status + atalhos pra Kafka UI / Grafana |
Stack: Vite (build), React 18 + TypeScript, Tailwind CSS + shadcn/ui (Radix primitives), TanStack Query (cache + refetch), React Router.
Comandos:
make web-dev # Vite dev server em :5173 com proxy pra :8080 (hot reload)
make web-build # builda só o frontend pra src/main/resources/static/
make run # build full + roda app (API + UI bundled em :8080)
make run-fast # roda só backend, pula build do frontendArquitetura hexagonal (Ports & Adapters). O domínio é puro: nem Spring, nem Jakarta, nem JPA. ArchUnit valida as fronteiras no CI.
flowchart LR
subgraph In[" "]
direction TB
WEB["adapter.web<br/>REST controllers"]
WHK["adapter.web<br/>BCB webhooks"]
end
subgraph App["application"]
direction TB
UC["Use cases"]
SAGA["Charge Saga"]
end
subgraph Dom["domain (puro)"]
direction TB
AGG["Aggregates<br/>Consent · Subscription · Charge"]
EVT["Domain Events"]
end
subgraph Out["infrastructure"]
direction TB
DB[("PostgreSQL<br/>JPA / Flyway")]
RDS[("Redis<br/>Idempotency")]
KFK[["Kafka<br/>Outbox publisher"]]
BCB(["BCB API<br/>OAuth2 / FAPI"])
end
WEB --> UC
WHK --> UC
UC --> AGG
UC --> SAGA
SAGA --> AGG
AGG -.eventos.-> EVT
UC --> DB
UC --> RDS
EVT --> KFK
SAGA --> BCB
classDef domain fill:#fff5d6,stroke:#b08800,color:#000
classDef app fill:#dff5e1,stroke:#2e7d32,color:#000
classDef adapter fill:#e3f0ff,stroke:#1565c0,color:#000
classDef infra fill:#fde2e2,stroke:#b71c1c,color:#000
class AGG,EVT domain
class UC,SAGA app
class WEB,WHK adapter
class DB,RDS,KFK,BCB infra
Diagramas C4 detalhados em docs/architecture/.
| Camada | Tecnologia |
|---|---|
| Runtime | Java 21 (virtual threads) |
| App framework | Spring Boot 4.0 + Spring Web MVC |
| Persistência | PostgreSQL 16 + Spring Data JPA + Flyway |
| Cache / Idempotência | Redis 7 |
| Mensageria | Apache Kafka 3.8 (KRaft, sem ZooKeeper) |
| Resiliência | Resilience4j 2.4 |
| Tracing | OpenTelemetry → Tempo |
| Métricas | Micrometer → Prometheus → Grafana |
| Logs | Logback JSON estruturado → Loki |
| Segurança | Spring Security + OAuth2 client/resource server |
| API docs | SpringDoc OpenAPI 3 |
| Testes | JUnit 5 · Testcontainers · Pact · ArchUnit · JaCoCo |
| Build | Maven 3.9 (wrapper) + frontend-maven-plugin (npm via Maven) |
| Container | Buildpacks (Spring Boot OCI image) |
| Frontend | Vite + React 18 + TypeScript + Tailwind + shadcn/ui + TanStack Query |
| CI/CD | GitHub Actions · CodeQL · Semgrep · Trivy · Dependabot |
Pré-requisitos: JDK 21, Docker Desktop, ~4 GB de RAM livre. Node 22 só é necessário se for desenvolver o frontend separado (make web-dev); o build pelo Maven baixa o Node automaticamente.
# 1. Clonar
git clone https://github.com/Paulo-Marcos-Lucio/pix-automatico-reference.git
cd pix-automatico-reference
# 2. Sobe Postgres + Redis + Kafka + stack de observabilidade
make up
# 3. Roda a app (perfil local) — builda backend + frontend e empacota tudo
make run
# 4. Abre o painel
open http://localhost:8080 # ou xdg-open / start, conforme OSAcessos:
| Serviço | URL | Notas |
|---|---|---|
| Painel operacional | http://localhost:8080 | SPA bundled — Dashboard, CRUD de consents/charges, timeline da saga |
| API REST | http://localhost:8080/v1/* | mesma origem do painel |
| OpenAPI UI | http://localhost:8080/swagger-ui.html | contratos vivos |
| Actuator | http://localhost:8080/actuator | health, metrics, info |
| Grafana | http://localhost:3000 | login anônimo (admin) |
| Prometheus | http://localhost:9090 | |
| Tempo (traces) | http://localhost:3200 | acessar via Grafana |
| Kafka UI | http://localhost:8089 |
Targets úteis do Makefile:
make up # docker-compose up -d
make down # derruba (preserva volumes)
make test # mvn test (unit)
make it # mvn verify (unit + integration via Testcontainers)
make image # build OCI image via Buildpacks
make clean # mvn clean + docker volumes pruneResumido — detalhe completo em docs/flows/charge-saga.md.
sequenceDiagram
autonumber
actor C as Cliente (devedor)
participant App as Sua plataforma<br/>(este repo)
participant DB as Postgres
participant K as Kafka
participant BC as BCB / OF API
C->>App: autoriza recorrência
App->>BC: POST /consents
BC-->>App: consentId AUTHORIZED
App->>DB: persist Consent + outbox event (tx)
App-->>K: publish ConsentAuthorized (async)
Note over App: agenda Subscription
loop Cada ciclo de cobrança
App->>DB: persist Charge SCHEDULED
App->>BC: POST /charges (idempotência)
BC-->>App: status + endToEndId
App->>DB: update Charge INITIATED
BC-->>App: webhook charge-status
App->>DB: reconcile + update SETTLED
App-->>K: publish ChargeSettled
end
Garantias do fluxo:
- ✅ Cobrança duplicada é matemática-impossível (idempotência em 3 camadas)
- ✅ Falha entre
update DBepublish Kafkaé resolvida pelo Outbox (eventual consistency) - ✅ Webhook reentrante: mesmo
endToEndIdchegando 2× não causa double-credit - ✅ Saga compensa ao falhar entre etapas (rollback de side-effects parciais)
| Método | Path | Descrição | Idempotente |
|---|---|---|---|
POST |
/v1/consents |
Cria Consent de recorrência | ✅ |
GET |
/v1/consents/{id} |
Consulta Consent | — |
POST |
/v1/consents/{id}/revoke |
Revoga Consent | ✅ |
POST |
/v1/subscriptions |
Cria Subscription vinculada a um Consent | ✅ |
POST |
/v1/charges |
Agenda/inicia Charge | ✅ |
GET |
/v1/charges/{id} |
Consulta Charge | — |
POST |
/webhooks/bcb/charge-status |
Recebe notificação do BCB | ✅ |
Todos os
POSTexigem headerIdempotency-Key: <UUIDv4>. Reuso da mesma key com payload divergente retorna422 Unprocessable Entity.
Exemplos prontos em requests.http (compatível com IntelliJ HTTP Client e VS Code REST Client).
pix-automatico-reference/
├── src/main/java/dev/pmlsp/pixauto/
│ ├── domain/ # puro: entidades, agregados, eventos, ports
│ │ ├── model/ # Consent, Subscription, Charge, value objects
│ │ ├── port/in/ # use case interfaces
│ │ ├── port/out/ # repositories, gateways, stores
│ │ └── exception/
│ ├── application/ # casos de uso, saga, services
│ ├── infrastructure/ # adapters de saída (JPA, Kafka, Redis, BCB HTTP)
│ │ ├── persistence/
│ │ ├── messaging/
│ │ ├── cache/
│ │ └── config/ # WebMvcConfig (SPA fallback), Security, Jackson...
│ └── adapter/web/ # controllers, DTOs, exception handlers
├── src/main/resources/
│ ├── application.yml
│ ├── db/migration/ # Flyway
│ └── static/ # gerado pelo build do frontend (gitignored)
├── src/test/java/.../
│ ├── domain/ # testes unitários puros
│ ├── application/ # casos de uso com ports mockados
│ ├── architecture/ # ArchUnit
│ └── integration/ # Testcontainers (Postgres + Redis + Kafka)
├── web/ # frontend React + TS + Tailwind
│ ├── src/
│ │ ├── components/ # ui/* (shadcn primitives), layout/*
│ │ ├── pages/ # Dashboard, Consents, Subscriptions, Charges, Webhooks
│ │ ├── hooks/ # TanStack Query bindings
│ │ └── lib/ # api client, types, query client, utils
│ ├── package.json
│ └── vite.config.ts # outDir aponta pra ../src/main/resources/static
├── config/ # configs do otel-collector, prometheus, grafana, etc
├── docs/
│ ├── adr/ # decisões arquiteturais
│ ├── architecture/ # C4 (context, container, component)
│ └── flows/ # diagramas de fluxos críticos
├── docker-compose.yml # stack completa de apoio
├── Makefile # DX: up/down/test/it/run/image/web-dev/web-build
└── pom.xml # frontend-maven-plugin builda o web/ na fase generate-resources
| Tipo | Onde | Ferramentas | O que valida |
|---|---|---|---|
| Unitário | domain/, application/ |
JUnit 5 + AssertJ | Regras de negócio do BCB, invariantes de agregados |
| Integração | integration/ |
Testcontainers (Postgres + Redis + Kafka) | Idempotência real, outbox, persistence, saga end-to-end |
| Arquitetura | architecture/ |
ArchUnit | Camadas hexagonais não vazam (domínio sem Spring etc.) |
| Contrato | pact/ |
Pact JVM | Compatibilidade do contrato consumido do BCB |
| Cobertura | — | JaCoCo | Reporta no PR via Codecov |
Regra de ouro: integração nunca usa mock para banco/Kafka/Redis — sempre Testcontainers. Mock em testes de I/O é dívida técnica disfarçada de teste rápido.
Tudo correlacionado por traceId propagado via W3C Trace Context.
flowchart LR
APP[App Spring Boot]
OTEL[OTel Collector]
PROM[(Prometheus)]
TEMPO[(Tempo)]
LOKI[(Loki)]
GRAF[Grafana]
APP -- OTLP traces --> OTEL
APP -- /actuator/prometheus --> PROM
APP -- JSON logs --> LOKI
OTEL --> TEMPO
PROM --> GRAF
TEMPO --> GRAF
LOKI --> GRAF
classDef store fill:#f0f4ff,stroke:#3949ab
class PROM,TEMPO,LOKI store
Cada request HTTP, evento Kafka, query JPA e chamada externa gera um span com tags consistentes (http.method, messaging.kafka.topic, db.statement). Métricas expostas via Micrometer (latência p50/p95/p99, taxa de erro, circuit breaker state). Logs JSON com traceId, spanId, idempotencyKey e consentId — joinable com traces no Grafana.
Toda decisão arquitetural não trivial é documentada como ADR (Architecture Decision Record).
| # | Decisão |
|---|---|
| 0001 | Arquitetura hexagonal (Ports & Adapters) |
| 0002 | Estratégia de idempotência em 3 camadas |
| 0003 | Outbox Pattern para consistência eventual |
| 0004 | Saga orquestrada vs coreografada |
| 0005 | Observabilidade e correlação por traceId |
A v0.2.0 ativa um perfil fapi que protege os endpoints /v1/consents/**, /v1/subscriptions/** e /v1/charges/** com DPoP sender-constrained access tokens — o padrão que o profile FAPI 2.0 do Open Finance Brasil herdará pros fluxos de Pix Automático recorrente.
SPRING_PROFILES_ACTIVE=local,fapi make runWire-protocol esperado pelo receiver (PISP):
POST /mock-auth/token # auth server in-process
DPoP: <proof JWT assinado pela chave do receiver>
X-Client-Id: demo-receiver
→ { "access_token": "...", "token_type": "DPoP", "expires_in": 600, "scope": "recurring-payments" }
POST /v1/consents
Authorization: DPoP <access_token> # carrega cnf.jkt = thumbprint da chave do receiver
DPoP: <proof JWT fresco pra esta requisição>
Idempotency-Key: <uuid v4>
{ ...payload do consent... }
→ 201 Created (ou 4xx por business; 401 só se DPoP falha)11 testes cobrem o validador isoladamente (DPoPValidatorTest × 8) e o flow E2E ponta-a-ponta (FapiE2EIT × 3 — token issuance, request sem headers retorna 401 + WWW-Authenticate: DPoP, replay attack com chave diferente retorna 401 invalid_token).
A chain FAPI declara @Order(0) e securityMatcher apenas sobre os 3 paths OF — paths fora (actuator, webhooks BCB, frontend SPA) seguem fluindo pela chain permissiva existente. Roubo de token + replay de outra máquina = 401 garantido pelo cnf.jkt mismatch.
| Categoria | Tests |
|---|---|
| Unit (incl. ArchUnit + 8 DPoPValidatorTest) | 38 |
| Integration (Testcontainers + 3 FapiE2EIT) | 10 |
| Total | 48 |
JaCoCo coverage report gerado em target/site/jacoco/index.html após ./mvnw verify.
API Documentation (live com a app rodando em http://localhost:8080):
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI 3 spec (JSON): http://localhost:8080/v3/api-docs
- Geração offline do spec:
./mvnw spring-boot:run # em outro terminal curl http://localhost:8080/v3/api-docs > docs/openapi.json
- v0.2.0 — FAPI 2.0 + DPoP RFC 9449 sender-constrained tokens · mock auth in-process · 11 testes (8 unit + 3 IT)
- v0.3.0 —
private_key_jwt(RFC 7523) + DCR (Dynamic Client Registration) + PAR (RFC 9126) - Suporte a DICT (consulta de chave Pix antes de cobrar)
- Conciliação batch com extrato BCB diário
- Multi-tenant via
tenantIdpropagado por toda a stack - Chaos testing com Toxiproxy nos Testcontainers
- Helm chart para deploy em Kubernetes
- Dashboards Grafana versionados como código (Jsonnet)
- Performance benchmark (p99 < 200ms para
POST /charges) - Migração para Java 25 (plano em
.github/java-upgrade/)
Veja issues abertas para o detalhe.
PRs muito bem-vindas. Leia CONTRIBUTING.md — em resumo:
- Conventional Commits
- Testes obrigatórios para mudanças funcionais
- ADR para decisões não triviais
- ArchUnit não pode regredir
Para reportar vulnerabilidade, não abra issue pública. Use Security Advisories. Detalhes em SECURITY.md.
MIT © 2026 Paulo SP.
Construído por Paulo SP — @Paulo-Marcos-Lucio no GitHub, LinkedIn — como referência pública para consultoria em integrações regulatórias (Pix Automático, Open Finance, Open Insurance).
Sua empresa precisa de revisão de arquitetura ou implementação de integração com Pix Automático ou Open Finance? Me chama no LinkedIn ou abre uma issue de contato.
Se este repositório te ajudou, deixa uma ⭐ — ajuda outros engenheiros a encontrarem.