Biz Telegram Payment Notify — Alerta em tempo real quando o Sepay recebe o pagamento
Este skill conecta a notificação do bot do Telegram ao webhook do Sepay já existente no seu projeto Next.js. Sempre que um cliente transfere dinheiro com sucesso para a conta bancária vinculada ao Sepay, o skill faz o bot do Telegram enviar uma mensagem em tempo real para o chat do proprietário (ou grupo da equipe de vendas) — incluindo nome do cliente, telefone, valor, produto e horário.
Filosofia: O auto-responder por e-mail para o cliente (Resend) e o alerta no Telegram para o proprietário atendem a dois atores diferentes. O e-mail é formal para o cliente. O Telegram é um "ping" para o proprietário — você está na rua, abre o Telegram e vê o novo pedido imediatamente, sem precisar abrir o Gmail e procurar entre 100 e-mails. Latência do Telegram < 2 segundos vs. e-mail 30-60 segundos.
Por que Telegram e não Slack/Discord/Zalo: A configuração do bot do Telegram leva 5 minutos (1 token + 1 chat_id), não requer workspace/administrador de servidor, é gratuito com mensagens ilimitadas, tem notificações push mais fortes que e-mail e aplicativo nativo iOS/Android/desktop. O Slack precisa de workspace + convite. O Discord precisa de servidor + configuração de permissão. O Zalo OA precisa de conta empresarial verificada (3-7 dias para aprovação) + taxa mensal por usuário ativo. O Telegram é o caminho de menor resistência para alertas do lado do proprietário.
Saída do skill: patch de código + guia de configuração do bot passo a passo + lista de variáveis de ambiente + plano de teste em 3 níveis. O skill NÃO testa o envio do Telegram automaticamente — ele solicita que o usuário cole o token do bot e o chat_id primeiro, pois essas credenciais são de propriedade do usuário.
Quando usar este skill
- O usuário acabou de configurar o webhook do Sepay e deseja ser notificado em tempo real no Telegram quando um cliente transferir dinheiro.
- O usuário já tem
/api/payment-success(ou equivalente) para processar e-mail com Resend e deseja adicionar o alerta do Telegram EM PARALELO — sem criar uma nova rota, sem duplicar a lógica. - O usuário diz "me avise quando houver um pedido" e o contexto é uma landing page de vendas com integração Sepay.
NÃO use este skill quando:
- O usuário deseja notificação no Slack/Discord → caso de uso diferente.
- O usuário deseja um bot do Zalo OA → precisa de conta empresarial verificada + estrutura de custos diferente.
- O usuário ainda não configurou o webhook do Sepay (ainda não tem conta bancária vinculada ao Sepay, ainda não tem endpoint para receber webhook) → este skill não configura o Sepay do zero, apenas se conecta a um webhook existente.
- O usuário quer apenas e-mail para o proprietário →
biz-email-setupjá tem o e-mail B (notificação ao proprietário).
Fluxo de trabalho geral (6 fases)
Fase 0: DETECTAR rota existente do webhook do Sepay (path + framework + formato do handler)
↓
Fase 0.5: DETECTAR armazenamento de leads (lib/leads-kv.ts de /biz-setup-sepay-payment)
→ sim: usar consulta KV real
→ não: oferecer 2 opções (modo bruto OU executar /biz-setup-sepay-payment primeiro)
↓
Fase 1: GUIAR usuário na criação do bot do Telegram (BotFather → token → chat_id)
↓ [BARREIRA — aguardar usuário colar token + chat_id]
↓
Fase 2: RASCUNHAR formato da mensagem → mostrar preview ao usuário → confirmar
↓
Fase 3: IMPLEMENTAR código (helper lib/telegram.ts + integrar no handler do webhook + variáveis de ambiente)
↓
Fase 4: PLANO DE TESTE (curl no bot direto → payload simulado do Sepay → verificação em produção)
As fases 1 e 4 têm barreira de espera pelo usuário porque exigem ação manual do usuário (obter token, verificar em produção). As fases 0/0.5/2/3 são feitas ativamente pelo skill.
Fase 0 — Detectar webhook do Sepay existente
Este skill se conecta a um webhook existente, NÃO cria uma nova rota. Primeiro, localize o handler.
Encontrar rota do webhook — grep por palavras-chave comuns:
# Na raiz do projeto
grep -r "sepay" --include="*.ts" --include="*.js" --include="*.tsx" -l
grep -r "transferAmount\|transferType\|payment-success\|payment-webhook" --include="*.ts" --include="*.js" -l
3 cenários comuns:
| Cenário | Path existente | Ação |
|---|---|---|
Usuário usou pipeline biz-email-setup | app/api/payment-success/route.ts (ou pages/api/payment-success.ts) já tem Resend chamando sendCustomerEmail + sendOwnerEmail | Conectar sendTelegramNotification no mesmo Promise.allSettled |
| Usuário tem rota específica do Sepay | app/api/sepay-webhook/route.ts ou app/api/webhook/sepay/route.ts | Conectar após analisar + validar payload do Sepay (ramo transferType === 'in') |
| Nenhuma rota encontrada | — | PARAR. Informar ao usuário: "Não encontrei uma rota de webhook para receber o Sepay. Configure o webhook do Sepay primeiro (vincule o banco ao Sepay → crie a URL do webhook apontando para o domínio) e depois volte para eu conectar o Telegram. Ou, se o webhook estiver em um endpoint com nome diferente, me indique o caminho." |
Ler o handler atual para entender o formato:
- Há autenticação da requisição do Sepay? (cabeçalho Authorization
Apikeyou HMAC) - Há deduplicação por
idoureferenceCode? (Sepay tenta novamente se não receber 200 → precisa de dedup para evitar notificação duplicada) - Há consulta de lead por
content(mensagem de transferência contendo ID do pedido)? - Formato da resposta para o Sepay:
{ success: true }ou200 OKsimples?
Se o handler atual não tiver dedup — avise o usuário, mas NÃO bloqueie. O skill apenas conecta o Telegram, a deduplicação é uma preocupação da lógica de pagamento. Sugira que o usuário adicione dedup separadamente, se necessário.
Fase 0.5 — Detectar armazenamento de leads (CRÍTICO)
O webhook do Sepay envia apenas content (texto que o cliente colou ao transferir) + transferAmount + referenceCode. O Sepay não sabe quem é o cliente — é necessário consultar o armazenamento local pelo content (que contém o ID do pedido).
O skill verifica 3 coisas:
# 1. Verificar se o helper de armazenamento de leads existe
test -f lib/leads-kv.ts || test -f lib/leads-kv.js && echo "lead store: ✓"
# 2. Verificar variáveis de ambiente do Vercel KV
grep -E "KV_REST_API_URL|KV_REST_API_TOKEN" .env.local && echo "KV env: ✓"
# 3. Verificar se @vercel/kv está instalado
grep '"@vercel/kv"' package.json && echo "KV package: ✓"
3 cenários:
| Cenário | Ação |
|---|---|
| Todos os 3 ✓ — armazenamento de leads pronto | Prosseguir para a Fase 1, usar getLeadByOrderId() de lib/leads-kv.ts no handler do webhook |
| 1+ ausente — não configurado | PARAR, perguntar ao usuário sobre 2 opções (veja abaixo) |
Se estiver faltando, pergunte ao usuário:
Percebi que você ainda não tem um armazenamento de leads (Vercel KV). O webhook do Sepay não sabe quem é o cliente — ele só tem o conteúdo da transferência + valor. O que você prefere?
Opção 1 — Configurar o armazenamento de leads completo via
/biz-setup-sepay-paymentprimeiro (recomendado):
- Esse skill configurará o Vercel KV, criará o scaffold de lib/leads-kv.ts e conectará o envio do formulário para salvar o lead quando o usuário se registrar
- Depois, volte aqui e eu conectarei o Telegram para consultar o nome/telefone/e-mail completo do cliente
- O alerta do Telegram mostrará: "Nguyễn Văn A — 0901234567 — [email protected] — 499K — Curso de Agente de IA"
Opção 2 — Modo bruto: enviar o payload do Sepay diretamente para o Telegram, sem consultar o lead:
- O Telegram mostrará apenas: "Vietcombank — 499.000đ — content: DH001 0901234567 — 14:32"
- O proprietário terá que adivinhar quem é o cliente pelo conteúdo e verificar o log de e-mail do Resend
- Simples, funciona imediatamente, mas experiência do usuário ruim
Qual você escolhe, 1 ou 2?
Se o usuário escolher 1 → PARE este skill, sugira /biz-setup-sepay-payment. Depois que o usuário concluir a configuração, volte a este skill.
Se o usuário escolher 2 → prossiga para a Fase 1, use o modelo bruto (veja templates/lib-telegram-raw.ts).
Fase 1 — Guiar o usuário na criação do bot do Telegram
Leia references/telegram-bot-setup.md para instruções detalhadas com capturas de tela em texto. Resumo em 3 etapas para o usuário:
Etapa 1 — Criar um novo bot via @BotFather
1. Abra o Telegram (celular/desktop)
2. Pesquise: @BotFather → clique em Iniciar
3. Digite o comando: /newbot
4. O BotFather pergunta o nome do bot — insira o nome de exibição (exemplo: "Pedidos Marca X")
5. O BotFather pergunta o username — insira um username terminado em "_bot" (exemplo: marcx_pedidos_bot) — deve ser único globalmente
6. O BotFather retorna uma mensagem com a linha:
"Use this token to access the HTTP API: 1234567890:AAxxxxxxxxxxxxxxxxxxxxxx"
7. Copie esse token e cole para mim.
ATENÇÃO À SEGURANÇA: Este token = controle total do bot. Não o comite no Git, não o compartilhe publicamente. Se vazar, volte ao @BotFather → /token → /revoke para criar um novo token.
Etapa 2 — Obter o chat_id
Chat 1-1 (apenas o proprietário recebe) — recomendado para empreendedores individuais:
1. No Telegram, pesquise: @userinfobot → clique em Iniciar
2. O bot retorna uma mensagem com "Id: 123456789" — copie esse número.
3. Esse é o seu chat_id.
Chat em grupo (equipe de vendas recebe junto) — recomendado para equipes:
1. Crie um novo grupo no Telegram (ou use um grupo existente)
2. Clique no nome do grupo → Adicionar membro → pesquise o bot que você acabou de criar (pelo username @marcx_pedidos_bot) → Adicionar
3. No grupo, digite: /start@marcx_pedidos_bot (ou qualquer mensagem)
4. Abra o navegador e acesse:
https://api.telegram.org/bot<TOKEN>/getUpdates
(substitua <TOKEN> pelo token da etapa 1)
5. Na resposta JSON, procure por "chat":{"id":-1001234567890,...} — copie esse número (lembre-se de manter o sinal de menos, se houver)
6. Esse é o chat_id do grupo.
Canal (broadcast unidirecional, sem discussão) — chat_id começa com -100. A forma de obter é a mesma do grupo, mas adicione o bot como administrador do canal.
Etapa 3 — Colar para o skill
Aguarde o usuário colar:
TELEGRAM_BOT_TOKEN=1234567890:AAxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=123456789
BARREIRA IMPORTANTE: NÃO prossiga para a Fase 2 até que o usuário cole AMBOS os valores. Se o usuário disser "já tenho, de outro lugar" → ainda aguarde a colagem, pois é necessário injetar no .env.local na Fase 3.
Fase 2 — Rascunhar formato da mensagem
Formato padrão em vietnamita, compatível com dispositivos móveis (o Telegram renderiza HTML):
🎉 <b>NOVO PEDIDO</b>
👤 <b>{{name}}</b>
📞 {{phone}}
📧 {{email}}
💰 <b>{{amount}}</b> — {{product_name}}
🕐 {{time_vn}}
🔗 Código da Trans.: <code>{{reference_code}}</code>
Após injetar as variáveis:
🎉 NOVO PEDIDO
👤 Nguyễn Văn A
📞 0901 234 567
📧 [email protected]
💰 499.000đ — Curso de Agente de IA
🕐 14:32 14/05/2026
🔗 Código da Trans.: FT26134567890
Mostrar preview ao usuário, perguntar:
- Este formato está OK? Deseja adicionar/remover algum campo? (exemplo: adicionar origem UTM, número de pedidos do dia, link do Zalo do cliente)
- Há muitos emojis? (alguns usuários preferem texto simples)
- Deseja adicionar um botão inline (CTA) com link para o CRM/Planilhas? (avançado — o skill pode adicionar teclado inline)
Regras de formatação:
- Modo HTML (
parse_mode: "HTML") — o Telegram suporta<b>,<i>,<u>,<code>,<pre>,<a href>. NÃO suporta<br>— use\n. - Formato do valor em VN:
499.000đ(ponto separador de milhar, comđno final) OU charm499K/1.99Mpara ficar bonito. Leia a configuraçãovoicedeoffer.json, se disponível. - Formato do telefone:
0901 234 567(separado 3-3-3) — mais fácil de escanear no celular. - Horário VN:
HH:mm dd/mm/yyyy— defina o horário do Vietnã (UTC+7), não UTC. - Evite spam de emojis (>5 emojis/mensagem → reduz a sensação de profissionalismo).
Fase 3 — Implementar o código
3 alterações:
3.1 Criar a função helper lib/telegram.ts
Leia templates/lib-telegram.ts (TypeScript) ou templates/lib-telegram.js (vanilla JS). Conteúdo:
// lib/telegram.ts
type TelegramPayload = {
name: string;
phone: string;
email: string;
amount: number; // VND, número bruto (ex: 499000)
productName: string;
referenceCode?: string;
};
export async function sendTelegramNotification(payload: TelegramPayload): Promise<void> {
const token = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!token || !chatId) {
console.error('[telegram] TELEGRAM_BOT_TOKEN ou TELEGRAM_CHAT_ID ausentes');
return; // Não lançar erro — falha do Telegram não deve bloquear a resposta de pagamento
}
const text = formatMessage(payload);
try {
const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text,
parse_mode: 'HTML',
disable_web_page_preview: true,
}),
});
if (!res.ok) {
const errBody = await res.text();
console.error(`[telegram] Falha no envio: ${res.status} ${errBody}`);
}
} catch (err) {
console.error('[telegram] Erro de rede:', err);
// Engolir — não deixe a falha do Telegram fazer o Sepay tentar novamente
}
}
function formatMessage(p: TelegramPayload): string {
const amountStr = p.amount.toLocaleString('vi-VN') + 'đ';
const phoneStr = p.phone.replace(/(\d{4})(\d{3})(\d{3})/, '$1 $2 $3');
const now = new Date().toLocaleString('vi-VN', {
timeZone: 'Asia/Ho_Chi_Minh',
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit',
});
return [
'🎉 <b>NOVO PEDIDO</b>',
'',
`👤 <b>${escapeHtml(p.name)}</b>`,
`📞 ${phoneStr}`,
`📧 ${escapeHtml(p.email)}`,
`💰 <b>${amountStr}</b> — ${escapeHtml(p.productName)}`,
`🕐 ${now}`,
p.referenceCode ? `\n🔗 Código da Trans.: <code>${escapeHtml(p.referenceCode)}</code>` : '',
].filter(Boolean).join('\n');
}
function escapeHtml(s: string): string {
return s.replace(/[<>&]/g, c => ({ '<': '<', '>': '>', '&': '&' }[c]!));
}
Por que engolir o erro: O Sepay tenta novamente o webhook se receber um código diferente de 200. Se o Telegram timeout/cair causar uma falha no webhook → Sepay tenta novamente → cada tentativa reenvia o e-mail + registra o pagamento novamente → o usuário recebe 5 e-mails + 5 alertas do Telegram para um único pedido. O Telegram é um canal lateral de melhor esforço, não um caminho crítico.
3.2 Conectar ao handler do webhook do Sepay
Leia o handler atual, localize o local onde o sucesso do pagamento é processado (após analisar o payload do Sepay + validar transferType === 'in'). Adicione:
// app/api/payment-success/route.ts (ou equivalente)
import { sendTelegramNotification } from '@/lib/telegram';
import { sendCustomerEmail, sendOwnerEmail } from '@/lib/resend'; // se já existir
export async function POST(req: Request) {
const payload = await req.json();
// [Existente] Validar autenticação do Sepay + analisar payload
if (payload.transferType !== 'in') {
return Response.json({ success: true }); // ignorar transferência de saída
}
// [Existente] Consultar lead pelo content (contém ID do pedido) ou referenceCode
const lead = await lookupLeadByTransferContent(payload.content);
// [NOVO] Distribuir 3 efeitos colaterais em paralelo — falha do Telegram não bloqueia o e-mail
await Promise.allSettled([
sendCustomerEmail(lead),
sendOwnerEmail(lead),
sendTelegramNotification({
name: lead.name,
phone: lead.phone,
email: lead.email,
amount: payload.transferAmount,
productName: lead.productName,
referenceCode: payload.referenceCode,
}),
]);
return Response.json({ success: true });
}
Por que usar Promise.allSettled em vez de Promise.all:
Promise.allrejeita se QUALQUER promise falhar → o handler lança erro → Sepay recebe 500 → tenta novamente → e-mail duplicado.Promise.allSettledaguarda todas as promises serem concluídas (resolvidas ou rejeitadas) e não lança erro. Mesmo que o Telegram esteja fora do ar, o e-mail ainda é enviado e o Sepay recebe 200.
3.3 Adicionar variáveis de ambiente
# .env.local
TELEGRAM_BOT_TOKEN=1234567890:AAxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=123456789
Instruções para o usuário:
- Copie estas 2 variáveis para o arquivo
.env.local(substituindo os valores de exemplo pelos seus). - IMPORTANTE: NUNCA comite o
.env.local. Verifique se ele está no.gitignore. Se não estiver, adicione-o IMEDIATAMENTE. - Se estiver usando Vercel, adicione as variáveis também no dashboard do Vercel (Settings → Environment Variables) para os ambientes de produção/preview.
Fase 4 — Plano de teste
Nível 1 — Testar o bot do Telegram diretamente (sem o webhook)
Depois que as variáveis de ambiente estiverem configuradas e o servidor de desenvolvimento estiver rodando:
# No terminal, execute um script ou use curl para chamar a função helper
curl -X POST http://localhost:3000/api/test-telegram \
-H "Content-Type: application/json" \
-d '{
"name": "Nguyễn Văn Test",
"phone": "0901234567",
"email": "[email protected]",
"amount": 499000,
"productName": "Curso de Teste",
"referenceCode": "TEST001"
}'
Você pode precisar criar uma rota de teste temporária app/api/test-telegram/route.ts que chame sendTelegramNotification.
Critério de sucesso: Você recebe uma mensagem no Telegram com o formato correto.
Nível 2 — Simular payload do Sepay
Use o payload de exemplo do Sepay para testar a rota real do webhook:
curl -X POST http://localhost:3000/api/sepay-webhook \
-H "Content-Type: application/json" \
-d '{
"id": 12345,
"referenceCode": "FT26134567890",
"transferType": "in",
"transferAmount": 499000,
"content": "DH001 0901234567",
"accountNumber": "1234567890",
"bankName": "Vietcombank",
"gateway": "sepay"
}'
Critério de sucesso:
- Resposta 200 OK (
{ "success": true }) - Você recebe a notificação no Telegram
- O cliente recebe o e-mail (se configurado)
- Nenhum erro no console do servidor
Nível 3 — Teste em produção
- Faça o deploy da alteração.
- Peça a um amigo ou use um pagamento de teste real (se o Sepay permitir) no ambiente de produção.
- Verifique:
- A notificação chega no Telegram em < 2 segundos após o pagamento.
- O formato está correto.
- Se o Sepay retentar (por exemplo, se houver um timeout de rede), NÃO há notificações duplicadas.
Solução de problemas
| Problema | Causa provável | Solução |
|---|---|---|
| Não recebe notificação no Telegram | Token ou chat_id incorretos | Verifique .env.local. Teste manualmente com curl: curl -X POST https://api.telegram.org/bot<TOKEN>/sendMessage -H "Content-Type: application/json" -d '{"chat_id":"<CHAT_ID>","text":"teste"}' |
| Notificação duplicada para um único pagamento | Sepay retentou o webhook (recebeu non-200) | Verifique se o Telegram está causando um erro no handler. Use Promise.allSettled. Se o problema persistir, adicione dedup por referenceCode. |
| Formato da mensagem quebrado no Telegram | HTML inválido ou caracteres especiais não escapados | Use a função escapeHtml() em todos os campos do usuário. Certifique-se de que parse_mode: 'HTML' está definido. |
| Bot não responde a comandos | Token inválido ou bot não iniciado | Refaça a Etapa 1 (criar novo bot no @BotFather). |
Problemas conhecidos
- O Sepay não envia informações do cliente: O Sepay envia apenas
content(a mensagem que o cliente colou na transferência) +amount+referenceCode. Para obter nome/telefone/e-mail, você PRECISA de um armazenamento de leads (consulta porcontentcontendo o ID do pedido). Sem isso, a notificação do Telegram será genérica. - Latência: O Telegram é geralmente rápido (< 2 segundos), mas em casos raros pode levar até 5 segundos. O fluxo de pagamento não é afetado — o Telegram é apenas um efeito colateral.
- Taxa de limite da API do Telegram: A API pública do Telegram tem um limite de taxa (aproximadamente 30 mensagens/segundo para bots). Para landing pages de alto volume, considere adicionar uma fila (por exemplo, Redis BullMQ ou Vercel Queues) para o envio de notificações do Telegram.
- Fuso horário: O horário é fixado em UTC+7 (Horário do Vietnã). Se o proprietário estiver em outro fuso horário, você precisará ajustar o helper de formatação.