From 37f5977efd6aff2999f2d315aece4c4b02d29332 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 11 Oct 2025 02:13:13 +0300 Subject: [PATCH] Revert "Revert "Revert "Polish mini app subscription state handling""" --- app/webapi/routes/miniapp.py | 136 ++++-------- app/webapi/schemas/miniapp.py | 7 +- miniapp/index.html | 391 ++-------------------------------- 3 files changed, 61 insertions(+), 473 deletions(-) diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 12abb9ca..9ebdec4b 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -2055,20 +2055,6 @@ async def _build_referral_info( ) -def _is_trial_available_for_user(user: User) -> bool: - if settings.TRIAL_DURATION_DAYS <= 0: - return False - - if getattr(user, "has_had_paid_subscription", False): - return False - - subscription = getattr(user, "subscription", None) - if subscription is not None: - return False - - return True - - @router.post("/subscription", response_model=MiniAppSubscriptionResponse) async def get_subscription_details( payload: MiniAppSubscriptionRequest, @@ -2099,23 +2085,40 @@ async def get_subscription_details( user = await get_user_by_telegram_id(db, telegram_id) purchase_url = (settings.MINIAPP_PURCHASE_URL or "").strip() - - if not user: - detail: Dict[str, Any] = { - "code": "user_not_found", - "message": "User not found. Please register in the bot to continue.", - "title": "Registration required", - } + if not user or not user.subscription: + detail: Union[str, Dict[str, str]] = "Subscription not found" if purchase_url: - detail["purchase_url"] = purchase_url + detail = { + "message": "Subscription not found", + "purchase_url": purchase_url, + } raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=detail, ) - subscription = getattr(user, "subscription", None) + subscription = user.subscription + traffic_used = _format_gb(subscription.traffic_used_gb) + traffic_limit = subscription.traffic_limit_gb or 0 lifetime_used = _bytes_to_gb(getattr(user, "lifetime_used_traffic_bytes", 0)) + status_actual = subscription.actual_status + links_payload = await _load_subscription_links(subscription) + + subscription_url = links_payload.get("subscription_url") or subscription.subscription_url + subscription_crypto_link = ( + links_payload.get("happ_crypto_link") + or subscription.subscription_crypto_link + ) + + happ_redirect_link = get_happ_cryptolink_redirect_link(subscription_crypto_link) + + connected_squads: List[str] = list(subscription.connected_squads or []) + connected_servers = await _resolve_connected_servers(db, connected_squads) + devices_count, devices = await _load_devices_info(user) + links: List[str] = links_payload.get("links") or connected_squads + ss_conf_links: Dict[str, str] = links_payload.get("ss_conf_links") or {} + transactions_query = ( select(Transaction) .where(Transaction.user_id == user.id) @@ -2183,10 +2186,7 @@ async def get_subscription_details( ) ) - if subscription: - active_offer_contexts.extend( - await _find_active_test_access_offers(db, subscription) - ) + active_offer_contexts.extend(await _find_active_test_access_offers(db, subscription)) promo_offers = await _build_promo_offer_models( db, @@ -2312,46 +2312,6 @@ async def get_subscription_details( updated_at=getattr(service_rules, "updated_at", None), ) - links_payload: Dict[str, Any] = {} - connected_squads: List[str] = [] - connected_servers: List[MiniAppConnectedServer] = [] - links: List[str] = [] - ss_conf_links: Dict[str, str] = {} - subscription_url: Optional[str] = None - subscription_crypto_link: Optional[str] = None - happ_redirect_link: Optional[str] = None - remnawave_short_uuid: Optional[str] = None - status_actual = "missing" - subscription_status_value = "none" - traffic_used_value = 0.0 - traffic_limit_value = 0 - device_limit_value: Optional[int] = settings.DEFAULT_DEVICE_LIMIT or None - autopay_enabled = False - - if subscription: - traffic_used_value = _format_gb(subscription.traffic_used_gb) - traffic_limit_value = subscription.traffic_limit_gb or 0 - status_actual = subscription.actual_status - subscription_status_value = subscription.status - links_payload = await _load_subscription_links(subscription) - subscription_url = ( - links_payload.get("subscription_url") or subscription.subscription_url - ) - subscription_crypto_link = ( - links_payload.get("happ_crypto_link") - or subscription.subscription_crypto_link - ) - happ_redirect_link = get_happ_cryptolink_redirect_link(subscription_crypto_link) - connected_squads = list(subscription.connected_squads or []) - connected_servers = await _resolve_connected_servers(db, connected_squads) - links = links_payload.get("links") or connected_squads - ss_conf_links = links_payload.get("ss_conf_links") or {} - remnawave_short_uuid = subscription.remnawave_short_uuid - device_limit_value = subscription.device_limit - autopay_enabled = bool(subscription.autopay_enabled) - - devices_count, devices = await _load_devices_info(user) - response_user = MiniAppSubscriptionUser( telegram_id=user.telegram_id, username=user.username, @@ -2367,15 +2327,15 @@ async def get_subscription_details( ), language=user.language, status=user.status, - subscription_status=subscription_status_value, + subscription_status=subscription.status, subscription_actual_status=status_actual, status_label=_status_label(status_actual), - expires_at=getattr(subscription, "end_date", None), - device_limit=device_limit_value, - traffic_used_gb=round(traffic_used_value, 2), - traffic_used_label=_format_gb_label(traffic_used_value), - traffic_limit_gb=traffic_limit_value, - traffic_limit_label=_format_limit_label(traffic_limit_value), + expires_at=subscription.end_date, + device_limit=subscription.device_limit, + traffic_used_gb=round(traffic_used, 2), + traffic_used_label=_format_gb_label(traffic_used), + traffic_limit_gb=traffic_limit, + traffic_limit_label=_format_limit_label(traffic_limit), lifetime_used_traffic_gb=lifetime_used, has_active_subscription=status_actual in {"active", "trial"}, promo_offer_discount_percent=active_discount_percent, @@ -2385,14 +2345,9 @@ async def get_subscription_details( referral_info = await _build_referral_info(db, user) - trial_available = _is_trial_available_for_user(user) - trial_duration_days = ( - settings.TRIAL_DURATION_DAYS if settings.TRIAL_DURATION_DAYS > 0 else None - ) - return MiniAppSubscriptionResponse( - subscription_id=getattr(subscription, "id", None), - remnawave_short_uuid=remnawave_short_uuid, + subscription_id=subscription.id, + remnawave_short_uuid=subscription.remnawave_short_uuid, user=response_user, subscription_url=subscription_url, subscription_crypto_link=subscription_crypto_link, @@ -2403,9 +2358,9 @@ async def get_subscription_details( connected_servers=connected_servers, connected_devices_count=devices_count, connected_devices=devices, - happ=links_payload.get("happ") if subscription else None, - happ_link=links_payload.get("happ_link") if subscription else None, - happ_crypto_link=links_payload.get("happ_crypto_link") if subscription else None, + happ=links_payload.get("happ"), + happ_link=links_payload.get("happ_link"), + happ_crypto_link=links_payload.get("happ_crypto_link"), happ_cryptolink_redirect_link=happ_redirect_link, balance_kopeks=user.balance_kopeks, balance_rubles=round(user.balance_rubles, 2), @@ -2425,21 +2380,12 @@ async def get_subscription_details( total_spent_kopeks=total_spent_kopeks, total_spent_rubles=round(total_spent_kopeks / 100, 2), total_spent_label=settings.format_price(total_spent_kopeks), - subscription_type=( - "trial" - if subscription and subscription.is_trial - else ("paid" if subscription else "none") - ), - autopay_enabled=autopay_enabled, + subscription_type="trial" if subscription.is_trial else "paid", + autopay_enabled=bool(subscription.autopay_enabled), branding=settings.get_miniapp_branding(), faq=faq_payload, legal_documents=legal_documents_payload, referral=referral_info, - subscription_missing=subscription is None, - subscription_missing_reason="not_found" if subscription is None else None, - trial_available=trial_available, - trial_duration_days=trial_duration_days, - trial_status="available" if trial_available else "unavailable", ) diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py index 5541b4da..243a2fae 100644 --- a/app/webapi/schemas/miniapp.py +++ b/app/webapi/schemas/miniapp.py @@ -383,7 +383,7 @@ class MiniAppPaymentStatusResponse(BaseModel): class MiniAppSubscriptionResponse(BaseModel): success: bool = True - subscription_id: Optional[int] = None + subscription_id: int remnawave_short_uuid: Optional[str] = None user: MiniAppSubscriptionUser subscription_url: Optional[str] = None @@ -415,11 +415,6 @@ class MiniAppSubscriptionResponse(BaseModel): faq: Optional[MiniAppFaq] = None legal_documents: Optional[MiniAppLegalDocuments] = None referral: Optional[MiniAppReferralInfo] = None - subscription_missing: bool = False - subscription_missing_reason: Optional[str] = None - trial_available: bool = False - trial_duration_days: Optional[int] = None - trial_status: Optional[str] = None class MiniAppSubscriptionServerOption(BaseModel): diff --git a/miniapp/index.html b/miniapp/index.html index dbfb1e98..f99c02b6 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -331,16 +331,6 @@ min-width: 220px; } - .error.error-user-missing { - background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.14), rgba(var(--primary-rgb), 0.08)); - box-shadow: var(--shadow-md); - border: 1px solid rgba(var(--primary-rgb), 0.25); - } - - .error.error-user-missing .error-text { - color: var(--text-primary); - } - /* Cards */ .card { background: var(--bg-secondary); @@ -1791,21 +1781,6 @@ background: rgba(255, 255, 255, 0.2); } - :root[data-theme="dark"] .subscription-missing-card { - background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.28), rgba(var(--primary-rgb), 0.12)); - border-color: rgba(var(--primary-rgb), 0.5); - } - - :root[data-theme="dark"] .subscription-missing-icon { - background: rgba(var(--primary-rgb), 0.3); - color: #bfdbfe; - } - - :root[data-theme="dark"] .status-missing { - background: rgba(59, 130, 246, 0.18); - color: #bfdbfe; - } - :root[data-theme="dark"] .promo-offer-chip { background: rgba(255, 255, 255, 0.08); color: rgba(255, 255, 255, 0.9); @@ -1857,64 +1832,6 @@ min-width: 0; } - .subscription-missing-card { - display: flex; - gap: 16px; - padding: 20px; - align-items: center; - background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.06), rgba(var(--primary-rgb), 0.12)); - border: 2px dashed rgba(var(--primary-rgb), 0.35); - } - - .subscription-missing-icon { - width: 56px; - height: 56px; - border-radius: 16px; - background: rgba(var(--primary-rgb), 0.15); - display: flex; - align-items: center; - justify-content: center; - font-size: 28px; - flex-shrink: 0; - color: var(--primary); - } - - .subscription-missing-content { - flex: 1; - min-width: 0; - } - - .subscription-missing-title { - font-size: 18px; - font-weight: 700; - margin-bottom: 6px; - color: var(--text-primary); - } - - .subscription-missing-description { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 12px; - } - - .subscription-missing-hint { - font-size: 13px; - color: var(--text-secondary); - margin-bottom: 16px; - } - - .subscription-missing-actions { - display: flex; - flex-wrap: wrap; - gap: 10px; - } - - .subscription-missing-actions .btn-primary, - .subscription-missing-actions .btn-secondary { - flex: 1; - min-width: 140px; - } - .user-name { font-size: 20px; font-weight: 700; @@ -1965,11 +1882,6 @@ color: #41464b; } - .status-missing { - background: linear-gradient(135deg, #e0e7ff, #eef2ff); - color: #1e3a8a; - } - /* Stats Grid */ .stats-grid { display: grid; @@ -4298,7 +4210,7 @@