mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
Renewal flow (per-subscription): - New /subscriptions/:id/renew page showing only period options for the specific tariff with balance display and renew button - PurchaseCTAButton links to /renew in multi-tariff mode Purchase flow (new tariffs only): - Hide already purchased tariffs (is_purchased flag from backend) - Fallback UI when all tariffs are owned - Types: added is_purchased, all_tariffs_purchased fields
743 lines
17 KiB
TypeScript
743 lines
17 KiB
TypeScript
// User types
|
|
export interface User {
|
|
id: number;
|
|
telegram_id: number | null; // Nullable для email-only пользователей
|
|
username: string | null;
|
|
first_name: string | null;
|
|
last_name: string | null;
|
|
email: string | null;
|
|
email_verified: boolean;
|
|
balance_kopeks: number;
|
|
balance_rubles: number;
|
|
referral_code: string | null;
|
|
language: string;
|
|
created_at: string;
|
|
auth_type: 'telegram' | 'email' | 'google' | 'yandex' | 'discord' | 'vk'; // Тип аутентификации
|
|
}
|
|
|
|
// OAuth types
|
|
export interface OAuthProvider {
|
|
name: string;
|
|
display_name: string;
|
|
}
|
|
|
|
// Campaign bonus info (returned during auth)
|
|
export interface CampaignBonusInfo {
|
|
campaign_name: string;
|
|
bonus_type: 'balance' | 'subscription' | 'tariff' | 'none';
|
|
balance_kopeks: number;
|
|
subscription_days: number | null;
|
|
tariff_name: string | null;
|
|
}
|
|
|
|
// Auth types
|
|
export interface AuthResponse {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
token_type: string;
|
|
expires_in: number;
|
|
user: User;
|
|
campaign_bonus?: CampaignBonusInfo | null;
|
|
}
|
|
|
|
export interface TokenResponse {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
token_type: string;
|
|
expires_in: number;
|
|
}
|
|
|
|
export interface RegisterResponse {
|
|
message: string;
|
|
email: string;
|
|
requires_verification: boolean;
|
|
}
|
|
|
|
// Subscription types
|
|
export interface ServerInfo {
|
|
uuid: string;
|
|
name: string;
|
|
country_code: string | null;
|
|
}
|
|
|
|
export interface TrafficPurchase {
|
|
id: number;
|
|
traffic_gb: number;
|
|
expires_at: string;
|
|
created_at: string;
|
|
days_remaining: number;
|
|
progress_percent: number;
|
|
}
|
|
|
|
export interface Subscription {
|
|
id: number;
|
|
status: string;
|
|
is_trial: boolean;
|
|
start_date: string;
|
|
end_date: string;
|
|
days_left: number;
|
|
hours_left: number;
|
|
minutes_left: number;
|
|
time_left_display: string;
|
|
traffic_limit_gb: number;
|
|
traffic_used_gb: number;
|
|
traffic_used_percent: number;
|
|
device_limit: number;
|
|
connected_squads: string[];
|
|
servers: ServerInfo[];
|
|
autopay_enabled: boolean;
|
|
autopay_days_before: number;
|
|
subscription_url: string | null;
|
|
hide_subscription_link: boolean;
|
|
is_active: boolean;
|
|
is_expired: boolean;
|
|
is_limited: boolean;
|
|
traffic_purchases?: TrafficPurchase[];
|
|
// Daily tariff fields
|
|
is_daily?: boolean;
|
|
is_daily_paused?: boolean;
|
|
daily_price_kopeks?: number;
|
|
next_daily_charge_at?: string; // ISO datetime string
|
|
// Tariff info
|
|
tariff_id?: number;
|
|
tariff_name?: string;
|
|
traffic_reset_mode?: string;
|
|
}
|
|
|
|
// Response wrapper for subscription status endpoint
|
|
export interface SubscriptionStatusResponse {
|
|
has_subscription: boolean;
|
|
subscription: Subscription | null;
|
|
}
|
|
|
|
// Multi-tariff subscription list item (from GET /cabinet/subscriptions)
|
|
export interface SubscriptionListItem {
|
|
id: number;
|
|
status: string;
|
|
tariff_id: number | null;
|
|
tariff_name: string | null;
|
|
traffic_limit_gb: number;
|
|
traffic_used_gb: number;
|
|
device_limit: number;
|
|
end_date: string | null;
|
|
subscription_url: string | null;
|
|
subscription_crypto_link: string | null;
|
|
is_trial: boolean;
|
|
autopay_enabled: boolean;
|
|
connected_squads: string[] | null;
|
|
}
|
|
|
|
// Response from GET /cabinet/subscriptions (multi-tariff)
|
|
export interface SubscriptionsListResponse {
|
|
subscriptions: SubscriptionListItem[];
|
|
multi_tariff_enabled: boolean;
|
|
}
|
|
|
|
// Device types
|
|
export interface Device {
|
|
hwid: string;
|
|
platform: string;
|
|
device_model: string;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface DevicesResponse {
|
|
devices: Device[];
|
|
total: number;
|
|
device_limit: number;
|
|
}
|
|
|
|
// Tariff switch preview
|
|
export interface TariffSwitchPreview {
|
|
can_switch: boolean;
|
|
current_tariff_id: number | null;
|
|
current_tariff_name: string | null;
|
|
new_tariff_id: number;
|
|
new_tariff_name: string;
|
|
remaining_days: number;
|
|
upgrade_cost_kopeks: number;
|
|
upgrade_cost_label: string;
|
|
balance_kopeks: number;
|
|
balance_label: string;
|
|
has_enough_balance: boolean;
|
|
missing_amount_kopeks: number;
|
|
missing_amount_label: string;
|
|
is_upgrade: boolean;
|
|
// Discount fields (from promo group)
|
|
base_upgrade_cost_kopeks?: number;
|
|
discount_percent?: number;
|
|
discount_kopeks?: number;
|
|
}
|
|
|
|
export interface RenewalOption {
|
|
period_days: number;
|
|
price_kopeks: number;
|
|
price_rubles: number;
|
|
discount_percent: number;
|
|
original_price_kopeks: number | null;
|
|
}
|
|
|
|
export interface TrafficPackage {
|
|
gb: number;
|
|
price_kopeks: number;
|
|
price_rubles: number;
|
|
is_unlimited: boolean;
|
|
// Discount fields (from promo group)
|
|
base_price_kopeks?: number;
|
|
discount_percent?: number;
|
|
discount_kopeks?: number;
|
|
}
|
|
|
|
export interface TrialInfo {
|
|
is_available: boolean;
|
|
duration_days: number;
|
|
traffic_limit_gb: number;
|
|
device_limit: number;
|
|
requires_payment: boolean;
|
|
price_kopeks: number;
|
|
price_rubles: number;
|
|
reason_unavailable: string | null;
|
|
}
|
|
|
|
// Purchase options types
|
|
export interface TrafficOption {
|
|
value: number;
|
|
label: string;
|
|
price_kopeks: number;
|
|
price_label: string;
|
|
original_price_kopeks?: number;
|
|
original_price_label?: string;
|
|
discount_percent?: number;
|
|
is_available: boolean;
|
|
is_default?: boolean;
|
|
}
|
|
|
|
export interface ServerOption {
|
|
uuid: string;
|
|
name: string;
|
|
price_kopeks: number;
|
|
price_label: string;
|
|
original_price_kopeks?: number;
|
|
original_price_label?: string;
|
|
discount_percent?: number;
|
|
is_available: boolean;
|
|
}
|
|
|
|
export interface DevicesConfig {
|
|
min: number;
|
|
max: number;
|
|
default: number;
|
|
current: number;
|
|
price_per_device_kopeks: number;
|
|
price_per_device_label: string;
|
|
price_per_device_original_kopeks?: number;
|
|
discount_percent?: number;
|
|
}
|
|
|
|
export interface TrafficConfig {
|
|
selectable: boolean;
|
|
mode: string;
|
|
options: TrafficOption[];
|
|
default?: number;
|
|
current?: number;
|
|
}
|
|
|
|
export interface ServersConfig {
|
|
options: ServerOption[];
|
|
min: number;
|
|
max: number;
|
|
default: string[];
|
|
selected: string[];
|
|
}
|
|
|
|
export interface PeriodOption {
|
|
id: string;
|
|
period_days: number;
|
|
months: number;
|
|
label: string;
|
|
price_kopeks: number;
|
|
price_label: string;
|
|
per_month_price_kopeks: number;
|
|
per_month_price_label: string;
|
|
discount_percent?: number;
|
|
original_price_kopeks?: number;
|
|
original_price_label?: string;
|
|
is_available: boolean;
|
|
traffic: TrafficConfig;
|
|
servers: ServersConfig;
|
|
devices: DevicesConfig;
|
|
}
|
|
|
|
// Tariff types for tariffs mode
|
|
export interface TariffPeriod {
|
|
days: number;
|
|
months: number;
|
|
label: string;
|
|
price_kopeks: number;
|
|
price_label: string;
|
|
price_per_month_kopeks: number;
|
|
price_per_month_label: string;
|
|
// Discount info (if promo group discount applied)
|
|
original_price_kopeks?: number;
|
|
original_price_label?: string;
|
|
original_per_month_kopeks?: number;
|
|
original_per_month_label?: string;
|
|
discount_percent?: number;
|
|
discount_amount_kopeks?: number;
|
|
discount_label?: string;
|
|
// Extra devices info (additional devices beyond tariff base)
|
|
extra_devices_count?: number;
|
|
extra_devices_cost_kopeks?: number;
|
|
extra_devices_cost_label?: string;
|
|
base_tariff_price_kopeks?: number;
|
|
base_tariff_price_label?: string;
|
|
}
|
|
|
|
export interface TariffServer {
|
|
uuid: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface Tariff {
|
|
id: number;
|
|
name: string;
|
|
description: string | null;
|
|
tier_level: number;
|
|
traffic_limit_gb: number;
|
|
traffic_limit_label: string;
|
|
is_unlimited_traffic: boolean;
|
|
device_limit: number;
|
|
base_device_limit?: number;
|
|
extra_devices_count: number;
|
|
servers_count: number;
|
|
servers: TariffServer[];
|
|
periods: TariffPeriod[];
|
|
is_current: boolean;
|
|
is_available: boolean;
|
|
// Custom days options
|
|
custom_days_enabled?: boolean;
|
|
price_per_day_kopeks?: number;
|
|
min_days?: number;
|
|
max_days?: number;
|
|
// Custom traffic options
|
|
custom_traffic_enabled?: boolean;
|
|
traffic_price_per_gb_kopeks?: number;
|
|
min_traffic_gb?: number;
|
|
max_traffic_gb?: number;
|
|
// Device price
|
|
device_price_kopeks?: number;
|
|
// Traffic topup options
|
|
traffic_topup_enabled?: boolean;
|
|
traffic_topup_packages?: number[];
|
|
max_topup_traffic_gb?: number;
|
|
// Daily tariff options
|
|
is_daily?: boolean;
|
|
daily_price_kopeks?: number;
|
|
// Promo group discount info
|
|
promo_group_name?: string;
|
|
original_device_price_kopeks?: number;
|
|
device_discount_percent?: number;
|
|
original_daily_price_kopeks?: number;
|
|
daily_discount_percent?: number;
|
|
original_price_per_day_kopeks?: number;
|
|
custom_days_discount_percent?: number;
|
|
// Traffic reset
|
|
traffic_reset_mode?: string;
|
|
// Multi-tariff: already purchased by user
|
|
is_purchased?: boolean;
|
|
}
|
|
|
|
export interface TariffsPurchaseOptions {
|
|
sales_mode: 'tariffs';
|
|
tariffs: Tariff[];
|
|
current_tariff_id: number | null;
|
|
balance_kopeks: number;
|
|
balance_label: string;
|
|
// New fields for expired subscription handling
|
|
subscription_status?: string;
|
|
subscription_is_expired?: boolean;
|
|
has_subscription?: boolean;
|
|
// Multi-tariff: all available tariffs already purchased
|
|
all_tariffs_purchased?: boolean;
|
|
}
|
|
|
|
export interface ClassicPurchaseOptions {
|
|
sales_mode: 'classic';
|
|
currency: string;
|
|
balance_kopeks: number;
|
|
balance_label: string;
|
|
subscription_id: number | null;
|
|
periods: PeriodOption[];
|
|
traffic: TrafficConfig;
|
|
servers: ServersConfig;
|
|
devices: DevicesConfig;
|
|
selection: {
|
|
period_id: string;
|
|
period_days: number;
|
|
traffic_value: number;
|
|
servers: string[];
|
|
devices: number;
|
|
};
|
|
}
|
|
|
|
export type PurchaseOptions = TariffsPurchaseOptions | ClassicPurchaseOptions;
|
|
|
|
export interface PurchaseSelection {
|
|
period_id?: string;
|
|
period_days?: number;
|
|
traffic_value?: number;
|
|
servers?: string[];
|
|
devices?: number;
|
|
}
|
|
|
|
export interface PurchasePreview {
|
|
total_price_kopeks: number;
|
|
total_price_label: string;
|
|
original_price_kopeks?: number;
|
|
original_price_label?: string;
|
|
discount_percent?: number;
|
|
discount_label?: string;
|
|
per_month_price_kopeks: number;
|
|
per_month_price_label: string;
|
|
breakdown: { label: string; value: string }[];
|
|
balance_kopeks: number;
|
|
balance_label: string;
|
|
missing_amount_kopeks: number;
|
|
missing_amount_label?: string;
|
|
can_purchase: boolean;
|
|
status_message?: string;
|
|
}
|
|
|
|
// Balance types
|
|
export interface Balance {
|
|
balance_kopeks: number;
|
|
balance_rubles: number;
|
|
}
|
|
|
|
export interface Transaction {
|
|
id: number;
|
|
type: string;
|
|
amount_kopeks: number;
|
|
amount_rubles: number;
|
|
description: string | null;
|
|
payment_method: string | null;
|
|
is_completed: boolean;
|
|
created_at: string;
|
|
completed_at: string | null;
|
|
}
|
|
|
|
export interface PaymentMethodOption {
|
|
id: string;
|
|
name: string;
|
|
description?: string | null;
|
|
}
|
|
|
|
export interface PaymentMethod {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
min_amount_kopeks: number;
|
|
max_amount_kopeks: number;
|
|
is_available: boolean;
|
|
options?: PaymentMethodOption[] | null;
|
|
}
|
|
|
|
// Referral types
|
|
export interface ReferralInfo {
|
|
referral_code: string;
|
|
referral_link: string;
|
|
bot_referral_link?: string;
|
|
total_referrals: number;
|
|
active_referrals: number;
|
|
total_earnings_kopeks: number;
|
|
total_earnings_rubles: number;
|
|
commission_percent: number;
|
|
available_balance_kopeks: number;
|
|
available_balance_rubles: number;
|
|
withdrawn_kopeks: number;
|
|
}
|
|
|
|
export interface ReferralTerms {
|
|
is_enabled: boolean;
|
|
commission_percent: number;
|
|
minimum_topup_kopeks: number;
|
|
minimum_topup_rubles: number;
|
|
first_topup_bonus_kopeks: number;
|
|
first_topup_bonus_rubles: number;
|
|
inviter_bonus_kopeks: number;
|
|
inviter_bonus_rubles: number;
|
|
max_commission_payments: number;
|
|
partner_section_visible?: boolean;
|
|
}
|
|
|
|
// Ticket types
|
|
export interface TicketMessage {
|
|
id: number;
|
|
message_text: string;
|
|
is_from_admin: boolean;
|
|
has_media: boolean;
|
|
media_type: string | null;
|
|
media_file_id: string | null;
|
|
media_caption: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Ticket {
|
|
id: number;
|
|
title: string;
|
|
status: string;
|
|
priority: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
closed_at: string | null;
|
|
messages_count: number;
|
|
last_message: TicketMessage | null;
|
|
}
|
|
|
|
export interface TicketDetail extends Omit<Ticket, 'messages_count' | 'last_message'> {
|
|
is_reply_blocked: boolean;
|
|
messages: TicketMessage[];
|
|
}
|
|
|
|
export interface SupportConfig {
|
|
tickets_enabled: boolean;
|
|
support_type: 'tickets' | 'profile' | 'url' | 'both';
|
|
support_url?: string | null;
|
|
support_username?: string | null;
|
|
}
|
|
|
|
// Paginated response
|
|
export interface PaginatedResponse<T> {
|
|
items: T[];
|
|
total: number;
|
|
page: number;
|
|
per_page: number;
|
|
pages: number;
|
|
}
|
|
|
|
// App config types (for connection setup)
|
|
export interface LocalizedText {
|
|
[key: string]: string;
|
|
}
|
|
|
|
// RemnaWave format types
|
|
export interface RemnawaveButtonClient {
|
|
url?: string;
|
|
link?: string;
|
|
text: LocalizedText;
|
|
type?: 'external' | 'subscriptionLink' | 'copyButton';
|
|
svgIconKey?: string;
|
|
resolvedUrl?: string;
|
|
}
|
|
|
|
export interface RemnawaveBlockClient {
|
|
title: LocalizedText;
|
|
description: LocalizedText;
|
|
buttons?: RemnawaveButtonClient[];
|
|
svgIconKey?: string;
|
|
svgIconColor?: string;
|
|
}
|
|
|
|
export interface RemnawaveAppClient {
|
|
name: string;
|
|
featured?: boolean;
|
|
deepLink?: string | null;
|
|
svgIconKey?: string;
|
|
blocks: RemnawaveBlockClient[];
|
|
}
|
|
|
|
export interface RemnawavePlatformData {
|
|
svgIconKey?: string;
|
|
displayName?: LocalizedText;
|
|
apps: RemnawaveAppClient[];
|
|
}
|
|
|
|
export interface AppConfig {
|
|
platformNames: Record<string, LocalizedText>;
|
|
hasSubscription: boolean;
|
|
subscriptionUrl: string | null;
|
|
hideLink?: boolean;
|
|
branding?: {
|
|
name?: string;
|
|
logoUrl?: string;
|
|
supportUrl?: string;
|
|
};
|
|
|
|
// RemnaWave
|
|
isRemnawave?: boolean;
|
|
svgLibrary?: Record<string, string | { svgString: string }>;
|
|
baseTranslations?: Record<string, LocalizedText>;
|
|
baseSettings?: { isShowTutorialButton: boolean; tutorialUrl: string };
|
|
uiConfig?: {
|
|
installationGuidesBlockType?: 'cards' | 'timeline' | 'accordion' | 'minimal';
|
|
};
|
|
|
|
platforms: Record<string, RemnawavePlatformData>;
|
|
}
|
|
|
|
// Pending payment types
|
|
export interface PendingPayment {
|
|
id: number;
|
|
method: string;
|
|
method_display: string;
|
|
identifier: string;
|
|
amount_kopeks: number;
|
|
amount_rubles: number;
|
|
status: string;
|
|
status_emoji: string;
|
|
status_text: string;
|
|
is_paid: boolean;
|
|
is_checkable: boolean;
|
|
created_at: string;
|
|
expires_at: string | null;
|
|
payment_url: string | null;
|
|
user_id?: number;
|
|
user_telegram_id?: number;
|
|
user_username?: string | null;
|
|
user_email?: string | null;
|
|
}
|
|
|
|
export interface ManualCheckResponse {
|
|
success: boolean;
|
|
message: string;
|
|
payment: PendingPayment | null;
|
|
status_changed: boolean;
|
|
old_status: string | null;
|
|
new_status: string | null;
|
|
}
|
|
|
|
// Saved payment method (card) for recurrent payments
|
|
export interface SavedCard {
|
|
id: number;
|
|
method_type: string;
|
|
card_last4: string | null;
|
|
card_type: string | null;
|
|
title: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface SavedCardsResponse {
|
|
cards: SavedCard[];
|
|
recurrent_enabled: boolean;
|
|
}
|
|
|
|
// Ticket notifications types
|
|
export interface TicketNotification {
|
|
id: number;
|
|
ticket_id: number;
|
|
notification_type: 'new_ticket' | 'admin_reply' | 'user_reply';
|
|
message: string | null;
|
|
is_read: boolean;
|
|
created_at: string;
|
|
read_at: string | null;
|
|
}
|
|
|
|
export interface TicketNotificationList {
|
|
items: TicketNotification[];
|
|
unread_count: number;
|
|
}
|
|
|
|
export interface UnreadCountResponse {
|
|
unread_count: number;
|
|
}
|
|
|
|
export interface TicketSettings {
|
|
sla_enabled: boolean;
|
|
sla_minutes: number;
|
|
sla_check_interval_seconds: number;
|
|
sla_reminder_cooldown_minutes: number;
|
|
support_system_mode: string;
|
|
cabinet_user_notifications_enabled: boolean;
|
|
cabinet_admin_notifications_enabled: boolean;
|
|
}
|
|
|
|
// Payment method config types (admin)
|
|
export interface PaymentMethodSubOptionInfo {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface PaymentMethodConfig {
|
|
method_id: string;
|
|
sort_order: number;
|
|
is_enabled: boolean;
|
|
display_name: string | null;
|
|
default_display_name: string;
|
|
sub_options: Record<string, boolean> | null;
|
|
available_sub_options: PaymentMethodSubOptionInfo[] | null;
|
|
min_amount_kopeks: number | null;
|
|
max_amount_kopeks: number | null;
|
|
default_min_amount_kopeks: number;
|
|
default_max_amount_kopeks: number;
|
|
user_type_filter: 'all' | 'telegram' | 'email';
|
|
first_topup_filter: 'any' | 'yes' | 'no';
|
|
promo_group_filter_mode: 'all' | 'selected';
|
|
allowed_promo_group_ids: number[];
|
|
is_provider_configured: boolean;
|
|
created_at: string | null;
|
|
updated_at: string | null;
|
|
}
|
|
|
|
export interface PromoGroupSimple {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
// Account Linking
|
|
export interface LinkedProvider {
|
|
provider: string;
|
|
linked: boolean;
|
|
identifier: string | null;
|
|
}
|
|
|
|
export interface LinkedProvidersResponse {
|
|
providers: LinkedProvider[];
|
|
}
|
|
|
|
export interface LinkCallbackResponse {
|
|
success: boolean;
|
|
message: string | null;
|
|
merge_required: boolean;
|
|
merge_token: string | null;
|
|
}
|
|
|
|
export interface ServerCompleteResponse extends LinkCallbackResponse {
|
|
provider: string;
|
|
}
|
|
|
|
// Account Merge
|
|
export interface MergeSubscriptionPreview {
|
|
status: string;
|
|
is_trial: boolean;
|
|
end_date: string | null;
|
|
traffic_limit_gb: number;
|
|
traffic_used_gb: number;
|
|
device_limit: number;
|
|
tariff_name: string | null;
|
|
autopay_enabled: boolean;
|
|
}
|
|
|
|
export interface MergeAccountPreview {
|
|
id: number;
|
|
username: string | null;
|
|
first_name: string | null;
|
|
email: string | null;
|
|
auth_methods: string[];
|
|
balance_kopeks: number;
|
|
subscription: MergeSubscriptionPreview | null;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface MergePreviewResponse {
|
|
primary: MergeAccountPreview;
|
|
secondary: MergeAccountPreview;
|
|
expires_in_seconds: number;
|
|
}
|
|
|
|
export interface MergeResponse {
|
|
success: boolean;
|
|
access_token: string | null;
|
|
refresh_token: string | null;
|
|
user: User | null;
|
|
}
|