diff --git a/app/config.py b/app/config.py
index 7a8c275e..a2cbaf80 100644
--- a/app/config.py
+++ b/app/config.py
@@ -117,8 +117,7 @@ class Settings(BaseSettings):
BASE_PROMO_GROUP_PERIOD_DISCOUNTS_ENABLED: bool = False
BASE_PROMO_GROUP_PERIOD_DISCOUNTS: str = ""
- TRAFFIC_SELECTION_MODE: str = "selectable"
- SUBSCRIPTION_PURCHASE_MODE: str = "custom"
+ TRAFFIC_SELECTION_MODE: str = "selectable"
FIXED_TRAFFIC_LIMIT_GB: int = 100
REFERRAL_MINIMUM_TOPUP_KOPEKS: int = 10000
@@ -528,20 +527,7 @@ class Settings(BaseSettings):
def is_traffic_fixed(self) -> bool:
return self.TRAFFIC_SELECTION_MODE.lower() == "fixed"
-
- def get_subscription_purchase_mode(self) -> str:
- mode = (self.SUBSCRIPTION_PURCHASE_MODE or "custom").strip().lower()
- return mode or "custom"
-
- def is_subscription_tariff_mode(self) -> bool:
- return self.get_subscription_purchase_mode() == "tariff"
-
- def is_subscription_custom_mode(self) -> bool:
- return self.get_subscription_purchase_mode() == "custom"
-
- def is_subscription_fixed_mode(self) -> bool:
- return self.get_subscription_purchase_mode() == "fixed"
-
+
def get_fixed_traffic_limit(self) -> int:
return self.FIXED_TRAFFIC_LIMIT_GB
diff --git a/app/database/crud/subscription.py b/app/database/crud/subscription.py
index 34948855..97a23b3c 100644
--- a/app/database/crud/subscription.py
+++ b/app/database/crud/subscription.py
@@ -22,13 +22,10 @@ logger = logging.getLogger(__name__)
async def get_subscription_by_user_id(db: AsyncSession, user_id: int) -> Optional[Subscription]:
result = await db.execute(
select(Subscription)
- .options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ .options(selectinload(Subscription.user))
.where(Subscription.user_id == user_id)
.order_by(Subscription.created_at.desc())
- .limit(1)
+ .limit(1)
)
subscription = result.scalar_one_or_none()
@@ -77,14 +74,13 @@ async def create_paid_subscription(
db: AsyncSession,
user_id: int,
duration_days: int,
- traffic_limit_gb: int = 0,
+ traffic_limit_gb: int = 0,
device_limit: int = 1,
- connected_squads: List[str] = None,
- tariff_id: Optional[int] = None,
+ connected_squads: List[str] = None
) -> Subscription:
-
+
end_date = datetime.utcnow() + timedelta(days=duration_days)
-
+
subscription = Subscription(
user_id=user_id,
status=SubscriptionStatus.ACTIVE.value,
@@ -93,8 +89,7 @@ async def create_paid_subscription(
end_date=end_date,
traffic_limit_gb=traffic_limit_gb,
device_limit=device_limit,
- connected_squads=connected_squads or [],
- tariff_id=tariff_id,
+ connected_squads=connected_squads or []
)
db.add(subscription)
@@ -271,10 +266,7 @@ async def get_expiring_subscriptions(
result = await db.execute(
select(Subscription)
- .options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ .options(selectinload(Subscription.user))
.where(
and_(
Subscription.status == SubscriptionStatus.ACTIVE.value,
@@ -290,10 +282,7 @@ async def get_expired_subscriptions(db: AsyncSession) -> List[Subscription]:
result = await db.execute(
select(Subscription)
- .options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ .options(selectinload(Subscription.user))
.where(
and_(
Subscription.status == SubscriptionStatus.ACTIVE.value,
@@ -309,15 +298,12 @@ async def get_subscriptions_for_autopay(db: AsyncSession) -> List[Subscription]:
result = await db.execute(
select(Subscription)
- .options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ .options(selectinload(Subscription.user))
.where(
and_(
Subscription.status == SubscriptionStatus.ACTIVE.value,
Subscription.autopay_enabled == True,
- Subscription.is_trial == False
+ Subscription.is_trial == False
)
)
)
@@ -463,10 +449,7 @@ async def get_all_subscriptions(
result = await db.execute(
select(Subscription)
- .options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ .options(selectinload(Subscription.user))
.order_by(Subscription.created_at.desc())
.offset(offset)
.limit(limit)
@@ -762,7 +745,6 @@ async def get_subscription_renewal_cost(
select(Subscription)
.options(
selectinload(Subscription.user).selectinload(User.promo_group),
- selectinload(Subscription.tariff),
)
.where(Subscription.id == subscription_id)
)
diff --git a/app/database/crud/tariff.py b/app/database/crud/tariff.py
deleted file mode 100644
index d28b1207..00000000
--- a/app/database/crud/tariff.py
+++ /dev/null
@@ -1,266 +0,0 @@
-import logging
-from typing import Iterable, List, Optional, Sequence
-
-from sqlalchemy import delete, func, select
-from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.orm import selectinload
-
-from app.database.models import (
- PromoGroup,
- ServerSquad,
- Subscription,
- SubscriptionTariff,
- SubscriptionTariffPrice,
-)
-
-logger = logging.getLogger(__name__)
-
-
-async def list_tariffs(
- db: AsyncSession,
- *,
- include_inactive: bool = False,
-) -> List[SubscriptionTariff]:
- query = (
- select(SubscriptionTariff)
- .options(
- selectinload(SubscriptionTariff.promo_groups),
- selectinload(SubscriptionTariff.server_squads),
- selectinload(SubscriptionTariff.prices),
- )
- .order_by(SubscriptionTariff.sort_order, SubscriptionTariff.name)
- )
-
- if not include_inactive:
- query = query.where(SubscriptionTariff.is_active.is_(True))
-
- result = await db.execute(query)
- return result.scalars().unique().all()
-
-
-async def get_tariff_by_id(
- db: AsyncSession,
- tariff_id: int,
- *,
- include_inactive: bool = False,
-) -> Optional[SubscriptionTariff]:
- query = (
- select(SubscriptionTariff)
- .options(
- selectinload(SubscriptionTariff.promo_groups),
- selectinload(SubscriptionTariff.server_squads),
- selectinload(SubscriptionTariff.prices),
- )
- .where(SubscriptionTariff.id == tariff_id)
- )
-
- if not include_inactive:
- query = query.where(SubscriptionTariff.is_active.is_(True))
-
- result = await db.execute(query)
- return result.scalars().unique().one_or_none()
-
-
-async def _resolve_servers(
- db: AsyncSession,
- server_uuids: Sequence[str],
-) -> List[ServerSquad]:
- if not server_uuids:
- return []
-
- seen = set()
- normalized: List[str] = []
- for raw_uuid in server_uuids:
- if not raw_uuid:
- continue
- cleaned = raw_uuid.strip()
- if not cleaned or cleaned in seen:
- continue
- seen.add(cleaned)
- normalized.append(cleaned)
- if not normalized:
- return []
-
- result = await db.execute(
- select(ServerSquad)
- .options(selectinload(ServerSquad.allowed_promo_groups))
- .where(ServerSquad.squad_uuid.in_(normalized))
- )
- servers = result.scalars().unique().all()
-
- missing = set(normalized) - {server.squad_uuid for server in servers}
- if missing:
- logger.warning("Не найдены серверы для тарифов: %s", ", ".join(sorted(missing)))
-
- ordered_servers = sorted(
- servers,
- key=lambda server: normalized.index(server.squad_uuid) if server.squad_uuid in normalized else len(normalized),
- )
- return ordered_servers
-
-
-async def _resolve_promo_groups(
- db: AsyncSession,
- promo_group_ids: Optional[Iterable[int]],
-) -> List[PromoGroup]:
- if promo_group_ids is None:
- return []
-
- normalized = [int(pg_id) for pg_id in {int(pg_id) for pg_id in promo_group_ids}]
- if not normalized:
- return []
-
- result = await db.execute(select(PromoGroup).where(PromoGroup.id.in_(normalized)))
- promo_groups = result.scalars().all()
-
- missing = set(normalized) - {group.id for group in promo_groups}
- if missing:
- logger.warning("Не найдены промогруппы для тарифов: %s", ", ".join(map(str, sorted(missing))))
-
- return promo_groups
-
-
-def _normalize_prices(prices: Iterable[dict]) -> List[SubscriptionTariffPrice]:
- unique_periods = {}
- for price in prices or []:
- try:
- period = int(price.get('period_days'))
- amount = int(price.get('price_kopeks'))
- except (TypeError, ValueError):
- continue
-
- if period <= 0 or amount < 0:
- continue
-
- unique_periods[period] = amount
-
- normalized = [
- SubscriptionTariffPrice(period_days=period, price_kopeks=amount)
- for period, amount in sorted(unique_periods.items())
- ]
- return normalized
-
-
-async def create_tariff(
- db: AsyncSession,
- *,
- name: str,
- description: Optional[str] = None,
- traffic_limit_gb: int = 0,
- device_limit: int = 1,
- server_uuids: Sequence[str] = (),
- promo_group_ids: Optional[Iterable[int]] = None,
- prices: Iterable[dict] = (),
- is_active: bool = True,
- sort_order: int = 0,
-) -> SubscriptionTariff:
- servers = await _resolve_servers(db, server_uuids)
- promo_groups = await _resolve_promo_groups(db, promo_group_ids)
- price_models = _normalize_prices(prices)
-
- tariff = SubscriptionTariff(
- name=name.strip(),
- description=description,
- traffic_limit_gb=max(0, int(traffic_limit_gb or 0)),
- device_limit=max(1, int(device_limit or 1)),
- is_active=bool(is_active),
- sort_order=int(sort_order or 0),
- server_squads=servers,
- promo_groups=promo_groups,
- prices=price_models,
- )
-
- db.add(tariff)
- await db.commit()
- await db.refresh(tariff)
-
- logger.info("Создан тариф '%s' (ID: %s)", tariff.name, tariff.id)
- return tariff
-
-
-async def update_tariff(
- db: AsyncSession,
- tariff_id: int,
- *,
- name: Optional[str] = None,
- description: Optional[str] = None,
- traffic_limit_gb: Optional[int] = None,
- device_limit: Optional[int] = None,
- server_uuids: Optional[Sequence[str]] = None,
- promo_group_ids: Optional[Iterable[int]] = None,
- prices: Optional[Iterable[dict]] = None,
- is_active: Optional[bool] = None,
- sort_order: Optional[int] = None,
-) -> Optional[SubscriptionTariff]:
- tariff = await get_tariff_by_id(db, tariff_id, include_inactive=True)
- if not tariff:
- return None
-
- if name is not None:
- tariff.name = name.strip()
- if description is not None:
- tariff.description = description
- if traffic_limit_gb is not None:
- tariff.traffic_limit_gb = max(0, int(traffic_limit_gb))
- if device_limit is not None:
- tariff.device_limit = max(1, int(device_limit))
- if is_active is not None:
- tariff.is_active = bool(is_active)
- if sort_order is not None:
- tariff.sort_order = int(sort_order)
-
- if server_uuids is not None:
- tariff.server_squads = await _resolve_servers(db, server_uuids)
- if promo_group_ids is not None:
- tariff.promo_groups = await _resolve_promo_groups(db, promo_group_ids)
- if prices is not None:
- tariff.prices = _normalize_prices(prices)
-
- await db.commit()
- await db.refresh(tariff)
- logger.info("Обновлен тариф '%s' (ID: %s)", tariff.name, tariff.id)
- return tariff
-
-
-async def delete_tariff(db: AsyncSession, tariff_id: int) -> bool:
- result = await db.execute(
- select(func.count(Subscription.id)).where(Subscription.tariff_id == tariff_id)
- )
- active_subscriptions = result.scalar() or 0
- if active_subscriptions > 0:
- logger.warning(
- "Нельзя удалить тариф %s: %s подписок использует его",
- tariff_id,
- active_subscriptions,
- )
- return False
-
- await db.execute(
- delete(SubscriptionTariff).where(SubscriptionTariff.id == tariff_id)
- )
- await db.commit()
- logger.info("Удален тариф ID %s", tariff_id)
- return True
-
-
-async def get_active_tariffs_for_promo_group(
- db: AsyncSession,
- promo_group_id: Optional[int],
-) -> List[SubscriptionTariff]:
- tariffs = await list_tariffs(db, include_inactive=False)
- result = []
- for tariff in tariffs:
- if not tariff.is_available_for_promo_group(promo_group_id):
- continue
- if not tariff.server_squads:
- continue
- available_servers = [
- server
- for server in tariff.server_squads
- if server.is_available and not server.is_full
- ]
- if not available_servers:
- continue
- tariff.server_squads = available_servers
- result.append(tariff)
- return result
diff --git a/app/database/models.py b/app/database/models.py
index c88962e3..26c4f860 100644
--- a/app/database/models.py
+++ b/app/database/models.py
@@ -43,42 +43,6 @@ server_squad_promo_groups = Table(
)
-subscription_tariff_promo_groups = Table(
- "subscription_tariff_promo_groups",
- Base.metadata,
- Column(
- "tariff_id",
- Integer,
- ForeignKey("subscription_tariffs.id", ondelete="CASCADE"),
- primary_key=True,
- ),
- Column(
- "promo_group_id",
- Integer,
- ForeignKey("promo_groups.id", ondelete="CASCADE"),
- primary_key=True,
- ),
-)
-
-
-subscription_tariff_server_squads = Table(
- "subscription_tariff_server_squads",
- Base.metadata,
- Column(
- "tariff_id",
- Integer,
- ForeignKey("subscription_tariffs.id", ondelete="CASCADE"),
- primary_key=True,
- ),
- Column(
- "server_squad_id",
- Integer,
- ForeignKey("server_squads.id", ondelete="CASCADE"),
- primary_key=True,
- ),
-)
-
-
class UserStatus(Enum):
ACTIVE = "active"
BLOCKED = "blocked"
@@ -341,13 +305,6 @@ class PromoGroup(Base):
lazy="selectin",
)
- tariffs = relationship(
- "SubscriptionTariff",
- secondary=subscription_tariff_promo_groups,
- back_populates="promo_groups",
- lazy="selectin",
- )
-
def _get_period_discounts_map(self) -> Dict[int, int]:
raw_discounts = self.period_discounts or {}
@@ -483,14 +440,12 @@ class Subscription(Base):
subscription_crypto_link = Column(String, nullable=True)
device_limit = Column(Integer, default=1)
-
+
connected_squads = Column(JSON, default=list)
-
- tariff_id = Column(Integer, ForeignKey("subscription_tariffs.id", ondelete="SET NULL"), nullable=True)
-
+
autopay_enabled = Column(Boolean, default=False)
autopay_days_before = Column(Integer, default=3)
-
+
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
@@ -498,7 +453,6 @@ class Subscription(Base):
user = relationship("User", back_populates="subscription")
discount_offers = relationship("DiscountOffer", back_populates="subscription")
- tariff = relationship("SubscriptionTariff", back_populates="subscriptions")
@property
def is_active(self) -> bool:
@@ -916,13 +870,6 @@ class ServerSquad(Base):
back_populates="server_squads",
lazy="selectin",
)
-
- tariffs = relationship(
- "SubscriptionTariff",
- secondary=subscription_tariff_server_squads,
- back_populates="server_squads",
- lazy="selectin",
- )
@property
def price_rubles(self) -> float:
@@ -944,75 +891,9 @@ class ServerSquad(Base):
return "Доступен"
-class SubscriptionTariff(Base):
- __tablename__ = "subscription_tariffs"
-
- id = Column(Integer, primary_key=True, index=True)
- name = Column(String(255), unique=True, nullable=False)
- description = Column(Text, nullable=True)
- traffic_limit_gb = Column(Integer, nullable=False, default=0)
- device_limit = Column(Integer, nullable=False, default=1)
- is_active = Column(Boolean, nullable=False, default=True)
- sort_order = Column(Integer, nullable=False, default=0)
- created_at = Column(DateTime, default=func.now())
- updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
-
- promo_groups = relationship(
- "PromoGroup",
- secondary=subscription_tariff_promo_groups,
- back_populates="tariffs",
- lazy="selectin",
- )
- server_squads = relationship(
- "ServerSquad",
- secondary=subscription_tariff_server_squads,
- back_populates="tariffs",
- lazy="selectin",
- )
- prices = relationship(
- "SubscriptionTariffPrice",
- back_populates="tariff",
- cascade="all, delete-orphan",
- order_by="SubscriptionTariffPrice.period_days",
- )
- subscriptions = relationship("Subscription", back_populates="tariff")
-
- def is_available_for_promo_group(self, promo_group_id: Optional[int]) -> bool:
- if not self.promo_groups:
- return True
- if promo_group_id is None:
- return False
- return any(pg.id == promo_group_id for pg in self.promo_groups if pg is not None)
-
- def get_price_for_period(self, period_days: int) -> Optional[int]:
- for price in self.prices:
- if price.period_days == period_days:
- return price.price_kopeks
- return None
-
- def get_server_uuids(self) -> List[str]:
- return [server.squad_uuid for server in self.server_squads if server and server.squad_uuid]
-
-
-class SubscriptionTariffPrice(Base):
- __tablename__ = "subscription_tariff_prices"
- __table_args__ = (
- UniqueConstraint("tariff_id", "period_days", name="uq_tariff_period_price"),
- )
-
- id = Column(Integer, primary_key=True, index=True)
- tariff_id = Column(Integer, ForeignKey("subscription_tariffs.id", ondelete="CASCADE"), nullable=False)
- period_days = Column(Integer, nullable=False)
- price_kopeks = Column(Integer, nullable=False)
- created_at = Column(DateTime, default=func.now())
- updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
-
- tariff = relationship("SubscriptionTariff", back_populates="prices")
-
-
class SubscriptionServer(Base):
__tablename__ = "subscription_servers"
-
+
id = Column(Integer, primary_key=True, index=True)
subscription_id = Column(Integer, ForeignKey("subscriptions.id"), nullable=False)
server_squad_id = Column(Integer, ForeignKey("server_squads.id"), nullable=False)
diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py
index cdbd944c..52800062 100644
--- a/app/handlers/subscription.py
+++ b/app/handlers/subscription.py
@@ -40,9 +40,7 @@ from app.keyboards.inline import (
get_happ_download_button_row,
get_payment_methods_keyboard_with_cart,
get_subscription_confirm_keyboard_with_cart,
- get_insufficient_balance_keyboard_with_cart,
- get_tariff_selection_keyboard,
- get_tariff_period_keyboard,
+ get_insufficient_balance_keyboard_with_cart
)
from app.localization.texts import get_texts
from app.services.admin_notification_service import AdminNotificationService
@@ -54,7 +52,6 @@ from app.services.subscription_checkout_service import (
should_offer_checkout_resume,
)
from app.services.subscription_service import SubscriptionService
-from app.services.tariff_service import TariffService
from app.states import SubscriptionStates
from app.utils.pagination import paginate_list
from app.utils.pricing_utils import (
@@ -444,68 +441,6 @@ def _build_subscription_period_prompt(db_user: User, texts) -> str:
return f"{base_text}\n\n{promo_text}\n"
-def _build_tariff_selection_prompt(texts) -> str:
- return texts.t(
- "TARIFF_SELECTION_PROMPT",
- (
- "Выберите тариф\n\n"
- "Доступные тарифы ниже зависят от вашей промогруппы и доступных серверов."
- ),
- )
-
-
-def _build_tariff_details_text(tariff, texts, language: str) -> str:
- description = tariff.description or texts.t("TARIFF_NO_DESCRIPTION", "Описание отсутствует")
- server_names = [server.display_name for server in tariff.server_squads]
- servers_text = "\n".join(f"• {name}" for name in server_names) if server_names else texts.t(
- "TARIFF_NO_SERVERS", "Нет доступных серверов"
- )
- traffic_text = texts.format_traffic(tariff.traffic_limit_gb)
- devices_text = texts.t("TARIFF_DEVICE_LIMIT", "{count} устройств").format(count=tariff.device_limit)
-
- return texts.t(
- "TARIFF_DETAILS_TEMPLATE",
- (
- "{name}\n\n"
- "{description}\n\n"
- "🌐 Серверы:\n{servers}\n\n"
- "📊 Трафик: {traffic}\n"
- "📱 Устройства: {devices}\n\n"
- "Выберите срок действия тарифа:""
- ),
- ).format(
- name=tariff.name,
- description=description,
- servers=servers_text,
- traffic=traffic_text,
- devices=devices_text,
- )
-
-
-def _build_tariff_summary_text(tariff, period_days: int, price_kopeks: int, texts, language: str) -> str:
- period_text = format_period_description(period_days, language)
- traffic_text = texts.format_traffic(tariff.traffic_limit_gb)
- devices_text = texts.t("TARIFF_DEVICE_LIMIT", "{count} устройств").format(count=tariff.device_limit)
-
- return texts.t(
- "TARIFF_CONFIRMATION_TEMPLATE",
- (
- "{name}\n\n"
- "📅 Период: {period}\n"
- "📊 Трафик: {traffic}\n"
- "📱 Устройства: {devices}\n"
- "💰 Стоимость: {price}\n\n"
- "Подтвердить покупку тарифа?"
- ),
- ).format(
- name=tariff.name,
- period=period_text,
- traffic=traffic_text,
- devices=devices_text,
- price=texts.format_price(price_kopeks),
- )
-
-
async def show_subscription_info(
callback: types.CallbackQuery,
db_user: User,
@@ -514,17 +449,6 @@ async def show_subscription_info(
await db.refresh(db_user)
texts = get_texts(db_user.language)
-
- if settings.is_subscription_tariff_mode():
- await callback.answer(
- texts.t(
- "TARIFF_DEVICE_MANAGEMENT_DISABLED",
- "ℹ️ Изменение количества устройств недоступно при использовании тарифов",
- ),
- show_alert=True,
- )
- return
-
subscription = db_user.subscription
if not subscription:
@@ -1108,38 +1032,10 @@ async def activate_trial(
async def start_subscription_purchase(
callback: types.CallbackQuery,
state: FSMContext,
- db_user: User,
- db: AsyncSession
+ db_user: User
):
texts = get_texts(db_user.language)
- if settings.is_subscription_tariff_mode():
- service = TariffService()
- tariffs = await service.get_available_tariffs(db, db_user)
-
- await state.clear()
-
- if not tariffs:
- await callback.message.edit_text(
- texts.t(
- "NO_TARIFFS_AVAILABLE",
- "❌ Доступных тарифов не найдено. Пожалуйста, обратитесь в поддержку.",
- ),
- reply_markup=get_back_keyboard(db_user.language),
- parse_mode="HTML",
- )
- else:
- await state.set_state(SubscriptionStates.selecting_tariff)
- await state.update_data({'tariff_mode': True})
- await callback.message.edit_text(
- _build_tariff_selection_prompt(texts),
- reply_markup=get_tariff_selection_keyboard(tariffs, db_user.language),
- parse_mode="HTML",
- )
-
- await callback.answer()
- return
-
await callback.message.edit_text(
_build_subscription_period_prompt(db_user, texts),
reply_markup=get_subscription_period_keyboard(db_user.language)
@@ -1168,187 +1064,6 @@ async def start_subscription_purchase(
await callback.answer()
-async def handle_tariff_selection(
- callback: types.CallbackQuery,
- state: FSMContext,
- db_user: User,
- db: AsyncSession
-):
- if not settings.is_subscription_tariff_mode():
- await callback.answer()
- return
-
- try:
- tariff_id = int(callback.data.split('_')[2])
- except (IndexError, ValueError):
- await callback.answer("❌ Некорректный тариф", show_alert=True)
- return
-
- texts = get_texts(db_user.language)
- service = TariffService()
- tariff = await service.get_tariff_for_user(db, tariff_id, db_user)
-
- if not tariff or not getattr(tariff, "prices", None):
- await callback.answer(
- texts.t("TARIFF_UNAVAILABLE", "⚠️ Этот тариф сейчас недоступен"),
- show_alert=True,
- )
- return
-
- await state.update_data({
- 'tariff_mode': True,
- 'selected_tariff_id': tariff.id,
- })
-
- await state.set_state(SubscriptionStates.selecting_tariff_period)
- await callback.message.edit_text(
- _build_tariff_details_text(tariff, texts, db_user.language),
- reply_markup=get_tariff_period_keyboard(tariff, db_user.language),
- parse_mode="HTML",
- )
- await callback.answer()
-
-
-async def handle_tariff_back(
- callback: types.CallbackQuery,
- state: FSMContext,
- db_user: User,
- db: AsyncSession
-):
- if not settings.is_subscription_tariff_mode():
- await callback.answer()
- return
-
- texts = get_texts(db_user.language)
- service = TariffService()
- tariffs = await service.get_available_tariffs(db, db_user)
-
- if not tariffs:
- await callback.message.edit_text(
- texts.t(
- "NO_TARIFFS_AVAILABLE",
- "❌ Доступных тарифов не найдено. Пожалуйста, обратитесь в поддержку.",
- ),
- reply_markup=get_back_keyboard(db_user.language),
- parse_mode="HTML",
- )
- await state.clear()
- await callback.answer()
- return
-
- await state.set_state(SubscriptionStates.selecting_tariff)
- await state.update_data({'tariff_mode': True})
- await callback.message.edit_text(
- _build_tariff_selection_prompt(texts),
- reply_markup=get_tariff_selection_keyboard(tariffs, db_user.language),
- parse_mode="HTML",
- )
- await callback.answer()
-
-
-async def handle_tariff_period_selection(
- callback: types.CallbackQuery,
- state: FSMContext,
- db_user: User,
- db: AsyncSession
-):
- if not settings.is_subscription_tariff_mode():
- await callback.answer()
- return
-
- try:
- _, _, tariff_id_str, period_str = callback.data.split('_')
- tariff_id = int(tariff_id_str)
- period_days = int(period_str)
- except (ValueError, IndexError):
- await callback.answer("❌ Некорректный срок тарифа", show_alert=True)
- return
-
- texts = get_texts(db_user.language)
- service = TariffService()
- tariff = await service.get_tariff_for_user(db, tariff_id, db_user)
-
- if not tariff:
- await callback.answer(
- texts.t("TARIFF_UNAVAILABLE", "⚠️ Этот тариф сейчас недоступен"),
- show_alert=True,
- )
- return
-
- price_kopeks = tariff.get_price_for_period(period_days)
- if price_kopeks is None:
- await callback.answer(
- texts.t("TARIFF_PERIOD_UNAVAILABLE", "⚠️ Для этого тарифа нет указанного периода"),
- show_alert=True,
- )
- return
-
- server_uuids = tariff.get_server_uuids()
- summary_text = _build_tariff_summary_text(tariff, period_days, price_kopeks, texts, db_user.language)
-
- months_in_period = calculate_months_from_days(period_days)
-
- state_payload = {
- 'tariff_mode': True,
- 'tariff_id': tariff.id,
- 'tariff_name': tariff.name,
- 'period_days': period_days,
- 'total_price': price_kopeks,
- 'base_price': price_kopeks,
- 'base_price_original': price_kopeks,
- 'base_discount_percent': 0,
- 'base_discount_total': 0,
- 'traffic_gb': tariff.traffic_limit_gb,
- 'traffic_price_per_month': 0,
- 'traffic_discount_percent': 0,
- 'traffic_discount_total': 0,
- 'traffic_discounted_price_per_month': 0,
- 'total_traffic_price': 0,
- 'devices': tariff.device_limit,
- 'devices_price_per_month': 0,
- 'devices_discount_percent': 0,
- 'devices_discount_total': 0,
- 'devices_discounted_price_per_month': 0,
- 'total_devices_price': 0,
- 'countries': server_uuids,
- 'servers_price_per_month': 0,
- 'servers_discount_percent': 0,
- 'servers_discount_total': 0,
- 'servers_discounted_price_per_month': 0,
- 'server_prices_for_period': [0 for _ in server_uuids],
- 'total_servers_price': 0,
- 'discounted_monthly_additions': 0,
- 'months_in_period': months_in_period,
- }
-
- await state.set_data(state_payload)
- await state.set_state(SubscriptionStates.confirming_purchase)
-
- await callback.message.edit_text(
- summary_text,
- reply_markup=get_subscription_confirm_keyboard(db_user.language),
- parse_mode="HTML",
- )
- await callback.answer()
-
-
-async def handle_change_tariff(
- callback: types.CallbackQuery,
- state: FSMContext,
- db_user: User,
- db: AsyncSession
-):
- if not settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_MODE_DISABLED", "ℹ️ Смена тарифа недоступна в текущем режиме"),
- show_alert=True,
- )
- return
-
- await start_subscription_purchase(callback, state, db_user, db)
-
-
async def save_cart_and_redirect_to_topup(
callback: types.CallbackQuery,
state: FSMContext,
@@ -1451,14 +1166,6 @@ async def handle_add_countries(
db: AsyncSession,
state: FSMContext
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_COUNTRY_MANAGEMENT_DISABLED", "ℹ️ Смена серверов недоступна при использовании тарифов"),
- show_alert=True,
- )
- return
-
if not await _should_show_countries_management(db_user):
texts = get_texts(db_user.language)
await callback.answer(
@@ -2018,17 +1725,6 @@ async def confirm_change_devices(
db_user: User,
db: AsyncSession
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t(
- "TARIFF_DEVICE_MANAGEMENT_DISABLED",
- "ℹ️ Изменение количества устройств недоступно при использовании тарифов",
- ),
- show_alert=True,
- )
- return
-
new_devices_count = int(callback.data.split('_')[2])
texts = get_texts(db_user.language)
subscription = db_user.subscription
@@ -2172,17 +1868,6 @@ async def execute_change_devices(
db_user: User,
db: AsyncSession
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t(
- "TARIFF_DEVICE_MANAGEMENT_DISABLED",
- "ℹ️ Изменение количества устройств недоступно при использовании тарифов",
- ),
- show_alert=True,
- )
- return
-
callback_parts = callback.data.split('_')
new_devices_count = int(callback_parts[3])
price = int(callback_parts[4])
@@ -2788,14 +2473,6 @@ async def handle_reset_traffic(
):
from app.config import settings
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_TRAFFIC_MANAGEMENT_DISABLED", "ℹ️ Управление трафиком недоступно при использовании тарифов"),
- show_alert=True,
- )
- return
-
if settings.is_traffic_fixed():
await callback.answer("⚠️ В текущем режиме трафик фиксированный и не может быть сброшен", show_alert=True)
return
@@ -3253,14 +2930,6 @@ async def confirm_reset_traffic(
):
from app.config import settings
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_TRAFFIC_MANAGEMENT_DISABLED", "ℹ️ Управление трафиком недоступно при использовании тарифов"),
- show_alert=True,
- )
- return
-
if settings.is_traffic_fixed():
await callback.answer("⚠️ В текущем режиме трафик фиксированный", show_alert=True)
return
@@ -3465,17 +3134,6 @@ async def get_subscription_info_text(subscription, texts, db_user, db: AsyncSess
subscription_url = getattr(subscription, 'subscription_url', None) or "Генерируется..."
- tariff_name = None
- if getattr(subscription, 'tariff_id', None):
- try:
- await db.refresh(subscription, attribute_names=["tariff"])
- except Exception:
- pass
-
- tariff = getattr(subscription, "tariff", None)
- if tariff:
- tariff_name = tariff.name
-
if subscription.is_trial:
status_text = "🎁 Тестовая"
type_text = "Триал"
@@ -3515,9 +3173,6 @@ async def get_subscription_info_text(subscription, texts, db_user, db: AsyncSess
if subscription_cost > 0:
info_text += f"\n💰 Стоимость подписки в месяц: {texts.format_price(subscription_cost)}"
- if tariff_name:
- info_text += f"\n📦 Тариф: {tariff_name}"
-
if (
subscription_url
and subscription_url != "Генерируется..."
@@ -3590,14 +3245,6 @@ async def select_country(
db_user: User,
db: AsyncSession
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_COUNTRY_MANAGEMENT_DISABLED", "ℹ️ Смена серверов недоступна при использовании тарифов"),
- show_alert=True,
- )
- return
-
country_uuid = callback.data.split('_')[1]
data = await state.get_data()
@@ -3653,14 +3300,6 @@ async def countries_continue(
state: FSMContext,
db_user: User
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_COUNTRY_MANAGEMENT_DISABLED", "ℹ️ Смена серверов недоступна при использовании тарифов"),
- show_alert=True,
- )
- return
-
data = await state.get_data()
texts = get_texts(db_user.language)
@@ -4085,7 +3724,6 @@ async def confirm_purchase(
existing_subscription.traffic_limit_gb = final_traffic_gb
existing_subscription.device_limit = data['devices']
existing_subscription.connected_squads = data['countries']
- existing_subscription.tariff_id = data.get('tariff_id')
existing_subscription.start_date = current_time
existing_subscription.end_date = current_time + timedelta(days=data['period_days']) + bonus_period
@@ -4105,8 +3743,7 @@ async def confirm_purchase(
duration_days=data['period_days'],
device_limit=data['devices'],
connected_squads=data['countries'],
- traffic_gb=final_traffic_gb,
- tariff_id=data.get('tariff_id'),
+ traffic_gb=final_traffic_gb
)
from app.utils.user_utils import mark_user_as_had_paid_subscription
@@ -4352,14 +3989,6 @@ async def add_traffic(
db_user: User,
db: AsyncSession
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_TRAFFIC_MANAGEMENT_DISABLED", "ℹ️ Управление трафиком недоступно при использовании тарифов"),
- show_alert=True,
- )
- return
-
if settings.is_traffic_fixed():
await callback.answer("⚠️ В текущем режиме трафик фиксированный", show_alert=True)
return
@@ -4493,8 +4122,7 @@ async def create_paid_subscription_with_traffic_mode(
duration_days: int,
device_limit: int,
connected_squads: List[str],
- traffic_gb: Optional[int] = None,
- tariff_id: Optional[int] = None,
+ traffic_gb: Optional[int] = None
):
from app.config import settings
@@ -4512,8 +4140,7 @@ async def create_paid_subscription_with_traffic_mode(
duration_days=duration_days,
traffic_limit_gb=traffic_limit_gb,
device_limit=device_limit,
- connected_squads=connected_squads,
- tariff_id=tariff_id,
+ connected_squads=connected_squads
)
logger.info(f"📋 Создана подписка с трафиком: {traffic_limit_gb} ГБ (режим: {settings.TRAFFIC_SELECTION_MODE})")
@@ -4551,39 +4178,11 @@ async def handle_subscription_settings(
devices_used = await get_current_devices_count(db_user)
- tariff_line = ""
- if settings.is_subscription_tariff_mode():
- tariff_name = None
- try:
- await db.refresh(subscription, attribute_names=["tariff"])
- except Exception:
- pass
-
- tariff = getattr(subscription, "tariff", None)
- if tariff:
- tariff_name = tariff.name
- elif getattr(subscription, "tariff_id", None):
- try:
- from app.database.crud.tariff import get_tariff_by_id
-
- tariff_model = await get_tariff_by_id(db, subscription.tariff_id, include_inactive=True)
- if tariff_model:
- tariff_name = tariff_model.name
- except Exception:
- tariff_name = None
-
- if tariff_name:
- tariff_line = texts.t(
- "SUBSCRIPTION_SETTINGS_TARIFF_LINE",
- "📦 Тариф: {tariff}\n",
- ).format(tariff=tariff_name)
-
settings_text = texts.t(
"SUBSCRIPTION_SETTINGS_OVERVIEW",
(
"⚙️ Настройки подписки\n\n"
"📊 Текущие параметры:\n"
- "{tariff_line}"
"🌐 Стран: {countries_count}\n"
"📈 Трафик: {traffic_used} / {traffic_limit}\n"
"📱 Устройства: {devices_used} / {devices_limit}\n\n"
@@ -4595,7 +4194,6 @@ async def handle_subscription_settings(
traffic_limit=texts.format_traffic(subscription.traffic_limit_gb),
devices_used=devices_used,
devices_limit=subscription.device_limit,
- tariff_line=tariff_line,
)
show_countries = await _should_show_countries_management(db_user)
@@ -4958,9 +4556,6 @@ async def handle_add_country_to_subscription(
async def _should_show_countries_management(user: Optional[User] = None) -> bool:
- if settings.is_subscription_tariff_mode():
- return False
-
try:
promo_group_id = user.promo_group_id if user else None
@@ -5002,14 +4597,6 @@ async def confirm_add_countries_to_subscription(
db: AsyncSession,
state: FSMContext
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_COUNTRY_MANAGEMENT_DISABLED", "ℹ️ Смена серверов недоступна при использовании тарифов"),
- show_alert=True,
- )
- return
-
data = await state.get_data()
texts = get_texts(db_user.language)
subscription = db_user.subscription
@@ -6005,14 +5592,6 @@ async def handle_switch_traffic(
):
from app.config import settings
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_TRAFFIC_MANAGEMENT_DISABLED", "ℹ️ Управление трафиком недоступно при использовании тарифов"),
- show_alert=True,
- )
- return
-
if settings.is_traffic_fixed():
await callback.answer("⚠️ В текущем режиме трафик фиксированный", show_alert=True)
return
@@ -6056,14 +5635,6 @@ async def confirm_switch_traffic(
db_user: User,
db: AsyncSession
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_TRAFFIC_MANAGEMENT_DISABLED", "ℹ️ Управление трафиком недоступно при использовании тарифов"),
- show_alert=True,
- )
- return
-
new_traffic_gb = int(callback.data.split('_')[2])
texts = get_texts(db_user.language)
subscription = db_user.subscription
@@ -6177,14 +5748,6 @@ async def execute_switch_traffic(
db_user: User,
db: AsyncSession
):
- if settings.is_subscription_tariff_mode():
- texts = get_texts(db_user.language)
- await callback.answer(
- texts.t("TARIFF_TRAFFIC_MANAGEMENT_DISABLED", "ℹ️ Управление трафиком недоступно при использовании тарифов"),
- show_alert=True,
- )
- return
-
callback_parts = callback.data.split('_')
new_traffic_gb = int(callback_parts[3])
price_difference = int(callback_parts[4])
@@ -6389,26 +5952,6 @@ def register_handlers(dp: Dispatcher):
F.data.in_(["menu_buy", "subscription_upgrade"])
)
- dp.callback_query.register(
- handle_tariff_selection,
- F.data.startswith("tariff_select_")
- )
-
- dp.callback_query.register(
- handle_tariff_period_selection,
- F.data.startswith("tariff_period_")
- )
-
- dp.callback_query.register(
- handle_tariff_back,
- F.data == "tariff_back"
- )
-
- dp.callback_query.register(
- handle_change_tariff,
- F.data == "subscription_change_tariff"
- )
-
dp.callback_query.register(
handle_add_countries,
F.data == "subscription_add_countries"
diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py
index e1de21c2..f2f89624 100644
--- a/app/keyboards/inline.py
+++ b/app/keyboards/inline.py
@@ -1,4 +1,4 @@
-from typing import Iterable, List, Optional, Sequence
+from typing import List, Optional
from aiogram import types
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from datetime import datetime
@@ -635,7 +635,7 @@ def get_trial_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
def get_subscription_period_keyboard(language: str = DEFAULT_LANGUAGE) -> InlineKeyboardMarkup:
texts = get_texts(language)
keyboard = []
-
+
available_periods = settings.get_available_subscription_periods()
period_texts = {
@@ -659,91 +659,14 @@ def get_subscription_period_keyboard(language: str = DEFAULT_LANGUAGE) -> Inline
keyboard.append([
InlineKeyboardButton(text=texts.BACK, callback_data="back_to_menu")
])
-
- return InlineKeyboardMarkup(inline_keyboard=keyboard)
-
-
-def get_tariff_selection_keyboard(
- tariffs: Sequence["SubscriptionTariff"],
- language: str = DEFAULT_LANGUAGE,
-) -> InlineKeyboardMarkup:
- texts = get_texts(language)
- keyboard: List[List[InlineKeyboardButton]] = []
-
- for tariff in tariffs:
- if not getattr(tariff, "prices", None):
- continue
-
- price_values = [price.price_kopeks for price in tariff.prices if price.price_kopeks is not None]
- if price_values:
- min_price = min(price_values)
- button_text = texts.t(
- "TARIFF_SELECT_BUTTON",
- "{name} • от {price}",
- ).format(name=tariff.name, price=texts.format_price(min_price))
- else:
- button_text = tariff.name
-
- keyboard.append([
- InlineKeyboardButton(
- text=button_text,
- callback_data=f"tariff_select_{tariff.id}",
- )
- ])
-
- if not keyboard:
- keyboard.append([
- InlineKeyboardButton(
- text=texts.t("NO_TARIFFS_AVAILABLE", "❌ Тарифы недоступны"),
- callback_data="no_tariffs",
- )
- ])
-
- keyboard.append([
- InlineKeyboardButton(text=texts.BACK, callback_data="menu_subscription")
- ])
-
- return InlineKeyboardMarkup(inline_keyboard=keyboard)
-
-
-def get_tariff_period_keyboard(
- tariff: "SubscriptionTariff",
- language: str = DEFAULT_LANGUAGE,
-) -> InlineKeyboardMarkup:
- texts = get_texts(language)
- keyboard: List[List[InlineKeyboardButton]] = []
-
- sorted_prices = sorted(
- getattr(tariff, "prices", []),
- key=lambda price: price.period_days,
- )
-
- for price in sorted_prices:
- period_text = format_period_description(price.period_days, language)
- button_text = texts.t(
- "TARIFF_PERIOD_BUTTON",
- "{period} • {price}",
- ).format(period=period_text, price=texts.format_price(price.price_kopeks))
-
- keyboard.append([
- InlineKeyboardButton(
- text=button_text,
- callback_data=f"tariff_period_{tariff.id}_{price.period_days}",
- )
- ])
-
- keyboard.append([
- InlineKeyboardButton(text=texts.BACK, callback_data="tariff_back"),
- InlineKeyboardButton(text=texts.CANCEL, callback_data="subscription_cancel"),
- ])
-
+
return InlineKeyboardMarkup(inline_keyboard=keyboard)
def get_traffic_packages_keyboard(language: str = DEFAULT_LANGUAGE) -> InlineKeyboardMarkup:
import logging
logger = logging.getLogger(__name__)
-
+
from app.config import settings
if settings.is_traffic_fixed():
@@ -1839,34 +1762,10 @@ def get_devices_management_keyboard(
def get_updated_subscription_settings_keyboard(language: str = DEFAULT_LANGUAGE, show_countries_management: bool = True) -> InlineKeyboardMarkup:
from app.config import settings
-
+
texts = get_texts(language)
- if settings.is_subscription_tariff_mode():
- keyboard = [
- [
- InlineKeyboardButton(
- text=texts.t("CHANGE_TARIFF_BUTTON", "🔁 Сменить тариф"),
- callback_data="subscription_change_tariff",
- )
- ],
- [
- InlineKeyboardButton(
- text=texts.t("MANAGE_DEVICES_BUTTON", "🔧 Управление устройствами"),
- callback_data="subscription_manage_devices",
- )
- ],
- [
- InlineKeyboardButton(
- text=texts.t("RESET_ALL_DEVICES_BUTTON", "🔄 Сбросить все устройства"),
- callback_data="reset_all_devices",
- )
- ],
- [InlineKeyboardButton(text=texts.BACK, callback_data="menu_subscription")],
- ]
- return InlineKeyboardMarkup(inline_keyboard=keyboard)
-
keyboard = []
-
+
if show_countries_management:
keyboard.append([
InlineKeyboardButton(text=texts.t("ADD_COUNTRIES_BUTTON", "🌐 Добавить страны"), callback_data="subscription_add_countries")
@@ -1874,7 +1773,7 @@ def get_updated_subscription_settings_keyboard(language: str = DEFAULT_LANGUAGE,
keyboard.extend([
[
- InlineKeyboardButton(text=texts.t("CHANGE_DEVICES_BUTTON", "📱 Изменить устройства"), callback_data="subscription_change_devices")
+ InlineKeyboardButton(text=texts.t("CHANGE_DEVICES_BUTTON", "📱 Изменить устройства"), callback_data="subscription_change_devices")
],
[
InlineKeyboardButton(text=texts.t("MANAGE_DEVICES_BUTTON", "🔧 Управление устройствами"), callback_data="subscription_manage_devices")
@@ -1888,11 +1787,11 @@ def get_updated_subscription_settings_keyboard(language: str = DEFAULT_LANGUAGE,
keyboard.insert(-2, [
InlineKeyboardButton(text=texts.t("RESET_TRAFFIC_BUTTON", "🔄 Сбросить трафик"), callback_data="subscription_reset_traffic")
])
-
+
keyboard.append([
InlineKeyboardButton(text=texts.BACK, callback_data="menu_subscription")
])
-
+
return InlineKeyboardMarkup(inline_keyboard=keyboard)
diff --git a/app/services/tariff_service.py b/app/services/tariff_service.py
deleted file mode 100644
index b78484c3..00000000
--- a/app/services/tariff_service.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from typing import List, Optional
-
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from app.database.crud.tariff import (
- get_active_tariffs_for_promo_group,
- get_tariff_by_id,
-)
-from app.database.models import SubscriptionTariff, User
-
-
-class TariffService:
- @staticmethod
- async def get_available_tariffs(
- db: AsyncSession,
- user: Optional[User],
- ) -> List[SubscriptionTariff]:
- promo_group_id = getattr(user, "promo_group_id", None) if user else None
- tariffs = await get_active_tariffs_for_promo_group(db, promo_group_id)
- return sorted(tariffs, key=lambda tariff: (tariff.sort_order, tariff.id))
-
- @staticmethod
- async def get_tariff_for_user(
- db: AsyncSession,
- tariff_id: int,
- user: Optional[User],
- ) -> Optional[SubscriptionTariff]:
- promo_group_id = getattr(user, "promo_group_id", None) if user else None
- tariff = await get_tariff_by_id(db, tariff_id, include_inactive=False)
- if not tariff:
- return None
-
- available_servers = [
- server
- for server in tariff.server_squads
- if server.is_available and not server.is_full
- ]
- if not available_servers:
- return None
-
- if not tariff.is_available_for_promo_group(promo_group_id):
- return None
-
- tariff.server_squads = available_servers
- return tariff
diff --git a/app/states.py b/app/states.py
index dac39fea..43655b35 100644
--- a/app/states.py
+++ b/app/states.py
@@ -6,8 +6,6 @@ class RegistrationStates(StatesGroup):
waiting_for_referral_code = State()
class SubscriptionStates(StatesGroup):
- selecting_tariff = State()
- selecting_tariff_period = State()
selecting_period = State()
selecting_traffic = State()
selecting_countries = State()
diff --git a/app/webapi/app.py b/app/webapi/app.py
index db2d54fd..0761ecad 100644
--- a/app/webapi/app.py
+++ b/app/webapi/app.py
@@ -17,7 +17,6 @@ from .routes import (
remnawave,
stats,
subscriptions,
- tariffs,
tickets,
tokens,
transactions,
@@ -46,10 +45,6 @@ OPENAPI_TAGS = [
"name": "subscriptions",
"description": "Создание, продление и настройка подписок бота.",
},
- {
- "name": "tariffs",
- "description": "Управление преднастроенными тарифами подписок.",
- },
{
"name": "support",
"description": "Работа с тикетами поддержки, приоритетами и ограничениями на ответы.",
@@ -106,7 +101,6 @@ def create_web_api_app() -> FastAPI:
app.include_router(config.router, prefix="/settings", tags=["settings"])
app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(subscriptions.router, prefix="/subscriptions", tags=["subscriptions"])
- app.include_router(tariffs.router, prefix="/tariffs", tags=["tariffs"])
app.include_router(tickets.router, prefix="/tickets", tags=["support"])
app.include_router(transactions.router, prefix="/transactions", tags=["transactions"])
app.include_router(promo_groups.router, prefix="/promo-groups", tags=["promo-groups"])
diff --git a/app/webapi/routes/__init__.py b/app/webapi/routes/__init__.py
index ed548ae9..31aef685 100644
--- a/app/webapi/routes/__init__.py
+++ b/app/webapi/routes/__init__.py
@@ -5,7 +5,6 @@ from . import (
remnawave,
stats,
subscriptions,
- tariffs,
tickets,
tokens,
transactions,
@@ -19,7 +18,6 @@ __all__ = [
"remnawave",
"stats",
"subscriptions",
- "tariffs",
"tickets",
"tokens",
"transactions",
diff --git a/app/webapi/routes/subscriptions.py b/app/webapi/routes/subscriptions.py
index 01ab9a88..2ec77fe2 100644
--- a/app/webapi/routes/subscriptions.py
+++ b/app/webapi/routes/subscriptions.py
@@ -50,7 +50,6 @@ def _serialize_subscription(subscription: Subscription) -> SubscriptionResponse:
subscription_url=subscription.subscription_url,
subscription_crypto_link=subscription.subscription_crypto_link,
connected_squads=list(subscription.connected_squads or []),
- tariff_id=subscription.tariff_id,
created_at=subscription.created_at,
updated_at=subscription.updated_at,
)
@@ -59,10 +58,7 @@ def _serialize_subscription(subscription: Subscription) -> SubscriptionResponse:
async def _get_subscription(db: AsyncSession, subscription_id: int) -> Subscription:
result = await db.execute(
select(Subscription)
- .options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ .options(selectinload(Subscription.user))
.where(Subscription.id == subscription_id)
)
subscription = result.scalar_one_or_none()
@@ -81,10 +77,7 @@ async def list_subscriptions(
user_id: Optional[int] = Query(default=None),
is_trial: Optional[bool] = Query(default=None),
) -> list[SubscriptionResponse]:
- query = select(Subscription).options(
- selectinload(Subscription.user),
- selectinload(Subscription.tariff),
- )
+ query = select(Subscription).options(selectinload(Subscription.user))
if status_filter:
query = query.where(Subscription.status == status_filter.value)
@@ -138,7 +131,6 @@ async def create_subscription(
traffic_limit_gb=payload.traffic_limit_gb or settings.DEFAULT_TRAFFIC_LIMIT_GB,
device_limit=payload.device_limit or settings.DEFAULT_DEVICE_LIMIT,
connected_squads=payload.connected_squads or [],
- tariff_id=payload.tariff_id,
)
subscription = await _get_subscription(db, subscription.id)
diff --git a/app/webapi/routes/tariffs.py b/app/webapi/routes/tariffs.py
deleted file mode 100644
index 36aa20f6..00000000
--- a/app/webapi/routes/tariffs.py
+++ /dev/null
@@ -1,122 +0,0 @@
-from fastapi import APIRouter, Depends, HTTPException, Security, status
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from app.database.crud.tariff import (
- create_tariff,
- delete_tariff,
- get_tariff_by_id,
- list_tariffs,
- update_tariff,
-)
-from app.database.models import SubscriptionTariff
-
-from ..dependencies import get_db_session, require_api_token
-from ..schemas.tariffs import (
- TariffCreateRequest,
- TariffResponse,
- TariffUpdateRequest,
- TariffPricePayload,
-)
-
-router = APIRouter()
-
-
-def _serialize_tariff(tariff: SubscriptionTariff) -> TariffResponse:
- return TariffResponse(
- id=tariff.id,
- name=tariff.name,
- description=tariff.description,
- traffic_limit_gb=tariff.traffic_limit_gb,
- device_limit=tariff.device_limit,
- is_active=tariff.is_active,
- sort_order=tariff.sort_order,
- server_squads=[server.squad_uuid for server in tariff.server_squads if getattr(server, "squad_uuid", None)],
- promo_group_ids=[group.id for group in tariff.promo_groups if group is not None],
- prices=[
- TariffPricePayload(period_days=price.period_days, price_kopeks=price.price_kopeks)
- for price in sorted(tariff.prices, key=lambda item: item.period_days)
- ],
- created_at=tariff.created_at,
- updated_at=tariff.updated_at,
- )
-
-
-@router.get("", response_model=list[TariffResponse], tags=["tariffs"])
-async def list_subscription_tariffs(
- include_inactive: bool = False,
- _: str = Security(require_api_token),
- db: AsyncSession = Depends(get_db_session),
-) -> list[TariffResponse]:
- tariffs = await list_tariffs(db, include_inactive=include_inactive)
- return [_serialize_tariff(tariff) for tariff in tariffs]
-
-
-@router.get("/{tariff_id}", response_model=TariffResponse, tags=["tariffs"])
-async def get_subscription_tariff(
- tariff_id: int,
- _: str = Security(require_api_token),
- db: AsyncSession = Depends(get_db_session),
-) -> TariffResponse:
- tariff = await get_tariff_by_id(db, tariff_id, include_inactive=True)
- if not tariff:
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tariff not found")
- return _serialize_tariff(tariff)
-
-
-@router.post("", response_model=TariffResponse, status_code=status.HTTP_201_CREATED, tags=["tariffs"])
-async def create_subscription_tariff(
- payload: TariffCreateRequest,
- _: str = Security(require_api_token),
- db: AsyncSession = Depends(get_db_session),
-) -> TariffResponse:
- tariff = await create_tariff(
- db,
- name=payload.name,
- description=payload.description,
- traffic_limit_gb=payload.traffic_limit_gb,
- device_limit=payload.device_limit,
- server_uuids=payload.server_squads,
- promo_group_ids=payload.promo_group_ids,
- prices=[price.model_dump() for price in payload.prices],
- is_active=payload.is_active,
- sort_order=payload.sort_order,
- )
- return _serialize_tariff(tariff)
-
-
-@router.put("/{tariff_id}", response_model=TariffResponse, tags=["tariffs"])
-async def update_subscription_tariff(
- tariff_id: int,
- payload: TariffUpdateRequest,
- _: str = Security(require_api_token),
- db: AsyncSession = Depends(get_db_session),
-) -> TariffResponse:
- tariff = await update_tariff(
- db,
- tariff_id,
- name=payload.name,
- description=payload.description,
- traffic_limit_gb=payload.traffic_limit_gb,
- device_limit=payload.device_limit,
- server_uuids=payload.server_squads,
- promo_group_ids=payload.promo_group_ids,
- prices=[price.model_dump() for price in payload.prices] if payload.prices is not None else None,
- is_active=payload.is_active,
- sort_order=payload.sort_order,
- )
- if not tariff:
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tariff not found")
- return _serialize_tariff(tariff)
-
-
-@router.delete("/{tariff_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["tariffs"])
-async def delete_subscription_tariff(
- tariff_id: int,
- _: str = Security(require_api_token),
- db: AsyncSession = Depends(get_db_session),
-) -> None:
- success = await delete_tariff(db, tariff_id)
- if not success:
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Unable to delete tariff")
-
- return None
diff --git a/app/webapi/schemas/subscriptions.py b/app/webapi/schemas/subscriptions.py
index 08691023..f09b5405 100644
--- a/app/webapi/schemas/subscriptions.py
+++ b/app/webapi/schemas/subscriptions.py
@@ -22,7 +22,6 @@ class SubscriptionResponse(BaseModel):
subscription_url: Optional[str] = None
subscription_crypto_link: Optional[str] = None
connected_squads: List[str] = Field(default_factory=list)
- tariff_id: Optional[int] = None
created_at: datetime
updated_at: datetime
@@ -35,7 +34,6 @@ class SubscriptionCreateRequest(BaseModel):
device_limit: Optional[int] = None
squad_uuid: Optional[str] = None
connected_squads: Optional[List[str]] = None
- tariff_id: Optional[int] = None
class SubscriptionExtendRequest(BaseModel):
diff --git a/app/webapi/schemas/tariffs.py b/app/webapi/schemas/tariffs.py
deleted file mode 100644
index 1885ff9d..00000000
--- a/app/webapi/schemas/tariffs.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from datetime import datetime
-from typing import List, Optional
-
-from pydantic import BaseModel, Field
-
-
-class TariffPricePayload(BaseModel):
- period_days: int = Field(..., gt=0)
- price_kopeks: int = Field(..., ge=0)
-
-
-class TariffResponse(BaseModel):
- id: int
- name: str
- description: Optional[str] = None
- traffic_limit_gb: int
- device_limit: int
- is_active: bool
- sort_order: int
- server_squads: List[str] = Field(default_factory=list)
- promo_group_ids: List[int] = Field(default_factory=list)
- prices: List[TariffPricePayload] = Field(default_factory=list)
- created_at: datetime
- updated_at: datetime
-
-
-class TariffCreateRequest(BaseModel):
- name: str
- description: Optional[str] = None
- traffic_limit_gb: int = Field(..., ge=0)
- device_limit: int = Field(..., ge=1)
- is_active: bool = True
- sort_order: int = 0
- server_squads: List[str] = Field(default_factory=list)
- promo_group_ids: Optional[List[int]] = None
- prices: List[TariffPricePayload] = Field(default_factory=list)
-
-
-class TariffUpdateRequest(BaseModel):
- name: Optional[str] = None
- description: Optional[str] = None
- traffic_limit_gb: Optional[int] = Field(default=None, ge=0)
- device_limit: Optional[int] = Field(default=None, ge=1)
- is_active: Optional[bool] = None
- sort_order: Optional[int] = None
- server_squads: Optional[List[str]] = None
- promo_group_ids: Optional[List[int]] = None
- prices: Optional[List[TariffPricePayload]] = None