From 78209c862320ef030134ac7e77c57229b5701cfd Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 28 Mar 2026 19:27:08 +0300 Subject: [PATCH] fix: block legacy subscription renewal bypass in tariff mode When switching from configurator to tariff mode, users with old subscriptions (tariff_id=NULL) could still renew them through unguarded paths, bypassing tariff pricing entirely. Vulnerable paths fixed: - MiniApp POST /subscription/renewal/options: returns empty list for classic subscriptions in tariff mode - MiniApp POST /subscription/renewal: raises 400 with classic_subscription_blocked error code - Bot confirm_extend_subscription: blocks stale extend_period_ callbacks with tariff mode check - Monitoring _process_autopayments: skips classic subscriptions (tariff_id=NULL) in autopay loop when tariff mode active Already protected (no changes needed): - Cabinet GET/POST renewal endpoints (renewal.py:51,117) - Auto-purchase service (_prepare_auto_extend_context:244) - Bot handle_extend_subscription menu (purchase.py:1657) - Tariff extend flow (tariff_purchase.py:2047) --- app/handlers/subscription/purchase.py | 8 ++++++++ app/services/monitoring_service.py | 9 +++++++++ app/webapi/routes/miniapp.py | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index 432afd5c..ded07ac5 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -1817,6 +1817,14 @@ async def confirm_extend_subscription( days = int(callback.data.split('_')[2]) texts = get_texts(db_user.language) + # Block classic subscription renewal when tariff mode is active + if settings.is_tariffs_mode(): + await callback.answer( + texts.t('TARIFF_MODE_RENEWAL_BLOCKED', '❌ Продление в этом режиме недоступно. Выберите тариф.'), + show_alert=True, + ) + return + # Валидация что период доступен для продления available_renewal_periods = settings.get_available_renewal_periods() if days not in available_renewal_periods: diff --git a/app/services/monitoring_service.py b/app/services/monitoring_service.py index 6070f778..b73f3929 100644 --- a/app/services/monitoring_service.py +++ b/app/services/monitoring_service.py @@ -1075,6 +1075,15 @@ class MonitoringService: ) continue + # Skip classic subscriptions (tariff_id=NULL) when tariff mode is active + if settings.is_tariffs_mode() and not sub.tariff_id: + logger.debug( + 'Пропускаем классическую подписку без тарифа в autopay (tariff mode)', + sub_id=sub.id, + user_id=sub.user_id, + ) + continue + days_before_expiry = (sub.end_date - current_time).days if days_before_expiry <= min(sub.autopay_days_before or 3, 3): autopay_subscriptions.append(sub) diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 4fd1d620..32788e3d 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -5106,6 +5106,14 @@ async def get_subscription_renewal_options_endpoint( ) _validate_subscription_id(payload.subscription_id, subscription) + # Block classic subscription renewal when tariff mode is active + if settings.is_tariffs_mode() and not subscription.tariff_id: + return MiniAppSubscriptionRenewalOptionsResponse( + periods=[], + balance_kopeks=getattr(user, 'balance_kopeks', 0), + balance_currency=(getattr(user, 'balance_currency', None) or 'RUB').upper(), + ) + periods, pricing_map, default_period_id = await _prepare_subscription_renewal_options( db, user, @@ -5197,6 +5205,16 @@ async def submit_subscription_renewal_endpoint( ) _validate_subscription_id(payload.subscription_id, subscription) + # Block classic subscription renewal when tariff mode is active + if settings.is_tariffs_mode() and not subscription.tariff_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={ + 'code': 'classic_subscription_blocked', + 'message': 'Classic subscriptions cannot be renewed. Please purchase a tariff.', + }, + ) + period_days: int | None = None if payload.period_days is not None: try: