Fase 03 — Product Delivery
HITLs #7-#10 Aprovados Architect + Back End + Front End + QA

Fase 03 — Product Delivery

Arquitetura, ADRs, API, banco de dados, regras de negócio, segurança e QA.

6
Bounded Contexts
6
ADRs
29
Endpoints
10
Tabelas
8
Páginas Admin
82/100
QA Score

Arquitetura — Provider Pattern

O ECP Pay usa o Provider Pattern para abstrair gateways de pagamento. Apps do ecossistema chamam uma única API — a factory seleciona o adapter ativo (Internal ou Asaas) via feature flag, sem que os apps saibam qual está ativo.

graph TD
    A["ecp-bank :3333"] -->|X-API-Key| B["ECP Pay API :3335"]
    C["ecp-emps :3334"] -->|X-API-Key| B
    D["ecp-food :3000"] -->|X-API-Key| B
    B --> E["Payment Service"]
    E --> F["Provider Factory"]
    F -->|"feature flag"| G["Internal Adapter"]
    F -->|"feature flag"| H["Asaas Adapter"]
    G --> I[("SQLite")]
    H --> J["Asaas API"]
    B --> K["Admin Panel :5176"]
      

Bounded Contexts

Payment Processing
Core domain: processa Pix, Card e Boleto via camada de abstração de provider. Aggregates: Transaction, Refund.
Provider Abstraction
Provider Pattern — abstrai simulação interna vs. gateway externo (Asaas). Switchable via feature flag em runtime.
Card Vault
Armazenamento tokenizado de cartões. Nunca persiste números completos. Tokens reutilizáveis cross-app.
Split Engine
Lógica de split de pagamento para transações multi-parte (plataforma + vendedor + entrega).
Webhook & Callback
Processamento de webhooks inbound (Asaas) e delivery de callbacks outbound (apps) com retry e backoff.
Admin Panel
Subdomínio de suporte: dashboard, gestão de transações, config de providers, feature flags, audit logs.
architecture-output.json

Architecture Decision Records (ADRs)

ADR-001
Provider Pattern para abstração de gateway de pagamento
Aceito
Contexto: Ecossistema precisa funcionar offline (dev/staging) e com gateways reais (produção). Apps não devem saber qual gateway está ativo.
Decisão: Interface PaymentProvider com dois adapters (InternalAdapter, AsaasAdapter). Factory seleciona via feature flag PAYMENT_PROVIDER, switchable em runtime via admin panel.
Consequências: Adicionar novo gateway = implementar um adapter. Zero mudanças nos apps consumidores.
ADR-002
SQLite como banco de dados embedded single-file
Aceito
Contexto: ECP Pay é um serviço monorepo para dev local, staging e produção de pequena escala. Precisa de banco zero-config.
Decisão: better-sqlite3 com WAL mode. Arquivo único database-pay.sqlite. Migrations rodam no startup.
Consequências: Sem servidor de DB externo. Um npm install e o serviço roda. Trade-off: não adequado para deploy horizontal de alta escala.
ADR-003
Autenticação dual: API Key para apps, JWT para admin
Aceito
Contexto: Dois grupos de usuários distintos: apps do ecossistema (machine-to-machine) e humanos admin (web panel).
Decisão: Apps autenticam via X-API-Key. Admin via JWT emitido no login. Middlewares separados.
Consequências: Separação limpa de concerns. Apps nunca precisam de credenciais de usuário.
ADR-004
Idempotência via chave única por transação
Aceito
Contexto: Falhas de rede podem causar requests de pagamento duplicados. Deve garantir que pagamento é criado exatamente uma vez.
Decisão: Header X-Idempotency-Key obrigatório em todos os endpoints de mutação. Armazenado como UNIQUE na tabela transactions. Duplicata retorna resultado existente.
Consequências: Retries seguros para todos os apps. UNIQUE constraint previne double-charging no nível do banco.
ADR-005
Dados de cartão nunca persistidos — cofre token-only
Aceito
Contexto: Compliance PCI e segurança: números de cartão e CVVs não devem ser armazenados.
Decisão: Apenas token + last4 + brand + holder_name na tabela card_tokens. Dados raw passados ao provider e descartados imediatamente.
Consequências: Seguro by design. Token reutilizável cross-app. Trade-off: não pode exibir número completo do cartão.
ADR-006
Deduplicação de webhook via event_id
Aceito
Contexto: Providers externos (Asaas) podem reenviar webhooks, causando processamento duplicado.
Decisão: event_id como UNIQUE na tabela webhook_events. Verifica antes de processar. Se duplicata, retorna 200 sem reprocessar.
Consequências: Handling idempotente de webhooks. Sem mudanças de status ou callbacks duplicados.

Diagramas de Sequência

Pagamento Pix — Fluxo Completo (Internal Provider)

sequenceDiagram
    participant App as App (ecp-food)
    participant Pay as ECP Pay API :3335
    participant Svc as Payment Service
    participant Prov as Internal Adapter
    participant DB as SQLite

    App->>Pay: POST /pay/pix
    Note over Pay: Headers: X-API-Key, X-Idempotency-Key

    Pay->>Pay: Validar API Key (app_registrations)
    Pay->>DB: SELECT WHERE idempotency_key = ?
    DB-->>Pay: null (novo)

    Pay->>Svc: createPixPayment(data)
    Svc->>Prov: ProviderFactory.get("internal")
    Prov->>Prov: Gerar QR Code + copia-e-cola

    Note over Svc,DB: Transação atômica
    Svc->>DB: INSERT transactions (status: pending)
    Svc->>DB: INSERT splits (se definido)
    DB-->>Svc: transactionId

    Prov->>Prov: Delay configurável (3-5s)
    Prov->>DB: UPDATE transactions SET status = paid

    Svc->>App: POST callback_url (status: paid)
    Note over App: Webhook delivery com retry

    Pay-->>App: { id, status: pending, qrCode, copyPaste }
      

Pagamento Cartão — Provider Asaas (External)

sequenceDiagram
    participant App as App (ecp-bank)
    participant Pay as ECP Pay API
    participant Svc as Payment Service
    participant Prov as Asaas Adapter
    participant Asaas as Asaas API
    participant DB as SQLite

    App->>Pay: POST /pay/card
    Note over Pay: { amount, card: {number, cvv, ...}, customer }

    Pay->>Pay: Validar API Key + Idempotency
    Pay->>Svc: createCardPayment(data)
    Svc->>Prov: ProviderFactory.get("external")

    Prov->>DB: SELECT card_tokens WHERE customer_document
    alt Cartão novo
        Prov->>Asaas: POST /payments (card data)
        Asaas-->>Prov: { id, status, token }
        Prov->>DB: INSERT card_tokens (token, last4, brand)
    else Cartão tokenizado
        Prov->>Asaas: POST /payments (token)
        Asaas-->>Prov: { id, status }
    end

    Svc->>DB: INSERT transactions (status: pending)

    Note over Asaas,Pay: Webhook assíncrono
    Asaas->>Pay: POST /pay/webhooks/asaas
    Pay->>DB: INSERT webhook_events (dedup event_id)
    Pay->>DB: UPDATE transactions SET status = paid
    Pay->>App: POST callback_url (status: paid)
      

Switch de Provider — Internal ↔ External

