API REST multi-tenant para gestão financeira com bot de WhatsApp e IA integrada.
O Loong API é o backend de uma plataforma fintech para pequenos negócios. Gerencia múltiplos usuários e workspaces isolados, processa webhooks do Stripe e do WhatsApp em tempo real, executa tarefas assíncronas com Celery e expõe uma API REST consumida por clientes web e pelo bot.
O código-fonte é privado. Este repositório documenta a arquitetura, os módulos, as decisões de design e os contratos de API.
graph TD
subgraph Clientes
WA[WhatsApp User]
WEB[Frontend React]
STRIPE[Stripe]
end
subgraph Infra
NGINX[Nginx<br/>reverse proxy]
end
subgraph Django API
GUNICORN[Gunicorn<br/>WSGI server]
ACCOUNTS[accounts<br/>auth & users]
COMPANIES[companies<br/>workspaces & tenancy]
BILLING[billing<br/>Stripe & planos]
POCKET[pocket<br/>finanças pessoais]
WHATSAPP[whatsapp<br/>bot & webhooks]
BLOG[blog<br/>conteúdo com IA]
end
subgraph Async
REDIS[(Redis<br/>broker & cache)]
WORKER[Celery Worker]
BEAT[Celery Beat<br/>agendador]
end
subgraph Dados
PG[(PostgreSQL)]
end
subgraph Externas
META[Meta Cloud API]
STRIPE_API[Stripe API]
GROQ[Groq / LLaMA 3.3 70B]
GEMINI[Google Gemini]
SENTRY[Sentry]
end
WA -->|webhook| NGINX
WEB -->|HTTPS| NGINX
STRIPE -->|webhook| NGINX
NGINX --> GUNICORN
GUNICORN --> ACCOUNTS & COMPANIES & BILLING & POCKET & WHATSAPP & BLOG
ACCOUNTS & COMPANIES & BILLING & POCKET & WHATSAPP & BLOG --> PG
WHATSAPP --> REDIS
POCKET --> REDIS
WORKER --> REDIS
BEAT --> REDIS
WORKER --> META & GROQ & GEMINI
BILLING --> STRIPE_API
GUNICORN --> SENTRY
Gerencia o modelo customizado de usuário (email como username), registro, login, logout e fluxos de senha.
Principais decisões:
AbstractBaseUsercustomizado — email como campo de login em vez de username- Hashing de senha com Argon2 (vencedor do Password Hashing Competition)
- JWT via
djangorestframework-simplejwtcom rotação de tokens e blacklist - Autenticação de dois fatores com TOTP (compatível com Google Authenticator)
AuditLogimutável — registra login, logout, mudança de senha, IP e user agent
accounts/
├── models.py # User, AuditLog
├── serializers.py # CustomTokenObtainPairSerializer, RegisterSerializer
├── views.py # Login, Register, Logout, PasswordChange
├── validators.py # PasswordStrengthValidator
├── sanitizers.py # Bleach — sanitização de inputs
├── audit.py # Helper para registrar AuditLog
└── phone_otp.py # Verificação de telefone via OTP
O Workspace é o tenant central do sistema. Toda entidade (transação, conta, categoria, configuração) pertence a um workspace. Um usuário pode ter múltiplos workspaces.
# Toda entidade do sistema segue este padrão:
class AnyModel(BaseModel):
workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE)
# ... outros camposTipos de workspace: pocket (pessoal, criado automaticamente no cadastro), personal, business, enterprise.
BaseModel — classe abstrata base de todos os modelos:
| Campo | Tipo | Descrição |
|---|---|---|
id |
UUIDField |
PK em UUID v4 — impossível de adivinhar, sem coordenação central |
created_at |
DateTimeField |
Preenchido automaticamente na criação |
updated_at |
DateTimeField |
Atualizado a cada save() |
Integração completa com Stripe para assinaturas recorrentes (planos FREE, PRO, POCKET).
Fluxo de assinatura:
sequenceDiagram
participant U as Usuário
participant API as Loong API
participant S as Stripe
U->>API: POST /billing/checkout/
API->>S: Cria checkout session
S-->>API: session_url
API-->>U: Redireciona para Stripe
U->>S: Preenche cartão e confirma
S->>API: POST /billing/webhook/ (checkout.session.completed)
API->>API: Valida HMAC-SHA256
API->>API: select_for_update() — lock para idempotência
API->>API: Ativa assinatura no banco
S->>API: POST /billing/webhook/ (subscription events)
API->>API: Atualiza status da assinatura
Decisão técnica chave: select_for_update() no _activate_subscription garante que dois webhooks concorrentes (o Stripe pode reenviar) não ativem a assinatura duas vezes.
Core financeiro do produto. Gerencia contas, transações, categorias, contas a pagar, metas e transações recorrentes.
pocket/
├── models.py # Account, Transaction, Category, Bill, RecurringTransaction, FinancialGoal
├── analytics.py # Engine de analytics: budget diário, projeções, insights
├── jarvis_engine.py # Regra do silêncio, janela 24h WhatsApp, insights proativos
├── charts.py # Geração de gráficos com Matplotlib (PNG → WhatsApp)
├── tasks.py # Celery: lembretes, recorrentes, radar diário do Jarvis
├── importers/ # OFX, PDF, XLSX — importação de extratos bancários
└── services/ # Lógica de negócio isolada da view
Soft Delete — transações nunca são deletadas do banco:
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(deleted_at__isnull=True)Analytics Engine — cálculos financeiros comportamentais em Python puro:
- Budget diário disponível (levando em conta teto de gastos, metas e recorrentes pendentes)
- Projeção de gastos até o fim do mês
- Detecção de anomalias de gasto por categoria
Webhook que recebe mensagens do WhatsApp, roteia para o handler correto e responde via Meta Cloud API.
Fluxo de mensagem:
flowchart LR
MSG[Mensagem recebida] --> VERIFY{Verifica\nassinatura Meta}
VERIFY -->|inválida| IGNORE[Ignora]
VERIFY -->|válida| PARSE[Parser]
PARSE --> CMD{Comando\nsimples?}
CMD -->|sim| HANDLER[Handler direto\nsaldo/resumo/etc]
CMD -->|não| TXPARSE{É uma\ntransação?}
TXPARSE -->|sim| SAVE[Salva transação]
TXPARSE -->|não| LLM[Groq / LLaMA 3.3 70B\nconsultor financeiro]
HANDLER & SAVE & LLM --> REPLY[Responde via\nMeta Cloud API]
Parser de linguagem natural:
O parser usa regex para capturar os padrões mais comuns antes de acionar a IA. Isso mantém custo de API em menos de 1% das interações.
"+50 gasolina" → transação receita R$50, descrição "gasolina"
"-30 mercado" → transação despesa R$30, descrição "mercado"
"saldo" → retorna saldo consolidado de todas as contas
"resumo" → retorna resumo do mês com gráfico
[foto de boleto] → OCR (pyzbar + pytesseract) → extrai valor e vencimento
[qualquer pergunta aberta] → LLM com snapshot financeiro do usuário
Regra do silêncio (Jarvis):
- Máximo 1 mensagem proativa por dia por workspace
- Verifica janela de 24h da Meta API antes de enviar (evita custo de template)
- Estado armazenado no Redis com TTL de 24h
Gera artigos automaticamente toda segunda-feira às 9h via Celery Beat + Google Gemini. Os artigos entram como rascunho para revisão humana antes de publicar.
| Task | Horário | Descrição |
|---|---|---|
send_bill_reminders |
8h diário | Notifica usuários sobre contas a vencer |
mark_overdue_bills |
8h05 diário | Marca contas vencidas como overdue |
process_recurring_transactions |
6h diário | Gera transações das recorrências ativas |
jarvis_daily_radar |
8h15 diário | Envia insight financeiro proativo pelo WhatsApp |
generate_blog_article |
9h segunda | Gera artigo com Gemini (publica como rascunho) |
| # | Camada | Implementação |
|---|---|---|
| 1 | Hashing de senha | Argon2 (Password Hashing Competition winner) |
| 2 | Autenticação | JWT com access (15min) + refresh (7d) + blacklist |
| 3 | 2FA | TOTP via django-otp (Google Authenticator) |
| 4 | Rate limiting | Scoped throttling por endpoint (login: 5/min, register: 10/h) |
| 5 | Sanitização | Bleach — remove HTML/JS de todos os inputs |
| 6 | CSP | django-csp — Content Security Policy via header HTTP |
| 7 | Admin restrito | Path ofuscado + restrição por IP (ADMIN_ALLOWED_IPS) |
| 8 | X-Forwarded-For | Só confia no header se REMOTE_ADDR for proxy confiável |
| 9 | Audit logs | Imutáveis — login, logout, mudança de senha, IP, user agent |
| 10 | Prompt injection | Input do usuário isolado em bloco <USER_INPUT> no system prompt |
Todos os endpoints (exceto auth) exigem
Authorization: Bearer <access_token>. Base URL:/api/v1/
| Método | Endpoint | Descrição |
|---|---|---|
POST |
/auth/register/ |
Cria conta e workspace padrão |
POST |
/auth/login/ |
Retorna access + refresh tokens |
POST |
/auth/logout/ |
Invalida o refresh token (blacklist) |
POST |
/auth/token/refresh/ |
Troca refresh por novo access token |
POST |
/auth/password/change/ |
Altera senha (requer senha atual) |
POST |
/auth/password/reset/ |
Envia email de reset |
| Método | Endpoint | Descrição |
|---|---|---|
GET/POST |
/pocket/transactions/ |
Lista e cria transações |
GET/PATCH/DELETE |
/pocket/transactions/{id}/ |
Detalhe, edição e soft-delete |
GET/POST |
/pocket/accounts/ |
Contas bancárias/carteiras |
GET/POST |
/pocket/categories/ |
Categorias de transação |
GET/POST |
/pocket/bills/ |
Contas a pagar |
GET/POST |
/pocket/recurring/ |
Transações recorrentes |
GET |
/pocket/analytics/summary/ |
Resumo financeiro do mês |
GET |
/pocket/analytics/daily-budget/ |
Budget disponível hoje |
POST |
/pocket/import/ofx/ |
Importa extrato OFX |
POST |
/pocket/import/pdf/ |
Importa extrato PDF |
| Método | Endpoint | Descrição |
|---|---|---|
POST |
/billing/checkout/ |
Cria sessão de checkout Stripe |
GET |
/billing/subscription/ |
Status e detalhes da assinatura atual |
POST |
/billing/cancel/ |
Cancela assinatura |
POST |
/billing/webhook/ |
Webhook Stripe (público, validado por HMAC) |
| Método | Endpoint | Descrição |
|---|---|---|
GET |
/whatsapp/webhook/ |
Verificação do webhook (Meta) |
POST |
/whatsapp/webhook/ |
Recebe mensagens (Meta) |
┌─────────────────────────────────────────────────────────┐
│ Docker Compose │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ nginx │──▶│ gunicorn │──▶│ Django Application │ │
│ │ :80 │ │ :8000 │ │ (4 workers) │ │
│ └─────────┘ └──────────┘ └──────────────────────┘ │
│ │ │
│ ┌──────────────────┐ ┌─────────────▼────────────┐ │
│ │ celery-beat │ │ redis │ │
│ │ (agendador) │────▶│ broker + cache + rate │ │
│ └──────────────────┘ └─────────────┬────────────┘ │
│ │ │
│ ┌──────────────────┐ │ │
│ │ celery-worker │◀──────────────────┘ │
│ │ (executor) │ │
│ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PostgreSQL │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
IDs inteiros sequenciais (1, 2, 3...) permitem que um atacante adivinhe IDs de outros recursos (/transactions/1, /transactions/2). UUID v4 são 128 bits gerados aleatoriamente — impossível de enumerar. Também permitem gerar IDs em qualquer nó sem coordenação central, facilitando escalabilidade.
Webhooks da Meta têm timeout de poucos segundos. Processar OCR de boleto, chamar LLM e responder ao WhatsApp dentro desse tempo é arriscado e bloqueia o processo Gunicorn. Com Celery, a requisição retorna 200 OK imediatamente, e o processamento acontece em background — melhor UX e sem risco de timeout.
Mais de 95% das interações no WhatsApp são comandos simples (+50 gasolina, saldo, resumo). Processar cada um com um LLM seria lento (latência de rede) e caro (custo por token). O parser regex captura esses casos em microssegundos. O LLM só é acionado para perguntas abertas onde regex não resolve.
Transações financeiras deletadas precisam existir para auditoria e para o caso de "desfazer". Com soft delete, deleted_at é marcado e o registro fica invisível para queries normais, mas recuperável. Também preserva integridade referencial de registros relacionados.
O Stripe pode reenviar o mesmo webhook se não receber confirmação a tempo. Dois workers Celery processando o mesmo evento de checkout.completed em paralelo poderiam ativar a mesma assinatura duas vezes. select_for_update() coloca um lock no banco — o segundo worker espera o primeiro terminar e, ao verificar o estado, descobre que a assinatura já está ativa.
Desenvolvido por Caio Fiori Martins — Desenvolvedor Full-Stack · Python / Django / React