fix: multi-tariff Stage 2 HIGH fixes — 18 issues across 12 files
Bot handlers (H1-H5): - confirm_extend_subscription: error alert instead of wrong sub fallback - open_subscription_link/subscription_connect: startswith registration - handle_subscription_settings: multi-tariff guard - confirm_reset_traffic: FSM state check in multi-tariff Services (H6-H12): - subscription_service: 5 UUID fallback fixes — no user.remnawave_uuid in multi-tariff, return None if subscription.remnawave_uuid missing - auto_purchase: use cart subscription_id for tariff match - remnawave_service: migrate_squad_users checks subscription.remnawave_uuid - campaign_service: extend existing sub or create new in multi-tariff - broadcast_service: check ALL subs for paid-subscription guard - blocked_users_service: remnawave_uuids list, iterate in cleanup - user_service: log sub.remnawave_uuid in multi-tariff Admin (H13-H16): - grant_trial/paid_subscription: allow in multi-tariff mode - promo_offers: pick sub with URL, aggregate squads from all subs CRUD/Frontend (H17-H18): - get_users_list: .unique() for outerjoin dedup - refreshTraffic: withSubId in params instead of body
This commit is contained in:
@@ -901,7 +901,7 @@ async def get_users_list(
|
||||
query = query.offset(offset).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
users = result.scalars().all()
|
||||
users = result.scalars().unique().all()
|
||||
|
||||
# Загружаем дополнительные зависимости для всех пользователей
|
||||
for user in users:
|
||||
|
||||
@@ -1902,7 +1902,11 @@ async def show_selected_user_details(
|
||||
|
||||
|
||||
def _build_connect_button_rows(user: User, texts) -> list[list[InlineKeyboardButton]]:
|
||||
subscription = getattr(user, 'subscription', None)
|
||||
if settings.is_multi_tariff_enabled():
|
||||
subs = getattr(user, 'subscriptions', None) or []
|
||||
subscription = next((s for s in subs if s.subscription_url), None)
|
||||
else:
|
||||
subscription = getattr(user, 'subscription', None)
|
||||
if not subscription:
|
||||
return []
|
||||
|
||||
@@ -2122,8 +2126,14 @@ async def send_offer_to_segment(callback: CallbackQuery, db_user: User, db: Asyn
|
||||
if template.offer_type == 'test_access' and squad_uuid:
|
||||
filtered_users: list[User] = []
|
||||
for user in users:
|
||||
subscription = getattr(user, 'subscription', None)
|
||||
connected = set(subscription.connected_squads or []) if subscription else set()
|
||||
if settings.is_multi_tariff_enabled():
|
||||
all_squads: set[str] = set()
|
||||
for s in (getattr(user, 'subscriptions', None) or []):
|
||||
all_squads.update(s.connected_squads or [])
|
||||
connected = all_squads
|
||||
else:
|
||||
subscription = getattr(user, 'subscription', None)
|
||||
connected = set(subscription.connected_squads or []) if subscription else set()
|
||||
if squad_uuid in connected:
|
||||
continue
|
||||
filtered_users.append(user)
|
||||
|
||||
@@ -4375,7 +4375,7 @@ async def _grant_trial_subscription(
|
||||
from app.services.subscription_service import SubscriptionService
|
||||
|
||||
existing_subscription = await _resolve_admin_subscription(db, user_id, subscription_id)
|
||||
if existing_subscription:
|
||||
if existing_subscription and not settings.is_multi_tariff_enabled():
|
||||
logger.error('У пользователя уже есть подписка', user_id=user_id)
|
||||
return False
|
||||
|
||||
@@ -4409,7 +4409,7 @@ async def _grant_paid_subscription(
|
||||
from app.services.subscription_service import SubscriptionService
|
||||
|
||||
existing_subscription = await _resolve_admin_subscription(db, user_id, subscription_id)
|
||||
if existing_subscription:
|
||||
if existing_subscription and not settings.is_multi_tariff_enabled():
|
||||
logger.error('У пользователя уже есть подписка', user_id=user_id)
|
||||
return False
|
||||
|
||||
|
||||
@@ -1813,7 +1813,9 @@ async def confirm_extend_subscription(
|
||||
if _fsm_sub_id:
|
||||
subscription = await get_subscription_by_id_for_user(db, _fsm_sub_id, db_user.id)
|
||||
else:
|
||||
subscription = db_user.subscription
|
||||
# Multi-tariff without FSM state — cannot determine which subscription
|
||||
await callback.answer('Выберите подписку через "Мои подписки"', show_alert=True)
|
||||
return
|
||||
else:
|
||||
subscription = db_user.subscription
|
||||
|
||||
@@ -2845,10 +2847,11 @@ async def handle_subscription_settings(callback: types.CallbackQuery, db_user: U
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
# Multi-tariff note: this handler is reached via 'subscription_settings' callback
|
||||
# which is shown in the single-subscription info keyboard. In multi-tariff mode,
|
||||
# show_subscription_info redirects to show_my_subscriptions, so per-subscription
|
||||
# settings are handled from the my_subscriptions flow. db_user.subscription is safe.
|
||||
|
||||
if settings.is_multi_tariff_enabled():
|
||||
await callback.answer('Настройки доступны через "Мои подписки"', show_alert=True)
|
||||
return
|
||||
|
||||
subscription = db_user.subscription
|
||||
|
||||
# Получаем тариф подписки если есть
|
||||
@@ -4137,7 +4140,7 @@ def register_handlers(dp: Dispatcher):
|
||||
|
||||
dp.callback_query.register(handle_happ_download_back, F.data == 'happ_download_back')
|
||||
|
||||
dp.callback_query.register(handle_connect_subscription, F.data == 'subscription_connect')
|
||||
dp.callback_query.register(handle_connect_subscription, F.data.startswith('subscription_connect'))
|
||||
|
||||
dp.callback_query.register(handle_device_guide, F.data.startswith('device_guide_'))
|
||||
|
||||
@@ -4145,7 +4148,7 @@ def register_handlers(dp: Dispatcher):
|
||||
|
||||
dp.callback_query.register(handle_specific_app_guide, F.data.startswith('app_') & ~F.data.startswith('app_list_'))
|
||||
|
||||
dp.callback_query.register(handle_open_subscription_link, F.data == 'open_subscription_link')
|
||||
dp.callback_query.register(handle_open_subscription_link, F.data.startswith('open_subscription_link'))
|
||||
|
||||
dp.callback_query.register(handle_subscription_settings, F.data == 'subscription_settings')
|
||||
|
||||
|
||||
@@ -314,6 +314,12 @@ async def confirm_reset_traffic(
|
||||
await callback.answer('⚠️ В текущем режиме трафик фиксированный', show_alert=True)
|
||||
return
|
||||
|
||||
if settings.is_multi_tariff_enabled():
|
||||
_state_data = await state.get_data() if state else {}
|
||||
if not _state_data.get('active_subscription_id'):
|
||||
await callback.answer('Выберите подписку через "Мои подписки"', show_alert=True)
|
||||
return
|
||||
|
||||
from app.database.crud.user import lock_user_for_pricing
|
||||
|
||||
db_user = await lock_user_for_pricing(db, db_user.id)
|
||||
|
||||
@@ -88,6 +88,7 @@ class BlockCheckResult:
|
||||
status: BlockCheckStatus
|
||||
error_message: str | None = None
|
||||
remnawave_uuid: str | None = None
|
||||
remnawave_uuids: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -159,6 +160,13 @@ class BlockedUsersService:
|
||||
|
||||
async def _check_single_user(self, user: User) -> BlockCheckResult:
|
||||
"""Проверяет одного пользователя."""
|
||||
sub_uuids = [
|
||||
s.remnawave_uuid
|
||||
for s in (getattr(user, 'subscriptions', None) or [])
|
||||
if s.remnawave_uuid
|
||||
]
|
||||
remnawave_uuids = sub_uuids or ([user.remnawave_uuid] if user.remnawave_uuid else [])
|
||||
|
||||
if not user.telegram_id:
|
||||
return BlockCheckResult(
|
||||
user_id=user.id,
|
||||
@@ -167,6 +175,7 @@ class BlockedUsersService:
|
||||
full_name=user.full_name,
|
||||
status=BlockCheckStatus.NO_TELEGRAM_ID,
|
||||
remnawave_uuid=user.remnawave_uuid,
|
||||
remnawave_uuids=remnawave_uuids,
|
||||
)
|
||||
|
||||
status = await self.check_user_blocked(user.telegram_id)
|
||||
@@ -178,6 +187,7 @@ class BlockedUsersService:
|
||||
full_name=user.full_name,
|
||||
status=status,
|
||||
remnawave_uuid=user.remnawave_uuid,
|
||||
remnawave_uuids=remnawave_uuids,
|
||||
)
|
||||
|
||||
async def scan_all_users(
|
||||
@@ -417,12 +427,17 @@ class BlockedUsersService:
|
||||
for i, user_result in enumerate(blocked_users):
|
||||
try:
|
||||
if action in (BlockedUserAction.DELETE_FROM_REMNAWAVE, BlockedUserAction.DELETE_BOTH):
|
||||
if user_result.remnawave_uuid:
|
||||
success = await self.delete_user_from_remnawave(user_result.remnawave_uuid)
|
||||
uuids_to_delete = user_result.remnawave_uuids or (
|
||||
[user_result.remnawave_uuid] if user_result.remnawave_uuid else []
|
||||
)
|
||||
for rw_uuid in uuids_to_delete:
|
||||
success = await self.delete_user_from_remnawave(rw_uuid)
|
||||
if success:
|
||||
result.deleted_from_remnawave += 1
|
||||
else:
|
||||
result.errors.append(f'Ошибка удаления {user_result.telegram_id} из Remnawave')
|
||||
result.errors.append(
|
||||
f'Ошибка удаления {user_result.telegram_id} (uuid={rw_uuid}) из Remnawave'
|
||||
)
|
||||
# Задержка для избежания rate limit
|
||||
await asyncio.sleep(self.API_DELAY_SECONDS)
|
||||
|
||||
|
||||
@@ -544,14 +544,11 @@ async def cleanup_blocked_broadcast_users(blocked_telegram_ids: list[int]) -> No
|
||||
from app.database.crud.subscription import is_active_paid_subscription
|
||||
|
||||
sub_result = await session.execute(
|
||||
select(Subscription)
|
||||
.where(Subscription.user_id == user.id)
|
||||
.order_by(Subscription.created_at.desc())
|
||||
.limit(1)
|
||||
select(Subscription).where(Subscription.user_id == user.id)
|
||||
)
|
||||
user_subscription = sub_result.scalar_one_or_none()
|
||||
all_subs = sub_result.scalars().all()
|
||||
|
||||
if is_active_paid_subscription(user_subscription):
|
||||
if any(is_active_paid_subscription(s) for s in all_subs):
|
||||
logger.info(
|
||||
'⏭️ Пропуск отключения подписки: у пользователя активная оплаченная подписка',
|
||||
telegram_id=telegram_id,
|
||||
|
||||
@@ -134,6 +134,7 @@ class AdvertisingCampaignService:
|
||||
|
||||
active_subs = await get_active_subscriptions_by_user_id(db, user.id)
|
||||
if active_subs:
|
||||
# Multi-tariff: extend the best existing subscription instead of blocking
|
||||
_non_daily = [s for s in active_subs if not getattr(s, 'is_daily_tariff', False)]
|
||||
_pool = _non_daily or active_subs
|
||||
existing_subscription = max(_pool, key=lambda s: s.days_left)
|
||||
@@ -141,13 +142,13 @@ class AdvertisingCampaignService:
|
||||
existing_subscription = None
|
||||
else:
|
||||
existing_subscription = await get_subscription_by_user_id(db, user.id)
|
||||
if existing_subscription:
|
||||
logger.warning(
|
||||
'⚠️ У пользователя уже есть подписка, бонус кампании пропущен',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
)
|
||||
return CampaignBonusResult(success=False)
|
||||
if existing_subscription:
|
||||
logger.warning(
|
||||
'⚠️ У пользователя уже есть подписка, бонус кампании пропущен',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
)
|
||||
return CampaignBonusResult(success=False)
|
||||
|
||||
duration_days = campaign.subscription_duration_days or 0
|
||||
if duration_days <= 0:
|
||||
@@ -170,21 +171,46 @@ class AdvertisingCampaignService:
|
||||
except Exception as error:
|
||||
logger.error('Не удалось подобрать сквад для кампании', campaign_id=campaign.id, error=error)
|
||||
|
||||
new_subscription = await create_paid_subscription(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
duration_days=duration_days,
|
||||
traffic_limit_gb=traffic_limit or 0,
|
||||
device_limit=device_limit,
|
||||
connected_squads=squads,
|
||||
update_server_counters=True,
|
||||
is_trial=True,
|
||||
)
|
||||
if existing_subscription:
|
||||
# Multi-tariff: extend the best existing subscription
|
||||
from app.database.crud.subscription import extend_subscription
|
||||
|
||||
try:
|
||||
await self.subscription_service.create_remnawave_user(db, new_subscription)
|
||||
except Exception as error:
|
||||
logger.error('❌ Ошибка синхронизации RemnaWave для кампании', campaign_id=campaign.id, error=error)
|
||||
await extend_subscription(db, existing_subscription, duration_days)
|
||||
try:
|
||||
await self.subscription_service.update_remnawave_user(db, existing_subscription)
|
||||
except Exception as error:
|
||||
logger.error('❌ Ошибка синхронизации RemnaWave при продлении кампании', campaign_id=campaign.id, error=error)
|
||||
|
||||
logger.info(
|
||||
'🎁 Подписка пользователя продлена по кампании на дней',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
duration_days=duration_days,
|
||||
subscription_id=existing_subscription.id,
|
||||
)
|
||||
else:
|
||||
new_subscription = await create_paid_subscription(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
duration_days=duration_days,
|
||||
traffic_limit_gb=traffic_limit or 0,
|
||||
device_limit=device_limit,
|
||||
connected_squads=squads,
|
||||
update_server_counters=True,
|
||||
is_trial=True,
|
||||
)
|
||||
|
||||
try:
|
||||
await self.subscription_service.create_remnawave_user(db, new_subscription)
|
||||
except Exception as error:
|
||||
logger.error('❌ Ошибка синхронизации RemnaWave для кампании', campaign_id=campaign.id, error=error)
|
||||
|
||||
logger.info(
|
||||
'🎁 Пользователю выдана подписка по кампании на дней',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
duration_days=duration_days,
|
||||
)
|
||||
|
||||
await record_campaign_registration(
|
||||
db,
|
||||
@@ -194,13 +220,6 @@ class AdvertisingCampaignService:
|
||||
subscription_duration_days=duration_days,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
'🎁 Пользователю выдана подписка по кампании на дней',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
duration_days=duration_days,
|
||||
)
|
||||
|
||||
return CampaignBonusResult(
|
||||
success=True,
|
||||
bonus_type='subscription',
|
||||
@@ -242,25 +261,26 @@ class AdvertisingCampaignService:
|
||||
campaign: AdvertisingCampaign,
|
||||
) -> CampaignBonusResult:
|
||||
"""Выдача тарифа на определённое время."""
|
||||
existing_subscription = None
|
||||
if settings.is_multi_tariff_enabled():
|
||||
from app.database.crud.subscription import get_active_subscriptions_by_user_id
|
||||
|
||||
active_subs = await get_active_subscriptions_by_user_id(db, user.id)
|
||||
if active_subs:
|
||||
_non_daily = [s for s in active_subs if not getattr(s, 'is_daily_tariff', False)]
|
||||
_pool = _non_daily or active_subs
|
||||
existing_subscription = max(_pool, key=lambda s: s.days_left)
|
||||
else:
|
||||
existing_subscription = None
|
||||
if active_subs and campaign.tariff_id:
|
||||
# Multi-tariff: only check for THIS specific tariff
|
||||
same_tariff_subs = [s for s in active_subs if s.tariff_id == campaign.tariff_id]
|
||||
if same_tariff_subs:
|
||||
existing_subscription = max(same_tariff_subs, key=lambda s: s.days_left)
|
||||
# If no sub for this tariff, existing_subscription stays None -> create new
|
||||
else:
|
||||
existing_subscription = await get_subscription_by_user_id(db, user.id)
|
||||
if existing_subscription:
|
||||
logger.warning(
|
||||
'⚠️ У пользователя уже есть подписка, бонус тарифа кампании пропущен',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
)
|
||||
return CampaignBonusResult(success=False)
|
||||
if existing_subscription:
|
||||
logger.warning(
|
||||
'⚠️ У пользователя уже есть подписка, бонус тарифа кампании пропущен',
|
||||
format_user_log=_format_user_log(user),
|
||||
campaign_id=campaign.id,
|
||||
)
|
||||
return CampaignBonusResult(success=False)
|
||||
|
||||
if not campaign.tariff_id:
|
||||
logger.error('❌ Кампания не имеет указанного тарифа для выдачи', campaign_id=campaign.id)
|
||||
@@ -295,23 +315,54 @@ class AdvertisingCampaignService:
|
||||
except Exception as error:
|
||||
logger.error('Не удалось подобрать сквад для тарифа кампании', campaign_id=campaign.id, error=error)
|
||||
|
||||
# Создаём подписку как платную (не trial) с привязкой к тарифу
|
||||
new_subscription = await create_paid_subscription(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
duration_days=duration_days,
|
||||
traffic_limit_gb=traffic_limit or 0,
|
||||
device_limit=device_limit,
|
||||
connected_squads=squads,
|
||||
update_server_counters=True,
|
||||
is_trial=False, # Это полноценная подписка, не пробная
|
||||
tariff_id=tariff.id,
|
||||
)
|
||||
if existing_subscription:
|
||||
# Multi-tariff: extend the existing subscription for this tariff
|
||||
from app.database.crud.subscription import extend_subscription
|
||||
|
||||
try:
|
||||
await self.subscription_service.create_remnawave_user(db, new_subscription)
|
||||
except Exception as error:
|
||||
logger.error('❌ Ошибка синхронизации RemnaWave для тарифа кампании', campaign_id=campaign.id, error=error)
|
||||
await extend_subscription(db, existing_subscription, duration_days, tariff_id=tariff.id)
|
||||
try:
|
||||
await self.subscription_service.update_remnawave_user(db, existing_subscription)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
'❌ Ошибка синхронизации RemnaWave при продлении тарифа кампании',
|
||||
campaign_id=campaign.id,
|
||||
error=error,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
'🎁 Подписка пользователя продлена по тарифу кампании на дней',
|
||||
format_user_log=_format_user_log(user),
|
||||
tariff_name=tariff.name,
|
||||
campaign_id=campaign.id,
|
||||
duration_days=duration_days,
|
||||
subscription_id=existing_subscription.id,
|
||||
)
|
||||
else:
|
||||
# Создаём подписку как платную (не trial) с привязкой к тарифу
|
||||
new_subscription = await create_paid_subscription(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
duration_days=duration_days,
|
||||
traffic_limit_gb=traffic_limit or 0,
|
||||
device_limit=device_limit,
|
||||
connected_squads=squads,
|
||||
update_server_counters=True,
|
||||
is_trial=False,
|
||||
tariff_id=tariff.id,
|
||||
)
|
||||
|
||||
try:
|
||||
await self.subscription_service.create_remnawave_user(db, new_subscription)
|
||||
except Exception as error:
|
||||
logger.error('❌ Ошибка синхронизации RemnaWave для тарифа кампании', campaign_id=campaign.id, error=error)
|
||||
|
||||
logger.info(
|
||||
'🎁 Пользователю выдан тариф по кампании на дней',
|
||||
format_user_log=_format_user_log(user),
|
||||
tariff_name=tariff.name,
|
||||
campaign_id=campaign.id,
|
||||
duration_days=duration_days,
|
||||
)
|
||||
|
||||
await record_campaign_registration(
|
||||
db,
|
||||
@@ -322,14 +373,6 @@ class AdvertisingCampaignService:
|
||||
tariff_duration_days=duration_days,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"🎁 Пользователю выдан тариф '' по кампании на дней",
|
||||
format_user_log=_format_user_log(user),
|
||||
tariff_name=tariff.name,
|
||||
campaign_id=campaign.id,
|
||||
duration_days=duration_days,
|
||||
)
|
||||
|
||||
return CampaignBonusResult(
|
||||
success=True,
|
||||
bonus_type='tariff',
|
||||
|
||||
@@ -1003,7 +1003,12 @@ class RemnaWaveService:
|
||||
|
||||
try:
|
||||
needs_panel_update = any(
|
||||
subscription.user and subscription.user.remnawave_uuid for subscription in subscriptions
|
||||
(
|
||||
subscription.remnawave_uuid
|
||||
if settings.is_multi_tariff_enabled()
|
||||
else (subscription.user and subscription.user.remnawave_uuid)
|
||||
)
|
||||
for subscription in subscriptions
|
||||
)
|
||||
|
||||
api = None
|
||||
|
||||
@@ -756,10 +756,17 @@ async def _auto_purchase_tariff(
|
||||
from app.database.crud.subscription import get_active_subscriptions_by_user_id
|
||||
|
||||
active_subs = await get_active_subscriptions_by_user_id(db, user.id)
|
||||
existing_subscription = next(
|
||||
(s for s in active_subs if s.tariff_id == tariff_id),
|
||||
None,
|
||||
)
|
||||
_cart_sub_id = cart_data.get('subscription_id')
|
||||
if _cart_sub_id:
|
||||
existing_subscription = next(
|
||||
(s for s in active_subs if s.id == int(_cart_sub_id)),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
existing_subscription = next(
|
||||
(s for s in active_subs if s.tariff_id == tariff_id),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
existing_subscription = await get_subscription_by_user_id(db, user.id)
|
||||
|
||||
@@ -1153,10 +1160,17 @@ async def _auto_purchase_daily_tariff(
|
||||
from app.database.crud.subscription import get_active_subscriptions_by_user_id
|
||||
|
||||
active_subs = await get_active_subscriptions_by_user_id(db, user.id)
|
||||
existing_subscription = next(
|
||||
(s for s in active_subs if s.tariff_id == tariff_id),
|
||||
None,
|
||||
)
|
||||
_cart_sub_id = cart_data.get('subscription_id')
|
||||
if _cart_sub_id:
|
||||
existing_subscription = next(
|
||||
(s for s in active_subs if s.id == int(_cart_sub_id)),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
existing_subscription = next(
|
||||
(s for s in active_subs if s.tariff_id == tariff_id),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
existing_subscription = await get_subscription_by_user_id(db, user.id)
|
||||
|
||||
|
||||
@@ -406,11 +406,17 @@ class SubscriptionService:
|
||||
return None
|
||||
|
||||
# Resolve the Remnawave UUID: prefer subscription-level in multi-tariff mode
|
||||
remnawave_uuid = (
|
||||
subscription.remnawave_uuid
|
||||
if settings.is_multi_tariff_enabled() and subscription.remnawave_uuid
|
||||
else user.remnawave_uuid
|
||||
)
|
||||
if settings.is_multi_tariff_enabled():
|
||||
remnawave_uuid = subscription.remnawave_uuid
|
||||
if not remnawave_uuid:
|
||||
logger.warning(
|
||||
'Multi-tariff: subscription has no remnawave_uuid, cannot update panel',
|
||||
subscription_id=subscription.id,
|
||||
user_id=subscription.user_id,
|
||||
)
|
||||
return None
|
||||
else:
|
||||
remnawave_uuid = user.remnawave_uuid
|
||||
if not remnawave_uuid:
|
||||
logger.error('RemnaWave UUID не найден для пользователя', user_id=subscription.user_id)
|
||||
return None
|
||||
@@ -489,17 +495,23 @@ class SubscriptionService:
|
||||
updated_user = await api.update_user(**update_kwargs)
|
||||
|
||||
if reset_traffic:
|
||||
reset_uuid = (
|
||||
subscription.remnawave_uuid
|
||||
if settings.is_multi_tariff_enabled() and subscription.remnawave_uuid
|
||||
else user.remnawave_uuid
|
||||
)
|
||||
await self._reset_user_traffic(
|
||||
api,
|
||||
reset_uuid,
|
||||
user,
|
||||
reset_reason,
|
||||
)
|
||||
if settings.is_multi_tariff_enabled():
|
||||
reset_uuid = subscription.remnawave_uuid
|
||||
if not reset_uuid:
|
||||
logger.warning(
|
||||
'Multi-tariff: subscription has no remnawave_uuid, skipping traffic reset',
|
||||
subscription_id=subscription.id,
|
||||
user_id=subscription.user_id,
|
||||
)
|
||||
else:
|
||||
reset_uuid = user.remnawave_uuid
|
||||
if reset_uuid:
|
||||
await self._reset_user_traffic(
|
||||
api,
|
||||
reset_uuid,
|
||||
user,
|
||||
reset_reason,
|
||||
)
|
||||
|
||||
subscription.subscription_url = updated_user.subscription_url
|
||||
subscription.subscription_crypto_link = updated_user.happ_crypto_link
|
||||
@@ -609,12 +621,20 @@ class SubscriptionService:
|
||||
async def revoke_subscription(self, db: AsyncSession, subscription: Subscription) -> str | None:
|
||||
try:
|
||||
user = await get_user_by_id(db, subscription.user_id)
|
||||
revoke_uuid = (
|
||||
subscription.remnawave_uuid
|
||||
if settings.is_multi_tariff_enabled() and subscription.remnawave_uuid
|
||||
else user.remnawave_uuid
|
||||
)
|
||||
if not user or not revoke_uuid:
|
||||
if not user:
|
||||
return None
|
||||
if settings.is_multi_tariff_enabled():
|
||||
revoke_uuid = subscription.remnawave_uuid
|
||||
if not revoke_uuid:
|
||||
logger.warning(
|
||||
'Multi-tariff: subscription has no remnawave_uuid, cannot revoke',
|
||||
subscription_id=subscription.id,
|
||||
user_id=subscription.user_id,
|
||||
)
|
||||
return None
|
||||
else:
|
||||
revoke_uuid = user.remnawave_uuid
|
||||
if not revoke_uuid:
|
||||
return None
|
||||
|
||||
async with self.get_api_client() as api:
|
||||
@@ -645,12 +665,20 @@ class SubscriptionService:
|
||||
async def sync_subscription_usage(self, db: AsyncSession, subscription: Subscription) -> bool:
|
||||
try:
|
||||
user = await get_user_by_id(db, subscription.user_id)
|
||||
sync_uuid = (
|
||||
subscription.remnawave_uuid
|
||||
if settings.is_multi_tariff_enabled() and subscription.remnawave_uuid
|
||||
else user.remnawave_uuid
|
||||
)
|
||||
if not user or not sync_uuid:
|
||||
if not user:
|
||||
return False
|
||||
if settings.is_multi_tariff_enabled():
|
||||
sync_uuid = subscription.remnawave_uuid
|
||||
if not sync_uuid:
|
||||
logger.warning(
|
||||
'Multi-tariff: subscription has no remnawave_uuid, cannot sync usage',
|
||||
subscription_id=subscription.id,
|
||||
user_id=subscription.user_id,
|
||||
)
|
||||
return False
|
||||
else:
|
||||
sync_uuid = user.remnawave_uuid
|
||||
if not sync_uuid:
|
||||
return False
|
||||
|
||||
async with self.get_api_client() as api:
|
||||
@@ -941,12 +969,20 @@ class SubscriptionService:
|
||||
async with semaphore:
|
||||
try:
|
||||
user = users_map.get(sub.user_id)
|
||||
remnawave_uuid = (
|
||||
sub.remnawave_uuid
|
||||
if settings.is_multi_tariff_enabled() and sub.remnawave_uuid
|
||||
else (user.remnawave_uuid if user else None)
|
||||
)
|
||||
if not user or not remnawave_uuid:
|
||||
if not user:
|
||||
return False
|
||||
if settings.is_multi_tariff_enabled():
|
||||
remnawave_uuid = sub.remnawave_uuid
|
||||
if not remnawave_uuid:
|
||||
logger.warning(
|
||||
'Multi-tariff: subscription has no remnawave_uuid, skipping squad sync',
|
||||
subscription_id=sub.id,
|
||||
user_id=sub.user_id,
|
||||
)
|
||||
return False
|
||||
else:
|
||||
remnawave_uuid = user.remnawave_uuid
|
||||
if not remnawave_uuid:
|
||||
return False
|
||||
|
||||
current_time = datetime.now(UTC)
|
||||
|
||||
@@ -755,7 +755,7 @@ class UserService:
|
||||
logger.info(
|
||||
'✅ RemnaWave подписка восстановлена при разблокировке',
|
||||
subscription_id=sub.id,
|
||||
remnawave_uuid=user.remnawave_uuid,
|
||||
remnawave_uuid=sub.remnawave_uuid if settings.is_multi_tariff_enabled() else user.remnawave_uuid,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
||||
Reference in New Issue
Block a user