From 9ca3320a0204346aac2c76601f221b0bc70226a7 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 22 Apr 2026 05:12:27 +0300 Subject: [PATCH] fix: classic mode renewal resets device_limit to 1 via cart key mismatch - Fix cart key mismatch: extend cart saved 'device_limit' but confirm_purchase read 'devices' key, falling back to DEFAULT=1. Now both keys are saved in both cart-save paths - Fix confirm_purchase device resolution: use explicit is None checks instead of or-chain to avoid falsy-zero trap - Fix return_to_saved_cart display: fall back to 'device_limit' and 'traffic_limit_gb' keys when 'devices'/'traffic_gb' are absent - Fix second cart-save path in _extend_existing_subscription with same dual-key pattern - Fix RemnaWaveService import path in renewal service - Add RESET_DEVICES_ON_RENEWAL setting: resets all connected devices (hwid) via RemnaWave API on each subscription renewal --- .env.example | 3 +++ app/config.py | 1 + app/handlers/subscription/purchase.py | 21 ++++++++++++++++-- app/services/subscription_renewal_service.py | 23 ++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index ce3ff93d..0aecb243 100644 --- a/.env.example +++ b/.env.example @@ -263,6 +263,9 @@ SALES_MODE=tariffs TARIFF_SWITCH_UPGRADE_ENABLED=true TARIFF_SWITCH_DOWNGRADE_ENABLED=true +# Сброс привязанных устройств при продлении подписки (однократно при каждом продлении) +RESET_DEVICES_ON_RENEWAL=false + # ===== ТРИАЛ ПОДПИСКА ===== TRIAL_DURATION_DAYS=3 TRIAL_TRAFFIC_LIMIT_GB=10 diff --git a/app/config.py b/app/config.py index 4d0a2499..174b7d88 100644 --- a/app/config.py +++ b/app/config.py @@ -148,6 +148,7 @@ class Settings(BaseSettings): DEFAULT_TRAFFIC_RESET_STRATEGY: str = 'MONTH' RESET_TRAFFIC_ON_PAYMENT: bool = False RESET_TRAFFIC_ON_TARIFF_SWITCH: bool = True + RESET_DEVICES_ON_RENEWAL: bool = False TARIFF_SWITCH_UPGRADE_ENABLED: bool = True TARIFF_SWITCH_DOWNGRADE_ENABLED: bool = True MAX_DEVICES_LIMIT: int = 20 diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index d31eab29..ff98ad64 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -1581,11 +1581,15 @@ async def return_to_saved_cart(callback: types.CallbackQuery, state: FSMContext, if settings.is_traffic_fixed(): traffic_value = prepared_cart_data.get('traffic_gb') + if traffic_value is None: + traffic_value = prepared_cart_data.get('traffic_limit_gb') if traffic_value is None: traffic_value = settings.get_fixed_traffic_limit() traffic_display = 'Безлимитный' if traffic_value == 0 else f'{traffic_value} ГБ' else: - traffic_value = prepared_cart_data.get('traffic_gb', 0) or 0 + traffic_value = prepared_cart_data.get('traffic_gb') + if traffic_value is None: + traffic_value = prepared_cart_data.get('traffic_limit_gb', 0) traffic_display = 'Безлимитный' if traffic_value == 0 else f'{traffic_value} ГБ' summary_lines = [ @@ -1598,6 +1602,8 @@ async def return_to_saved_cart(callback: types.CallbackQuery, state: FSMContext, if settings.is_devices_selection_enabled(): devices_value = prepared_cart_data.get('devices') + if devices_value is None: + devices_value = prepared_cart_data.get('device_limit') if devices_value is not None: summary_lines.append(f'📱 Устройства: {devices_value}') @@ -1972,7 +1978,10 @@ async def confirm_extend_subscription( 'description': f'Продление подписки на {days} дней', 'consume_promo_offer': bool(promo_offer_discount > 0), 'device_limit': device_limit, + 'devices': device_limit, 'traffic_limit_gb': renewal_traffic_gb, + 'traffic_gb': renewal_traffic_gb, + 'countries': list(subscription.connected_squads or []), } await user_cart_service.save_user_cart(db_user.id, cart_data) @@ -2227,7 +2236,12 @@ async def confirm_purchase(callback: types.CallbackQuery, state: FSMContext, db_ devices_selection_enabled = settings.is_devices_selection_enabled() forced_disabled_limit: int | None = None if devices_selection_enabled: - devices_selected = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) + # Для extend-корзины ключ может быть 'device_limit' вместо 'devices' + devices_selected = data.get('devices') + if devices_selected is None: + devices_selected = data.get('device_limit') + if devices_selected is None: + devices_selected = settings.DEFAULT_DEVICE_LIMIT else: forced_disabled_limit = settings.get_disabled_mode_device_limit() if forced_disabled_limit is None: @@ -4512,8 +4526,11 @@ async def _extend_existing_subscription( 'return_to_cart': True, 'description': f'Продление подписки на {period_days} дней', 'device_limit': device_limit, + 'devices': device_limit, 'traffic_limit_gb': traffic_limit_gb, + 'traffic_gb': traffic_limit_gb, 'squad_uuid': squad_uuid, + 'countries': [squad_uuid] if squad_uuid else [], 'consume_promo_offer': consume_promo, } diff --git a/app/services/subscription_renewal_service.py b/app/services/subscription_renewal_service.py index 548369c0..77ca3a7f 100644 --- a/app/services/subscription_renewal_service.py +++ b/app/services/subscription_renewal_service.py @@ -488,6 +488,7 @@ class SubscriptionRenewalService: ) reset_traffic = was_expired and settings.RESET_TRAFFIC_ON_PAYMENT + reset_devices = settings.RESET_DEVICES_ON_RENEWAL subscription_service = SubscriptionService() try: await db.refresh(user) @@ -526,6 +527,28 @@ class SubscriptionRenewalService: action='create' if not getattr(subscription_after, 'remnawave_uuid', None) else 'update', ) + # Сброс привязанных устройств при продлении (если включено) + if reset_devices: + try: + from app.services.remnawave_service import RemnaWaveService + + rw_service = RemnaWaveService() + _uuid = ( + getattr(subscription_after, 'remnawave_uuid', None) + if settings.is_multi_tariff_enabled() + else getattr(user, 'remnawave_uuid', None) + ) + if _uuid: + async with rw_service.get_api_client() as api: + await api.reset_user_devices(_uuid) + logger.info( + 'Devices reset on renewal', + subscription_id=subscription_after.id, + user_id=user.id, + ) + except Exception as error: + logger.warning('Failed to reset devices on renewal', error=error, exc_info=True) + transaction: Transaction | None = None try: transaction = await create_transaction(