diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py index c73261f5..97b91d0b 100644 --- a/app/handlers/subscription.py +++ b/app/handlers/subscription.py @@ -6,7 +6,7 @@ from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession import json import os -from typing import Dict, List, Any, Tuple, Optional, Set +from typing import Dict, List, Any, Tuple, Optional from app.config import settings, PERIOD_PRICES, get_traffic_prices from app.states import SubscriptionStates @@ -49,7 +49,7 @@ from app.keyboards.inline import ( from app.localization.texts import get_texts from app.services.remnawave_service import RemnaWaveService from app.services.admin_notification_service import AdminNotificationService -from app.services.subscription_service import SubscriptionService, resolve_addon_discount_percent +from app.services.subscription_service import SubscriptionService from app.services.subscription_checkout_service import ( clear_subscription_checkout_draft, get_subscription_checkout_draft, @@ -62,7 +62,6 @@ from app.utils.pricing_utils import ( calculate_prorated_price, validate_pricing_calculation, format_period_description, - apply_percentage_discount, ) from app.utils.pagination import paginate_list from app.utils.subscription_utils import ( @@ -1138,22 +1137,14 @@ async def handle_add_countries( text += "⚪ - не выбрана\n\n" text += "⚠️ Важно: Повторное подключение отключенных стран будет платным!" - ( - countries_with_pricing, - _country_map, - _available_country_ids, - _servers_discount_percent, - _months_to_pay, - ) = _prepare_countries_with_addon_pricing(db_user, countries) - await state.update_data(countries=current_countries.copy()) - + await callback.message.edit_text( text, reply_markup=get_manage_countries_keyboard( - countries_with_pricing, - current_countries.copy(), - current_countries, + countries, + current_countries.copy(), + current_countries, db_user.language, subscription.end_date ), @@ -1217,19 +1208,10 @@ async def handle_manage_country( return data = await state.get_data() - current_countries = subscription.connected_squads - current_selected = data.get('countries', current_countries.copy()) + current_selected = data.get('countries', subscription.connected_squads.copy()) countries = await _get_available_countries(db_user.promo_group_id) - ( - countries_with_pricing, - _country_map, - available_country_ids, - _servers_discount_percent, - _months_to_pay, - ) = _prepare_countries_with_addon_pricing(db_user, countries) - - allowed_country_ids = available_country_ids | set(current_countries) + allowed_country_ids = {country['uuid'] for country in countries} if country_uuid not in allowed_country_ids and country_uuid not in current_selected: await callback.answer("❌ Сервер недоступен для вашей промогруппы", show_alert=True) @@ -1249,11 +1231,11 @@ async def handle_manage_country( try: await callback.message.edit_reply_markup( reply_markup=get_manage_countries_keyboard( - countries_with_pricing, - current_selected, - current_countries, + countries, + current_selected, + subscription.connected_squads, db_user.language, - subscription.end_date + subscription.end_date ) ) logger.info(f"✅ Клавиатура обновлена") @@ -1288,15 +1270,7 @@ async def apply_countries_changes( current_countries = subscription.connected_squads countries = await _get_available_countries(db_user.promo_group_id) - ( - countries_with_pricing, - _country_map, - available_country_ids, - servers_discount_percent, - months_to_pay, - ) = _prepare_countries_with_addon_pricing(db_user, countries) - - allowed_country_ids = available_country_ids | set(current_countries) + allowed_country_ids = {country['uuid'] for country in countries} selected_countries = [ country_uuid @@ -1304,10 +1278,7 @@ async def apply_countries_changes( if country_uuid in allowed_country_ids or country_uuid in current_countries ] - added = [ - c for c in selected_countries - if c not in current_countries and c in available_country_ids - ] + added = [c for c in selected_countries if c not in current_countries] removed = [c for c in current_countries if c not in selected_countries] if not added and not removed: @@ -1316,37 +1287,31 @@ async def apply_countries_changes( logger.info(f"🔧 Добавлено: {added}, Удалено: {removed}") + months_to_pay = get_remaining_months(subscription.end_date) + cost_per_month = 0 added_names = [] removed_names = [] - + added_server_prices = [] - - for country in countries_with_pricing: + + for country in countries: if country['uuid'] in added: - server_price_per_month = country.get('discounted_price_kopeks', country['price_kopeks']) + server_price_per_month = country['price_kopeks'] cost_per_month += server_price_per_month added_names.append(country['name']) if country['uuid'] in removed: removed_names.append(country['name']) - - charged_months = months_to_pay - total_cost = cost_per_month * charged_months - - for country in countries_with_pricing: + + total_cost, charged_months = calculate_prorated_price(cost_per_month, subscription.end_date) + + for country in countries: if country['uuid'] in added: - server_price_per_month = country.get('discounted_price_kopeks', country['price_kopeks']) + server_price_per_month = country['price_kopeks'] server_total_price = server_price_per_month * charged_months added_server_prices.append(server_total_price) - - if cost_per_month > 0: - logger.info( - "Стоимость новых серверов: %s₽/мес × %s мес = %s₽ (скидка %s%%)", - cost_per_month / 100, - charged_months, - total_cost / 100, - servers_discount_percent, - ) + + logger.info(f"Стоимость новых серверов: {cost_per_month/100}₽/мес × {charged_months} мес = {total_cost/100}₽") if total_cost > 0 and db_user.balance_kopeks < total_cost: missing_kopeks = total_cost - db_user.balance_kopeks @@ -1381,7 +1346,7 @@ async def apply_countries_changes( try: if added and total_cost > 0: success = await subtract_user_balance( - db, db_user, total_cost, + db, db_user, total_cost, f"Добавление стран: {', '.join(added_names)} на {charged_months} мес" ) if not success: @@ -1559,22 +1524,8 @@ async def confirm_change_devices( chargeable_devices = additional_devices devices_price_per_month = chargeable_devices * settings.PRICE_PER_DEVICE - end_date = getattr(subscription, "end_date", None) - months_to_pay = get_remaining_months(end_date) if end_date else 1 - period_hint_days = months_to_pay * 30 if months_to_pay > 0 else None - devices_discount_percent = resolve_addon_discount_percent( - db_user, - getattr(db_user, "promo_group", None), - "devices", - period_days=period_hint_days, - ) - discounted_devices_price_per_month, discount_per_month = apply_percentage_discount( - devices_price_per_month, - devices_discount_percent, - ) - price = discounted_devices_price_per_month * months_to_pay - charged_months = months_to_pay - + price, charged_months = calculate_prorated_price(devices_price_per_month, subscription.end_date) + if price > 0 and db_user.balance_kopeks < price: missing_kopeks = price - db_user.balance_kopeks required_text = f"{texts.format_price(price)} (за {charged_months} мес)" @@ -2188,32 +2139,10 @@ async def confirm_add_devices( return devices_price_per_month = devices_count * settings.PRICE_PER_DEVICE - end_date = getattr(subscription, "end_date", None) - months_to_pay = get_remaining_months(end_date) if end_date else 1 - period_hint_days = months_to_pay * 30 if months_to_pay > 0 else None - devices_discount_percent = resolve_addon_discount_percent( - db_user, - getattr(db_user, "promo_group", None), - "devices", - period_days=period_hint_days, - ) - discounted_devices_price_per_month, discount_per_month = apply_percentage_discount( - devices_price_per_month, - devices_discount_percent, - ) - price = discounted_devices_price_per_month * months_to_pay - charged_months = months_to_pay - - logger.info( - "Добавление %s устройств: %s₽/мес × %s мес = %s₽ (скидка %s%%: -%s₽/мес)", - devices_count, - devices_price_per_month / 100, - charged_months, - price / 100, - devices_discount_percent, - discount_per_month / 100, - ) - + price, charged_months = calculate_prorated_price(devices_price_per_month, subscription.end_date) + + logger.info(f"Добавление {devices_count} устройств: {devices_price_per_month/100}₽/мес × {charged_months} мес = {price/100}₽") + if db_user.balance_kopeks < price: missing_kopeks = price - db_user.balance_kopeks required_text = f"{texts.format_price(price)} (за {charged_months} мес)" @@ -3572,29 +3501,14 @@ async def add_traffic( texts = get_texts(db_user.language) subscription = db_user.subscription - price_per_month = settings.get_traffic_price(traffic_gb) - - if price_per_month == 0 and traffic_gb != 0: + price = settings.get_traffic_price(traffic_gb) + + if price == 0 and traffic_gb != 0: await callback.answer("⚠️ Цена для этого пакета не настроена", show_alert=True) return - - end_date = getattr(subscription, "end_date", None) - months_to_pay = get_remaining_months(end_date) if end_date else 1 - period_hint_days = months_to_pay * 30 if months_to_pay > 0 else None - traffic_discount_percent = resolve_addon_discount_percent( - db_user, - getattr(db_user, "promo_group", None), - "traffic", - period_days=period_hint_days, - ) - discounted_price_per_month, discount_per_month = apply_percentage_discount( - price_per_month, - traffic_discount_percent, - ) - total_price = discounted_price_per_month * months_to_pay - - if db_user.balance_kopeks < total_price: - missing_kopeks = total_price - db_user.balance_kopeks + + if db_user.balance_kopeks < price: + missing_kopeks = price - db_user.balance_kopeks message_text = texts.t( "ADDON_INSUFFICIENT_FUNDS_MESSAGE", ( @@ -3605,7 +3519,7 @@ async def add_traffic( "Выберите способ пополнения. Сумма подставится автоматически." ), ).format( - required=f"{texts.format_price(total_price)} (за {months_to_pay} мес)", + required=texts.format_price(price), balance=texts.format_price(db_user.balance_kopeks), missing=texts.format_price(missing_kopeks), ) @@ -3620,13 +3534,13 @@ async def add_traffic( ) await callback.answer() return - + try: success = await subtract_user_balance( - db, db_user, total_price, + db, db_user, price, f"Добавление {traffic_gb} ГБ трафика" ) - + if not success: await callback.answer("⚠️ Ошибка списания средств", show_alert=True) return @@ -3643,45 +3557,28 @@ async def add_traffic( db=db, user_id=db_user.id, type=TransactionType.SUBSCRIPTION_PAYMENT, - amount_kopeks=total_price, + amount_kopeks=price, description=f"Добавление {traffic_gb} ГБ трафика" ) - - + + await db.refresh(db_user) await db.refresh(subscription) - + success_text = f"✅ Трафик успешно добавлен!\n\n" if traffic_gb == 0: success_text += "🎉 Теперь у вас безлимитный трафик!" else: success_text += f"📈 Добавлено: {traffic_gb} ГБ\n" success_text += f"Новый лимит: {texts.format_traffic(subscription.traffic_limit_gb)}" - if total_price > 0: - success_text += f"\n💰 Списано: {texts.format_price(total_price)} (за {months_to_pay} мес)" - if total_price > 0 and traffic_discount_percent > 0: - saved_total = discount_per_month * months_to_pay - if saved_total > 0: - success_text += ( - f"\n💸 Применена скидка {traffic_discount_percent}%" - f" (экономия {texts.format_price(saved_total)})" - ) - + await callback.message.edit_text( success_text, reply_markup=get_back_keyboard(db_user.language) ) - - logger.info( - "✅ Пользователь %s добавил %s ГБ трафика: %s₽/мес × %s мес = %s₽ (скидка %s%%)", - db_user.telegram_id, - traffic_gb, - price_per_month / 100, - months_to_pay, - total_price / 100, - traffic_discount_percent, - ) - + + logger.info(f"✅ Пользователь {db_user.telegram_id} добавил {traffic_gb} ГБ трафика") + except Exception as e: logger.error(f"Ошибка добавления трафика: {e}") await callback.message.edit_text( @@ -3937,9 +3834,8 @@ async def _get_available_countries(promo_group_id: Optional[int] = None): countries = [] for server in available_servers: countries.append({ - "id": server.id, "uuid": server.squad_uuid, - "name": server.display_name, + "name": server.display_name, "price_kopeks": server.price_kopeks, "country_code": server.country_code, "is_available": server.is_available and not server.is_full @@ -3967,10 +3863,9 @@ async def _get_available_countries(promo_group_id: Optional[int] = None): squad_name = f"🌐 {squad_name}" countries.append({ - "id": None, "uuid": squad["uuid"], "name": squad_name, - "price_kopeks": 0, + "price_kopeks": 0, "is_available": True }) @@ -3990,50 +3885,6 @@ async def _get_countries_info(squad_uuids): countries = await _get_available_countries() return [c for c in countries if c['uuid'] in squad_uuids] - -def _prepare_countries_with_addon_pricing( - user: User, - countries: List[dict], -) -> Tuple[List[dict], Dict[str, dict], Set[str], int, int]: - subscription = user.subscription - end_date = getattr(subscription, "end_date", None) if subscription else None - months_to_pay = get_remaining_months(end_date) if end_date else 1 - period_hint_days = months_to_pay * 30 if months_to_pay > 0 else None - servers_discount_percent = resolve_addon_discount_percent( - user, - getattr(user, "promo_group", None), - "servers", - period_days=period_hint_days, - ) - - countries_with_pricing: List[dict] = [] - country_map: Dict[str, dict] = {} - available_country_ids: Set[str] = set() - - for country in countries: - country_copy = dict(country) - price_per_month = country_copy.get("price_kopeks", 0) - discounted_price, _ = apply_percentage_discount( - price_per_month, - servers_discount_percent, - ) - country_copy["discounted_price_kopeks"] = discounted_price - country_copy["discount_percent"] = servers_discount_percent - - if country_copy.get("is_available", True): - available_country_ids.add(country_copy["uuid"]) - - countries_with_pricing.append(country_copy) - country_map[country_copy["uuid"]] = country_copy - - return ( - countries_with_pricing, - country_map, - available_country_ids, - servers_discount_percent, - months_to_pay, - ) - async def handle_reset_devices( callback: types.CallbackQuery, db_user: User, @@ -4061,17 +3912,7 @@ async def handle_add_country_to_subscription( selected_countries = data.get('countries', []) countries = await _get_available_countries(db_user.promo_group_id) - - ( - countries_with_pricing, - country_map, - available_country_ids, - servers_discount_percent, - months_to_pay, - ) = _prepare_countries_with_addon_pricing(db_user, countries) - - subscription = db_user.subscription - allowed_country_ids = available_country_ids | set(subscription.connected_squads) + allowed_country_ids = {country['uuid'] for country in countries} if country_uuid not in allowed_country_ids and country_uuid not in selected_countries: await callback.answer("❌ Сервер недоступен для вашей промогруппы", show_alert=True) @@ -4085,31 +3926,21 @@ async def handle_add_country_to_subscription( logger.info(f"🔍 Добавлена страна: {country_uuid}") total_price = 0 - for uuid in selected_countries: - if uuid in country_map and uuid not in subscription.connected_squads and uuid in available_country_ids: - country_data = country_map[uuid] - discounted_price = country_data.get("discounted_price_kopeks", country_data.get("price_kopeks", 0)) - total_price += discounted_price * months_to_pay - + for country in countries: + if country['uuid'] in selected_countries and country['uuid'] not in db_user.subscription.connected_squads: + total_price += country['price_kopeks'] + data['countries'] = selected_countries data['total_price'] = total_price - data['servers_discount_percent'] = servers_discount_percent - data['months_to_pay'] = months_to_pay await state.set_data(data) - + logger.info(f"🔍 Новые выбранные страны: {selected_countries}") logger.info(f"🔍 Общая стоимость: {total_price}") - + try: from app.keyboards.inline import get_manage_countries_keyboard await callback.message.edit_reply_markup( - reply_markup=get_manage_countries_keyboard( - countries_with_pricing, - selected_countries, - db_user.subscription.connected_squads, - db_user.language, - subscription_end_date=subscription.end_date, - ) + reply_markup=get_manage_countries_keyboard(countries, selected_countries, db_user.subscription.connected_squads, db_user.language) ) logger.info(f"✅ Клавиатура обновлена") except Exception as e: @@ -4151,36 +3982,24 @@ async def confirm_add_countries_to_subscription( if country_uuid in allowed_country_ids or country_uuid in current_countries ] - new_countries = [ - c for c in selected_countries - if c not in current_countries and c in available_country_ids - ] + new_countries = [c for c in selected_countries if c not in current_countries] removed_countries = [c for c in current_countries if c not in selected_countries] - + if not new_countries and not removed_countries: await callback.answer("⚠️ Изменения не обнаружены", show_alert=True) return - + total_price = 0 new_countries_names = [] removed_countries_names = [] - - for uuid in new_countries: - country = country_map.get(uuid) - if not country: - continue - discounted_price = country.get( - 'discounted_price_kopeks', - country.get('price_kopeks', 0), - ) - total_price += discounted_price * months_to_pay - new_countries_names.append(country['name']) - - for uuid in removed_countries: - country = country_map.get(uuid) - if country: + + for country in countries: + if country['uuid'] in new_countries: + total_price += country['price_kopeks'] + new_countries_names.append(country['name']) + if country['uuid'] in removed_countries: removed_countries_names.append(country['name']) - + if new_countries and db_user.balance_kopeks < total_price: missing_kopeks = total_price - db_user.balance_kopeks message_text = texts.t( @@ -4244,10 +4063,7 @@ async def confirm_add_countries_to_subscription( if new_countries_names: success_text += f"➕ Добавлены страны:\n{chr(10).join(f'• {name}' for name in new_countries_names)}\n" if total_price > 0: - success_text += ( - f"💰 Списано: {texts.format_price(total_price)}" - f" (за {months_to_pay} мес)\n" - ) + success_text += f"💰 Списано: {texts.format_price(total_price)}\n" if removed_countries_names: success_text += f"\n➖ Отключены страны:\n{chr(10).join(f'• {name}' for name in removed_countries_names)}\n" diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index 80bbf89d..adb4462f 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -1313,10 +1313,8 @@ def get_manage_countries_keyboard( for country in countries: uuid = country['uuid'] name = country['name'] - price_per_month = country.get('price_kopeks', 0) - discounted_price_per_month = country.get('discounted_price_kopeks', price_per_month) - discount_percent = country.get('discount_percent', 0) - + price_per_month = country['price_kopeks'] + if uuid in current_subscription_countries: if uuid in selected: icon = "✅" @@ -1325,41 +1323,31 @@ def get_manage_countries_keyboard( else: if uuid in selected: icon = "➕" - total_cost += discounted_price_per_month * months_multiplier + total_cost += price_per_month * months_multiplier else: icon = "⚪" if uuid not in current_subscription_countries and uuid in selected: - total_price = discounted_price_per_month * months_multiplier + total_price = price_per_month * months_multiplier if months_multiplier > 1: - price_text = ( - f" ({texts.format_price(discounted_price_per_month)} / мес × {months_multiplier}" - f" = {texts.format_price(total_price)})" - ) - logger.info( - "🔍 Сервер %s: %s/мес × %s мес = %s (скидка %s%%)", - name, - texts.format_price(discounted_price_per_month), - months_multiplier, - texts.format_price(total_price), - discount_percent, - ) + price_text = f" ({price_per_month//100}₽/мес × {months_multiplier} = {total_price//100}₽)" + logger.info(f"🔍 Сервер {name}: {price_per_month/100}₽/мес × {months_multiplier} мес = {total_price/100}₽") else: - price_text = f" ({texts.format_price(total_price)})" + price_text = f" ({total_price//100}₽)" display_name = f"{icon} {name}{price_text}" else: display_name = f"{icon} {name}" - + buttons.append([ InlineKeyboardButton( text=display_name, callback_data=f"country_manage_{uuid}" ) ]) - + if total_cost > 0: - apply_text = f"✅ Применить изменения ({texts.format_price(total_cost)})" - logger.info("🔍 Общая стоимость новых серверов: %s", texts.format_price(total_cost)) + apply_text = f"✅ Применить изменения ({total_cost//100} ₽)" + logger.info(f"🔍 Общая стоимость новых серверов: {total_cost/100}₽") else: apply_text = "✅ Применить изменения" diff --git a/app/services/subscription_service.py b/app/services/subscription_service.py index 8460b886..54ba6720 100644 --- a/app/services/subscription_service.py +++ b/app/services/subscription_service.py @@ -39,7 +39,7 @@ def _resolve_discount_percent( return 0 -def resolve_addon_discount_percent( +def _resolve_addon_discount_percent( user: Optional[User], promo_group: Optional[PromoGroup], category: str, @@ -878,7 +878,7 @@ class SubscriptionService: if additional_traffic_gb > 0: traffic_price_per_month = settings.get_traffic_price(additional_traffic_gb) - traffic_discount_percent = resolve_addon_discount_percent( + traffic_discount_percent = _resolve_addon_discount_percent( user, promo_group, "traffic", @@ -901,7 +901,7 @@ class SubscriptionService: if additional_devices > 0: devices_price_per_month = additional_devices * settings.PRICE_PER_DEVICE - devices_discount_percent = resolve_addon_discount_percent( + devices_discount_percent = _resolve_addon_discount_percent( user, promo_group, "devices", @@ -928,7 +928,7 @@ class SubscriptionService: server = await get_server_squad_by_id(db, server_id) if server and server.is_available: server_price_per_month = server.price_kopeks - servers_discount_percent = resolve_addon_discount_percent( + servers_discount_percent = _resolve_addon_discount_percent( user, promo_group, "servers",