diff --git a/app/main.py b/app/main.py
index 335e041..abf5156 100644
--- a/app/main.py
+++ b/app/main.py
@@ -72,6 +72,10 @@ ADMIN_TOKEN = os.getenv("ADMIN_TOKEN", "")
# ── Подписка через Telegram-бота (подписанные deep-link) ──────────────────────
SUB_LINK_SECRET = os.getenv("SUB_LINK_SECRET", "") # общий секрет бота и веба
+# ── Уведомления о платежах в Telegram ─────────────────────────────────────────
+BOT_TOKEN = os.getenv("TOKEN", "")
+PAYMENT_NOTIFY_CHAT_ID = int(os.getenv("PAYMENT_NOTIFY_CHAT_ID", "156921693"))
+
# ── Вход по почте (magic-code через SMTP) ─────────────────────────────────────
SMTP_HOST = os.getenv("SMTP_HOST", "")
SMTP_PORT = int(os.getenv("SMTP_PORT", "465"))
@@ -362,6 +366,13 @@ def _db_ensure_auth_table():
con.execute(f"ALTER TABLE WEB_ACCOUNTS ADD COLUMN {_c} {_ddl}")
except Exception as _amig:
print(f"WEB_ACCOUNTS migrate error: {_amig}")
+ # Авто-миграция WEB_PAYMENTS: флаг отправленного уведомления (дедуп)
+ try:
+ pcols = {row[1] for row in con.execute("PRAGMA table_info(WEB_PAYMENTS)")}
+ if "notified" not in pcols:
+ con.execute("ALTER TABLE WEB_PAYMENTS ADD COLUMN notified INTEGER DEFAULT 0")
+ except Exception as _pmig:
+ print(f"WEB_PAYMENTS migrate error: {_pmig}")
con.commit()
except Exception as e:
print(f"WEB_AUTH_TOKENS create error: {e}")
@@ -1748,6 +1759,45 @@ def _db_set_autorenew_flag(account_id, enable: bool) -> dict:
return {"autorenew": False, "has_method": False}
+def _tg_send(chat_id, text):
+ """Отправка сообщения в Telegram напрямую через Bot API (для уведомлений из веба)."""
+ if not BOT_TOKEN or not chat_id:
+ return
+ try:
+ data = _urlparse.urlencode({"chat_id": chat_id, "text": text,
+ "disable_web_page_preview": "true"}).encode()
+ req = _urlreq.Request(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
+ data=data, method="POST")
+ with _urlreq.urlopen(req, timeout=15) as resp:
+ resp.read()
+ except Exception as e:
+ print(f"tg notify error: {e}")
+
+
+def _notify_owner_payment(payment_id):
+ """Уведомляет владельца об УСПЕШНОМ платеже. Дедуп через WEB_PAYMENTS.notified (шлём один раз)."""
+ try:
+ with sqlite3.connect(DB_PATH, timeout=10) as con:
+ con.row_factory = sqlite3.Row
+ cur = con.execute("UPDATE WEB_PAYMENTS SET notified=1 WHERE payment_id=? AND COALESCE(notified,0)=0",
+ (payment_id,))
+ con.commit()
+ if cur.rowcount != 1:
+ return # уже уведомляли
+ row = con.execute("SELECT amount, purpose, email FROM WEB_PAYMENTS WHERE payment_id=?",
+ (payment_id,)).fetchone()
+ except Exception:
+ return
+ if not row:
+ return
+ label = {"donate": "Донат", "api_pro": "API-тариф", "file": "Оплата файла",
+ "sub": "Подписка"}.get(row["purpose"], row["purpose"] or "Платёж")
+ text = f"💰 Оплата получена\n{label}: {int(round(row['amount'] or 0))} ₽"
+ if row["email"]:
+ text += f"\n📧 {row['email']}"
+ _tg_send(PAYMENT_NOTIFY_CHAT_ID, text)
+
+
def _db_mark_payment_paid(payment_id, purpose, api_key, extra=None, payment_method=None):
"""extra: metadata платежа (job_id для file, account_id для sub).
payment_method: объект payment_method из ЮKassa (для сохранения при автопродлении)."""
@@ -1777,6 +1827,8 @@ def _db_mark_payment_paid(payment_id, purpose, api_key, extra=None, payment_meth
job_id = extra.get("job_id")
if job_id and _db_get_url_res_f(job_id) == "awaiting_payment":
_db_set_url_res_f(job_id, "-")
+ # Уведомление владельцу в Telegram (в отдельном потоке — не блокируем event loop)
+ threading.Thread(target=_notify_owner_payment, args=(payment_id,), daemon=True).start()
except Exception as e:
print(f"mark paid error: {e}")
@@ -2681,6 +2733,8 @@ _ADMIN_HTML = """
.bars{ display:flex; align-items:flex-end; gap:5px; height:90px; }
.bars .b{ flex:1; background:var(--blue); border-radius:4px 4px 0 0; min-height:2px; position:relative; opacity:.85; }
.bars .b span{ position:absolute; bottom:-18px; left:0; right:0; text-align:center; font-size:10px; color:var(--sec); }
+ .bars .b b{ position:absolute; top:-15px; left:0; right:0; text-align:center; font-size:10px; font-weight:600; color:var(--ink); }
+ .bars{ margin-top:16px; }
.muted{ color:var(--sec); }
#gate{ max-width:360px; margin:80px auto; background:#fff; border-radius:16px; padding:24px; text-align:center; }
#gate input{ width:100%; padding:11px 12px; border:1px solid var(--line); border-radius:10px; font:inherit; margin:14px 0; }
@@ -2711,6 +2765,7 @@ _ADMIN_HTML = """
+
@@ -2724,6 +2779,12 @@ _ADMIN_HTML = """
+
+