mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-27 14:51:19 +00:00
- Add pyproject.toml with uv and ruff configuration - Pin Python version to 3.13 via .python-version - Add Makefile commands: lint, format, fix - Apply ruff formatting to entire codebase - Remove unused imports (base64 in yookassa/simple_subscription) - Update .gitignore for new config files
1034 lines
35 KiB
Python
1034 lines
35 KiB
Python
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from enum import Enum
|
||
from typing import Any
|
||
from urllib.parse import urlparse
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||
|
||
|
||
class MiniAppBranding(BaseModel):
|
||
service_name: dict[str, str | None] = Field(default_factory=dict)
|
||
service_description: dict[str, str | None] = 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: str | None = None
|
||
reason: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionUser(BaseModel):
|
||
telegram_id: int | None = None
|
||
username: str | None = None
|
||
first_name: str | None = None
|
||
last_name: str | None = None
|
||
display_name: str
|
||
language: str | None = None
|
||
status: str
|
||
subscription_status: str
|
||
subscription_actual_status: str
|
||
status_label: str
|
||
expires_at: datetime | None = None
|
||
device_limit: int | None = None
|
||
traffic_used_gb: float = 0.0
|
||
traffic_used_label: str
|
||
traffic_limit_gb: int | None = 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: datetime | None = None
|
||
promo_offer_discount_source: str | None = None
|
||
# Суточные тарифы
|
||
is_daily_tariff: bool = False
|
||
is_daily_paused: bool = False
|
||
daily_tariff_name: str | None = None
|
||
daily_price_kopeks: int | None = None
|
||
daily_price_label: str | None = None
|
||
daily_next_charge_at: datetime | None = 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: str | None = None
|
||
platform: str | None = None
|
||
device_model: str | None = None
|
||
app_version: str | None = None
|
||
last_seen: str | None = None
|
||
last_ip: str | None = None
|
||
|
||
|
||
class MiniAppDeviceRemovalRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
hwid: str
|
||
|
||
|
||
class MiniAppDeviceRemovalResponse(BaseModel):
|
||
success: bool = True
|
||
message: str | None = None
|
||
|
||
|
||
class MiniAppTransaction(BaseModel):
|
||
id: int
|
||
type: str
|
||
amount_kopeks: int
|
||
amount_rubles: float
|
||
description: str | None = None
|
||
payment_method: str | None = None
|
||
external_id: str | None = None
|
||
is_completed: bool
|
||
created_at: datetime
|
||
completed_at: datetime | None = None
|
||
|
||
|
||
class MiniAppPromoOffer(BaseModel):
|
||
id: int
|
||
status: str
|
||
notification_type: str | None = None
|
||
offer_type: str | None = None
|
||
effect_type: str | None = None
|
||
discount_percent: int = 0
|
||
bonus_amount_kopeks: int = 0
|
||
bonus_amount_label: str | None = None
|
||
expires_at: datetime | None = None
|
||
claimed_at: datetime | None = None
|
||
is_active: bool = False
|
||
template_id: int | None = None
|
||
template_name: str | None = None
|
||
button_text: str | None = None
|
||
title: str | None = None
|
||
message_text: str | None = None
|
||
icon: str | None = None
|
||
test_squads: list[MiniAppConnectedServer] = Field(default_factory=list)
|
||
active_discount_expires_at: datetime | None = None
|
||
active_discount_started_at: datetime | None = None
|
||
active_discount_duration_seconds: int | None = None
|
||
|
||
|
||
class MiniAppPromoOfferClaimRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
|
||
|
||
class MiniAppPromoOfferClaimResponse(BaseModel):
|
||
success: bool = True
|
||
code: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionAutopay(BaseModel):
|
||
enabled: bool = False
|
||
autopay_enabled: bool | None = None
|
||
autopay_enabled_at: datetime | None = None
|
||
days_before: int | None = None
|
||
autopay_days_before: int | None = None
|
||
default_days_before: int | None = 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: int | None = None
|
||
months: int | None = None
|
||
price_kopeks: int | None = Field(default=None, alias='priceKopeks')
|
||
price_label: str | None = Field(default=None, alias='priceLabel')
|
||
original_price_kopeks: int | None = Field(default=None, alias='originalPriceKopeks')
|
||
original_price_label: str | None = Field(default=None, alias='originalPriceLabel')
|
||
discount_percent: int = Field(default=0, alias='discountPercent')
|
||
price_per_month_kopeks: int | None = Field(default=None, alias='pricePerMonthKopeks')
|
||
price_per_month_label: str | None = Field(default=None, alias='pricePerMonthLabel')
|
||
is_recommended: bool = Field(default=False, alias='isRecommended')
|
||
description: str | None = None
|
||
badge: str | None = None
|
||
title: str | None = None
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionRenewalOptionsRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
subscription_id: int | None = Field(default=None, alias='subscriptionId')
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionRenewalOptionsResponse(BaseModel):
|
||
success: bool = True
|
||
subscription_id: int | None = Field(default=None, alias='subscriptionId')
|
||
currency: str
|
||
balance_kopeks: int | None = Field(default=None, alias='balanceKopeks')
|
||
balance_label: str | None = Field(default=None, alias='balanceLabel')
|
||
promo_group: MiniAppPromoGroup | None = Field(default=None, alias='promoGroup')
|
||
promo_offer: dict[str, Any] | None = Field(default=None, alias='promoOffer')
|
||
periods: list[MiniAppSubscriptionRenewalPeriod] = Field(default_factory=list)
|
||
default_period_id: str | None = Field(default=None, alias='defaultPeriodId')
|
||
missing_amount_kopeks: int | None = Field(default=None, alias='missingAmountKopeks')
|
||
status_message: str | None = Field(default=None, alias='statusMessage')
|
||
autopay_enabled: bool = False
|
||
autopay_days_before: int | None = None
|
||
autopay_days_options: list[int] = Field(default_factory=list)
|
||
autopay: MiniAppSubscriptionAutopay | None = None
|
||
autopay_settings: MiniAppSubscriptionAutopay | None = 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: int | None = Field(default=None, alias='subscriptionId')
|
||
period_id: str | None = Field(default=None, alias='periodId')
|
||
period_days: int | None = Field(default=None, alias='periodDays')
|
||
method: str | None = None
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionRenewalResponse(BaseModel):
|
||
success: bool = True
|
||
message: str | None = None
|
||
balance_kopeks: int | None = Field(default=None, alias='balanceKopeks')
|
||
balance_label: str | None = Field(default=None, alias='balanceLabel')
|
||
subscription_id: int | None = Field(default=None, alias='subscriptionId')
|
||
renewed_until: datetime | None = Field(default=None, alias='renewedUntil')
|
||
requires_payment: bool = Field(default=False, alias='requiresPayment')
|
||
payment_method: str | None = Field(default=None, alias='paymentMethod')
|
||
payment_url: str | None = Field(default=None, alias='paymentUrl')
|
||
payment_amount_kopeks: int | None = Field(default=None, alias='paymentAmountKopeks')
|
||
payment_id: int | None = Field(default=None, alias='paymentId')
|
||
invoice_id: str | None = Field(default=None, alias='invoiceId')
|
||
payment_payload: str | None = Field(default=None, alias='paymentPayload')
|
||
payment_extra: dict[str, Any] | None = Field(default=None, alias='paymentExtra')
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionAutopayRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
subscription_id: int | None = Field(default=None, alias='subscriptionId')
|
||
enabled: bool | None = None
|
||
days_before: int | None = Field(default=None, alias='daysBefore')
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionAutopayResponse(BaseModel):
|
||
success: bool = True
|
||
subscription_id: int | None = Field(default=None, alias='subscriptionId')
|
||
autopay_enabled: bool = False
|
||
autopay_days_before: int | None = None
|
||
autopay_days_options: list[int] = Field(default_factory=list)
|
||
autopay: MiniAppSubscriptionAutopay | None = None
|
||
autopay_settings: MiniAppSubscriptionAutopay | None = None
|
||
|
||
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
||
|
||
|
||
class MiniAppPromoCode(BaseModel):
|
||
code: str
|
||
type: str | None = None
|
||
balance_bonus_kopeks: int = 0
|
||
subscription_days: int = 0
|
||
max_uses: int | None = None
|
||
current_uses: int | None = None
|
||
valid_until: datetime | None = None
|
||
|
||
|
||
class MiniAppPromoCodeActivationRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
code: str
|
||
|
||
|
||
class MiniAppPromoCodeActivationResponse(BaseModel):
|
||
success: bool = True
|
||
description: str | None = None
|
||
promocode: MiniAppPromoCode | None = None
|
||
|
||
|
||
class MiniAppFaqItem(BaseModel):
|
||
id: int
|
||
title: str | None = None
|
||
content: str | None = None
|
||
display_order: int | None = 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: str | None = None
|
||
is_enabled: bool = True
|
||
content: str = ''
|
||
created_at: datetime | None = None
|
||
updated_at: datetime | None = None
|
||
|
||
|
||
class MiniAppLegalDocuments(BaseModel):
|
||
public_offer: MiniAppRichTextDocument | None = None
|
||
service_rules: MiniAppRichTextDocument | None = None
|
||
privacy_policy: MiniAppRichTextDocument | None = None
|
||
|
||
|
||
class MiniAppReferralTerms(BaseModel):
|
||
minimum_topup_kopeks: int = 0
|
||
minimum_topup_label: str | None = None
|
||
first_topup_bonus_kopeks: int = 0
|
||
first_topup_bonus_label: str | None = None
|
||
inviter_bonus_kopeks: int = 0
|
||
inviter_bonus_label: str | None = 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: str | None = None
|
||
month_earned_kopeks: int = 0
|
||
month_earned_label: str | None = None
|
||
conversion_rate: float = 0.0
|
||
|
||
|
||
class MiniAppReferralRecentEarning(BaseModel):
|
||
amount_kopeks: int = 0
|
||
amount_label: str | None = None
|
||
reason: str | None = None
|
||
referral_name: str | None = None
|
||
created_at: datetime | None = None
|
||
|
||
|
||
class MiniAppReferralItem(BaseModel):
|
||
id: int
|
||
telegram_id: int | None = None
|
||
full_name: str | None = None
|
||
username: str | None = None
|
||
created_at: datetime | None = None
|
||
last_activity: datetime | None = None
|
||
has_made_first_topup: bool = False
|
||
balance_kopeks: int = 0
|
||
balance_label: str | None = None
|
||
total_earned_kopeks: int = 0
|
||
total_earned_label: str | None = None
|
||
topups_count: int = 0
|
||
days_since_registration: int | None = None
|
||
days_since_activity: int | None = None
|
||
status: str | None = 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: str | None = None
|
||
referral_link: str | None = None
|
||
terms: MiniAppReferralTerms | None = None
|
||
stats: MiniAppReferralStats | None = None
|
||
recent_earnings: list[MiniAppReferralRecentEarning] = Field(default_factory=list)
|
||
referrals: MiniAppReferralList | None = None
|
||
|
||
|
||
class MiniAppPaymentMethodsRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
|
||
|
||
class MiniAppPaymentIntegrationType(str, Enum):
|
||
IFRAME = 'iframe'
|
||
REDIRECT = 'redirect'
|
||
|
||
|
||
class MiniAppPaymentOption(BaseModel):
|
||
id: str
|
||
icon: str | None = None
|
||
title: str | None = None
|
||
description: str | None = None
|
||
title_key: str | None = Field(default=None, alias='titleKey')
|
||
description_key: str | None = 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: str | None = None
|
||
icon: str | None = None
|
||
requires_amount: bool = False
|
||
currency: str = 'RUB'
|
||
min_amount_kopeks: int | None = None
|
||
max_amount_kopeks: int | None = None
|
||
amount_step_kopeks: int | None = None
|
||
integration_type: MiniAppPaymentIntegrationType
|
||
options: list[MiniAppPaymentOption] = Field(default_factory=list)
|
||
iframe_config: MiniAppPaymentIframeConfig | None = 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: float | None = Field(default=None, alias='amountRubles')
|
||
amount_kopeks: int | None = Field(default=None, alias='amountKopeks')
|
||
payment_option: str | None = Field(default=None, alias='option')
|
||
|
||
|
||
class MiniAppPaymentCreateResponse(BaseModel):
|
||
success: bool = True
|
||
method: str
|
||
payment_url: str | None = None
|
||
amount_kopeks: int | None = None
|
||
extra: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class MiniAppPaymentStatusQuery(BaseModel):
|
||
method: str
|
||
local_payment_id: int | None = Field(default=None, alias='localPaymentId')
|
||
payment_link_id: str | None = Field(default=None, alias='paymentLinkId')
|
||
invoice_id: str | None = Field(default=None, alias='invoiceId')
|
||
payment_id: str | None = Field(default=None, alias='paymentId')
|
||
payload: str | None = None
|
||
amount_kopeks: int | None = Field(default=None, alias='amountKopeks')
|
||
started_at: str | None = 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: int | None = None
|
||
currency: str | None = None
|
||
completed_at: datetime | None = None
|
||
transaction_id: int | None = None
|
||
external_id: str | None = None
|
||
message: str | None = 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: int | None = None
|
||
label: str
|
||
price_kopeks: int
|
||
price_label: str
|
||
price_per_month_kopeks: int | None = None
|
||
price_per_month_label: str | None = None
|
||
# Скидка промогруппы
|
||
original_price_kopeks: int | None = None # Цена без скидки
|
||
original_price_label: str | None = None
|
||
discount_percent: int = 0 # Процент скидки
|
||
|
||
|
||
class MiniAppTariff(BaseModel):
|
||
"""Тариф для отображения в miniapp."""
|
||
|
||
id: int
|
||
name: str
|
||
description: str | None = 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: int | None = None # Стоимость переключения (None если не в режиме switch)
|
||
switch_cost_label: str | None = None # Форматированная стоимость
|
||
is_upgrade: bool | None = None # True = повышение, False = понижение
|
||
is_switch_free: bool | None = None # True = бесплатное переключение
|
||
# Суточные тарифы
|
||
is_daily: bool = False
|
||
daily_price_kopeks: int = 0
|
||
daily_price_label: str | None = None
|
||
|
||
|
||
class MiniAppTrafficTopupPackage(BaseModel):
|
||
"""Пакет докупки трафика."""
|
||
|
||
gb: int
|
||
price_kopeks: int
|
||
price_label: str
|
||
# Скидка промогруппы на трафик
|
||
original_price_kopeks: int | None = None
|
||
original_price_label: str | None = None
|
||
discount_percent: int = 0
|
||
|
||
|
||
class MiniAppCurrentTariff(BaseModel):
|
||
"""Текущий тариф пользователя."""
|
||
|
||
id: int
|
||
name: str
|
||
description: str | None = 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: int | None = None # Сколько еще можно докупить (None = без лимита)
|
||
# Суточные тарифы
|
||
is_daily: bool = False
|
||
daily_price_kopeks: int = 0
|
||
daily_price_label: str | None = None
|
||
|
||
|
||
class MiniAppTrafficTopupRequest(BaseModel):
|
||
"""Запрос на докупку трафика."""
|
||
|
||
init_data: str = Field(..., alias='initData')
|
||
subscription_id: int | None = 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: MiniAppCurrentTariff | None = None
|
||
balance_kopeks: int = 0
|
||
balance_label: str | None = None
|
||
promo_group: MiniAppPromoGroup | None = 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: str | None = None
|
||
subscription_id: int | None = None
|
||
tariff_id: int | None = None
|
||
tariff_name: str | None = None
|
||
new_end_date: datetime | None = None
|
||
balance_kopeks: int | None = None
|
||
balance_label: str | None = None
|
||
|
||
|
||
class MiniAppTariffSwitchRequest(BaseModel):
|
||
"""Запрос на переключение тарифа (без выбора периода)."""
|
||
|
||
init_data: str = Field(...)
|
||
tariff_id: int = Field(...)
|
||
|
||
|
||
class MiniAppTariffSwitchPreviewResponse(BaseModel):
|
||
"""Предпросмотр переключения тарифа."""
|
||
|
||
can_switch: bool = True
|
||
current_tariff_id: int | None = None
|
||
current_tariff_name: str | None = 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: str | None = None
|
||
|
||
|
||
class MiniAppTariffSwitchResponse(BaseModel):
|
||
"""Ответ на переключение тарифа."""
|
||
|
||
success: bool = True
|
||
message: str | None = 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: str | None = 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: int | None = None
|
||
remnawave_short_uuid: str | None = None
|
||
user: MiniAppSubscriptionUser
|
||
traffic_purchases: list[MiniAppTrafficPurchase] = Field(default_factory=list)
|
||
subscription_url: str | None = None
|
||
hide_subscription_link: bool = False # Скрывать ли отображение ссылки (но кнопки работают)
|
||
subscription_crypto_link: str | None = None
|
||
subscription_purchase_url: str | None = 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: dict[str, Any] | None = None
|
||
happ_link: str | None = None
|
||
happ_crypto_link: str | None = None
|
||
happ_cryptolink_redirect_link: str | None = None
|
||
happ_cryptolink_redirect_template: str | None = None
|
||
balance_kopeks: int = 0
|
||
balance_rubles: float = 0.0
|
||
balance_currency: str | None = None
|
||
transactions: list[MiniAppTransaction] = Field(default_factory=list)
|
||
promo_offers: list[MiniAppPromoOffer] = Field(default_factory=list)
|
||
promo_group: MiniAppPromoGroup | None = 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: str | None = None
|
||
subscription_type: str
|
||
autopay_enabled: bool = False
|
||
autopay_days_before: int | None = None
|
||
autopay_days_options: list[int] = Field(default_factory=list)
|
||
autopay: MiniAppSubscriptionAutopay | None = None
|
||
autopay_settings: MiniAppSubscriptionAutopay | None = None
|
||
branding: MiniAppBranding | None = None
|
||
faq: MiniAppFaq | None = None
|
||
legal_documents: MiniAppLegalDocuments | None = None
|
||
referral: MiniAppReferralInfo | None = None
|
||
subscription_missing: bool = False
|
||
subscription_missing_reason: str | None = None
|
||
trial_available: bool = False
|
||
trial_duration_days: int | None = None
|
||
trial_status: str | None = None
|
||
trial_payment_required: bool = Field(default=False, alias='trialPaymentRequired')
|
||
trial_price_kopeks: int | None = Field(default=None, alias='trialPriceKopeks')
|
||
trial_price_label: str | None = Field(default=None, alias='trialPriceLabel')
|
||
|
||
# Режим продаж и тариф
|
||
sales_mode: str = Field(default='classic', alias='salesMode')
|
||
current_tariff: MiniAppCurrentTariff | None = Field(default=None, alias='currentTariff')
|
||
|
||
model_config = ConfigDict(extra='allow', populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionServerOption(BaseModel):
|
||
uuid: str
|
||
name: str | None = None
|
||
price_kopeks: int | None = None
|
||
price_label: str | None = None
|
||
discount_percent: int | None = None
|
||
is_connected: bool = False
|
||
is_available: bool = True
|
||
disabled_reason: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionTrafficOption(BaseModel):
|
||
value: int | None = None
|
||
label: str | None = None
|
||
price_kopeks: int | None = None
|
||
price_label: str | None = None
|
||
is_current: bool = False
|
||
is_available: bool = True
|
||
description: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionDeviceOption(BaseModel):
|
||
value: int
|
||
label: str | None = None
|
||
price_kopeks: int | None = None
|
||
price_label: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionCurrentSettings(BaseModel):
|
||
servers: list[MiniAppConnectedServer] = Field(default_factory=list)
|
||
traffic_limit_gb: int | None = None
|
||
traffic_limit_label: str | None = 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: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionTrafficSettings(BaseModel):
|
||
options: list[MiniAppSubscriptionTrafficOption] = Field(default_factory=list)
|
||
can_update: bool = True
|
||
current_value: int | None = 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: int | None = None
|
||
price_label: str | None = None
|
||
|
||
|
||
class MiniAppSubscriptionBillingContext(BaseModel):
|
||
months_remaining: int = 1
|
||
period_hint_days: int | None = None
|
||
renews_at: datetime | None = None
|
||
|
||
|
||
class MiniAppSubscriptionSettings(BaseModel):
|
||
subscription_id: int
|
||
currency: str = 'RUB'
|
||
current: MiniAppSubscriptionCurrentSettings
|
||
servers: MiniAppSubscriptionServersSettings
|
||
traffic: MiniAppSubscriptionTrafficSettings
|
||
devices: MiniAppSubscriptionDevicesSettings
|
||
billing: MiniAppSubscriptionBillingContext | None = None
|
||
|
||
|
||
class MiniAppSubscriptionSettingsResponse(BaseModel):
|
||
success: bool = True
|
||
settings: MiniAppSubscriptionSettings
|
||
|
||
|
||
class MiniAppSubscriptionSettingsRequest(BaseModel):
|
||
init_data: str = Field(..., alias='initData')
|
||
subscription_id: int | None = 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: int | None = None
|
||
servers: list[str] | None = None
|
||
squads: list[str] | None = None
|
||
server_uuids: list[str] | None = None
|
||
squad_uuids: list[str] | None = 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: int | None = None
|
||
traffic: int | None = None
|
||
traffic_gb: int | None = 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: int | None = None
|
||
devices: int | None = None
|
||
device_limit: int | None = 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: str | None = 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: int | None = Field(default=None, alias='balanceKopeks')
|
||
balance_label: str | None = Field(default=None, alias='balanceLabel')
|
||
subscription_id: int | None = 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: int | None = Field(default=None, alias='subscriptionId')
|
||
selection: dict[str, Any] | None = None
|
||
period_id: str | None = Field(default=None, alias='periodId')
|
||
period_days: int | None = Field(default=None, alias='periodDays')
|
||
period: str | None = None
|
||
traffic_value: int | None = Field(default=None, alias='trafficValue')
|
||
traffic: int | None = None
|
||
traffic_gb: int | None = Field(default=None, alias='trafficGb')
|
||
servers: list[str] | None = None
|
||
countries: list[str] | None = None
|
||
server_uuids: list[str] | None = Field(default=None, alias='serverUuids')
|
||
devices: int | None = None
|
||
device_limit: int | None = 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: int | None = Field(default=None, alias='balanceKopeks')
|
||
balance_label: str | None = Field(default=None, alias='balanceLabel')
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
|
||
class MiniAppSubscriptionPurchaseRequest(MiniAppSubscriptionPurchasePreviewRequest):
|
||
pass
|
||
|
||
|
||
class MiniAppSubscriptionPurchaseResponse(BaseModel):
|
||
success: bool = True
|
||
message: str | None = None
|
||
balance_kopeks: int | None = Field(default=None, alias='balanceKopeks')
|
||
balance_label: str | None = Field(default=None, alias='balanceLabel')
|
||
subscription_id: int | None = 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: str | None = None
|
||
subscription_id: int | None = Field(default=None, alias='subscriptionId')
|
||
trial_status: str | None = Field(default=None, alias='trialStatus')
|
||
trial_duration_days: int | None = Field(default=None, alias='trialDurationDays')
|
||
charged_amount_kopeks: int | None = Field(default=None, alias='chargedAmountKopeks')
|
||
charged_amount_label: str | None = Field(default=None, alias='chargedAmountLabel')
|
||
balance_kopeks: int | None = Field(default=None, alias='balanceKopeks')
|
||
balance_label: str | None = Field(default=None, alias='balanceLabel')
|
||
|
||
model_config = ConfigDict(populate_by_name=True)
|