refactor: migrate trial subscription to database-driven tariffs

This commit is contained in:
Capybara-z
2025-07-29 20:49:01 +03:00
parent 8dce80b3a5
commit 27140d2f95
10 changed files with 70 additions and 87 deletions
+3 -1
View File
@@ -103,7 +103,8 @@ async def start_tariff_creation(callback: CallbackQuery, state: FSMContext):
"<b>Специальные группы:</b>\n"
"• <code>discounts</code> — тарифы со скидкой\n"
"• <code>discounts_max</code> — тарифы с максимальной скидкой\n"
"• <code>gifts</code> — тарифы для подарков",
"• <code>gifts</code> — тарифы для подарков\n"
"• <code>trial</code> — тариф для пробного периода",
reply_markup=build_cancel_kb(),
)
@@ -276,6 +277,7 @@ async def show_tariff_groups(callback: CallbackQuery, session: AsyncSession):
"discounts": "🔻 Скидки",
"discounts_max": "🔻 Макс. скидки",
"gifts": "🎁 Подарки",
"trial": "🚀 Пробный период",
}
text = "<b>📋 Выберите тарифную группу:</b>\n\n"
+3 -4
View File
@@ -7,7 +7,6 @@ from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import text
from config import TRIAL_CONFIG
from database import (
get_key_details,
get_servers,
@@ -89,9 +88,9 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac
tariff = await get_tariff_by_id(session, record["tariff_id"]) if record.get("tariff_id") else None
if not tariff:
logger.info("[Unfreeze] Тариф не найден — возможно ключ триальный. Применяем дефолтные значения.")
total_gb = TRIAL_CONFIG["traffic_limit_gb"]
hwid_limit = TRIAL_CONFIG["hwid_limit"]
logger.info("[Unfreeze] Тариф не найден — применяем дефолтные значения.")
total_gb = 0
hwid_limit = 0
else:
total_gb = int(tariff.get("traffic_limit") or 0)
hwid_limit = int(tariff.get("device_limit") or 0)
+12 -28
View File
@@ -15,7 +15,7 @@ from aiogram.types import (
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from config import CONNECT_PHONE_BUTTON, SUPPORT_CHAT_URL, TRIAL_CONFIG
from config import CONNECT_PHONE_BUTTON, SUPPORT_CHAT_URL
from database import (
get_key_details,
get_tariff_by_id,
@@ -23,6 +23,7 @@ from database import (
update_balance,
update_trial,
)
from handlers.buttons import (
CONNECT_DEVICE,
CONNECT_PHONE,
@@ -84,10 +85,7 @@ async def key_cluster_mode(
device_limit = 0
traffic_limit_gb = 0
if is_trial:
device_limit = TRIAL_CONFIG.get("hwid_limit", 0)
traffic_limit_gb = TRIAL_CONFIG.get("traffic_limit_gb", 100)
elif plan:
if plan:
tariff = await get_tariff_by_id(session, plan)
if tariff:
if tariff.get("device_limit") is not None:
@@ -182,30 +180,16 @@ async def key_cluster_mode(
if plan:
tariff_info = await get_tariff_by_id(session, plan)
if is_trial:
trial_days = TRIAL_CONFIG.get("duration_days", 1)
if trial_days >= 30:
months = trial_days // 30
tariff_duration = format_months(months)
else:
tariff_duration = format_days(trial_days)
key_message_text = key_message_success(
final_link,
tariff_name=tariff_duration,
traffic_limit=TRIAL_CONFIG.get("traffic_limit_gb", 100),
device_limit=TRIAL_CONFIG.get("hwid_limit", 0),
)
else:
tariff_duration = tariff_info["name"]
subgroup_title = tariff_info.get("subgroup_title", "") if tariff_info else ""
tariff_duration = tariff_info["name"]
subgroup_title = tariff_info.get("subgroup_title", "") if tariff_info else ""
key_message_text = key_message_success(
final_link,
tariff_name=tariff_duration,
traffic_limit=tariff_info.get("traffic_limit", 0) if tariff_info else 0,
device_limit=tariff_info.get("device_limit", 0) if tariff_info else 0,
subgroup_title=subgroup_title,
)
key_message_text = key_message_success(
final_link,
tariff_name=tariff_duration,
traffic_limit=tariff_info.get("traffic_limit", 0) if tariff_info else 0,
device_limit=tariff_info.get("device_limit", 0) if tariff_info else 0,
subgroup_title=subgroup_title,
)
default_media_path = "img/pic.jpg"
if safe_to_edit:
+12 -28
View File
@@ -24,7 +24,6 @@ from config import (
REMNAWAVE_LOGIN,
REMNAWAVE_PASSWORD,
SUPPORT_CHAT_URL,
TRIAL_CONFIG,
)
from database import (
add_user,
@@ -34,7 +33,9 @@ from database import (
get_trial,
update_balance,
update_trial,
get_tariff_by_id,
)
from database.models import Key, Server, Tariff
from handlers.buttons import BACK, CONNECT_DEVICE, CONNECT_PHONE, MAIN_MENU, MY_SUB, PC_BUTTON, SUPPORT, TV_BUTTON
from handlers.keys.key_utils import create_client_on_server
@@ -335,10 +336,7 @@ async def finalize_key_creation(
data = await state.get_data() if state else {}
is_trial = data.get("is_trial", False)
if is_trial:
traffic_limit_bytes = int(TRIAL_CONFIG.get("traffic_limit_gb", 100)) * 1024**3
device_limit = TRIAL_CONFIG.get("hwid_limit", 0)
elif data.get("tariff_id") or tariff_id:
if data.get("tariff_id") or tariff_id:
tariff_id = data.get("tariff_id") or tariff_id
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
tariff = result.scalar_one_or_none()
@@ -511,30 +509,16 @@ async def finalize_key_creation(
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
tariff_info = result.scalar_one_or_none()
if is_trial:
trial_days = TRIAL_CONFIG.get("duration_days", 1)
if trial_days >= 30:
months = trial_days // 30
tariff_duration = format_months(months)
else:
tariff_duration = format_days(trial_days)
key_message_text = key_message_success(
link_to_show,
tariff_name=tariff_duration,
traffic_limit=TRIAL_CONFIG.get("traffic_limit_gb", 100),
device_limit=TRIAL_CONFIG.get("hwid_limit", 0),
)
else:
tariff_duration = tariff_info["name"] if tariff_info else None
subgroup_title = tariff_info.get("subgroup_title", "") if tariff_info else ""
tariff_duration = tariff_info["name"]
subgroup_title = tariff_info.get("subgroup_title", "") if tariff_info else ""
key_message_text = key_message_success(
link_to_show,
tariff_name=tariff_duration,
traffic_limit=tariff_info.get("traffic_limit", 0) if tariff_info else 0,
device_limit=tariff_info.get("device_limit", 0) if tariff_info else 0,
subgroup_title=subgroup_title,
)
key_message_text = key_message_success(
link_to_show,
tariff_name=tariff_duration,
traffic_limit=tariff_info.get("traffic_limit", 0) if tariff_info else 0,
device_limit=tariff_info.get("device_limit", 0) if tariff_info else 0,
subgroup_title=subgroup_title,
)
await edit_or_send_message(
target_message=callback_query.message,
+18 -5
View File
@@ -14,7 +14,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from config import (
NOTIFY_EXTRA_DAYS,
TRIAL_CONFIG,
TRIAL_TIME_DISABLE,
USE_COUNTRY_SELECTION,
USE_NEW_PAYMENT_FLOW,
@@ -29,7 +28,7 @@ from database import (
get_trial,
)
from database.models import Admin
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash, get_tariffs
from handlers.admin.panel.keyboard import AdminPanelCallback
from handlers.buttons import MAIN_MENU, PAYMENT
from handlers.payments.robokassa_pay import handle_custom_amount_input
@@ -90,7 +89,21 @@ async def handle_key_creation(
if not TRIAL_TIME_DISABLE:
trial_status = await get_trial(session, tg_id)
if trial_status in [0, -1]:
base_days = TRIAL_CONFIG["duration_days"]
trial_tariffs = await get_tariffs(session, group_code="trial")
if not trial_tariffs:
await edit_or_send_message(
target_message=(
message_or_query.message if isinstance(message_or_query, CallbackQuery) else message_or_query
),
text="❌ Пробная подписка временно недоступна.",
reply_markup=InlineKeyboardBuilder()
.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
.as_markup(),
)
return
trial_tariff = trial_tariffs[0]
base_days = trial_tariff["duration_days"]
extra_days = NOTIFY_EXTRA_DAYS if trial_status == -1 else 0
total_days = base_days + extra_days
expiry_time = current_time + timedelta(days=total_days)
@@ -105,8 +118,8 @@ async def handle_key_creation(
reply_markup=None,
)
await state.update_data(is_trial=True)
await create_key(tg_id, expiry_time, state, session, message_or_query)
await state.update_data(is_trial=True, plan=trial_tariff["id"])
await create_key(tg_id, expiry_time, state, session, message_or_query, plan=trial_tariff["id"])
return
try:
+1 -1
View File
@@ -92,7 +92,7 @@ async def process_callback_renew_key(callback_query: CallbackQuery, state: FSMCo
if tariff_id:
if await check_tariff_exists(session, tariff_id):
current_tariff = await get_tariff_by_id(session, tariff_id)
if current_tariff["group_code"] not in ["discounts", "discounts_max", "gifts"]:
if current_tariff["group_code"] not in ["discounts", "discounts_max", "gifts", "trial"]:
group_code = current_tariff["group_code"]
tariffs = await get_tariffs(session, group_code=group_code)
+3 -6
View File
@@ -6,8 +6,9 @@ from typing import Any
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE, TRIAL_CONFIG
from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
from database import delete_notification, get_servers, get_tariff_by_id, store_key
from database.models import Key, Server, Tariff
from handlers.utils import check_server_key_limit, get_least_loaded_cluster
from logger import logger
@@ -220,11 +221,7 @@ async def create_client_on_server(
total_gb_value = 0
device_limit_value = 0
if is_trial:
total_gb_value = TRIAL_CONFIG.get("traffic_limit_gb", 0)
device_limit_value = TRIAL_CONFIG.get("hwid_limit")
logger.info(f"[Trial] Используются параметры триала: {total_gb_value} GB, {device_limit_value} устройств")
elif plan is not None:
if plan is not None:
tariff = await get_tariff_by_id(session, plan)
logger.info(f"[Tariff Debug] Получен тариф: {tariff}")
if not tariff:
@@ -476,7 +476,7 @@ async def process_auto_renew_or_notify(
else:
if await check_tariff_exists(conn, tariff_id):
current_tariff = await get_tariff_by_id(conn, tariff_id)
if current_tariff["group_code"] in ["discounts", "discounts_max", "gifts"]:
if current_tariff["group_code"] in ["discounts", "discounts_max", "gifts", "trial"]:
cluster_tariffs = [t for t in tariffs if t["is_active"] and balance >= t["price_rub"]]
if cluster_tariffs:
cluster_tariffs_31 = [t for t in cluster_tariffs if t["duration_days"] <= 31]
@@ -13,7 +13,6 @@ from config import (
NOTIFY_INACTIVE,
NOTIFY_INACTIVE_TRAFFIC,
SUPPORT_CHAT_URL,
TRIAL_CONFIG,
)
from database import (
add_notification,
@@ -22,6 +21,7 @@ from database import (
update_key_notified,
)
from database.models import Key
from database.tariffs import get_tariffs
from handlers.buttons import CONNECT_DEVICE, CONNECT_PHONE, MAIN_MENU, PC_BUTTON, TV_BUTTON
from handlers.keys.key_utils import get_user_traffic
from handlers.notifications.notify_utils import send_messages_with_limit
@@ -44,7 +44,12 @@ async def notify_inactive_trial_users(bot: Bot, session: AsyncSession):
logger.info(f"Найдено {len(users)} неактивных пользователей для уведомления.")
messages = []
trial_days = TRIAL_CONFIG["duration_days"]
trial_tariffs = await get_tariffs(session, group_code="trial")
if not trial_tariffs:
logger.error("[Notifications] Триальный тариф не найден")
return
trial_days = trial_tariffs[0]["duration_days"]
for user in users:
tg_id = user["tg_id"]
+10 -11
View File
@@ -20,15 +20,7 @@ from sqlalchemy import desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from bot import bot
from config import (
ADMIN_ID,
INLINE_MODE,
REFERRAL_BONUS_PERCENTAGES,
REFERRAL_QR,
TOP_REFERRAL_BUTTON,
TRIAL_CONFIG,
USERNAME_BOT,
)
from config import ADMIN_ID, INLINE_MODE, REFERRAL_BONUS_PERCENTAGES, TOP_REFERRAL_BUTTON, USERNAME_BOT, REFERRAL_QR
from database import (
add_referral,
add_user,
@@ -37,6 +29,7 @@ from database import (
get_referral_stats,
)
from database.models import Referral
from database.tariffs import get_tariffs
from handlers.buttons import BACK, INVITE, MAIN_MENU, QR, TOP_FIVE
from handlers.texts import (
INVITE_MESSAGE_TEMPLATE,
@@ -118,9 +111,15 @@ async def invite_handler(callback_query_or_message: Message | CallbackQuery, ses
@router.inline_query(F.query.in_(["referral", "ref", "invite"]))
async def inline_referral_handler(inline_query: InlineQuery):
async def inline_referral_handler(inline_query: InlineQuery, session: AsyncSession):
referral_link = f"https://t.me/{USERNAME_BOT}?start=referral_{inline_query.from_user.id}"
trial_days = TRIAL_CONFIG["duration_days"]
trial_tariffs = await get_tariffs(session, group_code="trial")
if not trial_tariffs:
await inline_query.answer(results=[], cache_time=0)
return
trial_days = trial_tariffs[0]["duration_days"]
trial_time_formatted = format_days(trial_days)
results: list[InlineQueryResultArticle] = []