feat(admin): add admin notification system for deposits, purchases, and renewals

This commit is contained in:
2026-06-04 12:35:32 +07:00
parent 5ec9491e22
commit 4c5a16a3ca
11 changed files with 249 additions and 59 deletions

View File

@@ -0,0 +1,111 @@
import logging
from html import escape
from aiogram import Bot
from config import settings
logger = logging.getLogger(__name__)
async def _send_admin_log(bot: Bot, topic_id: int | None, text: str) -> None:
if settings.admin_log_chat_id is None or topic_id is None:
return
try:
await bot.send_message(
settings.admin_log_chat_id,
text,
message_thread_id=topic_id,
disable_web_page_preview=True,
)
except Exception:
logger.warning("Failed to send admin log notification", exc_info=True)
def _format_username(username: str | None) -> str:
if not username:
return "-"
return "@" + escape(username)
async def notify_deposit(
bot: Bot,
*,
user_id: int,
amount: int,
balance_before: int,
balance_after: int,
bill_id: int,
payment_id: str,
) -> None:
await _send_admin_log(
bot,
settings.admin_log_deposit_topic_id,
"\n".join(
[
"<b>Пополнение баланса</b>",
f"Пользователь: <code>{user_id}</code>",
f"Сумма: <b>{amount}₽</b>",
f"Баланс: {balance_before}₽ -> {balance_after}",
f"Bill ID: <code>{bill_id}</code>",
f"Payment ID: <code>{escape(payment_id)}</code>",
]
),
)
async def notify_subscription_purchase(
bot: Bot,
*,
user_id: int,
username: str | None,
amount: int,
balance_before: int,
balance_after: int,
devices: int,
duration_days: int,
whitelists: bool,
credit: int,
) -> None:
lines = [
"<b>Покупка подписки</b>",
f"Пользователь: <code>{user_id}</code> ({_format_username(username)})",
f"Стоимость: <b>{amount}₽</b>",
f"Баланс: {balance_before}₽ -> {balance_after}",
f"Устройства: {devices}",
f"Срок: {duration_days} дн.",
f"Whitelist: {'да' if whitelists else 'нет'}",
]
if credit > 0:
lines.append(f"Зачет неиспользованных дней: {credit}")
await _send_admin_log(bot, settings.admin_log_subscriptions_topic_id, "\n".join(lines))
async def notify_subscription_renewal(
bot: Bot,
*,
user_id: int,
amount: int,
balance_before: int,
balance_after: int,
devices: int,
duration_days: int,
whitelists: bool,
end_date: str,
) -> None:
await _send_admin_log(
bot,
settings.admin_log_subscriptions_topic_id,
(
"<b>Продление подписки</b>",
f"Пользователь: <code>{user_id}</code>",
f"Стоимость: <b>{amount}₽</b>",
f"Баланс: {balance_before}₽ -> {balance_after}",
f"Устройства: {devices}",
f"Срок: {duration_days} дн.",
f"Whitelist: {'да' if whitelists else 'нет'}",
f"Новая дата окончания: {escape(end_date)}",
),
)

View File

@@ -14,6 +14,7 @@ from repositories.subscriptions import SubscriptionRepository
from repositories.users import UserRepository
from schemas.subscriptions import InternalSquads, SubscriptionPlan, SubscriptionStatus
from services import rw as rw_service
from services.admin_notifications import notify_subscription_renewal
logger = logging.getLogger(__name__)
@@ -91,7 +92,7 @@ class RenewalService:
await self._notify_user(user_id, AUTORENEWAL_INSUFFICIENT_FUNDS)
return False
result = await self.user_repository.decrease_user_balance(
renewed_user = await self.user_repository.decrease_user_balance(
session,
user_id,
price,
@@ -99,7 +100,7 @@ class RenewalService:
description=f"autorenewal: devices={subscription.devices}, duration={subscription.duration_days}d, whitelists={subscription.whitelists}",
)
if not result:
if not renewed_user:
logger.error("Failed to decrease balance for user %d", user_id)
return False
@@ -136,6 +137,17 @@ class RenewalService:
end_date=new_end_date.strftime("%d.%m.%Y"),
),
)
await notify_subscription_renewal(
self.bot,
user_id=user_id,
amount=price,
balance_before=renewed_user.balance + price,
balance_after=renewed_user.balance,
devices=subscription.devices,
duration_days=subscription.duration_days,
whitelists=subscription.whitelists,
end_date=new_end_date.strftime("%d.%m.%Y"),
)
logger.info("Successfully renewed subscription for user %d", user_id)
return True