sequenceDiagram
    participant Admin as Admin (Browser)
    participant Panel as Admin Panel :5176
    participant API as Admin API
    participant DB as SQLite

    Admin->>Panel: Abre página Providers
    Panel->>API: GET /admin/providers
    API->>DB: SELECT feature_flags WHERE key = PAYMENT_PROVIDER
    DB-->>API: { value: "internal" }
    API-->>Panel: { active: "internal", available: ["internal", "external"] }
    Panel-->>Admin: Two-card com toggle (Internal ativo)

    Admin->>Panel: Clica "Ativar External"
    Panel-->>Admin: Modal de confirmação
    Admin->>Panel: Confirma switch

    Panel->>API: POST /admin/providers/switch
    Note over API: Middleware: requireRole("admin")
    API->>DB: UPDATE feature_flags SET value = "external"
    API->>DB: INSERT audit_logs (action: provider_switch)
    API-->>Panel: { active: "external", switchedAt }

    Panel-->>Admin: Banner atualizado + toast de confirmação
    Note over Admin: Todas as novas transações usam Asaas
      

Webhook Delivery — Retry com Backoff Exponencial

sequenceDiagram
    participant Pay as ECP Pay
    participant DB as SQLite
    participant App as App callback_url

    Note over Pay,App: Status de transação mudou

    Pay->>DB: SELECT callback_url FROM transactions
    Pay->>App: POST callback_url (attempt 1)

    alt Sucesso (2xx)
        App-->>Pay: 200 OK
        Pay->>DB: UPDATE: delivery_status = delivered
    else Falha (timeout/5xx)
        App-->>Pay: 500 / timeout

        Note over Pay: Retry 1: aguarda 30s
        Pay->>App: POST callback_url (attempt 2)
        App-->>Pay: 500 / timeout

        Note over Pay: Retry 2: aguarda 2min
        Pay->>App: POST callback_url (attempt 3)
        App-->>Pay: 500 / timeout

        Note over Pay: Retry 3: aguarda 10min
        Pay->>App: POST callback_url (attempt 4)

        alt Sucesso
            App-->>Pay: 200 OK
            Pay->>DB: UPDATE: delivery_status = delivered
        else Falha final
            Pay->>DB: UPDATE: delivery_status = failed
            Note over Pay: Admin pode fazer retry manual
        end
    end
      

API Endpoints

Payment API (app-facing) — /pay

Autenticação via X-API-Key header. 9 endpoints.

POST/pay/pix — Criar cobrança Pix (QR code + copia-e-cola)
POST/pay/card — Cobrar cartão de crédito (novo ou tokenizado)
POST/pay/boleto — Emitir boleto com barcode + Pix QR
GET/pay/transactions/:id — Status da transação
POST/pay/transactions/:id/refund — Estorno total ou parcial
GET/pay/cards/:customer_document — Listar cartões salvos
DELETE/pay/cards/tokens/:token_id — Remover token de cartão
POST/pay/webhooks/asaas — Receber webhook do Asaas
GET/pay/health — Health check + provider ativo

Admin API (web panel) — /admin

Autenticação via JWT Bearer token. 20 endpoints.

POST/admin/auth/login — Login admin
GET/admin/auth/me — Usuário admin atual
GET/admin/dashboard — KPIs e dados agregados
GET/admin/transactions — Lista paginada de transações
GET/admin/transactions/summary — Resumo por tipo/app/período
GET/admin/transactions/:id — Detalhe da transação
POST/admin/transactions/:id/simulate-payment — Simular pagamento (internal)
GET/admin/providers — Info do provider ativo
POST/admin/providers/switch — Alternar internal/external
GET/admin/feature-flags — Listar feature flags
PATCH/admin/feature-flags/:key — Atualizar feature flag
GET/admin/config — Configuração geral
PATCH/admin/config — Atualizar configuração
GET/admin/apps — Apps registrados
POST/admin/apps — Registrar novo app
PATCH/admin/apps/:id — Atualizar config do app
GET/admin/webhooks — Listar webhooks
POST/admin/webhooks/:id/retry — Retry manual de webhook
GET/admin/audit-logs — Logs de auditoria
GET/admin/splits — Listar splits de transações
architecture-output.json

Schema do Banco de Dados

10 tabelas + 16 índices. SQLite com WAL mode via better-sqlite3. Migration: 001-initial.sql.

TabelaDescriçãoColunas-Chave
transactionsRegistro central de todas as transações (Pix, Card, Boleto)id, type, status, amount, source_app, idempotency_key, provider, callback_url
refundsEstornos totais ou parciais vinculados a transaçõesid, transaction_id, amount, status, reason
card_tokensCofre de tokens (nunca dados raw). Cross-app via customer_documentid, customer_document, token, last4, brand, holder_name, is_active
splitsRegras de split por transação (plataforma, vendedor, entrega)id, transaction_id, account_id, amount, type
webhook_eventsWebhooks recebidos do provider (Asaas). Dedup via event_id UNIQUEid, event_id, event_type, payload, processed, transaction_id
app_registrationsApps do ecossistema registrados com API key e callback URLid, app_name, api_key, callback_base_url, is_active
admin_usersUsuários do painel admin com roles (admin, operator, viewer)id, name, email, password_hash, role
feature_flagsFeature flags editáveis em runtime (PAYMENT_PROVIDER, etc.)id, key, value, description
audit_logsLog de auditoria de todas as ações administrativasid, user_id, action, resource, metadata, ip_address, timestamp
configConfigurações gerais do sistema (simulation delay, etc.)id, key, value, description

Diagrama Entidade-Relacionamento

