Skip to content

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ícioSem adapterCom adapter
Troca de provedorAltera N edge functionsAltera 1 arquivo
CredenciaisEspalhadas pelo códigoLidas em 1 ponto
Tratamento de erroInconsistentePadronizado
TestabilidadeDifícil (mock por função)Fácil (mock do adapter)
AuditoriaSem padrãoConsumidor registra via registrarLog

Anatomia de um Adapter

supabase/functions/_shared/<provedor>.ts

Regras estruturais

  1. Funções puras — sem side-effects no banco. Updates no Supabase ficam na Edge Function consumidora.
  2. Credenciais internas — o adapter lê Deno.env.get(...) internamente; o consumidor nunca manipula tokens.
  3. Retorno tipado — toda função retorna uma interface Result com success: boolean + dados ou erro.
  4. Try/catch em toda chamada HTTP — erros de rede nunca propagam exceções não tratadas.
  5. Sem imports de logging/supabase — logging e persistência são responsabilidade do consumidor.

Estrutura canônica

typescript
// 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çãoDescriçã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):

EventoFinalidade
session.statusMonitora conexão/desconexão da sessão WhatsApp. Dispara alerta ntfy urgente se desconectar.
messages.updateStatus de entrega: usa campo ack (0=pending, 1=sent, 2=delivered, 3=read, 4=played)
message.sentConfirmaçã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çãoDescriçã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çãoDescriçã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

markdown
□ 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ário

Anti-padrões

❌ Errado✅ Correto
fetch("https://api.wasender...") inline na Edge Functionawait enviarMensagemComLog(...) do adapter
Credenciais passadas como parâmetroAdapter lê Deno.env.get() internamente
throw new Error(...) em falha de APIRetornar { success: false, error: "..." }
Construir string de mensagem inlinegerarMensagemSMS('tipo', params)
Chamar API de mensageria diretamenteUsar 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

DataMudança
2026-04-02Removido _shared/twilio-sms.ts (dead code). Guards messagingConfigured simplificados nos 3 portais. Docs atualizados.
2026-03-30Adicionada constante MESSAGING_RATE_LIMITS centralizada. Cooldown 60s no reenviar OTP. Documentação de rate limits criada.
2026-03-28Migraçã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-28Limpeza final: removidos admin-twilio-billing.tsx, useAdminTwilioBilling.ts, consultarSaldoTwilio(), action twilio_admins. Cron registry atualizado (check_whatsapp_session).
2026-03-28Migração Twilio → Wasender. Adapter wasender-whatsapp.ts como primário com fallback Twilio. Webhook de delivery status. Templates com formatação WhatsApp.
2026-03-10Documento criado. consultarSaldoTwilio() centralizado no adapter.