diff --git a/app/config.py b/app/config.py index 2877faff..788bc8e6 100644 --- a/app/config.py +++ b/app/config.py @@ -83,7 +83,6 @@ class Settings(BaseSettings): TRIAL_ADD_REMAINING_DAYS_TO_PAID: bool = False DEFAULT_TRAFFIC_LIMIT_GB: int = 100 DEFAULT_DEVICE_LIMIT: int = 1 - DEVICES_SELECTION_ENABLED: bool = True TRIAL_SQUAD_UUID: Optional[str] = None DEFAULT_TRAFFIC_RESET_STRATEGY: str = "MONTH" RESET_TRAFFIC_ON_PAYMENT: bool = False @@ -798,12 +797,9 @@ class Settings(BaseSettings): def is_traffic_fixed(self) -> bool: return self.TRAFFIC_SELECTION_MODE.lower() == "fixed" - + def get_fixed_traffic_limit(self) -> int: return self.FIXED_TRAFFIC_LIMIT_GB - - def is_devices_selection_enabled(self) -> bool: - return bool(self.DEVICES_SELECTION_ENABLED) def is_yookassa_enabled(self) -> bool: return (self.YOOKASSA_ENABLED and diff --git a/app/handlers/subscription/autopay.py b/app/handlers/subscription/autopay.py index 73afa5d1..7921bb1f 100644 --- a/app/handlers/subscription/autopay.py +++ b/app/handlers/subscription/autopay.py @@ -208,33 +208,6 @@ async def handle_subscription_config_back( await state.set_state(SubscriptionStates.selecting_period) elif current_state == SubscriptionStates.selecting_devices.state: - if not settings.is_devices_selection_enabled(): - if await _should_show_countries_management(db_user): - countries = await _get_available_countries(db_user.promo_group_id) - data = await state.get_data() - selected_countries = data.get('countries', []) - - await callback.message.edit_text( - texts.SELECT_COUNTRIES, - reply_markup=get_countries_keyboard(countries, selected_countries, db_user.language) - ) - await state.set_state(SubscriptionStates.selecting_countries) - elif settings.is_traffic_selectable(): - await callback.message.edit_text( - texts.SELECT_TRAFFIC, - reply_markup=get_traffic_packages_keyboard(db_user.language) - ) - await state.set_state(SubscriptionStates.selecting_traffic) - else: - await callback.message.edit_text( - await _build_subscription_period_prompt(db_user, texts, db), - reply_markup=get_subscription_period_keyboard(db_user.language), - parse_mode="HTML", - ) - await state.set_state(SubscriptionStates.selecting_period) - await callback.answer() - return - if await _should_show_countries_management(db_user): countries = await _get_available_countries(db_user.promo_group_id) data = await state.get_data() @@ -261,36 +234,13 @@ async def handle_subscription_config_back( elif current_state == SubscriptionStates.confirming_purchase.state: data = await state.get_data() - if settings.is_devices_selection_enabled(): - selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) + selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) - await callback.message.edit_text( - texts.SELECT_DEVICES, - reply_markup=get_devices_keyboard(selected_devices, db_user.language) - ) - await state.set_state(SubscriptionStates.selecting_devices) - elif await _should_show_countries_management(db_user): - countries = await _get_available_countries(db_user.promo_group_id) - selected_countries = data.get('countries', []) - - await callback.message.edit_text( - texts.SELECT_COUNTRIES, - reply_markup=get_countries_keyboard(countries, selected_countries, db_user.language) - ) - await state.set_state(SubscriptionStates.selecting_countries) - elif settings.is_traffic_selectable(): - await callback.message.edit_text( - texts.SELECT_TRAFFIC, - reply_markup=get_traffic_packages_keyboard(db_user.language) - ) - await state.set_state(SubscriptionStates.selecting_traffic) - else: - await callback.message.edit_text( - await _build_subscription_period_prompt(db_user, texts, db), - reply_markup=get_subscription_period_keyboard(db_user.language), - parse_mode="HTML", - ) - await state.set_state(SubscriptionStates.selecting_period) + await callback.message.edit_text( + texts.SELECT_DEVICES, + reply_markup=get_devices_keyboard(selected_devices, db_user.language) + ) + await state.set_state(SubscriptionStates.selecting_devices) else: from app.handlers.menu import show_main_menu diff --git a/app/handlers/subscription/countries.py b/app/handlers/subscription/countries.py index a0a4336a..1e964189 100644 --- a/app/handlers/subscription/countries.py +++ b/app/handlers/subscription/countries.py @@ -79,7 +79,6 @@ from app.utils.promo_offer import ( ) from .common import _get_addon_discount_percent_for_user, _get_period_hint_from_subscription, logger -from .workflow import present_subscription_summary async def handle_add_countries( callback: types.CallbackQuery, @@ -589,21 +588,15 @@ async def countries_continue( await callback.answer("⚠️ Выберите хотя бы одну страну!", show_alert=True) return - if settings.is_devices_selection_enabled(): - selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) + selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) - await callback.message.edit_text( - texts.SELECT_DEVICES, - reply_markup=get_devices_keyboard(selected_devices, db_user.language) - ) + await callback.message.edit_text( + texts.SELECT_DEVICES, + reply_markup=get_devices_keyboard(selected_devices, db_user.language) + ) - await state.set_state(SubscriptionStates.selecting_devices) - await callback.answer() - return - - success = await present_subscription_summary(callback, state, db_user, texts) - if success: - await callback.answer() + await state.set_state(SubscriptionStates.selecting_devices) + await callback.answer() async def _get_available_countries(promo_group_id: Optional[int] = None): from app.utils.cache import cache, cache_key diff --git a/app/handlers/subscription/devices.py b/app/handlers/subscription/devices.py index 0cbb809b..3ca44442 100644 --- a/app/handlers/subscription/devices.py +++ b/app/handlers/subscription/devices.py @@ -183,16 +183,6 @@ async def handle_change_devices( texts = get_texts(db_user.language) subscription = db_user.subscription - if not settings.is_devices_selection_enabled(): - await callback.answer( - texts.t( - "DEVICES_SELECTION_DISABLED", - "⚠️ Изменение количества устройств недоступно", - ), - show_alert=True, - ) - return - if not subscription or subscription.is_trial: await callback.answer( texts.t("PAID_FEATURE_ONLY", "⚠️ Эта функция доступна только для платных подписок"), @@ -243,16 +233,6 @@ async def confirm_change_devices( texts = get_texts(db_user.language) subscription = db_user.subscription - if not settings.is_devices_selection_enabled(): - await callback.answer( - texts.t( - "DEVICES_SELECTION_DISABLED", - "⚠️ Изменение количества устройств недоступно", - ), - show_alert=True, - ) - return - current_devices = subscription.device_limit if new_devices_count == current_devices: @@ -399,16 +379,6 @@ async def execute_change_devices( subscription = db_user.subscription current_devices = subscription.device_limit - if not settings.is_devices_selection_enabled(): - await callback.answer( - texts.t( - "DEVICES_SELECTION_DISABLED", - "⚠️ Изменение количества устройств недоступно", - ), - show_alert=True, - ) - return - try: if price > 0: success = await subtract_user_balance( @@ -893,16 +863,6 @@ async def confirm_add_devices( texts = get_texts(db_user.language) subscription = db_user.subscription - if not settings.is_devices_selection_enabled(): - await callback.answer( - texts.t( - "DEVICES_SELECTION_DISABLED", - "⚠️ Изменение количества устройств недоступно", - ), - show_alert=True, - ) - return - resume_callback = None new_total_devices = subscription.device_limit + devices_count diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index 572917dd..d9f8bbcc 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -140,7 +140,6 @@ from .traffic import ( handle_switch_traffic, select_traffic, ) -from .workflow import present_subscription_summary async def show_subscription_info( callback: types.CallbackQuery, @@ -1257,15 +1256,12 @@ async def select_period( reply_markup=get_countries_keyboard(countries, [], db_user.language) ) await state.set_state(SubscriptionStates.selecting_countries) - await callback.answer() - return + else: + countries = await _get_available_countries(db_user.promo_group_id) + available_countries = [c for c in countries if c.get('is_available', True)] + data['countries'] = [available_countries[0]['uuid']] if available_countries else [] + await state.set_data(data) - countries = await _get_available_countries(db_user.promo_group_id) - available_countries = [c for c in countries if c.get('is_available', True)] - data['countries'] = [available_countries[0]['uuid']] if available_countries else [] - await state.set_data(data) - - if settings.is_devices_selection_enabled(): selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) await callback.message.edit_text( @@ -1273,13 +1269,6 @@ async def select_period( reply_markup=get_devices_keyboard(selected_devices, db_user.language) ) await state.set_state(SubscriptionStates.selecting_devices) - await callback.answer() - return - - success = await present_subscription_summary(callback, state, db_user, texts) - if success: - await callback.answer() - return await callback.answer() @@ -1292,17 +1281,6 @@ async def select_devices( await callback.answer("❌ Некорректный запрос", show_alert=True) return - if not settings.is_devices_selection_enabled(): - texts = get_texts(db_user.language) - await callback.answer( - texts.t( - "DEVICES_SELECTION_DISABLED", - "⚠️ Изменение количества устройств недоступно", - ), - show_alert=True, - ) - return - try: devices = int(callback.data.split('_')[1]) except (ValueError, IndexError): @@ -1346,9 +1324,24 @@ async def devices_continue( data = await state.get_data() texts = get_texts(db_user.language) - success = await present_subscription_summary(callback, state, db_user, texts) - if success: - await callback.answer() + try: + summary_text, prepared_data = await _prepare_subscription_summary(db_user, data, texts) + except ValueError: + logger.error(f"Ошибка в расчете цены подписки для пользователя {db_user.telegram_id}") + await callback.answer("Ошибка расчета цены. Обратитесь в поддержку.", show_alert=True) + return + + await state.set_data(prepared_data) + await save_subscription_checkout_draft(db_user.id, prepared_data) + + await callback.message.edit_text( + summary_text, + reply_markup=get_subscription_confirm_keyboard(db_user.language), + parse_mode="HTML", + ) + + await state.set_state(SubscriptionStates.confirming_purchase) + await callback.answer() async def confirm_purchase( callback: types.CallbackQuery, diff --git a/app/handlers/subscription/traffic.py b/app/handlers/subscription/traffic.py index dbd40bef..6733db4b 100644 --- a/app/handlers/subscription/traffic.py +++ b/app/handlers/subscription/traffic.py @@ -80,7 +80,6 @@ from app.utils.promo_offer import ( from .common import _apply_addon_discount, _get_addon_discount_percent_for_user, _get_period_hint_from_subscription, get_confirm_switch_traffic_keyboard, get_traffic_switch_keyboard, logger from .countries import _get_available_countries, _should_show_countries_management -from .workflow import present_subscription_summary async def handle_add_traffic( callback: types.CallbackQuery, @@ -353,29 +352,19 @@ async def select_traffic( reply_markup=get_countries_keyboard(countries, [], db_user.language) ) await state.set_state(SubscriptionStates.selecting_countries) - await callback.answer() - return else: countries = await _get_available_countries(db_user.promo_group_id) available_countries = [c for c in countries if c.get('is_available', True)] data['countries'] = [available_countries[0]['uuid']] if available_countries else [] await state.set_data(data) - if settings.is_devices_selection_enabled(): - selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) + selected_devices = data.get('devices', settings.DEFAULT_DEVICE_LIMIT) - await callback.message.edit_text( - texts.SELECT_DEVICES, - reply_markup=get_devices_keyboard(selected_devices, db_user.language) - ) - await state.set_state(SubscriptionStates.selecting_devices) - await callback.answer() - return - - success = await present_subscription_summary(callback, state, db_user, texts) - if success: - await callback.answer() - return + await callback.message.edit_text( + texts.SELECT_DEVICES, + reply_markup=get_devices_keyboard(selected_devices, db_user.language) + ) + await state.set_state(SubscriptionStates.selecting_devices) await callback.answer() diff --git a/app/handlers/subscription/workflow.py b/app/handlers/subscription/workflow.py deleted file mode 100644 index 88b6c831..00000000 --- a/app/handlers/subscription/workflow.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -from typing import Any - -from aiogram import types -from aiogram.fsm.context import FSMContext - -from app.database.models import User -from app.keyboards.inline import get_subscription_confirm_keyboard -from app.services.subscription_checkout_service import save_subscription_checkout_draft -from app.states import SubscriptionStates - -from .pricing import _prepare_subscription_summary - - -logger = logging.getLogger(__name__) - - -async def present_subscription_summary( - callback: types.CallbackQuery, - state: FSMContext, - db_user: User, - texts: Any, -) -> bool: - data = await state.get_data() - - try: - summary_text, prepared_data = await _prepare_subscription_summary(db_user, data, texts) - except ValueError: - logger.error( - "Ошибка в расчете цены подписки для пользователя %s", - db_user.telegram_id, - ) - await callback.answer( - "Ошибка расчета цены. Обратитесь в поддержку.", - show_alert=True, - ) - return False - - await state.set_data(prepared_data) - await save_subscription_checkout_draft(db_user.id, prepared_data) - - await callback.message.edit_text( - summary_text, - reply_markup=get_subscription_confirm_keyboard(db_user.language), - parse_mode="HTML", - ) - await state.set_state(SubscriptionStates.confirming_purchase) - - return True diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index 43611507..2656392f 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -2091,25 +2091,15 @@ def get_updated_subscription_settings_keyboard(language: str = DEFAULT_LANGUAGE, InlineKeyboardButton(text=texts.t("ADD_COUNTRIES_BUTTON", "🌐 Добавить страны"), callback_data="subscription_add_countries") ]) - settings_buttons = [] - - if settings.is_devices_selection_enabled(): - settings_buttons.append([ - InlineKeyboardButton( - text=texts.t("CHANGE_DEVICES_BUTTON", "📱 Изменить устройства"), - callback_data="subscription_change_devices", - ) - ]) - - settings_buttons.append([ - InlineKeyboardButton( - text=texts.t("MANAGE_DEVICES_BUTTON", "🔧 Управление устройствами"), - callback_data="subscription_manage_devices", - ) + keyboard.extend([ + [ + InlineKeyboardButton(text=texts.t("CHANGE_DEVICES_BUTTON", "📱 Изменить устройства"), callback_data="subscription_change_devices") + ], + [ + InlineKeyboardButton(text=texts.t("MANAGE_DEVICES_BUTTON", "🔧 Управление устройствами"), callback_data="subscription_manage_devices") + ] ]) - keyboard.extend(settings_buttons) - if settings.is_traffic_selectable(): keyboard.insert(-2, [ InlineKeyboardButton(text=texts.t("SWITCH_TRAFFIC_BUTTON", "🔄 Переключить трафик"), callback_data="subscription_switch_traffic") diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json index 7c8326b1..b4d0666e 100644 --- a/app/localization/locales/en.json +++ b/app/localization/locales/en.json @@ -796,7 +796,6 @@ "CANCEL_REPLY": "❌ Cancel reply", "CANCEL_TICKET_CREATION": "❌ Cancel ticket creation", "CHANGE_DEVICES_BUTTON": "📱 Change devices", - "DEVICES_SELECTION_DISABLED": "⚠️ Changing the number of devices is disabled", "CHANGE_DEVICES_CONFIRM": "\n📱 Confirm change\n\nCurrent amount: {current_devices} devices\nNew amount: {new_devices} devices\n\nAction: {action}\n💰 {cost}\n\nApply this change?\n", "CHANGE_DEVICES_INFO": "\n📱 Adjust device limit\n\nCurrent limit: {current_devices} devices\n\nChoose the new number of devices:\n\n💡 Important:\n• Increasing — extra charge proportional to the remaining time\n• Decreasing — funds are not refunded\n", "CHANGE_DEVICES_PROMPT": "📱 Adjust device limit\n\nCurrent limit: {current_devices} devices\nChoose the new number of devices:\n\n💡 Important:\n• Increasing — extra cost prorated by remaining time\n• Decreasing — payments are not refunded", diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json index 4ee977be..aa75bdd6 100644 --- a/app/localization/locales/ru.json +++ b/app/localization/locales/ru.json @@ -796,7 +796,6 @@ "CANCEL_REPLY": "❌ Отменить ответ", "CANCEL_TICKET_CREATION": "❌ Отменить создание тикета", "CHANGE_DEVICES_BUTTON": "📱 Изменить устройства", - "DEVICES_SELECTION_DISABLED": "⚠️ Изменение количества устройств недоступно", "CHANGE_DEVICES_CONFIRM": "\n 📱 Подтверждение изменения\n\n Текущее количество: {current_devices} устройств\n Новое количество: {new_devices} устройств\n\n Действие: {action}\n 💰 {cost}\n\n Подтвердить изменение?\n ", "CHANGE_DEVICES_INFO": "\n 📱 Изменение количества устройств\n\n Текущий лимит: {current_devices} устройств\n\n Выберите новое количество устройств:\n\n 💡 Важно:\n • При увеличении - доплата пропорционально оставшемуся времени\n • При уменьшении - возврат средств не производится\n ", "CHANGE_DEVICES_PROMPT": "📱 Изменение количества устройств\n\nТекущий лимит: {current_devices} устройств\nВыберите новое количество устройств:\n\n💡 Важно:\n• При увеличении - доплата пропорционально оставшемуся времени\n• При уменьшении - возврат средств не производится", diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 81639dc6..24c54603 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -4133,21 +4133,20 @@ async def _build_subscription_settings( ) devices_options: List[MiniAppSubscriptionDeviceOption] = [] - if settings.is_devices_selection_enabled(): - for value in range(1, max_devices + 1): - chargeable = max(0, value - default_device_limit) - discounted_per_month, _ = apply_percentage_discount( - chargeable * settings.PRICE_PER_DEVICE, - devices_discount, - ) - devices_options.append( - MiniAppSubscriptionDeviceOption( - value=value, - label=None, - price_kopeks=discounted_per_month, - price_label=None, - ) + for value in range(1, max_devices + 1): + chargeable = max(0, value - default_device_limit) + discounted_per_month, _ = apply_percentage_discount( + chargeable * settings.PRICE_PER_DEVICE, + devices_discount, + ) + devices_options.append( + MiniAppSubscriptionDeviceOption( + value=value, + label=None, + price_kopeks=discounted_per_month, + price_label=None, ) + ) settings_payload = MiniAppSubscriptionSettings( subscription_id=subscription.id, @@ -4172,7 +4171,7 @@ async def _build_subscription_settings( ), devices=MiniAppSubscriptionDevicesSettings( options=devices_options, - can_update=settings.is_devices_selection_enabled(), + can_update=True, min=1, max=max_devices_setting or 0, step=1, @@ -5012,15 +5011,6 @@ async def update_subscription_devices_endpoint( subscription = _ensure_paid_subscription(user) _validate_subscription_id(payload.subscription_id, subscription) - if not settings.is_devices_selection_enabled(): - raise HTTPException( - status.HTTP_403_FORBIDDEN, - detail={ - "code": "devices_selection_disabled", - "message": "Изменение количества устройств отключено", - }, - ) - raw_value = payload.devices if payload.devices is not None else payload.device_limit if raw_value is None: raise HTTPException(