Integrações com Terceiros — Adapter Pattern
Regra máxima: Nenhuma Edge Function chama API externa diretamente. Toda integração com serviço de terceiro passa por um adapter centralizado em
supabase/functions/_shared/.
Por que usar Adapters?
| Benefício | Sem adapter | Com adapter |
|---|---|---|
| Troca de provedor | Altera N edge functions | Altera 1 arquivo |
| Credenciais | Espalhadas pelo código | Lidas em 1 ponto |
| Tratamento de erro | Inconsistente | Padronizado |
| Testabilidade | Difícil (mock por função) | Fácil (mock do adapter) |
| Auditoria | Sem padrão | Consumidor registra via registrarLog |
Anatomia de um Adapter
supabase/functions/_shared/<provedor>.tsRegras estruturais
- Funções puras — sem side-effects no banco. Updates no Supabase ficam na Edge Function consumidora.
- Credenciais internas — o adapter lê
Deno.env.get(...)internamente; o consumidor nunca manipula tokens. - Retorno tipado — toda função retorna uma interface
Resultcomsuccess: boolean+ dados ou erro. - Try/catch em toda chamada HTTP — erros de rede nunca propagam exceções não tratadas.
- Sem imports de logging/supabase — logging e persistência são responsabilidade do consumidor.
Estrutura canônica
// supabase/functions/_shared/meu-provedor.ts
const API_BASE = "https://api.provedor.com/v1";
// ── Interfaces públicas ──────────────────────
export interface MinhaOperacaoResult {
success: boolean;
data?: AlgumTipo;
error?: string;
}
// ── Função do adapter ────────────────────────
export async function minhaOperacao(params: Params): Promise<MinhaOperacaoResult> {
const apiKey = Deno.env.get("PROVEDOR_API_KEY");
if (!apiKey) {
return { success: false, error: "Credenciais não configuradas" };
}
try {
const res = await fetch(`${API_BASE}/endpoint`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
if (!res.ok) {
return { success: false, error: `API ${res.status}` };
}
const data = await res.json();
return { success: true, data };
} catch (e) {
return { success: false, error: e instanceof Error ? e.message : "Erro desconhecido" };
}
}Adapters Existentes
1. Wasender (WhatsApp) — _shared/wasender-whatsapp.ts
Centraliza envio de mensagens via WhatsApp API (Wasender).
| Função | Descrição |
|---|---|
enviarMensagemComLog() | Envia mensagem via Wasender → registra em sms_log |
Lógica: Envio direto via Wasender. Se falhar (API down, sessão desconectada), registra erro e dispara alerta ntfy.
Secret: WASENDER_API_KEY
Consumidores: Todas as Edge Functions que enviam mensagens (send-otp, faturamento-cron, maintenance-cron, escola-pagamentos, portal-login-aluno, portal-login-responsavel, portal-cadastro, mercadopago-webhook, mercadopago-preference).
Webhook de delivery status: wasender-webhook Edge Function recebe eventos do Wasender e atualiza sms_log.
Webhook: wasender-webhook/index.ts
Endpoint público que recebe callbacks do Wasender Dashboard.
URL: https://mjvuzsizjlcalyfmbquy.supabase.co/functions/v1/wasender-webhook
Eventos habilitados (3):
| Evento | Finalidade |
|---|---|
session.status | Monitora conexão/desconexão da sessão WhatsApp. Dispara alerta ntfy urgente se desconectar. |
messages.update | Status de entrega: usa campo ack (0=pending, 1=sent, 2=delivered, 3=read, 4=played) |
message.sent | Confirmação de envio do servidor Wasender |
Eventos desabilitados: messages.received, messages.delete (sistema é envio-only).
Segurança: Validação via WASENDER_WEBHOOK_SECRET (header x-webhook-secret). Se secret não configurado, opera em modo permissivo (graceful degradation).
Secret adicional: WASENDER_WEBHOOK_SECRET
2. Mercado Pago — _shared/payment-gateway.ts
Centraliza todas as chamadas HTTP à API do Mercado Pago.
| Função | Descrição |
|---|---|
createPreference() | Cria preferência de checkout |
getPayment() | Consulta pagamento por ID |
searchPayments() | Busca pagamentos por referência |
invalidatePreference() | Invalida preferência (PUT status) |
calcularValorLiquido() | Calcula valor líquido (desconta taxas MP) |
getWebhookNotificationUrl() | URL fixa do webhook |
getBackUrls() | URLs de retorno pós-checkout |
Secret: MP_ACCESS_TOKEN
3. ntfy.sh — _shared/ntfy-helper.ts
Centraliza alertas push para monitoramento operacional. Independente de Wasender — usa HTTP puro.
| Função | Descrição |
|---|---|
enviarAlertaPush() | Envia notificação push via ntfy.sh |
Secret: NTFY_TOPIC
4. Templates de mensagem — _shared/sms-templates.ts
Gera textos de mensagem via gerarMensagemSMS(tipo, params). Suporta formatação WhatsApp (negrito, itálico) e acentuação. Uso profissional — emojis apenas quando estritamente apropriado.
Checklist: Nova Integração com Terceiro
□ Criar adapter em `_shared/<provedor>.ts`
□ Definir interfaces de retorno tipadas (Result pattern)
□ Ler credenciais via `Deno.env.get()` dentro do adapter
□ Retornar `{ success: false, error }` se credenciais ausentes (não lançar exceção)
□ Try/catch em toda chamada HTTP
□ Não importar helpers de logging/supabase no adapter
□ Adicionar secrets via Supabase Dashboard (Settings → Functions)
□ Consumidores chamam APENAS funções do adapter (nunca `fetch` direto)
□ Consumidores registram via `registrarLog()` as operações de escrita
□ Documentar neste arquivo (tabela de funções + consumidores)
□ Atualizar `docs/README.md` se necessárioAnti-padrões
| ❌ Errado | ✅ Correto |
|---|---|
fetch("https://api.wasender...") inline na Edge Function | await enviarMensagemComLog(...) do adapter |
| Credenciais passadas como parâmetro | Adapter lê Deno.env.get() internamente |
throw new Error(...) em falha de API | Retornar { success: false, error: "..." } |
| Construir string de mensagem inline | gerarMensagemSMS('tipo', params) |
| Chamar API de mensageria diretamente | Usar enviarMensagemComLog() do adapter Wasender |
Rate Limits e Proteção Anti-Abuso
→ Documentação completa: docs/architecture/MESSAGING_RATE_LIMITS.md
→ Constante centralizada: _shared/messaging-rate-limits.ts (backend) / src/lib/messaging-rate-limits.ts (frontend)
Histórico
| Data | Mudança |
|---|---|
| 2026-04-02 | Removido _shared/twilio-sms.ts (dead code). Guards messagingConfigured simplificados nos 3 portais. Docs atualizados. |
| 2026-03-30 | Adicionada constante MESSAGING_RATE_LIMITS centralizada. Cooldown 60s no reenviar OTP. Documentação de rate limits criada. |
| 2026-03-28 | Migração completa: mercadopago-webhook e mercadopago-preference migrados para wasender-whatsapp.ts. Alertas ntfy adicionados no fallback Twilio (high) e falha total (urgent). Zero funções chamam twilio-sms.ts diretamente. |
| 2026-03-28 | Limpeza final: removidos admin-twilio-billing.tsx, useAdminTwilioBilling.ts, consultarSaldoTwilio(), action twilio_admins. Cron registry atualizado (check_whatsapp_session). |
| 2026-03-28 | Migração Twilio → Wasender. Adapter wasender-whatsapp.ts como primário com fallback Twilio. Webhook de delivery status. Templates com formatação WhatsApp. |
| 2026-03-10 | Documento criado. consultarSaldoTwilio() centralizado no adapter. |