diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py
index 09a130d1..1a446032 100644
--- a/app/webapi/routes/miniapp.py
+++ b/app/webapi/routes/miniapp.py
@@ -9,10 +9,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.crud.server_squad import get_server_squad_by_uuid
-from app.database.crud.promo_group import get_auto_assign_promo_groups
-from app.database.crud.transaction import get_user_total_spent_kopeks
from app.database.crud.user import get_user_by_telegram_id
-from app.database.models import PromoGroup, Subscription, Transaction, User
+from app.database.models import Subscription, Transaction, User
from app.services.remnawave_service import (
RemnaWaveConfigurationError,
RemnaWaveService,
@@ -28,7 +26,6 @@ from ..dependencies import get_db_session
from ..schemas.miniapp import (
MiniAppConnectedServer,
MiniAppDevice,
- MiniAppAutoPromoGroupLevel,
MiniAppPromoGroup,
MiniAppSubscriptionRequest,
MiniAppSubscriptionResponse,
@@ -339,27 +336,6 @@ async def get_subscription_details(
balance_currency = balance_currency.upper()
promo_group = getattr(user, "promo_group", None)
- total_spent_kopeks = await get_user_total_spent_kopeks(db, user.id)
- auto_assign_groups = await get_auto_assign_promo_groups(db)
-
- auto_promo_levels: List[MiniAppAutoPromoGroupLevel] = []
- for group in auto_assign_groups:
- threshold = group.auto_assign_total_spent_kopeks or 0
- if threshold <= 0:
- continue
-
- auto_promo_levels.append(
- MiniAppAutoPromoGroupLevel(
- id=group.id,
- name=group.name,
- threshold_kopeks=threshold,
- threshold_rubles=round(threshold / 100, 2),
- threshold_label=settings.format_price(threshold),
- is_reached=total_spent_kopeks >= threshold,
- is_current=bool(promo_group and promo_group.id == group.id),
- **_extract_promo_discounts(group),
- )
- )
response_user = MiniAppSubscriptionUser(
telegram_id=user.telegram_id,
@@ -410,66 +386,11 @@ async def get_subscription_details(
balance_rubles=round(user.balance_rubles, 2),
balance_currency=balance_currency,
transactions=[_serialize_transaction(tx) for tx in transactions],
- promo_group=(
- MiniAppPromoGroup(
- id=promo_group.id,
- name=promo_group.name,
- **_extract_promo_discounts(promo_group),
- )
- if promo_group
- else None
- ),
- auto_assign_promo_groups=auto_promo_levels,
- total_spent_kopeks=total_spent_kopeks,
- total_spent_rubles=round(total_spent_kopeks / 100, 2),
- total_spent_label=settings.format_price(total_spent_kopeks),
+ promo_group=MiniAppPromoGroup(id=promo_group.id, name=promo_group.name)
+ if promo_group
+ else None,
subscription_type="trial" if subscription.is_trial else "paid",
autopay_enabled=bool(subscription.autopay_enabled),
branding=settings.get_miniapp_branding(),
)
-def _safe_int(value: Any) -> int:
- try:
- return int(value)
- except (TypeError, ValueError):
- return 0
-
-
-def _normalize_period_discounts(
- raw: Optional[Dict[Any, Any]]
-) -> Dict[int, int]:
- if not isinstance(raw, dict):
- return {}
-
- normalized: Dict[int, int] = {}
- for key, value in raw.items():
- try:
- period = int(key)
- normalized[period] = int(value)
- except (TypeError, ValueError):
- continue
-
- return normalized
-
-
-def _extract_promo_discounts(group: Optional[PromoGroup]) -> Dict[str, Any]:
- if not group:
- return {
- "server_discount_percent": 0,
- "traffic_discount_percent": 0,
- "device_discount_percent": 0,
- "period_discounts": {},
- "apply_discounts_to_addons": True,
- }
-
- return {
- "server_discount_percent": max(0, _safe_int(getattr(group, "server_discount_percent", 0))),
- "traffic_discount_percent": max(0, _safe_int(getattr(group, "traffic_discount_percent", 0))),
- "device_discount_percent": max(0, _safe_int(getattr(group, "device_discount_percent", 0))),
- "period_discounts": _normalize_period_discounts(getattr(group, "period_discounts", None)),
- "apply_discounts_to_addons": bool(
- getattr(group, "apply_discounts_to_addons", True)
- ),
- }
-
-
diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py
index 360e6fa5..3461e170 100644
--- a/app/webapi/schemas/miniapp.py
+++ b/app/webapi/schemas/miniapp.py
@@ -39,26 +39,6 @@ class MiniAppSubscriptionUser(BaseModel):
class MiniAppPromoGroup(BaseModel):
id: int
name: str
- server_discount_percent: int = 0
- traffic_discount_percent: int = 0
- device_discount_percent: int = 0
- period_discounts: Dict[int, int] = Field(default_factory=dict)
- apply_discounts_to_addons: bool = True
-
-
-class MiniAppAutoPromoGroupLevel(BaseModel):
- id: int
- name: str
- threshold_kopeks: int
- threshold_rubles: float
- threshold_label: str
- is_reached: bool = False
- is_current: bool = False
- server_discount_percent: int = 0
- traffic_discount_percent: int = 0
- device_discount_percent: int = 0
- period_discounts: Dict[int, int] = Field(default_factory=dict)
- apply_discounts_to_addons: bool = True
class MiniAppConnectedServer(BaseModel):
@@ -110,10 +90,6 @@ class MiniAppSubscriptionResponse(BaseModel):
balance_currency: Optional[str] = None
transactions: List[MiniAppTransaction] = Field(default_factory=list)
promo_group: Optional[MiniAppPromoGroup] = None
- auto_assign_promo_groups: List[MiniAppAutoPromoGroupLevel] = Field(default_factory=list)
- total_spent_kopeks: int = 0
- total_spent_rubles: float = 0.0
- total_spent_label: Optional[str] = None
subscription_type: str
autopay_enabled: bool = False
branding: Optional[MiniAppBranding] = None
diff --git a/miniapp/index.html b/miniapp/index.html
index dd12503f..29d0c127 100644
--- a/miniapp/index.html
+++ b/miniapp/index.html
@@ -560,10 +560,6 @@
border-radius: var(--radius-sm);
}
- .info-item.promo-group-info {
- align-items: flex-start;
- }
-
.info-label {
font-size: 14px;
color: var(--text-secondary);
@@ -580,311 +576,6 @@
text-align: right;
}
- .promo-group-value {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- gap: 8px;
- width: 100%;
- }
-
- .promo-group-chip {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 6px 12px;
- border-radius: 999px;
- background: rgba(var(--primary-rgb), 0.12);
- color: var(--primary);
- font-size: 13px;
- font-weight: 600;
- border: 1px solid rgba(var(--primary-rgb), 0.16);
- min-width: 140px;
- text-align: center;
- }
-
- .promo-group-chip.muted {
- color: var(--text-secondary);
- background: rgba(var(--primary-rgb), 0.05);
- border-color: rgba(var(--primary-rgb), 0.1);
- }
-
- .promo-group-discounts,
- .promo-discount-row {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- justify-content: flex-end;
- }
-
- .promo-group-period-discounts,
- .promo-period-discounts {
- display: flex;
- flex-direction: column;
- gap: 4px;
- align-items: flex-end;
- }
-
- .promo-periods-title {
- font-size: 12px;
- text-transform: uppercase;
- letter-spacing: 0.4px;
- color: var(--text-secondary);
- }
-
- .promo-group-period-discounts .promo-periods-title {
- width: 100%;
- text-align: right;
- }
-
- .promo-period-badges {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- justify-content: flex-end;
- }
-
- .promo-period-badge {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 4px 10px;
- border-radius: 999px;
- background: rgba(var(--primary-rgb), 0.08);
- border: 1px solid rgba(var(--primary-rgb), 0.12);
- font-size: 12px;
- font-weight: 600;
- color: var(--text-secondary);
- white-space: nowrap;
- }
-
- .promo-period-badge .promo-period-value {
- color: var(--primary);
- }
-
- .promo-group-note {
- font-size: 12px;
- color: var(--warning);
- background: rgba(245, 158, 11, 0.08);
- padding: 6px 10px;
- border-radius: var(--radius-sm);
- border: 1px solid rgba(245, 158, 11, 0.25);
- max-width: 260px;
- text-align: right;
- }
-
- .promo-periods-empty {
- font-size: 12px;
- color: var(--text-secondary);
- text-align: right;
- }
-
- /* Promo levels */
- .promo-levels-card.expandable .card-content {
- padding-bottom: 0;
- }
-
- .promo-levels-card.expanded .card-content {
- padding-bottom: 16px;
- }
-
- .promo-levels-card .card-header {
- align-items: flex-start;
- }
-
- .promo-levels-card .card-header .expand-icon {
- margin-top: 4px;
- }
-
- .promo-header-content {
- display: flex;
- flex-direction: column;
- gap: 6px;
- flex: 1;
- }
-
- .promo-levels-header-meta {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- }
-
- .promo-levels-header-chip {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 4px 10px;
- border-radius: 999px;
- background: rgba(var(--primary-rgb), 0.08);
- border: 1px solid rgba(var(--primary-rgb), 0.12);
- font-size: 12px;
- font-weight: 600;
- color: var(--text-primary);
- }
-
- .promo-levels-header-chip.muted {
- color: var(--text-secondary);
- background: rgba(var(--primary-rgb), 0.05);
- }
-
- .promo-level-summary {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0 20px 12px;
- font-weight: 600;
- color: var(--text-primary);
- }
-
- .promo-level-summary {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0 20px 12px;
- font-weight: 600;
- color: var(--text-primary);
- }
-
- .promo-level-summary-label {
- color: var(--text-secondary);
- font-size: 13px;
- }
-
- .promo-level-summary-value {
- font-size: 16px;
- }
-
- .promo-level-list {
- list-style: none;
- padding: 0 20px 8px;
- margin: 0;
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
-
- .promo-level-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 14px 16px;
- border-radius: var(--radius);
- border: 1px solid var(--border-color);
- background: var(--bg-secondary);
- transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
- }
-
- .promo-level-item:hover {
- border-color: rgba(var(--primary-rgb), 0.4);
- box-shadow: var(--shadow-sm);
- transform: translateY(-1px);
- }
-
- .promo-level-item.current {
- border-color: var(--primary);
- box-shadow: var(--shadow-sm);
- }
-
- .promo-level-info {
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
-
- .promo-level-discounts {
- display: flex;
- flex-direction: column;
- gap: 8px;
- margin-top: 8px;
- }
-
- .promo-period-discounts .promo-periods-title {
- align-self: flex-start;
- }
-
- .promo-level-name {
- font-weight: 700;
- color: var(--text-primary);
- font-size: 15px;
- }
-
- .promo-level-threshold {
- font-size: 13px;
- color: var(--text-secondary);
- }
-
- .promo-level-badge {
- padding: 6px 12px;
- border-radius: 999px;
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.3px;
- }
-
- .promo-level-item.current .promo-level-badge {
- background: rgba(var(--primary-rgb), 0.18);
- color: var(--primary);
- }
-
- .promo-level-item.reached .promo-level-badge {
- background: rgba(16, 185, 129, 0.18);
- color: var(--success);
- }
-
- .promo-level-item.locked .promo-level-badge {
- background: rgba(148, 163, 184, 0.18);
- color: var(--text-secondary);
- }
-
- .promo-discount-badge {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- padding: 4px 8px;
- border-radius: 999px;
- border: 1px solid rgba(var(--primary-rgb), 0.12);
- background: rgba(var(--primary-rgb), 0.08);
- color: var(--text-secondary);
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
- }
-
- .promo-discount-badge .promo-discount-value {
- color: var(--text-primary);
- }
-
- .promo-discount-badge.muted {
- background: rgba(var(--primary-rgb), 0.05);
- border-color: rgba(var(--primary-rgb), 0.08);
- }
-
- .promo-discount-badge.muted .promo-discount-value {
- color: var(--text-secondary);
- }
-
- .promo-level-item.current .promo-discount-badge {
- border-color: rgba(var(--primary-rgb), 0.35);
- color: var(--primary);
- }
-
- .promo-level-item.current .promo-discount-badge .promo-discount-value {
- color: var(--primary);
- }
-
- .promo-level-item.reached .promo-discount-badge {
- border-color: rgba(16, 185, 129, 0.35);
- color: var(--success);
- }
-
- .promo-level-item.reached .promo-discount-badge .promo-discount-value {
- color: var(--success);
- }
-
- .promo-level-item.locked .promo-discount-badge {
- opacity: 0.8;
- }
-
/* Balance Card */
.balance-card {
background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.1), rgba(var(--primary-rgb), 0.05));
@@ -1551,45 +1242,6 @@
color: var(--text-secondary);
}
- :root[data-theme="dark"] .promo-discount-badge {
- background: rgba(var(--primary-rgb), 0.12);
- border-color: rgba(var(--primary-rgb), 0.2);
- }
-
- :root[data-theme="dark"] .promo-discount-badge.muted {
- background: rgba(148, 163, 184, 0.15);
- border-color: rgba(148, 163, 184, 0.25);
- color: rgba(226, 232, 240, 0.75);
- }
-
- :root[data-theme="dark"] .promo-discount-badge.muted .promo-discount-value {
- color: rgba(226, 232, 240, 0.75);
- }
-
- :root[data-theme="dark"] .promo-group-chip,
- :root[data-theme="dark"] .promo-levels-header-chip,
- :root[data-theme="dark"] .promo-period-badge {
- background: rgba(var(--primary-rgb), 0.16);
- border-color: rgba(var(--primary-rgb), 0.28);
- color: var(--text-primary);
- }
-
- :root[data-theme="dark"] .promo-group-chip.muted,
- :root[data-theme="dark"] .promo-levels-header-chip.muted {
- background: rgba(148, 163, 184, 0.18);
- border-color: rgba(148, 163, 184, 0.28);
- color: var(--text-secondary);
- }
-
- :root[data-theme="dark"] .promo-period-badge .promo-period-value {
- color: var(--primary);
- }
-
- :root[data-theme="dark"] .promo-group-note {
- background: rgba(245, 158, 11, 0.18);
- border-color: rgba(245, 158, 11, 0.45);
- }
-
:root[data-theme="dark"] body,
:root[data-theme="dark"] .card,
:root[data-theme="dark"] .user-card,
@@ -1728,15 +1380,6 @@
Type
-
-
Device Limit
-
@@ -1748,38 +1391,6 @@
-
-
-