erDiagram
    transactions {
        TEXT id PK
        TEXT type "pix|card|boleto"
        TEXT status "pending|paid|failed|refunded|cancelled"
        INTEGER amount_cents
        TEXT source_app FK
        TEXT idempotency_key UK
        TEXT provider "internal|asaas"
        TEXT external_id
        TEXT callback_url
        TEXT delivery_status
        TEXT customer_name
        TEXT customer_document
        TEXT metadata
        TEXT created_at
    }

    refunds {
        TEXT id PK
        TEXT transaction_id FK
        INTEGER amount_cents
        TEXT status
        TEXT reason
        TEXT created_at
    }

    card_tokens {
        TEXT id PK
        TEXT customer_document
        TEXT token UK
        TEXT last4
        TEXT brand
        TEXT holder_name
        INTEGER is_active
    }

    splits {
        TEXT id PK
        TEXT transaction_id FK
        TEXT account_id
        INTEGER amount_cents
        TEXT type "platform|seller|delivery"
    }

    webhook_events {
        TEXT id PK
        TEXT event_id UK
        TEXT event_type
        TEXT payload
        INTEGER processed
        TEXT transaction_id FK
        TEXT created_at
    }

    app_registrations {
        TEXT id PK
        TEXT app_name UK
        TEXT api_key UK
        TEXT callback_base_url
        INTEGER is_active
    }

    admin_users {
        TEXT id PK
        TEXT name
        TEXT email UK
        TEXT password_hash
        TEXT role "admin|operator|viewer"
    }

    feature_flags {
        TEXT id PK
        TEXT key UK
        TEXT value
        TEXT description
    }

    audit_logs {
        TEXT id PK
        TEXT user_id FK
        TEXT action
        TEXT resource
        TEXT metadata
        TEXT ip_address
        TEXT created_at
    }

    config {
        TEXT id PK
        TEXT key UK
        TEXT value
        TEXT description
    }

    transactions ||--o{ refunds : "has"
    transactions ||--o{ splits : "has"
    transactions ||--o{ webhook_events : "triggers"
    app_registrations ||--o{ transactions : "creates"
    admin_users ||--o{ audit_logs : "performs"
    

Regras de Negócio

RN-01
Idempotência — Toda transação exige idempotency_key UUID. Reenvio retorna resultado existente.
RN-02
Valores em centavos — Todos os valores monetários em centavos (integer). NUNCA float.
RN-03
Provider flag — PAYMENT_PROVIDER (internal/external) switchable em runtime via admin panel.
RN-04
Internal: Pix auto-approve — Pix aprovado automaticamente após delay configurável (default 3-5s).
RN-05
Internal: Cartão aprovado — Aprovado se amount < R$10.000 e last4 != 9999.
RN-06
Internal: Cartão rejeitado — Rejeitado se last4 = 9999 (CARD_DECLINED) ou amount > R$10.000 (LIMIT_EXCEEDED).
RN-07
Internal: Boleto mock — Boleto com barcode FEBRABAN mock. Pagável via botão no admin panel.
RN-08
Token vault — Token vinculado a CPF/CNPJ. Reutilizável cross-app. Nunca armazena dados raw.
RN-09
Split — Soma das partes DEVE ser igual ao total da transação. Validação obrigatória.
RN-10
Webhook retry — 3 tentativas com backoff exponencial: 30s, 2min, 10min. Após 3 falhas: delivery_failed.
RN-11
Soft delete — Nenhum registro é fisicamente deletado. Usa is_active/deleted_at.
RN-12
Audit log — Toda ação administrativa é registrada com user_id, action, resource, IP e timestamp.
RN-13
Rate limiting — 100 transações/minuto por app. Header X-RateLimit-* nas respostas.
RN-14
Auth entre serviços — Apps autenticam via X-API-Key validada contra app_registrations.
RN-15
Auth do painel — JWT com roles: admin (full), operator (transações + webhooks), viewer (read-only).
RN-16
Dados de cartão — Número completo e CVV NUNCA são persistidos. Apenas token, last4 e brand.
RN-17
Estorno — Cartão e Pix permitem estorno (total ou parcial). Boleto não permite estorno.
RN-18
Callback URL — URL de callback por transação sobrescreve o default do app. Fallback para callback_base_url.

Modelo de Segurança

Autenticação de Apps
  • X-API-Key header validado contra app_registrations
  • X-Source-App header validado contra o app registrado
  • Rate limiting: 100 tx/min por app
  • App inativo retorna 403
Autenticação Admin
  • Login via email/senha com bcrypt
  • JWT com 24h de expiração
  • 3 roles: admin, operator, viewer
  • Middleware requireRole() para RBAC
Proteção de Dados
  • Card vault: apenas token + last4 + brand
  • Número completo e CVV nunca persistidos
  • Soft delete em todos os registros
  • Audit log para rastreabilidade
Integridade Transacional
  • Idempotency key UNIQUE no banco
  • Webhook dedup via event_id UNIQUE
  • Valores sempre em centavos (integer)
  • UUID v4 para todos os primary keys

Relatório de QA

Quality Score
82/100 — Aprovado com observações approved

ECP Pay v1 está sólido e pronto para produção em modo internal. O fluxo central de pagamento (Pix, Card, Boleto), provider pattern, modelo de segurança, schema do banco e painel admin estão bem construídos.

Gaps identificados: 4 endpoints admin faltantes (splits, tokens, webhooks admin, webhook retry), 3 páginas frontend faltantes (splits, card-vault, audit-log como rotas standalone), e gaps menores em regras de negócio. Nenhum bloqueia o lançamento v1 em modo internal.

Regras de negócio: 14/18 implementadas, 3 parciais, 1 faltante Regras técnicas: 6/6 implementadas
Cobertura de Regras de Negócio

Implementadas (14): RN-01 a RN-08, RN-10 a RN-16

Parciais (3): RN-09 (split sem integração no fluxo), RN-17 (estorno sem limite 90 dias), RN-18 (fallback de callback URL incompleto)

Faltante (1): Limite de 90 dias para estorno (RN-17)

Cobertura de API

Payment endpoints: 9/9 implementados (100%)

Admin endpoints: 17/20 implementados (85%)

Total: 26/29 implementados (90%)

Faltantes: GET /admin/splits, GET /admin/webhooks, POST /admin/webhooks/:id/retry

qa-report.json backend-status.json frontend-status.json