159 lines
5.8 KiB
Python
159 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from enum import Enum, auto
|
|
from typing import Any, NamedTuple
|
|
|
|
from sqlalchemy import update
|
|
|
|
from database import (
|
|
add_notification,
|
|
check_notification_time,
|
|
get_balance,
|
|
update_balance,
|
|
update_key_expiry,
|
|
update_key_tariff,
|
|
)
|
|
from database.models import Key
|
|
from database.tariffs import check_tariff_exists, get_tariff_by_id, get_tariffs_for_cluster
|
|
from handlers.notifications.context import NotificationContext
|
|
from hooks.hooks import run_hooks
|
|
from logger import logger
|
|
from middlewares.session import release_session_early
|
|
from services.operations import renew_key_in_cluster
|
|
from services.tariffs.tariff_display import GB, get_effective_limits_for_key, resolve_price_to_charge
|
|
|
|
|
|
class RenewalStatus(Enum):
|
|
SUCCESS = auto()
|
|
FORBIDDEN_TARIFF = auto()
|
|
NO_BALANCE = auto()
|
|
NO_TARIFF = auto()
|
|
COOLDOWN = auto()
|
|
|
|
|
|
class RenewalResult(NamedTuple):
|
|
status: RenewalStatus
|
|
tariff: dict | None = None
|
|
new_expiry_time: int | None = None
|
|
|
|
|
|
FORBIDDEN_GROUPS = ["discounts", "discounts_max", "gifts", "trial"]
|
|
|
|
|
|
async def try_auto_renew(ctx: NotificationContext, key) -> RenewalResult:
|
|
tg_id = key.tg_id
|
|
email = key.email or ""
|
|
renew_notification_id = f"{email}_renew"
|
|
|
|
can_renew = await check_notification_time(ctx.session, tg_id, renew_notification_id, hours=24)
|
|
if not can_renew:
|
|
return RenewalResult(RenewalStatus.COOLDOWN)
|
|
|
|
server_id = key.server_id
|
|
tariff_id = key.tariff_id
|
|
|
|
tariffs = await get_tariffs_for_cluster(ctx.session, server_id)
|
|
if not tariffs:
|
|
return RenewalResult(RenewalStatus.NO_TARIFF)
|
|
|
|
current_tariff = None
|
|
if tariff_id:
|
|
current_tariff = ctx.get_tariff(tariff_id)
|
|
if not current_tariff and await check_tariff_exists(ctx.session, tariff_id):
|
|
current_tariff = await get_tariff_by_id(ctx.session, tariff_id)
|
|
|
|
if not current_tariff:
|
|
return RenewalResult(RenewalStatus.NO_TARIFF)
|
|
|
|
forbidden = list(FORBIDDEN_GROUPS)
|
|
try:
|
|
hook_results = await run_hooks("renewal_forbidden_groups", chat_id=tg_id, admin=False, session=ctx.session)
|
|
for hr in hook_results:
|
|
forbidden.extend(hr.get("additional_groups", []))
|
|
except Exception as error:
|
|
logger.warning(f"[RENEW] Ошибка хуков forbidden_groups: {error}")
|
|
|
|
if current_tariff["group_code"] in forbidden:
|
|
return RenewalResult(RenewalStatus.FORBIDDEN_TARIFF)
|
|
|
|
if ctx.preload_data and tg_id in ctx.preload_data.get("balances_cache", {}):
|
|
balance = ctx.preload_data["balances_cache"][tg_id]
|
|
else:
|
|
balance = await get_balance(ctx.session, tg_id)
|
|
|
|
renewal_cost = await resolve_price_to_charge(
|
|
ctx.session,
|
|
{
|
|
"tariff_id": current_tariff.get("id"),
|
|
"selected_device_limit": getattr(key, "selected_device_limit", None),
|
|
"selected_traffic_limit": getattr(key, "selected_traffic_limit", None),
|
|
"selected_price_rub": None,
|
|
},
|
|
)
|
|
|
|
if renewal_cost is None or balance < renewal_cost:
|
|
return RenewalResult(RenewalStatus.NO_BALANCE)
|
|
|
|
client_id = key.client_id
|
|
current_expiry = key.expiry_time
|
|
duration_days = current_tariff["duration_days"]
|
|
|
|
selected_device_limit = getattr(key, "selected_device_limit", None)
|
|
selected_traffic_limit = getattr(key, "selected_traffic_limit", None)
|
|
selected_traffic_gb = int(selected_traffic_limit) if selected_traffic_limit is not None else None
|
|
|
|
device_limit_effective, traffic_limit_bytes_effective = await get_effective_limits_for_key(
|
|
session=ctx.session,
|
|
tariff_id=int(current_tariff["id"]),
|
|
selected_device_limit=int(selected_device_limit) if selected_device_limit is not None else None,
|
|
selected_traffic_gb=selected_traffic_gb,
|
|
)
|
|
traffic_limit_gb = int(traffic_limit_bytes_effective / GB) if traffic_limit_bytes_effective else 0
|
|
|
|
now_ms = datetime.now(timezone.utc).timestamp() * 1000
|
|
base_expiry = current_expiry if current_expiry > now_ms else now_ms
|
|
new_expiry_time = int(base_expiry + duration_days * 24 * 60 * 60 * 1000)
|
|
|
|
logger.info(
|
|
f"Продление {email} на {duration_days}д для {tg_id}. Баланс: {balance}, списываем: {renewal_cost}"
|
|
)
|
|
|
|
key_subgroup = current_tariff.get("subgroup_title")
|
|
|
|
await release_session_early(ctx.session)
|
|
await renew_key_in_cluster(
|
|
cluster_id=server_id,
|
|
email=email,
|
|
client_id=client_id,
|
|
new_expiry_time=new_expiry_time,
|
|
total_gb=traffic_limit_gb,
|
|
hwid_device_limit=device_limit_effective,
|
|
session=ctx.session,
|
|
target_subgroup=key_subgroup,
|
|
old_subgroup=key_subgroup,
|
|
plan=current_tariff["id"],
|
|
)
|
|
|
|
await ctx.session.execute(
|
|
update(Key).where(Key.client_id == client_id).values(
|
|
current_device_limit=selected_device_limit,
|
|
current_traffic_limit=selected_traffic_limit,
|
|
selected_price_rub=renewal_cost,
|
|
)
|
|
)
|
|
|
|
if ctx.bulk_updates is not None:
|
|
bc = ctx.bulk_updates["balance_changes"]
|
|
bc[tg_id] = bc.get(tg_id, 0) - renewal_cost
|
|
ctx.bulk_updates["key_expiry_updates"].append((client_id, new_expiry_time))
|
|
ctx.bulk_updates["key_tariff_updates"].append((client_id, current_tariff["id"]))
|
|
ctx.bulk_updates["notifications_to_add"].append((tg_id, renew_notification_id))
|
|
else:
|
|
await update_balance(ctx.session, tg_id, -renewal_cost)
|
|
await update_key_expiry(ctx.session, client_id, new_expiry_time)
|
|
await update_key_tariff(ctx.session, client_id, current_tariff["id"])
|
|
await add_notification(ctx.session, tg_id, renew_notification_id)
|
|
|
|
return RenewalResult(RenewalStatus.SUCCESS, current_tariff, new_expiry_time)
|