diff --git a/.gitignore b/.gitignore
index 594cf03..e0aef9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/shell2.exe
+/BOT_PLAN.md
diff --git a/README.md b/README.md
index 48a54bf..30b3850 100644
--- a/README.md
+++ b/README.md
@@ -1250,3 +1250,36 @@ Reads/writes the panel's `config.json` and hot-applies changes.
#### `GET /check` — none
Public status lookup for SSH users and Xray/V2Ray UUIDs (CORS `*`). See the **Public CheckUser API** section above for query params, response fields, and examples.
+
+---
+
+## Bot de Vendas (Telegram + Mercado Pago)
+
+**PT-BR:** O painel inclui um bot de Telegram integrado ao próprio binário para vender contas **SSH e Xray** por **PIX** (Mercado Pago), com **teste grátis**, **renovação**, e um **sistema de revendedores por créditos**. Toda a gestão fica na aba **Bot / Vendas** do painel (somente superadmin).
+
+### 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`).
+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.
+7. Em **Mensagens**: edite os textos de boas-vindas, contato e link do app.
+
+### Segurança dos segredos
+Os tokens (Telegram, Mercado Pago e segredos de webhook) são gravados **criptografados (AES-256-GCM)** na tabela `bot_config` do PostgreSQL. A **chave-mestra** fica **fora do banco**: variável de ambiente `BOT_MASTER_KEY` (64 caracteres hex) ou, se ausente, um arquivo `0600` em `/opt/sshpanel/bot_master.key` gerado automaticamente no primeiro uso. **Faça backup desse arquivo** junto com o banco — sem ele os segredos não podem ser decifrados. A API do painel nunca retorna os tokens em texto puro.
+
+### Endpoints (superadmin, exceto webhooks)
+- `GET/POST /api/bot/config` — lê/grava a configuração (segredos só entram no POST; campo vazio mantém o atual; o GET retorna apenas `has_*`).
+- `GET/POST/DELETE /api/bot/plans` — CRUD de planos (`?id=` no DELETE).
+- `GET/POST/DELETE /api/bot/credit-packages` — CRUD de pacotes de crédito.
+- `GET/POST /api/bot/users` — lista clientes; POST com `action` = `set_role` \| `block` \| `unblock` \| `adjust_credits`.
+- `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`).
+
+**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.
diff --git a/admin/assets/js/02-shell.js b/admin/assets/js/02-shell.js
index 0bec7eb..f974a98 100644
--- a/admin/assets/js/02-shell.js
+++ b/admin/assets/js/02-shell.js
@@ -9,6 +9,7 @@ const tabTitles = {
stats: ["Server", "Monitoring"],
vnstat: ["Traffic", "VnStat"],
logs: ["System", "Logs"],
+ bot: ["Vendas", "Bot / Telegram"],
server: ["System", "Settings"],
};
function updatePageHeading() {
@@ -39,6 +40,7 @@ function selectTab(tab) {
if (tab === "servers-status" && currentRole === "superadmin") loadServersStatus();
if (tab === "resellers" && currentRole === "superadmin") loadResellers();
if (tab === "servers" && currentRole === "superadmin") loadServers();
+ if (tab === "bot" && currentRole === "superadmin" && typeof loadBotTab === "function") loadBotTab();
}
document.querySelectorAll(".tab-btn").forEach(btn => btn.addEventListener("click", () => selectTab(btn.dataset.tab)));
diff --git a/admin/assets/js/12-bot.js b/admin/assets/js/12-bot.js
new file mode 100644
index 0000000..926d887
--- /dev/null
+++ b/admin/assets/js/12-bot.js
@@ -0,0 +1,323 @@
+// ─── Bot / Vendas (Telegram + Mercado Pago) ─────────────────────────────────
+// Superadmin-only tab. Uses the shared api() helper from 01-core.js.
+
+function botEsc(s) {
+ return String(s == null ? "" : s)
+ .replace(/&/g, "&").replace(//g, ">")
+ .replace(/"/g, """).replace(/'/g, "'");
+}
+function botStatus(id, msg, ok) {
+ const el = document.getElementById(id);
+ if (el) { el.textContent = msg; el.style.color = ok === false ? "var(--danger,#e5484d)" : ""; }
+}
+function brl(cents) {
+ return "R$ " + (Number(cents || 0) / 100).toFixed(2).replace(".", ",");
+}
+
+async function loadBotTab() {
+ loadBotConfig();
+ loadBotInbounds();
+ loadBotPlans();
+ loadBotPkgs();
+ loadBotUsers();
+ loadBotTxns();
+ loadBotSettings();
+}
+
+// ─── Config ───
+async function loadBotConfig() {
+ try {
+ const res = await api("/api/bot/config");
+ const c = await res.json();
+ 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);
+ chk("botTrialEnabled", c.trial_enabled);
+ set("botTrialHours", c.trial_hours);
+ set("botTrialMaxConns", c.trial_max_connections);
+ set("botTrialKind", c.trial_kind);
+ set("botTrialInbound", c.trial_inbound_tag);
+ 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";
+ botStatus("botConfigStatus", "Carregado.");
+ } catch (e) { if (e.message !== "auth") botStatus("botConfigStatus", "Erro ao carregar.", false); }
+}
+
+async function saveBotConfig() {
+ const val = id => (document.getElementById(id)?.value || "").trim();
+ const num = id => parseInt(document.getElementById(id)?.value || "0", 10) || 0;
+ const chk = id => !!document.getElementById(id)?.checked;
+ const ids = val("botAdminIDs").split(",").map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
+ 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"),
+ mp_poll_interval: val("botMPPollInterval"),
+ pix_expiration_minutes: num("botPixExp"),
+ trial_enabled: chk("botTrialEnabled"),
+ trial_hours: num("botTrialHours"),
+ trial_max_connections: num("botTrialMaxConns"),
+ trial_kind: val("botTrialKind"),
+ trial_inbound_tag: val("botTrialInbound"),
+ admin_telegram_ids: ids,
+ public_host: val("botPublicHost"),
+ xray_public_host: val("botXrayPublicHost"),
+ };
+ 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 = ""; });
+ botStatus("botConfigStatus", "Configuração salva e bot reiniciado.");
+ loadBotConfig();
+ } catch (e) { if (e.message !== "auth") botStatus("botConfigStatus", "Erro ao salvar.", false); }
+}
+
+async function testBot() {
+ botStatus("botConfigStatus", "Testando...");
+ const body = {
+ telegram_token: (document.getElementById("botTelegramToken")?.value || "").trim(),
+ mp_access_token: (document.getElementById("botMPToken")?.value || "").trim(),
+ };
+ try {
+ const res = await api("/api/bot/test", { method: "POST", body: JSON.stringify(body) });
+ const r = await res.json();
+ const tg = r.telegram_ok ? `Telegram OK (${r.telegram_bot || ""})` : `Telegram: ${r.telegram_error || "falha"}`;
+ const mp = r.mp_ok ? "Mercado Pago OK" : `Mercado Pago: ${r.mp_error || "falha"}`;
+ botStatus("botConfigStatus", tg + " · " + mp, r.telegram_ok && r.mp_ok);
+ } catch (e) { if (e.message !== "auth") botStatus("botConfigStatus", "Erro no teste.", false); }
+}
+
+// ─── Inbounds datalist ───
+async function loadBotInbounds() {
+ try {
+ const res = await api("/api/xray/inbounds");
+ const list = await res.json();
+ const dl = document.getElementById("botInboundList");
+ if (dl) dl.innerHTML = (list || []).map(ib => ``).join("");
+ } catch (e) { /* xray may be off; ignore */ }
+}
+
+// ─── Plans ───
+async function loadBotPlans() {
+ try {
+ const res = await api("/api/bot/plans");
+ const plans = await res.json() || [];
+ document.getElementById("botPlanCount").textContent = plans.length;
+ document.getElementById("botPlansBody").innerHTML = plans.map(p => `
+
+ | ${botEsc(p.Name)} | ${botEsc(p.Kind)} | ${p.Days} |
+ ${brl(p.PriceCents)} | ${p.CreditCost} |
+ ${p.IsActive ? "✅" : "—"} |
+
+
+
+ |
+
`).join("");
+ botStatus("botPlansStatus", "Ready.");
+ } catch (e) { if (e.message !== "auth") botStatus("botPlansStatus", "Erro ao carregar.", false); }
+}
+function botEditPlan(p) {
+ const set = (id, v) => { const e = document.getElementById(id); if (e) e.value = v ?? ""; };
+ set("planId", p.ID); set("planName", p.Name); set("planKind", p.Kind); set("planDays", p.Days);
+ set("planMaxConns", p.MaxConnections); set("planUpMbps", p.LimitMbpsUp); set("planDownMbps", p.LimitMbpsDown);
+ set("planInbound", p.XrayInboundTag); set("planProtocol", p.XrayProtocol);
+ set("planPrice", (p.PriceCents / 100).toFixed(2)); set("planCreditCost", p.CreditCost);
+ set("planServerId", p.ServerID); set("planSort", p.SortOrder);
+ document.getElementById("planActive").checked = !!p.IsActive;
+ document.getElementById("botPlanFormTitle").textContent = "Editar Plano #" + p.ID;
+}
+function botClearPlanForm() {
+ document.getElementById("botPlanForm").reset();
+ document.getElementById("planId").value = "";
+ document.getElementById("botPlanFormTitle").textContent = "Novo Plano";
+}
+async function botSavePlan(ev) {
+ ev.preventDefault();
+ const val = id => document.getElementById(id).value;
+ const num = id => parseInt(document.getElementById(id).value || "0", 10) || 0;
+ const body = {
+ id: num("planId"), name: val("planName"), kind: val("planKind"), days: num("planDays"),
+ max_connections: num("planMaxConns"), limit_mbps_up: num("planUpMbps"), limit_mbps_down: num("planDownMbps"),
+ xray_inbound_tag: val("planInbound"), xray_protocol: val("planProtocol"),
+ price_cents: Math.round(parseFloat(val("planPrice") || "0") * 100),
+ credit_cost: num("planCreditCost"), server_id: val("planServerId"), sort_order: num("planSort"),
+ is_active: document.getElementById("planActive").checked,
+ };
+ // map to Go struct JSON tags (exported field names)
+ const payload = {
+ ID: body.id, Name: body.name, Kind: body.kind, Days: body.days, MaxConnections: body.max_connections,
+ LimitMbpsUp: body.limit_mbps_up, LimitMbpsDown: body.limit_mbps_down, XrayInboundTag: body.xray_inbound_tag,
+ XrayProtocol: body.xray_protocol, PriceCents: body.price_cents, CreditCost: body.credit_cost,
+ ServerID: body.server_id, IsActive: body.is_active, SortOrder: body.sort_order,
+ };
+ try {
+ await api("/api/bot/plans", { method: "POST", body: JSON.stringify(payload) });
+ botClearPlanForm(); loadBotPlans(); botStatus("botPlansStatus", "Plano salvo.");
+ } catch (e) { if (e.message !== "auth") botStatus("botPlansStatus", "Erro ao salvar.", false); }
+}
+async function botDeletePlan(id) {
+ if (!confirm("Excluir este plano?")) return;
+ try { await api("/api/bot/plans?id=" + id, { method: "DELETE" }); loadBotPlans(); }
+ catch (e) { if (e.message !== "auth") botStatus("botPlansStatus", "Erro ao excluir.", false); }
+}
+
+// ─── Credit packages ───
+async function loadBotPkgs() {
+ try {
+ const res = await api("/api/bot/credit-packages");
+ const pkgs = await res.json() || [];
+ document.getElementById("botPkgCount").textContent = pkgs.length;
+ document.getElementById("botPkgsBody").innerHTML = pkgs.map(p => `
+
+ | ${botEsc(p.Name)} | ${p.Credits} | ${brl(p.PriceCents)} |
+ ${p.IsActive ? "✅" : "—"} |
+
+
+
+ |
+
`).join("");
+ botStatus("botPkgStatus", "Ready.");
+ } catch (e) { if (e.message !== "auth") botStatus("botPkgStatus", "Erro ao carregar.", false); }
+}
+function botEditPkg(p) {
+ const set = (id, v) => { document.getElementById(id).value = v ?? ""; };
+ set("pkgId", p.ID); set("pkgName", p.Name); set("pkgCredits", p.Credits);
+ set("pkgPrice", (p.PriceCents / 100).toFixed(2)); set("pkgSort", p.SortOrder);
+ document.getElementById("pkgActive").checked = !!p.IsActive;
+}
+function botClearPkgForm() { document.getElementById("botPkgForm").reset(); document.getElementById("pkgId").value = ""; }
+async function botSavePkg(ev) {
+ ev.preventDefault();
+ const val = id => document.getElementById(id).value;
+ const num = id => parseInt(document.getElementById(id).value || "0", 10) || 0;
+ const payload = {
+ ID: num("pkgId"), Name: val("pkgName"), Credits: num("pkgCredits"),
+ PriceCents: Math.round(parseFloat(val("pkgPrice") || "0") * 100),
+ SortOrder: num("pkgSort"), IsActive: document.getElementById("pkgActive").checked,
+ };
+ try { await api("/api/bot/credit-packages", { method: "POST", body: JSON.stringify(payload) }); botClearPkgForm(); loadBotPkgs(); botStatus("botPkgStatus", "Pacote salvo."); }
+ catch (e) { if (e.message !== "auth") botStatus("botPkgStatus", "Erro ao salvar.", false); }
+}
+async function botDeletePkg(id) {
+ if (!confirm("Excluir este pacote?")) return;
+ try { await api("/api/bot/credit-packages?id=" + id, { method: "DELETE" }); loadBotPkgs(); }
+ catch (e) { if (e.message !== "auth") botStatus("botPkgStatus", "Erro ao excluir.", false); }
+}
+
+// ─── Bot users ───
+async function loadBotUsers() {
+ try {
+ const res = await api("/api/bot/users");
+ const users = await res.json() || [];
+ document.getElementById("botUserCount").textContent = users.length;
+ document.getElementById("botUsersBody").innerHTML = users.map(u => `
+
+ | ${u.TelegramID} | ${botEsc(u.FirstName)} | ${botEsc(u.Username)} |
+ ${botEsc(u.Role)} | ${botEsc(u.LinkedAdminUsername)} | ${u.CreditBalance} |
+
+
+
+
+ |
+
`).join("");
+ botStatus("botUsersStatus", "Ready.");
+ } catch (e) { if (e.message !== "auth") botStatus("botUsersStatus", "Erro ao carregar.", false); }
+}
+async function botPromote(tid) {
+ const role = prompt("Papel (customer / reseller / blocked):", "reseller");
+ if (!role) return;
+ let linked = "";
+ if (role === "reseller") { linked = prompt("Username do revendedor (admin_users) vinculado:", "") || ""; }
+ try { await api("/api/bot/users", { method: "POST", body: JSON.stringify({ telegram_id: tid, action: "set_role", role, linked_admin_username: linked }) }); loadBotUsers(); }
+ catch (e) { if (e.message !== "auth") botStatus("botUsersStatus", "Erro.", false); }
+}
+async function botCredit(tid) {
+ const v = prompt("Ajuste de créditos (use negativo para remover):", "10");
+ if (v === null) return;
+ const n = parseInt(v, 10); if (isNaN(n)) return;
+ try { await api("/api/bot/users", { method: "POST", body: JSON.stringify({ telegram_id: tid, action: "adjust_credits", credits: n }) }); loadBotUsers(); }
+ catch (e) { if (e.message !== "auth") botStatus("botUsersStatus", "Erro (saldo insuficiente?).", false); }
+}
+async function botBlock(tid, isBlocked) {
+ try { await api("/api/bot/users", { method: "POST", body: JSON.stringify({ telegram_id: tid, action: isBlocked ? "unblock" : "block" }) }); loadBotUsers(); }
+ catch (e) { if (e.message !== "auth") botStatus("botUsersStatus", "Erro.", false); }
+}
+
+// ─── Transactions ───
+async function loadBotTxns() {
+ const filter = document.getElementById("botTxnFilter")?.value || "";
+ try {
+ const res = await api("/api/bot/transactions?limit=200&status=" + encodeURIComponent(filter));
+ const txns = await res.json() || [];
+ document.getElementById("botTxnCount").textContent = txns.length;
+ document.getElementById("botTxnsBody").innerHTML = txns.map(t => `
+
+ | ${t.ID} | ${t.TelegramID} | ${botEsc(t.Type)} | ${brl(t.AmountCents)} |
+ ${botEsc(t.Status)} | ${botEsc(t.TargetUsername)} |
+ ${botEsc((t.CreatedAt || "").slice(0, 16).replace("T", " "))} |
+
+ ${t.Status === "pending" || t.Status === "approved" ? `` : ""}
+ ${t.Status !== "refunded" ? `` : ""}
+ |
+
`).join("");
+ botStatus("botTxnStatus", "Ready.");
+ } catch (e) { if (e.message !== "auth") botStatus("botTxnStatus", "Erro ao carregar.", false); }
+}
+async function botReprocess(id) {
+ try { await api("/api/bot/transactions", { method: "POST", body: JSON.stringify({ id, action: "reprocess" }) }); botStatus("botTxnStatus", "Reprocessando #" + id + "..."); setTimeout(loadBotTxns, 1500); }
+ catch (e) { if (e.message !== "auth") botStatus("botTxnStatus", "Erro (bot ativo?).", false); }
+}
+async function botRefund(id) {
+ if (!confirm("Marcar pagamento #" + id + " como estornado?")) return;
+ try { await api("/api/bot/transactions", { method: "POST", body: JSON.stringify({ id, action: "refund" }) }); loadBotTxns(); }
+ catch (e) { if (e.message !== "auth") botStatus("botTxnStatus", "Erro.", false); }
+}
+
+// ─── Settings (messages) ───
+async function loadBotSettings() {
+ try {
+ const res = await api("/api/bot/settings");
+ const s = await res.json() || {};
+ const set = (id, v) => { const e = document.getElementById(id); if (e) e.value = v || ""; };
+ set("setWelcome", s.welcome_text); set("setContact", s.contact_text);
+ set("setAppText", s.app_text); set("setAppUrl", s.app_url);
+ botStatus("botSettingsStatus", "Ready.");
+ } catch (e) { if (e.message !== "auth") botStatus("botSettingsStatus", "Erro ao carregar.", false); }
+}
+async function saveBotSettings() {
+ const val = id => document.getElementById(id)?.value || "";
+ const payload = { welcome_text: val("setWelcome"), contact_text: val("setContact"), app_text: val("setAppText"), app_url: val("setAppUrl") };
+ try { await api("/api/bot/settings", { method: "POST", body: JSON.stringify(payload) }); botStatus("botSettingsStatus", "Mensagens salvas."); }
+ catch (e) { if (e.message !== "auth") botStatus("botSettingsStatus", "Erro ao salvar.", false); }
+}
+
+// ─── Wiring ───
+document.getElementById("botConfigSaveBtn")?.addEventListener("click", saveBotConfig);
+document.getElementById("botConfigReloadBtn")?.addEventListener("click", loadBotConfig);
+document.getElementById("botTestBtn")?.addEventListener("click", testBot);
+document.getElementById("botReloadPlansBtn")?.addEventListener("click", loadBotPlans);
+document.getElementById("botNewPlanBtn")?.addEventListener("click", botClearPlanForm);
+document.getElementById("botCancelPlanBtn")?.addEventListener("click", botClearPlanForm);
+document.getElementById("botPlanForm")?.addEventListener("submit", botSavePlan);
+document.getElementById("botReloadPkgsBtn")?.addEventListener("click", loadBotPkgs);
+document.getElementById("botNewPkgBtn")?.addEventListener("click", botClearPkgForm);
+document.getElementById("botPkgForm")?.addEventListener("submit", botSavePkg);
+document.getElementById("botReloadUsersBtn")?.addEventListener("click", loadBotUsers);
+document.getElementById("botReloadTxnsBtn")?.addEventListener("click", loadBotTxns);
+document.getElementById("botTxnFilter")?.addEventListener("change", loadBotTxns);
+document.getElementById("botSaveSettingsBtn")?.addEventListener("click", saveBotSettings);
+document.getElementById("botReloadSettingsBtn")?.addEventListener("click", loadBotSettings);
diff --git a/admin/index.html b/admin/index.html
index e41de4c..88909ba 100644
--- a/admin/index.html
+++ b/admin/index.html
@@ -57,6 +57,7 @@
+
@@ -948,6 +949,177 @@
+
+
+
+
+
+
+
🤖 Configuração do Bot
+
+
+
+
+
+
+
+
+
+
Ready.
+
+
+
+
+
+
+
💎 Planos 0
+
+
+
+
+
+
+
+ | Nome | Tipo | Dias | Preço | Créd. | Ativo | Ações |
+
+
+
+
Ready.
+
+
+
+
+
+
+
+
+
+
+
💳 Pacotes de Crédito 0
+
+
+
+
+
+
+
+ | Nome | Créditos | Preço | Ativo | Ações |
+
+
+
+
+
Ready.
+
+
+
+
+
+
✉️ Mensagens do Bot
+
+
+
+
+
+
+
+
Ready.
+
+
+
+
+
+
+
👥 Clientes do Bot 0
+
+
+
+
+ | Telegram ID | Nome | @user | Papel | Revenda | Créditos | Ações |
+
+
+
+
+
+
+
+
+
+
💰 Pagamentos 0
+
+
+
+
+
+
+
+ | # | Telegram | Tipo | Valor | Status | Conta | Data | Ações |
+
+
+
+
Ready.
+
+
+
+
@@ -1271,6 +1443,7 @@
+