diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index c469335f..b19d8acd 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -197,6 +197,8 @@ from ..schemas.miniapp import ( MiniAppConnectedServer, MiniAppTrafficTopupRequest, MiniAppTrafficTopupResponse, + MiniAppDailySubscriptionToggleRequest, + MiniAppDailySubscriptionToggleResponse, ) @@ -3414,6 +3416,26 @@ async def get_subscription_details( devices_count, devices = await _load_devices_info(user) + # Загружаем данные суточного тарифа + is_daily_tariff = False + is_daily_paused = False + daily_tariff_name = None + daily_price_kopeks = None + daily_price_label = None + daily_next_charge_at = None + + if subscription and getattr(subscription, "tariff_id", None): + tariff = await get_tariff_by_id(db, subscription.tariff_id) + if tariff and getattr(tariff, 'is_daily', False): + is_daily_tariff = True + is_daily_paused = getattr(subscription, 'is_daily_paused', False) + daily_tariff_name = tariff.name + daily_price_kopeks = getattr(tariff, 'daily_price_kopeks', 0) + daily_price_label = settings.format_price(daily_price_kopeks) + "/день" if daily_price_kopeks > 0 else None + # Следующее списание - через 24 часа от последнего обновления подписки или от start_date + if subscription.end_date and not is_daily_paused: + daily_next_charge_at = subscription.end_date + response_user = MiniAppSubscriptionUser( telegram_id=user.telegram_id, username=user.username, @@ -3443,6 +3465,12 @@ async def get_subscription_details( promo_offer_discount_percent=active_discount_percent, promo_offer_discount_expires_at=active_discount_expires_at, promo_offer_discount_source=promo_offer_source, + is_daily_tariff=is_daily_tariff, + is_daily_paused=is_daily_paused, + daily_tariff_name=daily_tariff_name, + daily_price_kopeks=daily_price_kopeks, + daily_price_label=daily_price_label, + daily_next_charge_at=daily_next_charge_at, ) referral_info = await _build_referral_info(db, user) @@ -6340,6 +6368,11 @@ async def _build_tariff_model( is_upgrade = upgrade is_switch_free = cost == 0 + # Суточный тариф + is_daily = getattr(tariff, 'is_daily', False) + daily_price_kopeks = getattr(tariff, 'daily_price_kopeks', 0) if is_daily else 0 + daily_price_label = settings.format_price(daily_price_kopeks) + "/день" if is_daily and daily_price_kopeks > 0 else None + return MiniAppTariff( id=tariff.id, name=tariff.name, @@ -6358,6 +6391,9 @@ async def _build_tariff_model( switch_cost_label=switch_cost_label, is_upgrade=is_upgrade, is_switch_free=is_switch_free, + is_daily=is_daily, + daily_price_kopeks=daily_price_kopeks, + daily_price_label=daily_price_label, ) @@ -6378,6 +6414,11 @@ async def _build_current_tariff_model(db: AsyncSession, tariff, promo_group=None except (TypeError, ValueError): pass + # Суточный тариф + is_daily = getattr(tariff, 'is_daily', False) + daily_price_kopeks = getattr(tariff, 'daily_price_kopeks', 0) if is_daily else 0 + daily_price_label = settings.format_price(daily_price_kopeks) + "/день" if is_daily and daily_price_kopeks > 0 else None + return MiniAppCurrentTariff( id=tariff.id, name=tariff.name, @@ -6389,6 +6430,9 @@ async def _build_current_tariff_model(db: AsyncSession, tariff, promo_group=None device_limit=tariff.device_limit, servers_count=servers_count, monthly_price_kopeks=monthly_price, + is_daily=is_daily, + daily_price_kopeks=daily_price_kopeks, + daily_price_label=daily_price_label, ) @@ -7093,3 +7137,88 @@ async def purchase_traffic_topup_endpoint( new_balance_kopeks=user.balance_kopeks, charged_kopeks=final_price, ) + + +@router.post("/subscription/daily/toggle-pause") +async def toggle_daily_subscription_pause_endpoint( + payload: MiniAppDailySubscriptionToggleRequest, + db: AsyncSession = Depends(get_db_session), +): + """Переключает паузу/активацию суточной подписки.""" + from app.webapi.schemas.miniapp import MiniAppDailySubscriptionToggleResponse + from app.services.subscription_service import SubscriptionService + + user = await _authorize_miniapp_user(payload.init_data, db) + subscription = user.subscription + + if not subscription: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail={"code": "no_subscription", "message": "No subscription found"}, + ) + + # Проверяем наличие тарифа + tariff_id = getattr(subscription, 'tariff_id', None) + if not tariff_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"code": "no_tariff", "message": "Subscription has no tariff"}, + ) + + tariff = await get_tariff_by_id(db, tariff_id) + if not tariff or not getattr(tariff, 'is_daily', False): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"code": "not_daily_tariff", "message": "Subscription is not on a daily tariff"}, + ) + + # Переключаем состояние паузы + is_currently_paused = getattr(subscription, 'is_daily_paused', False) + new_paused_state = not is_currently_paused + subscription.is_daily_paused = new_paused_state + + # Если снимаем с паузы и подписка активна, нужно проверить баланс для активации + if not new_paused_state: + daily_price = getattr(tariff, 'daily_price_kopeks', 0) + if daily_price > 0 and user.balance_kopeks < daily_price: + raise HTTPException( + status_code=status.HTTP_402_PAYMENT_REQUIRED, + detail={ + "code": "insufficient_balance", + "message": "Insufficient balance to resume daily subscription", + "required": daily_price, + "balance": user.balance_kopeks, + }, + ) + + await db.commit() + await db.refresh(subscription) + await db.refresh(user) + + # Синхронизация с RemnaWave + try: + service = SubscriptionService() + if new_paused_state: + # При паузе отключаем пользователя в RemnaWave + if user.remnawave_uuid: + await service.disable_remnawave_user(user.remnawave_uuid) + else: + # При возобновлении включаем пользователя в RemnaWave + if user.remnawave_uuid: + await service.enable_remnawave_user(user.remnawave_uuid) + except Exception as e: + logger.error(f"Ошибка синхронизации с RemnaWave при паузе/возобновлении: {e}") + + lang = getattr(user, "language", settings.DEFAULT_LANGUAGE) + if new_paused_state: + message = "Суточная подписка приостановлена" if lang == "ru" else "Daily subscription paused" + else: + message = "Суточная подписка возобновлена" if lang == "ru" else "Daily subscription resumed" + + return MiniAppDailySubscriptionToggleResponse( + success=True, + message=message, + is_paused=new_paused_state, + balance_kopeks=user.balance_kopeks, + balance_label=settings.format_price(user.balance_kopeks), + )