discounts tariffs/notify hot leads/change success payment

This commit is contained in:
Vladless
2025-05-19 09:07:32 +03:00
parent a1f0449e5c
commit e8030c1db9
8 changed files with 220 additions and 2 deletions
+13
View File
@@ -1619,3 +1619,16 @@ async def get_tariffs_for_cluster(session, cluster_name: str) -> list[dict]:
async def get_tariff_by_id(session, tariff_id: int) -> dict | None:
row = await session.fetchrow("SELECT * FROM tariffs WHERE id = $1", tariff_id)
return dict(row) if row else None
async def get_hot_leads(conn):
"""
Возвращает пользователей, у которых есть успешные оплаты, но нет активных ключей.
"""
query = """
SELECT DISTINCT p.tg_id
FROM payments p
LEFT JOIN keys k ON p.tg_id = k.tg_id AND k.expiry_time > EXTRACT(EPOCH FROM now()) * 1000
WHERE p.amount > 0 AND k.tg_id IS NULL
"""
return await conn.fetch(query)
+2 -1
View File
@@ -30,7 +30,8 @@ YOOMONEY = "💳 ЮМани: перевод по карте"
CRYPTOBOT = "💰 CryptoBot: криптовалюта"
STARS = "⭐ Оплата Звездами"
ROBOKASSA = "⭐ RoboKassa"
DISCOUNT_TARIFF = "🔥 Получить скидку"
MAX_DISCOUNT_TARIFF = "⚡ Получить максимальную скидку"
# Подарки
+2
View File
@@ -5,6 +5,7 @@ from aiogram import Router
from .key_cluster_mode import router as cluster_router
from .key_country_mode import router as country_router
from .key_create import router as create_router
from .key_discount_mode import router as discount_router
router = Router(name="key_mode_router")
@@ -13,4 +14,5 @@ router.include_routers(
create_router,
cluster_router,
country_router,
discount_router
)
@@ -0,0 +1,91 @@
from aiogram import F, Router
from aiogram.types import CallbackQuery
from database import get_tariffs
from handlers.notifications.notify_kb import build_tariffs_keyboard
from .key_create import select_tariff_plan
from logger import logger
from datetime import datetime, timedelta
from config import DISCOUNT_ACTIVE_HOURS
from handlers.texts import DISCOUNT_TARIFF, DISCOUNT_TARIFF_MAX
router = Router()
@router.callback_query(F.data == "hot_lead_discount")
async def handle_discount_entry(callback: CallbackQuery, session):
tg_id = callback.from_user.id
last_time = await session.fetchval("""
SELECT last_notification_time
FROM notifications
WHERE tg_id = $1 AND notification_type = 'hot_lead_step_2'
""", tg_id)
if not last_time:
await callback.message.edit_text("❌ Скидка недоступна.")
return
now = datetime.utcnow()
if now - last_time > timedelta(hours=DISCOUNT_ACTIVE_HOURS):
await callback.message.edit_text("⏳ Срок действия скидки истёк.")
return
tariffs = await get_tariffs(session=session, group_code="discounts")
if not tariffs:
await callback.message.edit_text("❌ Скидочные тарифы временно недоступны.")
return
await callback.message.edit_text(
DISCOUNT_TARIFF,
reply_markup=build_tariffs_keyboard(tariffs, prefix="discount_tariff")
)
@router.callback_query(F.data.startswith("discount_tariff|"))
async def handle_discount_tariff_selection(callback: CallbackQuery, session, state):
try:
tariff_id = int(callback.data.split("|")[1])
fake_callback = CallbackQuery.model_construct(
id=callback.id,
from_user=callback.from_user,
chat_instance=callback.chat_instance,
message=callback.message,
data=f"select_tariff_plan|{tariff_id}",
)
await select_tariff_plan(fake_callback, session=session, state=state)
except Exception as e:
logger.error(f"Ошибка при выборе скидочного тарифа: {e}")
await callback.message.answer("❌ Произошла ошибка при выборе тарифа.")
@router.callback_query(F.data == "hot_lead_final_discount")
async def handle_ultra_discount(callback: CallbackQuery, session):
tg_id = callback.from_user.id
last_time = await session.fetchval("""
SELECT last_notification_time
FROM notifications
WHERE tg_id = $1 AND notification_type = 'hot_lead_step_3'
""", tg_id)
if not last_time:
await callback.message.edit_text("❌ Скидка недоступна.")
return
now = datetime.utcnow()
if now - last_time > timedelta(hours=DISCOUNT_ACTIVE_HOURS):
await callback.message.edit_text("⏳ Срок действия финальной скидки истёк.")
return
tariffs = await get_tariffs(session, group_code="discounts_max")
if not tariffs:
await callback.message.edit_text("❌ Скидочные тарифы временно недоступны.")
return
await callback.message.edit_text(
DISCOUNT_TARIFF_MAX,
reply_markup=build_tariffs_keyboard(tariffs, prefix="discount_tariff")
)
@@ -17,6 +17,7 @@ from config import (
NOTIFY_RENEW,
NOTIFY_RENEW_EXPIRED,
TRIAL_TIME_DISABLE,
NOTIFY_HOT_LEADS
)
from database import (
add_notification,
@@ -52,6 +53,7 @@ from logger import logger
from .notify_utils import send_messages_with_limit, send_notification
from .special_notifications import notify_inactive_trial_users, notify_users_no_traffic
from .hot_leads_notifications import notify_hot_leads
router = Router()
@@ -118,6 +120,12 @@ async def periodic_notifications(bot: Bot):
except Exception as e:
logger.error(f"Ошибка в notify_users_no_traffic: {e}")
await asyncio.sleep(0.5)
if NOTIFY_HOT_LEADS:
try:
await notify_hot_leads(bot)
except Exception as e:
logger.error(f"Ошибка в notify_hot_leads: {e}")
await asyncio.sleep(0.5)
logger.info("Завершена обработка уведомлений")
@@ -0,0 +1,81 @@
import asyncpg
from aiogram import Bot
from config import DATABASE_URL, HOT_LEAD_INTERVAL_HOURS
from database import check_notification_time, add_notification, get_hot_leads
from handlers.notifications.notify_utils import send_notification
from logger import logger
from handlers.notifications.notify_kb import build_hot_lead_kb
from handlers.texts import HOT_LEAD_MESSAGE, HOT_LEAD_FINAL_MESSAGE
async def notify_hot_leads(bot: Bot):
logger.info("🚀 Запуск уведомлений для горячих лидов.")
conn = await asyncpg.connect(DATABASE_URL)
try:
leads = await get_hot_leads(conn)
notified = 0
for row in leads:
tg_id = row["tg_id"]
has_step_1 = await conn.fetchval(
"SELECT EXISTS (SELECT 1 FROM notifications WHERE tg_id = $1 AND notification_type = 'hot_lead_step_1')",
tg_id
)
if not has_step_1:
await add_notification(tg_id, "hot_lead_step_1", session=conn)
logger.info(f"[HOT LEAD] Шаг 1 — зафиксировано без отправки: {tg_id}")
continue
has_step_2 = await conn.fetchval(
"SELECT EXISTS (SELECT 1 FROM notifications WHERE tg_id = $1 AND notification_type = 'hot_lead_step_2')",
tg_id
)
if not has_step_2:
can_send = await check_notification_time(
tg_id=tg_id,
notification_type="hot_lead_step_1",
hours=HOT_LEAD_INTERVAL_HOURS,
session=conn
)
if not can_send:
continue
keyboard = build_hot_lead_kb()
result = await send_notification(bot, tg_id, None, HOT_LEAD_MESSAGE, keyboard)
if result:
await add_notification(tg_id, "hot_lead_step_2", session=conn)
logger.info(f"🔥 Шаг 2 — отправлено первое уведомление: {tg_id}")
notified += 1
continue
has_step_3 = await conn.fetchval(
"SELECT EXISTS (SELECT 1 FROM notifications WHERE tg_id = $1 AND notification_type = 'hot_lead_step_3')",
tg_id
)
if not has_step_3:
can_send = await check_notification_time(
tg_id=tg_id,
notification_type="hot_lead_step_2",
hours=HOT_LEAD_INTERVAL_HOURS,
session=conn
)
if not can_send:
continue
keyboard = build_hot_lead_kb(final=True)
result = await send_notification(bot, tg_id, None, HOT_LEAD_FINAL_MESSAGE, keyboard)
if result:
await add_notification(tg_id, "hot_lead_step_3", session=conn)
logger.info(f"⚡ Шаг 3 — отправлено финальное уведомление: {tg_id}")
notified += 1
logger.info(f"✅ Уведомления завершены. Отправлено: {notified}")
except Exception as e:
logger.error(f"❌ Ошибка в notify_hot_leads: {e}")
finally:
await conn.close()
+23 -1
View File
@@ -1,6 +1,7 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from handlers.buttons import MAIN_MENU, RENEW_KEY
from handlers.buttons import DISCOUNT_TARIFF, MAX_DISCOUNT_TARIFF
def build_notification_kb(email: str) -> InlineKeyboardMarkup:
@@ -27,3 +28,24 @@ def build_notification_expired_kb() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.button(text=MAIN_MENU, callback_data="profile")
return builder.as_markup()
def build_hot_lead_kb(final: bool = False) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(
text=DISCOUNT_TARIFF if not final else MAX_DISCOUNT_TARIFF,
callback_data="hot_lead_discount" if not final else "hot_lead_final_discount"
)]
])
def build_tariffs_keyboard(tariffs: list[dict], prefix: str = "tariff") -> InlineKeyboardMarkup:
buttons = [
[InlineKeyboardButton(
text=f"{t['name']}{t['price_rub']}",
callback_data=f"{prefix}|{t['id']}"
)]
for t in tariffs
]
return InlineKeyboardMarkup(inline_keyboard=buttons)