Files
vpn-control-panel/app/bot/handlers/subscription.py
smolkik-code 7c7c88621d Initial commit: VPN Control Panel
- FastAPI REST API with JWT auth
- aiogram 3 Telegram bot with admin middleware
- APScheduler daily tasks (expiry, reminders, revoke, sync)
- SQLAlchemy 2 async ORM with Alembic migrations
- Jinja2 admin panel (Dashboard, Users, Payments, Servers, Tariffs)
- VPN provider abstraction with MockProvider
- Stats service with revenue/subscription analytics
- Docker Compose (PostgreSQL + Redis + app)
- Healthcheck endpoint
2026-07-05 17:50:11 +07:00

135 lines
4.6 KiB
Python

from datetime import datetime, timedelta, timezone
from aiogram import Router
from aiogram.filters import Command, CommandObject
from aiogram.types import Message
from loguru import logger
from app.bot.texts import (
user_info,
USER_NOT_FOUND,
INVALID_ARGS,
NO_DATA,
expired_list,
expiring_list,
)
from app.database import async_session_factory
from app.models.payment import PaymentStatus
from app.repositories.payment import PaymentRepository
from app.repositories.tariff import TariffRepository
from app.repositories.user import UserRepository
from app.services.billing import BillingService
router = Router(name="subscription")
@router.message(Command("renew"))
async def cmd_renew(message: Message, command: CommandObject) -> None:
if not command.args or not command.args.strip().isdigit():
await message.answer(
INVALID_ARGS.format("/renew <telegram_id>"),
parse_mode="HTML",
)
return
telegram_id = int(command.args.strip())
async with async_session_factory() as session:
user_repo = UserRepository(session)
user = await user_repo.get_by_telegram_id(telegram_id)
if user is None:
await message.answer(
USER_NOT_FOUND.format(telegram_id), parse_mode="HTML"
)
return
billing = BillingService(
PaymentRepository(session),
TariffRepository(session),
user_repo,
)
sub = await billing.get_active_subscription(user.id)
await message.answer(
user_info(
user_id=user.id,
telegram_id=user.telegram_id,
username=user.username,
full_name=user.full_name,
is_active=user.is_active,
created_at=user.created_at.strftime("%Y-%m-%d"),
subscription_active=sub is not None and sub.is_active,
tariff_name=sub.tariff.name if sub and sub.tariff else None,
remaining_days=sub.remaining_days if sub else 0,
),
parse_mode="HTML",
)
@router.message(Command("expired"))
async def cmd_expired(message: Message) -> None:
async with async_session_factory() as session:
payment_repo = PaymentRepository(session)
tariff_repo = TariffRepository(session)
user_repo = UserRepository(session)
confirmed = await payment_repo.get_by_status(PaymentStatus.CONFIRMED)
now = datetime.now(timezone.utc)
items = []
for payment in confirmed:
tariff = await tariff_repo.get(payment.tariff_id)
if tariff is None:
continue
start = payment.paid_at or payment.created_at
end = start + timedelta(days=tariff.duration_days)
if now > end:
user = await user_repo.get(payment.user_id)
items.append(
{
"user_id": payment.user_id,
"telegram_id": user.telegram_id if user else "?",
"tariff_name": tariff.name,
}
)
await message.answer(expired_list(items), parse_mode="HTML")
@router.message(Command("expiring"))
async def cmd_expiring(message: Message, command: CommandObject) -> None:
days = 3
if command.args and command.args.strip().isdigit():
days = int(command.args.strip())
async with async_session_factory() as session:
payment_repo = PaymentRepository(session)
tariff_repo = TariffRepository(session)
user_repo = UserRepository(session)
confirmed = await payment_repo.get_by_status(PaymentStatus.CONFIRMED)
now = datetime.now(timezone.utc)
items = []
for payment in confirmed:
tariff = await tariff_repo.get(payment.tariff_id)
if tariff is None:
continue
start = payment.paid_at or payment.created_at
end = start + timedelta(days=tariff.duration_days)
remaining = (end - now).days
if 0 <= remaining <= days:
user = await user_repo.get(payment.user_id)
items.append(
{
"user_id": payment.user_id,
"telegram_id": user.telegram_id if user else "?",
"tariff_name": tariff.name,
"remaining_days": remaining,
}
)
items.sort(key=lambda x: x["remaining_days"])
await message.answer(expiring_list(items, days), parse_mode="HTML")