mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-21 03:40:55 +00:00
Revert "Require confirmation before paid miniapp subscription updates"
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict, model_validator
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class MiniAppBranding(BaseModel):
|
||||
@@ -354,187 +354,3 @@ class MiniAppSubscriptionResponse(BaseModel):
|
||||
legal_documents: Optional[MiniAppLegalDocuments] = None
|
||||
referral: Optional[MiniAppReferralInfo] = None
|
||||
|
||||
|
||||
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 MiniAppSubscriptionSettings(BaseModel):
|
||||
subscription_id: int
|
||||
currency: str = "RUB"
|
||||
current: MiniAppSubscriptionCurrentSettings
|
||||
servers: MiniAppSubscriptionServersSettings
|
||||
traffic: MiniAppSubscriptionTrafficSettings
|
||||
devices: MiniAppSubscriptionDevicesSettings
|
||||
|
||||
|
||||
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
|
||||
confirm: Optional[bool] = 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",
|
||||
"confirmed": "confirm",
|
||||
"confirmation": "confirm",
|
||||
"confirmAction": "confirm",
|
||||
}
|
||||
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
|
||||
confirm: Optional[bool] = 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",
|
||||
"confirmed": "confirm",
|
||||
"confirmation": "confirm",
|
||||
"confirmAction": "confirm",
|
||||
}
|
||||
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
|
||||
confirm: Optional[bool] = 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",
|
||||
"confirmed": "confirm",
|
||||
"confirmation": "confirm",
|
||||
"confirmAction": "confirm",
|
||||
}
|
||||
for alias, target in alias_map.items():
|
||||
if alias in values and target not in values:
|
||||
values[target] = values[alias]
|
||||
return values
|
||||
|
||||
|
||||
class MiniAppSubscriptionUpdateConfirmation(BaseModel):
|
||||
title: Dict[str, str] = Field(default_factory=dict)
|
||||
message: Dict[str, str] = Field(default_factory=dict)
|
||||
confirm_label: Dict[str, str] = Field(default_factory=dict)
|
||||
cancel_label: Dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class MiniAppSubscriptionUpdateResponse(BaseModel):
|
||||
success: bool = True
|
||||
message: Optional[str] = None
|
||||
confirmation_required: bool = False
|
||||
confirmation: Optional[MiniAppSubscriptionUpdateConfirmation] = None
|
||||
|
||||
|
||||
@@ -3579,20 +3579,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop hidden" id="confirmationModal">
|
||||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="confirmationModalTitle">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="confirmationModalTitle"></div>
|
||||
<div class="modal-subtitle" id="confirmationModalSubtitle"></div>
|
||||
</div>
|
||||
<div class="modal-body" id="confirmationModalBody"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="modal-button secondary" type="button" id="confirmationModalCancel"></button>
|
||||
<button class="modal-button primary" type="button" id="confirmationModalConfirm"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card promo-code-card" id="promoCodeCard">
|
||||
<div class="promo-code-header">
|
||||
<div class="promo-code-title" data-i18n="promo_code.title">Activate promo code</div>
|
||||
@@ -3946,8 +3932,6 @@
|
||||
'button.connect.happ': 'Connect',
|
||||
'button.copy': 'Copy subscription link',
|
||||
'button.topup_balance': 'Top up balance',
|
||||
'button.confirm': 'Confirm',
|
||||
'button.cancel': 'Cancel',
|
||||
'topup.title': 'Top up balance',
|
||||
'topup.subtitle': 'Choose a payment method',
|
||||
'topup.methods.subtitle': 'Select how you want to pay',
|
||||
@@ -4211,8 +4195,6 @@
|
||||
'button.connect.happ': 'Подключиться',
|
||||
'button.copy': 'Скопировать ссылку подписки',
|
||||
'button.topup_balance': 'Пополнить баланс',
|
||||
'button.confirm': 'Подтвердить',
|
||||
'button.cancel': 'Отмена',
|
||||
'topup.title': 'Пополнение баланса',
|
||||
'topup.subtitle': 'Выберите способ оплаты',
|
||||
'topup.methods.subtitle': 'Выберите удобный способ оплаты',
|
||||
@@ -6848,32 +6830,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLocalizedText(source, fallback = '') {
|
||||
if (!source) {
|
||||
return fallback || '';
|
||||
}
|
||||
if (typeof source === 'string') {
|
||||
return source;
|
||||
}
|
||||
if (typeof source === 'object') {
|
||||
const language = (preferredLanguage || '').toLowerCase();
|
||||
if (language && typeof source[language] === 'string' && source[language].trim()) {
|
||||
return source[language];
|
||||
}
|
||||
if (language.startsWith('ru') && typeof source.ru === 'string' && source.ru.trim()) {
|
||||
return source.ru;
|
||||
}
|
||||
if (language.startsWith('en') && typeof source.en === 'string' && source.en.trim()) {
|
||||
return source.en;
|
||||
}
|
||||
const values = Object.values(source).filter(value => typeof value === 'string' && value.trim());
|
||||
if (values.length) {
|
||||
return values[0];
|
||||
}
|
||||
}
|
||||
return fallback || '';
|
||||
}
|
||||
|
||||
function formatLegalUpdatedLabel(value) {
|
||||
const formatted = formatDateTime(value);
|
||||
if (!formatted) {
|
||||
@@ -6912,108 +6868,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
function getConfirmationElements() {
|
||||
return {
|
||||
backdrop: document.getElementById('confirmationModal'),
|
||||
title: document.getElementById('confirmationModalTitle'),
|
||||
subtitle: document.getElementById('confirmationModalSubtitle'),
|
||||
body: document.getElementById('confirmationModalBody'),
|
||||
confirm: document.getElementById('confirmationModalConfirm'),
|
||||
cancel: document.getElementById('confirmationModalCancel'),
|
||||
};
|
||||
}
|
||||
|
||||
function closeConfirmationModal() {
|
||||
const { backdrop, body, subtitle, confirm, cancel } = getConfirmationElements();
|
||||
if (backdrop) {
|
||||
backdrop.classList.add('hidden');
|
||||
}
|
||||
document.body.classList.remove('modal-open');
|
||||
if (body) {
|
||||
body.innerHTML = '';
|
||||
}
|
||||
if (subtitle) {
|
||||
subtitle.textContent = '';
|
||||
}
|
||||
if (confirm) {
|
||||
confirm.disabled = false;
|
||||
}
|
||||
if (cancel) {
|
||||
cancel.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showSubscriptionConfirmationDialog(confirmation) {
|
||||
const elements = getConfirmationElements();
|
||||
const { backdrop, title, subtitle, body, confirm, cancel } = elements;
|
||||
if (!backdrop || !confirm || !cancel || !body) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const titleText = resolveLocalizedText(confirmation?.title, t('subscription_settings.title'));
|
||||
if (title) {
|
||||
title.textContent = titleText || t('subscription_settings.title');
|
||||
}
|
||||
|
||||
const subtitleText = resolveLocalizedText(confirmation?.subtitle || confirmation?.hint, '');
|
||||
if (subtitle) {
|
||||
subtitle.textContent = subtitleText;
|
||||
subtitle.classList.toggle('hidden', !subtitleText);
|
||||
}
|
||||
|
||||
const message = resolveLocalizedText(confirmation?.message, '');
|
||||
body.innerHTML = '';
|
||||
if (message) {
|
||||
const lines = String(message).split('\n');
|
||||
lines.forEach(line => {
|
||||
if (!line.trim()) {
|
||||
return;
|
||||
}
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.textContent = line.trim();
|
||||
body.appendChild(paragraph);
|
||||
});
|
||||
}
|
||||
|
||||
const confirmLabel = resolveLocalizedText(confirmation?.confirm_label, t('button.confirm') || 'Confirm');
|
||||
const cancelLabel = resolveLocalizedText(confirmation?.cancel_label, t('button.cancel') || t('topup.cancel') || 'Cancel');
|
||||
|
||||
confirm.textContent = confirmLabel;
|
||||
cancel.textContent = cancelLabel;
|
||||
|
||||
backdrop.classList.remove('hidden');
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
return new Promise(resolve => {
|
||||
const cleanup = () => {
|
||||
confirm.removeEventListener('click', handleConfirm);
|
||||
cancel.removeEventListener('click', handleCancel);
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
closeConfirmationModal();
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
cleanup();
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
cleanup();
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
const handleKeydown = event => {
|
||||
if (event.key === 'Escape') {
|
||||
handleCancel();
|
||||
}
|
||||
};
|
||||
|
||||
confirm.addEventListener('click', handleConfirm);
|
||||
cancel.addEventListener('click', handleCancel);
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
}
|
||||
|
||||
function setTopupModalTitle(key, fallback) {
|
||||
const { title } = getTopupElements();
|
||||
if (!title) {
|
||||
@@ -9883,8 +9737,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function submitSubscriptionServersChange(options = {}) {
|
||||
const { confirm = false } = options;
|
||||
async function submitSubscriptionServersChange() {
|
||||
if (subscriptionSettingsAction) {
|
||||
return;
|
||||
}
|
||||
@@ -9927,10 +9780,6 @@
|
||||
server_uuids: selected,
|
||||
subscription_id: data.subscriptionId || userData?.subscription_id || userData?.subscriptionId || null,
|
||||
};
|
||||
if (confirm) {
|
||||
payload.confirm = true;
|
||||
payload.confirmed = true;
|
||||
}
|
||||
|
||||
subscriptionSettingsAction = 'servers';
|
||||
setSubscriptionSettingsActionLoading('servers', true);
|
||||
@@ -9942,25 +9791,7 @@
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const body = await parseJsonSafe(response);
|
||||
if (!response.ok) {
|
||||
const message = extractSettingsError(body, response.status);
|
||||
throw createError('Subscription settings error', message, response.status);
|
||||
}
|
||||
|
||||
const confirmationRequired = coerceBoolean(
|
||||
body?.confirmation_required ?? body?.confirmationRequired ?? body?.needConfirmation,
|
||||
false
|
||||
);
|
||||
if (confirmationRequired && !confirm) {
|
||||
const confirmationData = body?.confirmation ?? body?.confirmationData ?? null;
|
||||
const proceed = await showSubscriptionConfirmationDialog(confirmationData || {});
|
||||
if (proceed) {
|
||||
await submitSubscriptionServersChange({ confirm: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (body && body.success === false) {
|
||||
if (!response.ok || (body && body.success === false)) {
|
||||
const message = extractSettingsError(body, response.status);
|
||||
throw createError('Subscription settings error', message, response.status);
|
||||
}
|
||||
@@ -9976,8 +9807,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function submitSubscriptionTrafficChange(options = {}) {
|
||||
const { confirm = false } = options;
|
||||
async function submitSubscriptionTrafficChange() {
|
||||
if (subscriptionSettingsAction) {
|
||||
return;
|
||||
}
|
||||
@@ -10014,10 +9844,6 @@
|
||||
trafficGb: selected,
|
||||
subscription_id: data.subscriptionId || userData?.subscription_id || userData?.subscriptionId || null,
|
||||
};
|
||||
if (confirm) {
|
||||
payload.confirm = true;
|
||||
payload.confirmed = true;
|
||||
}
|
||||
|
||||
subscriptionSettingsAction = 'traffic';
|
||||
setSubscriptionSettingsActionLoading('traffic', true);
|
||||
@@ -10029,25 +9855,7 @@
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const body = await parseJsonSafe(response);
|
||||
if (!response.ok) {
|
||||
const message = extractSettingsError(body, response.status);
|
||||
throw createError('Subscription settings error', message, response.status);
|
||||
}
|
||||
|
||||
const confirmationRequired = coerceBoolean(
|
||||
body?.confirmation_required ?? body?.confirmationRequired ?? body?.needConfirmation,
|
||||
false
|
||||
);
|
||||
if (confirmationRequired && !confirm) {
|
||||
const confirmationData = body?.confirmation ?? body?.confirmationData ?? null;
|
||||
const proceed = await showSubscriptionConfirmationDialog(confirmationData || {});
|
||||
if (proceed) {
|
||||
await submitSubscriptionTrafficChange({ confirm: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (body && body.success === false) {
|
||||
if (!response.ok || (body && body.success === false)) {
|
||||
const message = extractSettingsError(body, response.status);
|
||||
throw createError('Subscription settings error', message, response.status);
|
||||
}
|
||||
@@ -10063,8 +9871,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function submitSubscriptionDevicesChange(options = {}) {
|
||||
const { confirm = false } = options;
|
||||
async function submitSubscriptionDevicesChange() {
|
||||
if (subscriptionSettingsAction) {
|
||||
return;
|
||||
}
|
||||
@@ -10108,10 +9915,6 @@
|
||||
deviceLimit: selected,
|
||||
subscription_id: data.subscriptionId || userData?.subscription_id || userData?.subscriptionId || null,
|
||||
};
|
||||
if (confirm) {
|
||||
payload.confirm = true;
|
||||
payload.confirmed = true;
|
||||
}
|
||||
|
||||
subscriptionSettingsAction = 'devices';
|
||||
setSubscriptionSettingsActionLoading('devices', true);
|
||||
@@ -10123,25 +9926,7 @@
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const body = await parseJsonSafe(response);
|
||||
if (!response.ok) {
|
||||
const message = extractSettingsError(body, response.status);
|
||||
throw createError('Subscription settings error', message, response.status);
|
||||
}
|
||||
|
||||
const confirmationRequired = coerceBoolean(
|
||||
body?.confirmation_required ?? body?.confirmationRequired ?? body?.needConfirmation,
|
||||
false
|
||||
);
|
||||
if (confirmationRequired && !confirm) {
|
||||
const confirmationData = body?.confirmation ?? body?.confirmationData ?? null;
|
||||
const proceed = await showSubscriptionConfirmationDialog(confirmationData || {});
|
||||
if (proceed) {
|
||||
await submitSubscriptionDevicesChange({ confirm: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (body && body.success === false) {
|
||||
if (!response.ok || (body && body.success === false)) {
|
||||
const message = extractSettingsError(body, response.status);
|
||||
throw createError('Subscription settings error', message, response.status);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user