diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py index dce7148b..ec0191ae 100644 --- a/app/handlers/subscription.py +++ b/app/handlers/subscription.py @@ -30,12 +30,12 @@ from app.keyboards.inline import ( get_subscription_confirm_keyboard, get_autopay_keyboard, get_autopay_days_keyboard, get_back_keyboard, get_extend_subscription_keyboard, get_add_traffic_keyboard, - get_add_devices_keyboard, get_reset_traffic_confirm_keyboard, + get_change_devices_keyboard, get_reset_traffic_confirm_keyboard, get_manage_countries_keyboard, get_device_selection_keyboard, get_connection_guide_keyboard, get_app_selection_keyboard, get_specific_app_keyboard, get_subscription_settings_keyboard, get_insufficient_balance_keyboard, - get_extend_subscription_keyboard_with_prices, + get_extend_subscription_keyboard_with_prices, get_confirm_change_devices_keyboard ) from app.localization.texts import get_texts from app.services.remnawave_service import RemnaWaveService @@ -785,7 +785,7 @@ async def handle_add_traffic( await callback.answer() -async def handle_add_devices( +async def handle_change_devices( callback: types.CallbackQuery, db_user: User, db: AsyncSession @@ -794,22 +794,227 @@ async def handle_add_devices( subscription = db_user.subscription if not subscription or subscription.is_trial: - await callback.answer("⚠ Эта функция доступна только для платных подписок", show_alert=True) + await callback.answer("⚠️ Эта функция доступна только для платных подписок", show_alert=True) return current_devices = subscription.device_limit await callback.message.edit_text( - f"📱 Добавить устройства к подписке\n\n" + f"📱 Изменение количества устройств\n\n" f"Текущий лимит: {current_devices} устройств\n" - f"Выберите количество дополнительных устройств:", - reply_markup=get_add_devices_keyboard(current_devices, db_user.language, subscription.end_date), + f"Выберите новое количество устройств:\n\n" + f"💡 Важно:\n" + f"• При увеличении - доплата пропорционально оставшемуся времени\n" + f"• При уменьшении - возврат средств не производится", + reply_markup=get_change_devices_keyboard(current_devices, db_user.language, subscription.end_date), parse_mode="HTML" ) await callback.answer() + + new_devices_count = int(callback.data.split('_')[2]) + texts = get_texts(db_user.language) + subscription = db_user.subscription + + current_devices = subscription.device_limit + + if new_devices_count == current_devices: + await callback.answer("ℹ️ Количество устройств не изменилось", show_alert=True) + return + + if settings.MAX_DEVICES_LIMIT > 0 and new_devices_count > settings.MAX_DEVICES_LIMIT: + await callback.answer( + f"⚠️ Превышен максимальный лимит устройств ({settings.MAX_DEVICES_LIMIT})", + show_alert=True + ) + return + + devices_difference = new_devices_count - current_devices + + if devices_difference > 0: + additional_devices = devices_difference + + current_chargeable = max(0, current_devices - settings.DEFAULT_DEVICE_LIMIT) + new_chargeable = max(0, new_devices_count - settings.DEFAULT_DEVICE_LIMIT) + chargeable_devices = new_chargeable - current_chargeable + + devices_price_per_month = chargeable_devices * settings.PRICE_PER_DEVICE + price, charged_months = calculate_prorated_price(devices_price_per_month, subscription.end_date) + + if price > 0 and db_user.balance_kopeks < price: + await callback.answer( + f"⚠️ Недостаточно средств!\nТребуется: {texts.format_price(price)} (за {charged_months} мес)\nУ вас: {texts.format_price(db_user.balance_kopeks)}", + show_alert=True + ) + return + + action_text = f"увеличить до {new_devices_count}" + cost_text = f"Доплата: {texts.format_price(price)} (за {charged_months} мес)" if price > 0 else "Бесплатно" + + else: + price = 0 + action_text = f"уменьшить до {new_devices_count}" + cost_text = "Возврат средств не производится" + + confirm_text = f"📱 Подтверждение изменения\n\n" + confirm_text += f"Текущее количество: {current_devices} устройств\n" + confirm_text += f"Новое количество: {new_devices_count} устройств\n\n" + confirm_text += f"Действие: {action_text}\n" + confirm_text += f"💰 {cost_text}\n\n" + confirm_text += "Подтвердить изменение?" + + await callback.message.edit_text( + confirm_text, + reply_markup=get_confirm_change_devices_keyboard(new_devices_count, price, db_user.language), + parse_mode="HTML" + ) + + await callback.answer() + +async def confirm_change_devices( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price + + new_devices_count = int(callback.data.split('_')[2]) + texts = get_texts(db_user.language) + subscription = db_user.subscription + + current_devices = subscription.device_limit + + if new_devices_count == current_devices: + await callback.answer("ℹ️ Количество устройств не изменилось", show_alert=True) + return + + if settings.MAX_DEVICES_LIMIT > 0 and new_devices_count > settings.MAX_DEVICES_LIMIT: + await callback.answer( + f"⚠️ Превышен максимальный лимит устройств ({settings.MAX_DEVICES_LIMIT})", + show_alert=True + ) + return + + devices_difference = new_devices_count - current_devices + + if devices_difference > 0: + additional_devices = devices_difference + + if current_devices < settings.DEFAULT_DEVICE_LIMIT: + free_devices = settings.DEFAULT_DEVICE_LIMIT - current_devices + chargeable_devices = max(0, additional_devices - free_devices) + else: + chargeable_devices = additional_devices + + devices_price_per_month = chargeable_devices * settings.PRICE_PER_DEVICE + price, charged_months = calculate_prorated_price(devices_price_per_month, subscription.end_date) + + if price > 0 and db_user.balance_kopeks < price: + await callback.answer( + f"⚠️ Недостаточно средств!\nТребуется: {texts.format_price(price)} (за {charged_months} мес)\nУ вас: {texts.format_price(db_user.balance_kopeks)}", + show_alert=True + ) + return + + action_text = f"увеличить до {new_devices_count}" + cost_text = f"Доплата: {texts.format_price(price)} (за {charged_months} мес)" if price > 0 else "Бесплатно" + + else: + price = 0 + action_text = f"уменьшить до {new_devices_count}" + cost_text = "Возврат средств не производится" + + confirm_text = f"📱 Подтверждение изменения\n\n" + confirm_text += f"Текущее количество: {current_devices} устройств\n" + confirm_text += f"Новое количество: {new_devices_count} устройств\n\n" + confirm_text += f"Действие: {action_text}\n" + confirm_text += f"💰 {cost_text}\n\n" + confirm_text += "Подтвердить изменение?" + + await callback.message.edit_text( + confirm_text, + reply_markup=get_confirm_change_devices_keyboard(new_devices_count, price, db_user.language), + parse_mode="HTML" + ) + + await callback.answer() + + +async def execute_change_devices( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price + + callback_parts = callback.data.split('_') + new_devices_count = int(callback_parts[3]) + price = int(callback_parts[4]) + + texts = get_texts(db_user.language) + subscription = db_user.subscription + current_devices = subscription.device_limit + + try: + if price > 0: + success = await subtract_user_balance( + db, db_user, price, + f"Изменение количества устройств с {current_devices} до {new_devices_count}" + ) + + if not success: + await callback.answer("⚠️ Ошибка списания средств", show_alert=True) + return + + charged_months = get_remaining_months(subscription.end_date) + await create_transaction( + db=db, + user_id=db_user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=price, + description=f"Изменение устройств с {current_devices} до {new_devices_count} на {charged_months} мес" + ) + + subscription.device_limit = new_devices_count + subscription.updated_at = datetime.utcnow() + + await db.commit() + + subscription_service = SubscriptionService() + await subscription_service.update_remnawave_user(db, subscription) + + await db.refresh(db_user) + await db.refresh(subscription) + + if new_devices_count > current_devices: + success_text = f"✅ Количество устройств увеличено!\n\n" + success_text += f"📱 Было: {current_devices} → Стало: {new_devices_count}\n" + if price > 0: + success_text += f"💰 Списано: {texts.format_price(price)}" + else: + success_text = f"✅ Количество устройств уменьшено!\n\n" + success_text += f"📱 Было: {current_devices} → Стало: {new_devices_count}\n" + success_text += f"ℹ️ Возврат средств не производится" + + await callback.message.edit_text( + success_text, + reply_markup=get_back_keyboard(db_user.language) + ) + + logger.info(f"✅ Пользователь {db_user.telegram_id} изменил количество устройств с {current_devices} на {new_devices_count}, доплата: {price/100}₽") + + except Exception as e: + logger.error(f"Ошибка изменения количества устройств: {e}") + await callback.message.edit_text( + texts.ERROR, + reply_markup=get_back_keyboard(db_user.language) + ) + + await callback.answer() + + async def handle_extend_subscription( callback: types.CallbackQuery, db_user: User, @@ -3083,8 +3288,18 @@ def register_handlers(dp: Dispatcher): ) dp.callback_query.register( - handle_add_devices, - F.data == "subscription_add_devices" + handle_change_devices, + F.data == "subscription_change_devices" + ) + + dp.callback_query.register( + confirm_change_devices, + F.data.startswith("change_devices_") + ) + + dp.callback_query.register( + execute_change_devices, + F.data.startswith("confirm_change_devices_") ) dp.callback_query.register(