Files
remnawave-bedolaga-telegram…/app/webapi/schemas/miniapp.py
2026-01-15 17:34:39 +03:00

1022 lines
36 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
from pydantic import BaseModel, ConfigDict, Field, model_validator
class MiniAppBranding(BaseModel):
service_name: Dict[str, Optional[str]] = Field(default_factory=dict)
service_description: Dict[str, Optional[str]] = Field(default_factory=dict)
class MiniAppSubscriptionRequest(BaseModel):
init_data: str = Field(..., alias="initData")
class MiniAppMaintenanceStatusResponse(BaseModel):
model_config = ConfigDict(populate_by_name=True)
is_active: bool = Field(..., alias="isActive")
message: Optional[str] = None
reason: Optional[str] = None
class MiniAppSubscriptionUser(BaseModel):
telegram_id: int
username: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
display_name: str
language: Optional[str] = None
status: str
subscription_status: str
subscription_actual_status: str
status_label: str
expires_at: Optional[datetime] = None
device_limit: Optional[int] = None
traffic_used_gb: float = 0.0
traffic_used_label: str
traffic_limit_gb: Optional[int] = None
traffic_limit_label: str
lifetime_used_traffic_gb: float = 0.0
has_active_subscription: bool = False
promo_offer_discount_percent: int = 0
promo_offer_discount_expires_at: Optional[datetime] = None
promo_offer_discount_source: Optional[str] = None
# Суточные тарифы
is_daily_tariff: bool = False
is_daily_paused: bool = False
daily_tariff_name: Optional[str] = None
daily_price_kopeks: Optional[int] = None
daily_price_label: Optional[str] = None
daily_next_charge_at: Optional[datetime] = None # Время следующего списания
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[str, 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[str, int] = Field(default_factory=dict)
apply_discounts_to_addons: bool = True
class MiniAppConnectedServer(BaseModel):
uuid: str
name: str
class MiniAppDevice(BaseModel):
hwid: Optional[str] = None
platform: Optional[str] = None
device_model: Optional[str] = None
app_version: Optional[str] = None
last_seen: Optional[str] = None
last_ip: Optional[str] = None
class MiniAppDeviceRemovalRequest(BaseModel):
init_data: str = Field(..., alias="initData")
hwid: str
class MiniAppDeviceRemovalResponse(BaseModel):
success: bool = True
message: Optional[str] = None
class MiniAppTransaction(BaseModel):
id: int
type: str
amount_kopeks: int
amount_rubles: float
description: Optional[str] = None
payment_method: Optional[str] = None
external_id: Optional[str] = None
is_completed: bool
created_at: datetime
completed_at: Optional[datetime] = None
class MiniAppPromoOffer(BaseModel):
id: int
status: str
notification_type: Optional[str] = None
offer_type: Optional[str] = None
effect_type: Optional[str] = None
discount_percent: int = 0
bonus_amount_kopeks: int = 0
bonus_amount_label: Optional[str] = None
expires_at: Optional[datetime] = None
claimed_at: Optional[datetime] = None
is_active: bool = False
template_id: Optional[int] = None
template_name: Optional[str] = None
button_text: Optional[str] = None
title: Optional[str] = None
message_text: Optional[str] = None
icon: Optional[str] = None
test_squads: List[MiniAppConnectedServer] = Field(default_factory=list)
active_discount_expires_at: Optional[datetime] = None
active_discount_started_at: Optional[datetime] = None
active_discount_duration_seconds: Optional[int] = None
class MiniAppPromoOfferClaimRequest(BaseModel):
init_data: str = Field(..., alias="initData")
class MiniAppPromoOfferClaimResponse(BaseModel):
success: bool = True
code: Optional[str] = None
class MiniAppSubscriptionAutopay(BaseModel):
enabled: bool = False
autopay_enabled: Optional[bool] = None
autopay_enabled_at: Optional[datetime] = None
days_before: Optional[int] = None
autopay_days_before: Optional[int] = None
default_days_before: Optional[int] = None
autopay_days_options: List[int] = Field(default_factory=list)
days_options: List[int] = Field(default_factory=list)
options: List[int] = Field(default_factory=list)
model_config = ConfigDict(extra="allow")
class MiniAppSubscriptionRenewalPeriod(BaseModel):
id: str
days: Optional[int] = None
months: Optional[int] = None
price_kopeks: Optional[int] = Field(default=None, alias="priceKopeks")
price_label: Optional[str] = Field(default=None, alias="priceLabel")
original_price_kopeks: Optional[int] = Field(default=None, alias="originalPriceKopeks")
original_price_label: Optional[str] = Field(default=None, alias="originalPriceLabel")
discount_percent: int = Field(default=0, alias="discountPercent")
price_per_month_kopeks: Optional[int] = Field(default=None, alias="pricePerMonthKopeks")
price_per_month_label: Optional[str] = Field(default=None, alias="pricePerMonthLabel")
is_recommended: bool = Field(default=False, alias="isRecommended")
description: Optional[str] = None
badge: Optional[str] = None
title: Optional[str] = None
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionRenewalOptionsRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionRenewalOptionsResponse(BaseModel):
success: bool = True
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
currency: str
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
promo_group: Optional[MiniAppPromoGroup] = Field(default=None, alias="promoGroup")
promo_offer: Optional[Dict[str, Any]] = Field(default=None, alias="promoOffer")
periods: List[MiniAppSubscriptionRenewalPeriod] = Field(default_factory=list)
default_period_id: Optional[str] = Field(default=None, alias="defaultPeriodId")
missing_amount_kopeks: Optional[int] = Field(default=None, alias="missingAmountKopeks")
status_message: Optional[str] = Field(default=None, alias="statusMessage")
autopay_enabled: bool = False
autopay_days_before: Optional[int] = None
autopay_days_options: List[int] = Field(default_factory=list)
autopay: Optional[MiniAppSubscriptionAutopay] = None
autopay_settings: Optional[MiniAppSubscriptionAutopay] = None
# Флаги для определения типа действия (покупка vs продление)
is_trial: bool = Field(default=False, alias="isTrial")
sales_mode: str = Field(default="classic", alias="salesMode")
model_config = ConfigDict(populate_by_name=True, extra="allow")
class MiniAppSubscriptionRenewalRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
period_id: Optional[str] = Field(default=None, alias="periodId")
period_days: Optional[int] = Field(default=None, alias="periodDays")
method: Optional[str] = None
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionRenewalResponse(BaseModel):
success: bool = True
message: Optional[str] = None
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
renewed_until: Optional[datetime] = Field(default=None, alias="renewedUntil")
requires_payment: bool = Field(default=False, alias="requiresPayment")
payment_method: Optional[str] = Field(default=None, alias="paymentMethod")
payment_url: Optional[str] = Field(default=None, alias="paymentUrl")
payment_amount_kopeks: Optional[int] = Field(default=None, alias="paymentAmountKopeks")
payment_id: Optional[int] = Field(default=None, alias="paymentId")
invoice_id: Optional[str] = Field(default=None, alias="invoiceId")
payment_payload: Optional[str] = Field(default=None, alias="paymentPayload")
payment_extra: Optional[Dict[str, Any]] = Field(default=None, alias="paymentExtra")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionAutopayRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
enabled: Optional[bool] = None
days_before: Optional[int] = Field(default=None, alias="daysBefore")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionAutopayResponse(BaseModel):
success: bool = True
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
autopay_enabled: bool = False
autopay_days_before: Optional[int] = None
autopay_days_options: List[int] = Field(default_factory=list)
autopay: Optional[MiniAppSubscriptionAutopay] = None
autopay_settings: Optional[MiniAppSubscriptionAutopay] = None
model_config = ConfigDict(populate_by_name=True, extra="allow")
class MiniAppPromoCode(BaseModel):
code: str
type: Optional[str] = None
balance_bonus_kopeks: int = 0
subscription_days: int = 0
max_uses: Optional[int] = None
current_uses: Optional[int] = None
valid_until: Optional[datetime] = None
class MiniAppPromoCodeActivationRequest(BaseModel):
init_data: str = Field(..., alias="initData")
code: str
class MiniAppPromoCodeActivationResponse(BaseModel):
success: bool = True
description: Optional[str] = None
promocode: Optional[MiniAppPromoCode] = None
class MiniAppFaqItem(BaseModel):
id: int
title: Optional[str] = None
content: Optional[str] = None
display_order: Optional[int] = None
class MiniAppFaq(BaseModel):
requested_language: str
language: str
is_enabled: bool = True
total: int = 0
items: List[MiniAppFaqItem] = Field(default_factory=list)
class MiniAppRichTextDocument(BaseModel):
requested_language: str
language: str
title: Optional[str] = None
is_enabled: bool = True
content: str = ""
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class MiniAppLegalDocuments(BaseModel):
public_offer: Optional[MiniAppRichTextDocument] = None
service_rules: Optional[MiniAppRichTextDocument] = None
privacy_policy: Optional[MiniAppRichTextDocument] = None
class MiniAppReferralTerms(BaseModel):
minimum_topup_kopeks: int = 0
minimum_topup_label: Optional[str] = None
first_topup_bonus_kopeks: int = 0
first_topup_bonus_label: Optional[str] = None
inviter_bonus_kopeks: int = 0
inviter_bonus_label: Optional[str] = None
commission_percent: float = 0.0
class MiniAppReferralStats(BaseModel):
invited_count: int = 0
paid_referrals_count: int = 0
active_referrals_count: int = 0
total_earned_kopeks: int = 0
total_earned_label: Optional[str] = None
month_earned_kopeks: int = 0
month_earned_label: Optional[str] = None
conversion_rate: float = 0.0
class MiniAppReferralRecentEarning(BaseModel):
amount_kopeks: int = 0
amount_label: Optional[str] = None
reason: Optional[str] = None
referral_name: Optional[str] = None
created_at: Optional[datetime] = None
class MiniAppReferralItem(BaseModel):
id: int
telegram_id: Optional[int] = None
full_name: Optional[str] = None
username: Optional[str] = None
created_at: Optional[datetime] = None
last_activity: Optional[datetime] = None
has_made_first_topup: bool = False
balance_kopeks: int = 0
balance_label: Optional[str] = None
total_earned_kopeks: int = 0
total_earned_label: Optional[str] = None
topups_count: int = 0
days_since_registration: Optional[int] = None
days_since_activity: Optional[int] = None
status: Optional[str] = None
class MiniAppReferralList(BaseModel):
total_count: int = 0
has_next: bool = False
has_prev: bool = False
current_page: int = 1
total_pages: int = 1
items: List[MiniAppReferralItem] = Field(default_factory=list)
class MiniAppReferralInfo(BaseModel):
referral_code: Optional[str] = None
referral_link: Optional[str] = None
terms: Optional[MiniAppReferralTerms] = None
stats: Optional[MiniAppReferralStats] = None
recent_earnings: List[MiniAppReferralRecentEarning] = Field(default_factory=list)
referrals: Optional[MiniAppReferralList] = None
class MiniAppPaymentMethodsRequest(BaseModel):
init_data: str = Field(..., alias="initData")
class MiniAppPaymentIntegrationType(str, Enum):
IFRAME = "iframe"
REDIRECT = "redirect"
class MiniAppPaymentOption(BaseModel):
id: str
icon: Optional[str] = None
title: Optional[str] = None
description: Optional[str] = None
title_key: Optional[str] = Field(default=None, alias="titleKey")
description_key: Optional[str] = Field(default=None, alias="descriptionKey")
model_config = ConfigDict(populate_by_name=True)
class MiniAppPaymentIframeConfig(BaseModel):
expected_origin: str
@model_validator(mode="after")
def _normalize_expected_origin(
cls, values: "MiniAppPaymentIframeConfig"
) -> "MiniAppPaymentIframeConfig":
origin = (values.expected_origin or "").strip()
if not origin:
raise ValueError("expected_origin must not be empty")
parsed = urlparse(origin)
if not parsed.scheme or not parsed.netloc:
raise ValueError("expected_origin must include scheme and host")
values.expected_origin = f"{parsed.scheme}://{parsed.netloc}"
return values
class MiniAppPaymentMethod(BaseModel):
id: str
name: Optional[str] = None
icon: Optional[str] = None
requires_amount: bool = False
currency: str = "RUB"
min_amount_kopeks: Optional[int] = None
max_amount_kopeks: Optional[int] = None
amount_step_kopeks: Optional[int] = None
integration_type: MiniAppPaymentIntegrationType
options: List[MiniAppPaymentOption] = Field(default_factory=list)
iframe_config: Optional[MiniAppPaymentIframeConfig] = None
@model_validator(mode="after")
def _ensure_iframe_config(cls, values: "MiniAppPaymentMethod") -> "MiniAppPaymentMethod":
if (
values.integration_type == MiniAppPaymentIntegrationType.IFRAME
and values.iframe_config is None
):
raise ValueError("iframe_config is required when integration_type is 'iframe'")
return values
class MiniAppPaymentMethodsResponse(BaseModel):
methods: List[MiniAppPaymentMethod] = Field(default_factory=list)
class MiniAppPaymentCreateRequest(BaseModel):
init_data: str = Field(..., alias="initData")
method: str
amount_rubles: Optional[float] = Field(default=None, alias="amountRubles")
amount_kopeks: Optional[int] = Field(default=None, alias="amountKopeks")
payment_option: Optional[str] = Field(default=None, alias="option")
class MiniAppPaymentCreateResponse(BaseModel):
success: bool = True
method: str
payment_url: Optional[str] = None
amount_kopeks: Optional[int] = None
extra: Dict[str, Any] = Field(default_factory=dict)
class MiniAppPaymentStatusQuery(BaseModel):
method: str
local_payment_id: Optional[int] = Field(default=None, alias="localPaymentId")
payment_link_id: Optional[str] = Field(default=None, alias="paymentLinkId")
invoice_id: Optional[str] = Field(default=None, alias="invoiceId")
payment_id: Optional[str] = Field(default=None, alias="paymentId")
payload: Optional[str] = None
amount_kopeks: Optional[int] = Field(default=None, alias="amountKopeks")
started_at: Optional[str] = Field(default=None, alias="startedAt")
class MiniAppPaymentStatusRequest(BaseModel):
init_data: str = Field(..., alias="initData")
payments: List[MiniAppPaymentStatusQuery] = Field(default_factory=list)
class MiniAppPaymentStatusResult(BaseModel):
method: str
status: str
is_paid: bool = False
amount_kopeks: Optional[int] = None
currency: Optional[str] = None
completed_at: Optional[datetime] = None
transaction_id: Optional[int] = None
external_id: Optional[str] = None
message: Optional[str] = None
extra: Dict[str, Any] = Field(default_factory=dict)
class MiniAppPaymentStatusResponse(BaseModel):
results: List[MiniAppPaymentStatusResult] = Field(default_factory=list)
# =============================================================================
# Тарифы для режима продаж "Тарифы"
# =============================================================================
class MiniAppTariffPeriod(BaseModel):
"""Период тарифа с ценой."""
days: int
months: Optional[int] = None
label: str
price_kopeks: int
price_label: str
price_per_month_kopeks: Optional[int] = None
price_per_month_label: Optional[str] = None
# Скидка промогруппы
original_price_kopeks: Optional[int] = None # Цена без скидки
original_price_label: Optional[str] = None
discount_percent: int = 0 # Процент скидки
class MiniAppTariff(BaseModel):
"""Тариф для отображения в miniapp."""
id: int
name: str
description: Optional[str] = None
tier_level: int = 1
traffic_limit_gb: int
traffic_limit_label: str
is_unlimited_traffic: bool = False
device_limit: int
servers_count: int
servers: List[MiniAppConnectedServer] = Field(default_factory=list)
periods: List[MiniAppTariffPeriod] = Field(default_factory=list)
is_current: bool = False
is_available: bool = True
# Для режима мгновенного переключения тарифа
switch_cost_kopeks: Optional[int] = None # Стоимость переключения (None если не в режиме switch)
switch_cost_label: Optional[str] = None # Форматированная стоимость
is_upgrade: Optional[bool] = None # True = повышение, False = понижение
is_switch_free: Optional[bool] = None # True = бесплатное переключение
# Суточные тарифы
is_daily: bool = False
daily_price_kopeks: int = 0
daily_price_label: Optional[str] = None
class MiniAppTrafficTopupPackage(BaseModel):
"""Пакет докупки трафика."""
gb: int
price_kopeks: int
price_label: str
# Скидка промогруппы на трафик
original_price_kopeks: Optional[int] = None
original_price_label: Optional[str] = None
discount_percent: int = 0
class MiniAppCurrentTariff(BaseModel):
"""Текущий тариф пользователя."""
id: int
name: str
description: Optional[str] = None
tier_level: int = 1
traffic_limit_gb: int
traffic_limit_label: str
is_unlimited_traffic: bool = False
device_limit: int
servers_count: int
# Месячная цена для расчёта стоимости переключения тарифа
monthly_price_kopeks: int = 0
# Докупка трафика
traffic_topup_enabled: bool = False
traffic_topup_packages: List[MiniAppTrafficTopupPackage] = Field(default_factory=list)
# Лимит докупки трафика (0 = без лимита)
max_topup_traffic_gb: int = 0
available_topup_gb: Optional[int] = None # Сколько еще можно докупить (None = без лимита)
# Суточные тарифы
is_daily: bool = False
daily_price_kopeks: int = 0
daily_price_label: Optional[str] = None
class MiniAppTrafficTopupRequest(BaseModel):
"""Запрос на докупку трафика."""
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = Field(None, alias="subscriptionId")
gb: int
class MiniAppTrafficTopupResponse(BaseModel):
"""Ответ на докупку трафика."""
success: bool = True
message: str = ""
new_traffic_limit_gb: int = 0
new_balance_kopeks: int = 0
charged_kopeks: int = 0
class MiniAppTariffsRequest(BaseModel):
"""Запрос списка тарифов."""
init_data: str = Field(..., alias="initData")
class MiniAppTariffsResponse(BaseModel):
"""Ответ со списком тарифов."""
success: bool = True
sales_mode: str = "tariffs"
tariffs: List[MiniAppTariff] = Field(default_factory=list)
current_tariff: Optional[MiniAppCurrentTariff] = None
balance_kopeks: int = 0
balance_label: Optional[str] = None
promo_group: Optional[MiniAppPromoGroup] = None # Промогруппа пользователя для отображения скидок
class MiniAppTariffPurchaseRequest(BaseModel):
"""Запрос на покупку/смену тарифа."""
init_data: str = Field(..., alias="initData")
tariff_id: int = Field(..., alias="tariffId")
period_days: int = Field(..., alias="periodDays")
class MiniAppTariffPurchaseResponse(BaseModel):
"""Ответ на покупку тарифа."""
success: bool = True
message: Optional[str] = None
subscription_id: Optional[int] = None
tariff_id: Optional[int] = None
tariff_name: Optional[str] = None
new_end_date: Optional[datetime] = None
balance_kopeks: Optional[int] = None
balance_label: Optional[str] = None
class MiniAppTariffSwitchRequest(BaseModel):
"""Запрос на переключение тарифа (без выбора периода)."""
init_data: str = Field(...)
tariff_id: int = Field(...)
class MiniAppTariffSwitchPreviewResponse(BaseModel):
"""Предпросмотр переключения тарифа."""
can_switch: bool = True
current_tariff_id: Optional[int] = None
current_tariff_name: Optional[str] = None
new_tariff_id: int
new_tariff_name: str
remaining_days: int = 0
upgrade_cost_kopeks: int = 0 # 0 если даунгрейд или равная цена
upgrade_cost_label: str = ""
balance_kopeks: int = 0
balance_label: str = ""
has_enough_balance: bool = True
missing_amount_kopeks: int = 0
missing_amount_label: str = ""
is_upgrade: bool = False # True если новый тариф дороже
message: Optional[str] = None
class MiniAppTariffSwitchResponse(BaseModel):
"""Ответ на переключение тарифа."""
success: bool = True
message: Optional[str] = None
tariff_id: int
tariff_name: str
charged_kopeks: int = 0
balance_kopeks: int = 0
balance_label: str = ""
class MiniAppDailySubscriptionToggleRequest(BaseModel):
"""Запрос на паузу/возобновление суточной подписки."""
init_data: str = Field(...)
class MiniAppDailySubscriptionToggleResponse(BaseModel):
"""Ответ на паузу/возобновление суточной подписки."""
success: bool = True
message: Optional[str] = None
is_paused: bool = False
balance_kopeks: int = 0
balance_label: str = ""
class MiniAppTrafficPurchase(BaseModel):
"""Докупка трафика с индивидуальной датой истечения."""
id: int
traffic_gb: int
expires_at: datetime
created_at: datetime
days_remaining: int
progress_percent: float
class MiniAppSubscriptionResponse(BaseModel):
success: bool = True
subscription_id: Optional[int] = None
remnawave_short_uuid: Optional[str] = None
user: MiniAppSubscriptionUser
traffic_purchases: List[MiniAppTrafficPurchase] = Field(default_factory=list)
subscription_url: Optional[str] = None
subscription_crypto_link: Optional[str] = None
subscription_purchase_url: Optional[str] = None
links: List[str] = Field(default_factory=list)
ss_conf_links: Dict[str, str] = Field(default_factory=dict)
connected_squads: List[str] = Field(default_factory=list)
connected_servers: List[MiniAppConnectedServer] = Field(default_factory=list)
connected_devices_count: int = 0
connected_devices: List[MiniAppDevice] = Field(default_factory=list)
happ: Optional[Dict[str, Any]] = None
happ_link: Optional[str] = None
happ_crypto_link: Optional[str] = None
happ_cryptolink_redirect_link: Optional[str] = None
happ_cryptolink_redirect_template: Optional[str] = None
balance_kopeks: int = 0
balance_rubles: float = 0.0
balance_currency: Optional[str] = None
transactions: List[MiniAppTransaction] = Field(default_factory=list)
promo_offers: List[MiniAppPromoOffer] = 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
autopay_days_before: Optional[int] = None
autopay_days_options: List[int] = Field(default_factory=list)
autopay: Optional[MiniAppSubscriptionAutopay] = None
autopay_settings: Optional[MiniAppSubscriptionAutopay] = None
branding: Optional[MiniAppBranding] = None
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
trial_payment_required: bool = Field(default=False, alias="trialPaymentRequired")
trial_price_kopeks: Optional[int] = Field(default=None, alias="trialPriceKopeks")
trial_price_label: Optional[str] = Field(default=None, alias="trialPriceLabel")
# Режим продаж и тариф
sales_mode: str = Field(default="classic", alias="salesMode")
current_tariff: Optional[MiniAppCurrentTariff] = Field(default=None, alias="currentTariff")
model_config = ConfigDict(extra="allow", populate_by_name=True)
class MiniAppSubscriptionServerOption(BaseModel):
uuid: str
name: Optional[str] = None
price_kopeks: Optional[int] = None
price_label: Optional[str] = None
discount_percent: Optional[int] = None
is_connected: bool = False
is_available: bool = True
disabled_reason: Optional[str] = None
class MiniAppSubscriptionTrafficOption(BaseModel):
value: Optional[int] = None
label: Optional[str] = None
price_kopeks: Optional[int] = None
price_label: Optional[str] = None
is_current: bool = False
is_available: bool = True
description: Optional[str] = None
class MiniAppSubscriptionDeviceOption(BaseModel):
value: int
label: Optional[str] = None
price_kopeks: Optional[int] = None
price_label: Optional[str] = None
class MiniAppSubscriptionCurrentSettings(BaseModel):
servers: List[MiniAppConnectedServer] = Field(default_factory=list)
traffic_limit_gb: Optional[int] = None
traffic_limit_label: Optional[str] = None
device_limit: int = 0
class MiniAppSubscriptionServersSettings(BaseModel):
available: List[MiniAppSubscriptionServerOption] = Field(default_factory=list)
min: int = 0
max: int = 0
can_update: bool = True
hint: Optional[str] = None
class MiniAppSubscriptionTrafficSettings(BaseModel):
options: List[MiniAppSubscriptionTrafficOption] = Field(default_factory=list)
can_update: bool = True
current_value: Optional[int] = None
class MiniAppSubscriptionDevicesSettings(BaseModel):
options: List[MiniAppSubscriptionDeviceOption] = Field(default_factory=list)
can_update: bool = True
min: int = 0
max: int = 0
step: int = 1
current: int = 0
price_kopeks: Optional[int] = None
price_label: Optional[str] = None
class MiniAppSubscriptionBillingContext(BaseModel):
months_remaining: int = 1
period_hint_days: Optional[int] = None
renews_at: Optional[datetime] = None
class MiniAppSubscriptionSettings(BaseModel):
subscription_id: int
currency: str = "RUB"
current: MiniAppSubscriptionCurrentSettings
servers: MiniAppSubscriptionServersSettings
traffic: MiniAppSubscriptionTrafficSettings
devices: MiniAppSubscriptionDevicesSettings
billing: Optional[MiniAppSubscriptionBillingContext] = None
class MiniAppSubscriptionSettingsResponse(BaseModel):
success: bool = True
settings: MiniAppSubscriptionSettings
class MiniAppSubscriptionSettingsRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = None
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@classmethod
def _populate_aliases(cls, values: Any) -> Any:
if isinstance(values, dict):
if "subscriptionId" in values and "subscription_id" not in values:
values["subscription_id"] = values["subscriptionId"]
return values
class MiniAppSubscriptionServersUpdateRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = None
servers: Optional[List[str]] = None
squads: Optional[List[str]] = None
server_uuids: Optional[List[str]] = None
squad_uuids: Optional[List[str]] = None
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@classmethod
def _populate_aliases(cls, values: Any) -> Any:
if isinstance(values, dict):
alias_map = {
"subscriptionId": "subscription_id",
"serverUuids": "server_uuids",
"squadUuids": "squad_uuids",
}
for alias, target in alias_map.items():
if alias in values and target not in values:
values[target] = values[alias]
return values
class MiniAppSubscriptionTrafficUpdateRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = None
traffic: Optional[int] = None
traffic_gb: Optional[int] = None
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@classmethod
def _populate_aliases(cls, values: Any) -> Any:
if isinstance(values, dict):
alias_map = {
"subscriptionId": "subscription_id",
"trafficGb": "traffic_gb",
}
for alias, target in alias_map.items():
if alias in values and target not in values:
values[target] = values[alias]
return values
class MiniAppSubscriptionDevicesUpdateRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = None
devices: Optional[int] = None
device_limit: Optional[int] = None
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@classmethod
def _populate_aliases(cls, values: Any) -> Any:
if isinstance(values, dict):
alias_map = {
"subscriptionId": "subscription_id",
"deviceLimit": "device_limit",
}
for alias, target in alias_map.items():
if alias in values and target not in values:
values[target] = values[alias]
return values
class MiniAppSubscriptionUpdateResponse(BaseModel):
success: bool = True
message: Optional[str] = None
class MiniAppSubscriptionPurchaseOptionsRequest(BaseModel):
init_data: str = Field(..., alias="initData")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchaseOptionsResponse(BaseModel):
success: bool = True
currency: str
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
data: Dict[str, Any] = Field(default_factory=dict)
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchasePreviewRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
selection: Optional[Dict[str, Any]] = None
period_id: Optional[str] = Field(default=None, alias="periodId")
period_days: Optional[int] = Field(default=None, alias="periodDays")
period: Optional[str] = None
traffic_value: Optional[int] = Field(default=None, alias="trafficValue")
traffic: Optional[int] = None
traffic_gb: Optional[int] = Field(default=None, alias="trafficGb")
servers: Optional[List[str]] = None
countries: Optional[List[str]] = None
server_uuids: Optional[List[str]] = Field(default=None, alias="serverUuids")
devices: Optional[int] = None
device_limit: Optional[int] = Field(default=None, alias="deviceLimit")
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@classmethod
def _merge_selection(cls, values: Any) -> Any:
if not isinstance(values, dict):
return values
selection = values.get("selection")
if isinstance(selection, dict):
merged = {**selection, **values}
else:
merged = dict(values)
aliases = {
"period_id": ("periodId", "period", "code"),
"period_days": ("periodDays",),
"traffic_value": ("trafficValue", "traffic", "trafficGb"),
"servers": ("countries", "server_uuids", "serverUuids"),
"devices": ("deviceLimit",),
}
for target, sources in aliases.items():
if merged.get(target) is not None:
continue
for source in sources:
if source in merged and merged[source] is not None:
merged[target] = merged[source]
break
return merged
class MiniAppSubscriptionPurchasePreviewResponse(BaseModel):
success: bool = True
preview: Dict[str, Any] = Field(default_factory=dict)
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchaseRequest(MiniAppSubscriptionPurchasePreviewRequest):
pass
class MiniAppSubscriptionPurchaseResponse(BaseModel):
success: bool = True
message: Optional[str] = None
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionTrialRequest(BaseModel):
init_data: str = Field(..., alias="initData")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionTrialResponse(BaseModel):
success: bool = True
message: Optional[str] = None
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
trial_status: Optional[str] = Field(default=None, alias="trialStatus")
trial_duration_days: Optional[int] = Field(default=None, alias="trialDurationDays")
charged_amount_kopeks: Optional[int] = Field(default=None, alias="chargedAmountKopeks")
charged_amount_label: Optional[str] = Field(default=None, alias="chargedAmountLabel")
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
model_config = ConfigDict(populate_by_name=True)