diff --git a/README.md b/README.md index 30b3850..561a451 100644 --- a/README.md +++ b/README.md @@ -1260,9 +1260,10 @@ Public status lookup for SSH users and Xray/V2Ray UUIDs (CORS `*`). See the **Pu ### Como configurar 1. Abra o painel → aba **Bot / Vendas**. 2. Em **Configuração**: marque *Bot ativo*, cole o **Token do Telegram** (via @BotFather) e o **Access Token do Mercado Pago**. Clique **Testar conexão** e depois **Salvar**. -3. **Webhook × Polling** (o painel deixa você escolher): - - **Telegram**: `polling` (padrão, não precisa de domínio/HTTPS) ou `webhook` (informe a URL pública `https://SEU_DOMINIO/api/telegram/webhook`). - - **Mercado Pago**: `polling` (o bot consulta o status a cada intervalo) ou `webhook` (configure no painel do Mercado Pago a URL `https://SEU_DOMINIO/api/mp/webhook`). +3. **Confirmação do pagamento — Webhook × Polling** (o painel deixa você escolher): + - **Polling** (padrão): o bot consulta o status do PIX a cada intervalo. Não precisa de domínio/HTTPS. + - **Webhook**: configure no painel do Mercado Pago a URL `https://SEU_DOMINIO/api/mp/webhook`. O painel mostra a URL exata quando você seleciona esse modo. + - O **Telegram** usa long-polling automático — não requer domínio, webhook nem configuração extra. 4. Em **Planos**: crie planos SSH e/ou Xray (dias, conexões, preço em R$, e — para Xray — o *inbound* e protocolo). Para revendedores, defina o *custo em créditos*. 5. Em **Pacotes de Crédito**: defina os valores de recarga dos revendedores. 6. Em **Clientes do Bot**: promova um usuário a **revendedor** (vinculando-o a uma conta de revendedor em *Revendedores*), ajuste créditos ou bloqueie. @@ -1279,7 +1280,6 @@ Os tokens (Telegram, Mercado Pago e segredos de webhook) são gravados **criptog - `GET/POST /api/bot/transactions` — lista pagamentos; POST com `action` = `refund` \| `reprocess`. - `GET/POST /api/bot/settings` — textos do bot (chave/valor). - `POST /api/bot/test` — testa token do Telegram e do Mercado Pago. -- `POST /api/mp/webhook` — **público**, chamado pelo Mercado Pago (valida `x-signature` se houver segredo; sempre reconfirma o pagamento na API antes de liberar). -- `POST /api/telegram/webhook` — **público**, chamado pelo Telegram (valida o header `X-Telegram-Bot-Api-Secret-Token`). +- `POST /api/mp/webhook` — **público**, chamado pelo Mercado Pago quando o modo de confirmação é *webhook* (valida `x-signature` se houver segredo; sempre reconfirma o pagamento na API antes de liberar). Ignorado em modo *polling*. -**EN-US:** The panel ships an in-process Telegram bot that sells **SSH and Xray** accounts via **PIX (Mercado Pago)**, with free trial, renewal, and a **credit-based reseller system** — all managed from the superadmin **Bot / Vendas** tab. Bot secrets are stored **AES-256-GCM encrypted** in PostgreSQL; the master key lives outside the DB (`BOT_MASTER_KEY` env or a `0600` `/opt/sshpanel/bot_master.key` auto-generated on first use — back it up). Both Telegram delivery and Mercado Pago confirmation can be toggled between **polling** (default, no public HTTPS needed) and **webhook** in the panel. +**EN-US:** The panel ships an in-process Telegram bot that sells **SSH and Xray** accounts via **PIX (Mercado Pago)**, with free trial, renewal, and a **credit-based reseller system** — all managed from the superadmin **Bot / Vendas** tab. Bot secrets are stored **AES-256-GCM encrypted** in PostgreSQL; the master key lives outside the DB (`BOT_MASTER_KEY` env or a `0600` `/opt/sshpanel/bot_master.key` auto-generated on first use — back it up). Telegram runs on long-polling (no domain needed); only **Mercado Pago confirmation** is toggleable between **polling** (default) and **webhook** in the panel. diff --git a/admin/assets/js/12-bot.js b/admin/assets/js/12-bot.js index 926d887..8d930e2 100644 --- a/admin/assets/js/12-bot.js +++ b/admin/assets/js/12-bot.js @@ -32,8 +32,6 @@ async function loadBotConfig() { const set = (id, v) => { const e = document.getElementById(id); if (e) e.value = v ?? ""; }; const chk = (id, v) => { const e = document.getElementById(id); if (e) e.checked = !!v; }; chk("botEnabled", c.enabled); - set("botTelegramMode", c.telegram_mode); - set("botTelegramWebhookURL", c.telegram_webhook_url); set("botMPConfirmMode", c.mp_confirm_mode); set("botMPPollInterval", c.mp_poll_interval); set("botPixExp", c.pix_expiration_minutes); @@ -45,14 +43,23 @@ async function loadBotConfig() { set("botAdminIDs", (c.admin_telegram_ids || []).join(",")); set("botPublicHost", c.public_host); set("botXrayPublicHost", c.xray_public_host); - document.getElementById("botHasTgToken").textContent = c.has_telegram_token ? "✓ configurado" : "não definido"; - document.getElementById("botHasTgSecret").textContent = c.has_telegram_webhook_secret ? "✓ configurado" : "não definido"; - document.getElementById("botHasMpToken").textContent = c.has_mp_access_token ? "✓ configurado" : "não definido"; - document.getElementById("botHasMpSecret").textContent = c.has_mp_webhook_secret ? "✓ configurado" : "não definido"; + const hint = (id, ok) => { const e = document.getElementById(id); if (e) e.textContent = ok ? "✓ configurado" : "não definido"; }; + hint("botHasTgToken", c.has_telegram_token); + hint("botHasMpToken", c.has_mp_access_token); + hint("botHasMpSecret", c.has_mp_webhook_secret); + botToggleMPWebhookBox(); botStatus("botConfigStatus", "Carregado."); } catch (e) { if (e.message !== "auth") botStatus("botConfigStatus", "Erro ao carregar.", false); } } +function botToggleMPWebhookBox() { + const mode = document.getElementById("botMPConfirmMode")?.value; + const box = document.getElementById("botMPWebhookBox"); + if (box) box.style.display = mode === "webhook" ? "" : "none"; + const url = document.getElementById("botMPWebhookURL"); + if (url) url.textContent = location.origin + "/api/mp/webhook"; +} + async function saveBotConfig() { const val = id => (document.getElementById(id)?.value || "").trim(); const num = id => parseInt(document.getElementById(id)?.value || "0", 10) || 0; @@ -61,9 +68,6 @@ async function saveBotConfig() { const body = { enabled: chk("botEnabled"), telegram_token: val("botTelegramToken"), - telegram_mode: val("botTelegramMode"), - telegram_webhook_url: val("botTelegramWebhookURL"), - telegram_webhook_secret: val("botTelegramWebhookSecret"), mp_access_token: val("botMPToken"), mp_confirm_mode: val("botMPConfirmMode"), mp_webhook_secret: val("botMPWebhookSecret"), @@ -80,7 +84,7 @@ async function saveBotConfig() { }; try { await api("/api/bot/config", { method: "POST", body: JSON.stringify(body) }); - ["botTelegramToken", "botTelegramWebhookSecret", "botMPToken", "botMPWebhookSecret"].forEach(id => { const e = document.getElementById(id); if (e) e.value = ""; }); + ["botTelegramToken", "botMPToken", "botMPWebhookSecret"].forEach(id => { const e = document.getElementById(id); if (e) e.value = ""; }); botStatus("botConfigStatus", "Configuração salva e bot reiniciado."); loadBotConfig(); } catch (e) { if (e.message !== "auth") botStatus("botConfigStatus", "Erro ao salvar.", false); } @@ -309,6 +313,7 @@ async function saveBotSettings() { document.getElementById("botConfigSaveBtn")?.addEventListener("click", saveBotConfig); document.getElementById("botConfigReloadBtn")?.addEventListener("click", loadBotConfig); document.getElementById("botTestBtn")?.addEventListener("click", testBot); +document.getElementById("botMPConfirmMode")?.addEventListener("change", botToggleMPWebhookBox); document.getElementById("botReloadPlansBtn")?.addEventListener("click", loadBotPlans); document.getElementById("botNewPlanBtn")?.addEventListener("click", botClearPlanForm); document.getElementById("botCancelPlanBtn")?.addEventListener("click", botClearPlanForm); diff --git a/admin/index.html b/admin/index.html index 88909ba..14b970c 100644 --- a/admin/index.html +++ b/admin/index.html @@ -16,7 +16,7 @@ setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500); })(); - +
1) Cole os tokens abaixo e clique Testar · 2) Crie os Planos · 3) Promova revendedores em Clientes. O Telegram funciona por long-polling — não precisa de domínio.
+Enviado ao cliente nas credenciais após o pagamento.