Фиксы UI

This commit is contained in:
gy9vin
2026-01-16 15:58:22 +03:00
parent 5a64dbf209
commit 94cd06302a
2 changed files with 82 additions and 175 deletions

View File

@@ -752,95 +752,13 @@ async def auto_activate_subscription_after_topup(
subscription = await get_subscription_by_user_id(db, user.id)
# Если автоактивация отключена - только отправляем уведомление
# Если автоактивация отключена - уведомление отправится из _send_payment_success_notification
if not settings.is_auto_activate_after_topup_enabled():
# Если включен яркий промпт активации, НЕ отправляем уведомление здесь
# т.к. оно будет отправлено через _send_payment_success_notification
if settings.SHOW_ACTIVATION_PROMPT_AFTER_TOPUP:
logger.info(
"⚠️ Пропущена отправка уведомления пользователю %s (SHOW_ACTIVATION_PROMPT_AFTER_TOPUP=true, уведомление будет из payment service)",
user.telegram_id,
)
return (False, False)
# Старая логика уведомлений для режима без яркого промпта
notification_sent = False
if bot:
try:
texts = get_texts(getattr(user, "language", "ru"))
has_active_subscription = (
subscription
and subscription.status in ("active", "ACTIVE")
)
# Формируем строку с суммой пополнения
topup_line = ""
if topup_amount:
topup_line = f" Пополнено: <b>{settings.format_price(topup_amount)}</b>\n"
# Определяем состояние подписки
is_trial = subscription and getattr(subscription, 'is_trial', False)
if has_active_subscription and not is_trial:
# Активная платная подписка — 2 кнопки
warning_message = (
f"✅ <b>Баланс пополнен!</b>\n\n"
f"{topup_line}"
f"💳 Текущий баланс: <b>{settings.format_price(user.balance_kopeks)}</b>\n\n"
f"👇 <b>Выберите действие:</b>"
)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="💎 Продлить подписку",
callback_data="subscription_extend",
)],
[InlineKeyboardButton(
text="📱 Изменить устройства",
callback_data="subscription_change_devices",
)],
]
)
else:
# Триал или подписка закончилась — 1 кнопка
warning_message = (
f"✅ <b>Баланс пополнен!</b>\n\n"
f"{topup_line}"
f"💳 Текущий баланс: <b>{settings.format_price(user.balance_kopeks)}</b>\n\n"
f"{'' * 20}\n\n"
f"🚨🚨🚨 <b>ВНИМАНИЕ!</b> 🚨🚨🚨\n\n"
f"🔴 <b>ПОДПИСКА НЕ АКТИВНА!</b>\n\n"
f"⚠️ Пополнение баланса <b>НЕ активирует</b> подписку автоматически!\n\n"
f"👇 <b>Обязательно оформите подписку:</b>"
)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="🚀 КУПИТЬ ПОДПИСКУ",
callback_data="menu_buy",
)],
]
)
await bot.send_message(
chat_id=user.telegram_id,
text=warning_message,
reply_markup=keyboard,
parse_mode="HTML",
)
notification_sent = True
logger.info(
"⚠️ Отправлено уведомление о пополнении баланса пользователю %s (автоактивация выключена, подписка %s)",
user.telegram_id,
"активна" if has_active_subscription else "неактивна",
)
except Exception as notify_error:
logger.warning(
"⚠️ Не удалось отправить уведомление пользователю %s: %s",
user.telegram_id,
notify_error,
)
return (False, notification_sent)
logger.info(
"⚠️ Автоактивация отключена для пользователя %s, уведомление будет отправлено из payment service",
user.telegram_id,
)
return (False, False)
# Если подписка активна — ничего не делаем (автоактивация включена, но подписка уже есть)
if subscription and subscription.status == "ACTIVE" and subscription.end_date > datetime.utcnow():
@@ -921,93 +839,12 @@ async def auto_activate_subscription_after_topup(
user.telegram_id,
balance,
)
# Если включен яркий промпт активации, НЕ отправляем уведомление здесь
# т.к. оно будет отправлено через _send_payment_success_notification
if settings.SHOW_ACTIVATION_PROMPT_AFTER_TOPUP:
logger.info(
"⚠️ Пропущена отправка уведомления пользователю %s (SHOW_ACTIVATION_PROMPT_AFTER_TOPUP=true, уведомление будет из payment service)",
user.telegram_id,
)
return (False, False)
# Старая логика уведомлений для режима без яркого промпта
notification_sent = False
if bot:
try:
texts = get_texts(getattr(user, "language", "ru"))
has_active_subscription = (
subscription
and subscription.status in ("active", "ACTIVE")
)
# Формируем строку с суммой пополнения
topup_line2 = ""
if topup_amount:
topup_line2 = f" Пополнено: <b>{settings.format_price(topup_amount)}</b>\n"
# Определяем состояние подписки
is_trial2 = subscription and getattr(subscription, 'is_trial', False)
if has_active_subscription and not is_trial2:
# Активная платная подписка — 2 кнопки
warning_message = (
f"✅ <b>Баланс пополнен!</b>\n\n"
f"{topup_line2}"
f"💳 Текущий баланс: <b>{settings.format_price(balance)}</b>\n\n"
f"👇 <b>Выберите действие:</b>"
)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="💎 Продлить подписку",
callback_data="subscription_extend",
)],
[InlineKeyboardButton(
text="📱 Изменить устройства",
callback_data="subscription_change_devices",
)],
]
)
else:
# Триал или подписка закончилась — 1 кнопка
warning_message = (
f"✅ <b>Баланс пополнен!</b>\n\n"
f"{topup_line2}"
f"💳 Текущий баланс: <b>{settings.format_price(balance)}</b>\n\n"
f"{'' * 20}\n\n"
f"🚨🚨🚨 <b>ВНИМАНИЕ!</b> 🚨🚨🚨\n\n"
f"🔴 <b>ПОДПИСКА НЕ АКТИВНА!</b>\n\n"
f"⚠️ Пополнение баланса <b>НЕ активирует</b> подписку автоматически!\n\n"
f"👇 <b>Обязательно оформите подписку:</b>"
)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="🚀 КУПИТЬ ПОДПИСКУ",
callback_data="menu_buy",
)],
]
)
await bot.send_message(
chat_id=user.telegram_id,
text=warning_message,
reply_markup=keyboard,
parse_mode="HTML",
)
notification_sent = True
logger.info(
"⚠️ Отправлено уведомление о пополнении баланса пользователю %s (недостаточно средств, подписка %s)",
user.telegram_id,
"активна" if has_active_subscription else "неактивна",
)
except Exception as notify_error:
logger.warning(
"⚠️ Не удалось отправить уведомление пользователю %s: %s",
user.telegram_id,
notify_error,
)
return (False, notification_sent)
# Уведомление отправится из _send_payment_success_notification
logger.info(
"⚠️ Недостаточно средств для автоактивации пользователя %s, уведомление будет отправлено из payment service",
user.telegram_id,
)
return (False, False)
texts = get_texts(getattr(user, "language", "ru"))

View File

@@ -259,6 +259,7 @@ class BotConfigurationService:
"PAYMENT_BALANCE_TEMPLATE": "PAYMENT",
"PAYMENT_SUBSCRIPTION_TEMPLATE": "PAYMENT",
"AUTO_PURCHASE_AFTER_TOPUP_ENABLED": "PAYMENT",
"SHOW_ACTIVATION_PROMPT_AFTER_TOPUP": "PAYMENT",
"SIMPLE_SUBSCRIPTION_ENABLED": "SIMPLE_SUBSCRIPTION",
"SIMPLE_SUBSCRIPTION_PERIOD_DAYS": "SIMPLE_SUBSCRIPTION",
"SIMPLE_SUBSCRIPTION_DEVICE_LIMIT": "SIMPLE_SUBSCRIPTION",
@@ -271,6 +272,10 @@ class BotConfigurationService:
"NOTIFICATION_CACHE_HOURS": "NOTIFICATIONS",
"MONITORING_LOGS_RETENTION_DAYS": "MONITORING",
"MONITORING_INTERVAL": "MONITORING",
"TRAFFIC_MONITORING_ENABLED": "MONITORING",
"TRAFFIC_MONITORING_INTERVAL_HOURS": "MONITORING",
"TRAFFIC_MONITORED_NODES": "MONITORING",
"TRAFFIC_SNAPSHOT_TTL_HOURS": "MONITORING",
"ENABLE_LOGO_MODE": "INTERFACE_BRANDING",
"LOGO_FILE": "INTERFACE_BRANDING",
"HIDE_SUBSCRIPTION_LINK": "INTERFACE_SUBSCRIPTION",
@@ -570,6 +575,19 @@ class BotConfigurationService:
"Используйте с осторожностью: средства будут списаны мгновенно, если корзина найдена."
),
},
"SHOW_ACTIVATION_PROMPT_AFTER_TOPUP": {
"description": (
"Включает режим яркого промпта активации подписки после пополнения баланса. "
"Вместо обычного уведомления пользователь получит яркое сообщение с восклицательными знаками "
"и кнопками для активации/продления подписки или изменения количества устройств."
),
"format": "Булево значение.",
"example": "true",
"warning": (
"При включении пользователи будут получать только яркое уведомление без кнопок баланса и главного меню. "
"Эти кнопки появятся после выполнения действия (активация/продление/изменение устройств)."
),
},
"SUPPORT_TICKET_SLA_MINUTES": {
"description": "Лимит времени для ответа модераторов на тикет в минутах.",
"format": "Целое число от 1 до 1440.",
@@ -710,6 +728,58 @@ class BotConfigurationService:
"warning": "Убедитесь, что конфигурация существует в панели и содержит нужные приложения.",
"dependencies": "Настроенное подключение к RemnaWave API",
},
"TRAFFIC_MONITORING_ENABLED": {
"description": (
"Включает автоматический мониторинг трафика пользователей. "
"Система отслеживает изменения трафика (дельту) и сохраняет snapshot в Redis. "
"При превышении порогов отправляются уведомления пользователям и админам."
),
"format": "Булево значение.",
"example": "true",
"warning": (
"Требует настроенного подключения к Redis. "
"При включении будет запущен фоновый мониторинг трафика по расписанию."
),
"dependencies": "Redis, TRAFFIC_MONITORING_INTERVAL_HOURS, TRAFFIC_SNAPSHOT_TTL_HOURS",
},
"TRAFFIC_MONITORING_INTERVAL_HOURS": {
"description": (
"Интервал проверки трафика в часах. "
"Каждые N часов система проверяет трафик всех активных пользователей и сравнивает с предыдущим snapshot."
),
"format": "Целое число часов (минимум 1).",
"example": "24",
"warning": (
"Слишком маленький интервал может создать большую нагрузку на RemnaWave API. "
"Рекомендуется 24 часа для ежедневного мониторинга."
),
"dependencies": "TRAFFIC_MONITORING_ENABLED",
},
"TRAFFIC_MONITORED_NODES": {
"description": (
"Список UUID нод для мониторинга трафика через запятую. "
"Если пусто - мониторятся все ноды. "
"Позволяет ограничить мониторинг только определенными серверами."
),
"format": "UUID через запятую или пусто для всех нод.",
"example": "d4aa2b8c-9a36-4f31-93a2-6f07dad05fba, a1b2c3d4-5678-90ab-cdef-1234567890ab",
"warning": "UUID должны существовать в RemnaWave, иначе мониторинг не будет работать.",
"dependencies": "TRAFFIC_MONITORING_ENABLED",
},
"TRAFFIC_SNAPSHOT_TTL_HOURS": {
"description": (
"Время жизни (TTL) snapshot трафика в Redis в часах. "
"Snapshot используется для вычисления дельты (изменения трафика) между проверками. "
"После истечения TTL snapshot удаляется и создается новый."
),
"format": "Целое число часов (минимум 1).",
"example": "24",
"warning": (
"TTL должен быть >= интервала мониторинга. "
"Если TTL меньше интервала, snapshot будет удален до следующей проверки."
),
"dependencies": "TRAFFIC_MONITORING_ENABLED, Redis",
},
}
@classmethod