mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-22 12:21:26 +00:00
Revert "Ensure mini app autopay shows all day options"
This commit is contained in:
@@ -37,7 +37,6 @@ from app.database.crud.subscription import (
|
||||
create_trial_subscription,
|
||||
extend_subscription,
|
||||
remove_subscription_servers,
|
||||
update_subscription_autopay,
|
||||
)
|
||||
from app.database.crud.transaction import (
|
||||
create_transaction,
|
||||
@@ -151,9 +150,6 @@ from ..schemas.miniapp import (
|
||||
MiniAppSubscriptionPurchaseResponse,
|
||||
MiniAppSubscriptionTrialRequest,
|
||||
MiniAppSubscriptionTrialResponse,
|
||||
MiniAppSubscriptionAutopay,
|
||||
MiniAppSubscriptionAutopayRequest,
|
||||
MiniAppSubscriptionAutopayResponse,
|
||||
MiniAppSubscriptionRenewalOptionsRequest,
|
||||
MiniAppSubscriptionRenewalOptionsResponse,
|
||||
MiniAppSubscriptionRenewalPeriod,
|
||||
@@ -202,104 +198,6 @@ _PAYMENT_FAILURE_STATUSES = {
|
||||
_PERIOD_ID_PATTERN = re.compile(r"(\d+)")
|
||||
|
||||
|
||||
_AUTOPAY_DEFAULT_DAY_OPTIONS = (1, 3, 7, 14)
|
||||
|
||||
|
||||
def _normalize_autopay_days(value: Optional[Any]) -> Optional[int]:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
numeric = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return numeric if numeric > 0 else None
|
||||
|
||||
|
||||
def _get_autopay_day_options(subscription: Optional[Subscription]) -> List[int]:
|
||||
options: set[int] = set()
|
||||
for candidate in _AUTOPAY_DEFAULT_DAY_OPTIONS:
|
||||
normalized = _normalize_autopay_days(candidate)
|
||||
if normalized is not None:
|
||||
options.add(normalized)
|
||||
|
||||
default_setting = _normalize_autopay_days(
|
||||
getattr(settings, "DEFAULT_AUTOPAY_DAYS_BEFORE", None)
|
||||
)
|
||||
if default_setting is not None:
|
||||
options.add(default_setting)
|
||||
|
||||
if subscription is not None:
|
||||
current = _normalize_autopay_days(
|
||||
getattr(subscription, "autopay_days_before", None)
|
||||
)
|
||||
if current is not None:
|
||||
options.add(current)
|
||||
|
||||
return sorted(options)
|
||||
|
||||
|
||||
def _build_autopay_payload(
|
||||
subscription: Optional[Subscription],
|
||||
) -> Optional[MiniAppSubscriptionAutopay]:
|
||||
if subscription is None:
|
||||
return None
|
||||
|
||||
enabled = bool(getattr(subscription, "autopay_enabled", False))
|
||||
days_before = _normalize_autopay_days(
|
||||
getattr(subscription, "autopay_days_before", None)
|
||||
)
|
||||
options = _get_autopay_day_options(subscription)
|
||||
|
||||
default_days = days_before
|
||||
if default_days is None:
|
||||
default_days = _normalize_autopay_days(
|
||||
getattr(settings, "DEFAULT_AUTOPAY_DAYS_BEFORE", None)
|
||||
)
|
||||
if default_days is None and options:
|
||||
default_days = options[0]
|
||||
|
||||
autopay_kwargs: Dict[str, Any] = {
|
||||
"enabled": enabled,
|
||||
"autopay_enabled": enabled,
|
||||
"days_before": days_before,
|
||||
"autopay_days_before": days_before,
|
||||
"default_days_before": default_days,
|
||||
"autopay_days_options": options,
|
||||
"days_options": options,
|
||||
"options": options,
|
||||
"available_days": options,
|
||||
"availableDays": options,
|
||||
"autopayEnabled": enabled,
|
||||
"autopayDaysBefore": days_before,
|
||||
"autopayDaysOptions": options,
|
||||
"daysBefore": days_before,
|
||||
"daysOptions": options,
|
||||
"defaultDaysBefore": default_days,
|
||||
}
|
||||
|
||||
return MiniAppSubscriptionAutopay(**autopay_kwargs)
|
||||
|
||||
|
||||
def _autopay_response_extras(
|
||||
enabled: bool,
|
||||
days_before: Optional[int],
|
||||
options: List[int],
|
||||
autopay_payload: Optional[MiniAppSubscriptionAutopay],
|
||||
) -> Dict[str, Any]:
|
||||
extras: Dict[str, Any] = {
|
||||
"autopayEnabled": enabled,
|
||||
"autopayDaysBefore": days_before,
|
||||
"autopayDaysOptions": options,
|
||||
}
|
||||
if days_before is not None:
|
||||
extras["daysBefore"] = days_before
|
||||
if options:
|
||||
extras["daysOptions"] = options
|
||||
if autopay_payload is not None:
|
||||
extras["autopaySettings"] = autopay_payload
|
||||
return extras
|
||||
|
||||
|
||||
async def _get_usd_to_rub_rate() -> float:
|
||||
try:
|
||||
rate = await currency_converter.get_usd_to_rub_rate()
|
||||
@@ -2455,24 +2353,6 @@ async def get_subscription_details(
|
||||
device_limit_value = subscription.device_limit
|
||||
autopay_enabled = bool(subscription.autopay_enabled)
|
||||
|
||||
autopay_payload = _build_autopay_payload(subscription)
|
||||
autopay_days_before = (
|
||||
getattr(autopay_payload, "autopay_days_before", None)
|
||||
if autopay_payload
|
||||
else None
|
||||
)
|
||||
autopay_days_options = (
|
||||
list(getattr(autopay_payload, "autopay_days_options", []) or [])
|
||||
if autopay_payload
|
||||
else []
|
||||
)
|
||||
autopay_extras = _autopay_response_extras(
|
||||
autopay_enabled,
|
||||
autopay_days_before,
|
||||
autopay_days_options,
|
||||
autopay_payload,
|
||||
)
|
||||
|
||||
devices_count, devices = await _load_devices_info(user)
|
||||
|
||||
response_user = MiniAppSubscriptionUser(
|
||||
@@ -2561,10 +2441,6 @@ async def get_subscription_details(
|
||||
else ("paid" if subscription else "none")
|
||||
),
|
||||
autopay_enabled=autopay_enabled,
|
||||
autopay_days_before=autopay_days_before,
|
||||
autopay_days_options=autopay_days_options,
|
||||
autopay=autopay_payload,
|
||||
autopay_settings=autopay_payload,
|
||||
branding=settings.get_miniapp_branding(),
|
||||
faq=faq_payload,
|
||||
legal_documents=legal_documents_payload,
|
||||
@@ -2574,121 +2450,6 @@ async def get_subscription_details(
|
||||
trial_available=trial_available,
|
||||
trial_duration_days=trial_duration_days,
|
||||
trial_status="available" if trial_available else "unavailable",
|
||||
**autopay_extras,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/subscription/autopay",
|
||||
response_model=MiniAppSubscriptionAutopayResponse,
|
||||
)
|
||||
async def update_subscription_autopay_endpoint(
|
||||
payload: MiniAppSubscriptionAutopayRequest,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
) -> MiniAppSubscriptionAutopayResponse:
|
||||
user = await _authorize_miniapp_user(payload.init_data, db)
|
||||
subscription = _ensure_paid_subscription(user)
|
||||
_validate_subscription_id(payload.subscription_id, subscription)
|
||||
|
||||
target_enabled = (
|
||||
bool(payload.enabled)
|
||||
if payload.enabled is not None
|
||||
else bool(subscription.autopay_enabled)
|
||||
)
|
||||
|
||||
requested_days = payload.days_before
|
||||
normalized_days = _normalize_autopay_days(requested_days)
|
||||
current_days = _normalize_autopay_days(
|
||||
getattr(subscription, "autopay_days_before", None)
|
||||
)
|
||||
if normalized_days is None:
|
||||
normalized_days = current_days
|
||||
|
||||
options = _get_autopay_day_options(subscription)
|
||||
default_day = _normalize_autopay_days(
|
||||
getattr(settings, "DEFAULT_AUTOPAY_DAYS_BEFORE", None)
|
||||
)
|
||||
if default_day is None and options:
|
||||
default_day = options[0]
|
||||
|
||||
if target_enabled and normalized_days is None:
|
||||
if default_day is None:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "autopay_no_days",
|
||||
"message": "Auto-pay day selection is temporarily unavailable",
|
||||
},
|
||||
)
|
||||
normalized_days = default_day
|
||||
|
||||
if normalized_days is None:
|
||||
normalized_days = default_day or (options[0] if options else 1)
|
||||
|
||||
if (
|
||||
bool(subscription.autopay_enabled) == target_enabled
|
||||
and current_days == normalized_days
|
||||
):
|
||||
autopay_payload = _build_autopay_payload(subscription)
|
||||
autopay_days_before = (
|
||||
getattr(autopay_payload, "autopay_days_before", None)
|
||||
if autopay_payload
|
||||
else None
|
||||
)
|
||||
autopay_days_options = (
|
||||
list(getattr(autopay_payload, "autopay_days_options", []) or [])
|
||||
if autopay_payload
|
||||
else options
|
||||
)
|
||||
extras = _autopay_response_extras(
|
||||
target_enabled,
|
||||
autopay_days_before,
|
||||
autopay_days_options,
|
||||
autopay_payload,
|
||||
)
|
||||
return MiniAppSubscriptionAutopayResponse(
|
||||
subscription_id=subscription.id,
|
||||
autopay_enabled=target_enabled,
|
||||
autopay_days_before=autopay_days_before,
|
||||
autopay_days_options=autopay_days_options,
|
||||
autopay=autopay_payload,
|
||||
autopay_settings=autopay_payload,
|
||||
**extras,
|
||||
)
|
||||
|
||||
updated_subscription = await update_subscription_autopay(
|
||||
db,
|
||||
subscription,
|
||||
target_enabled,
|
||||
normalized_days,
|
||||
)
|
||||
|
||||
autopay_payload = _build_autopay_payload(updated_subscription)
|
||||
autopay_days_before = (
|
||||
getattr(autopay_payload, "autopay_days_before", None)
|
||||
if autopay_payload
|
||||
else None
|
||||
)
|
||||
autopay_days_options = (
|
||||
list(getattr(autopay_payload, "autopay_days_options", []) or [])
|
||||
if autopay_payload
|
||||
else _get_autopay_day_options(updated_subscription)
|
||||
)
|
||||
extras = _autopay_response_extras(
|
||||
bool(updated_subscription.autopay_enabled),
|
||||
autopay_days_before,
|
||||
autopay_days_options,
|
||||
autopay_payload,
|
||||
)
|
||||
|
||||
return MiniAppSubscriptionAutopayResponse(
|
||||
subscription_id=updated_subscription.id,
|
||||
autopay_enabled=bool(updated_subscription.autopay_enabled),
|
||||
autopay_days_before=autopay_days_before,
|
||||
autopay_days_options=autopay_days_options,
|
||||
autopay=autopay_payload,
|
||||
autopay_settings=autopay_payload,
|
||||
**extras,
|
||||
)
|
||||
|
||||
|
||||
@@ -3831,24 +3592,6 @@ async def get_subscription_renewal_options_endpoint(
|
||||
if isinstance(final_total, int) and balance_kopeks < final_total:
|
||||
missing_amount = final_total - balance_kopeks
|
||||
|
||||
renewal_autopay_payload = _build_autopay_payload(subscription)
|
||||
renewal_autopay_days_before = (
|
||||
getattr(renewal_autopay_payload, "autopay_days_before", None)
|
||||
if renewal_autopay_payload
|
||||
else None
|
||||
)
|
||||
renewal_autopay_days_options = (
|
||||
list(getattr(renewal_autopay_payload, "autopay_days_options", []) or [])
|
||||
if renewal_autopay_payload
|
||||
else []
|
||||
)
|
||||
renewal_autopay_extras = _autopay_response_extras(
|
||||
bool(subscription.autopay_enabled),
|
||||
renewal_autopay_days_before,
|
||||
renewal_autopay_days_options,
|
||||
renewal_autopay_payload,
|
||||
)
|
||||
|
||||
return MiniAppSubscriptionRenewalOptionsResponse(
|
||||
subscription_id=subscription.id,
|
||||
currency=currency,
|
||||
@@ -3860,12 +3603,6 @@ async def get_subscription_renewal_options_endpoint(
|
||||
default_period_id=default_period_id,
|
||||
missing_amount_kopeks=missing_amount,
|
||||
status_message=_build_renewal_status_message(user),
|
||||
autopay_enabled=bool(subscription.autopay_enabled),
|
||||
autopay_days_before=renewal_autopay_days_before,
|
||||
autopay_days_options=renewal_autopay_days_options,
|
||||
autopay=renewal_autopay_payload,
|
||||
autopay_settings=renewal_autopay_payload,
|
||||
**renewal_autopay_extras,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -134,20 +134,6 @@ class MiniAppPromoOfferClaimResponse(BaseModel):
|
||||
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
|
||||
@@ -186,13 +172,8 @@ class MiniAppSubscriptionRenewalOptionsResponse(BaseModel):
|
||||
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
|
||||
|
||||
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
|
||||
class MiniAppSubscriptionRenewalRequest(BaseModel):
|
||||
@@ -215,27 +196,6 @@ class MiniAppSubscriptionRenewalResponse(BaseModel):
|
||||
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
|
||||
@@ -451,10 +411,6 @@ class MiniAppSubscriptionResponse(BaseModel):
|
||||
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
|
||||
@@ -465,8 +421,6 @@ class MiniAppSubscriptionResponse(BaseModel):
|
||||
trial_duration_days: Optional[int] = None
|
||||
trial_status: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class MiniAppSubscriptionServerOption(BaseModel):
|
||||
uuid: str
|
||||
|
||||
@@ -1524,149 +1524,6 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.subscription-autopay-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.subscription-autopay-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.subscription-autopay-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.subscription-autopay-description {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.subscription-autopay-status {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.subscription-autopay-status::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.subscription-autopay-status.enabled {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.subscription-autopay-status.disabled {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.subscription-autopay-status.saving {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.subscription-autopay-status.loading {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.subscription-autopay-toggle-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.subscription-autopay-toggle {
|
||||
flex: 1;
|
||||
padding: 14px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.subscription-autopay-toggle:hover:not(:disabled) {
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.subscription-autopay-toggle.active {
|
||||
background: linear-gradient(135deg, var(--primary), rgba(var(--primary-rgb), 0.85));
|
||||
color: var(--tg-theme-button-text-color);
|
||||
border-color: transparent;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.subscription-autopay-toggle:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.subscription-autopay-days {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.subscription-autopay-days-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.subscription-autopay-days-options {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
margin-right: -8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.subscription-autopay-days-options::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subscription-autopay-days-options .subscription-settings-toggle {
|
||||
min-width: 140px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.subscription-autopay-hint {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .subscription-autopay-section {
|
||||
border-color: rgba(148, 163, 184, 0.2);
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
}
|
||||
|
||||
.subscription-renewal-summary-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -4847,24 +4704,6 @@
|
||||
<div class="subscription-renewal-price-note hidden" id="subscriptionRenewalPriceNote"></div>
|
||||
<div class="subscription-renewal-balance-warning hidden" id="subscriptionRenewalBalanceWarning"></div>
|
||||
</div>
|
||||
<div class="subscription-autopay-section" id="subscriptionAutopaySection">
|
||||
<div class="subscription-autopay-header">
|
||||
<div>
|
||||
<div class="subscription-autopay-title" data-i18n="subscription_autopay.title">Автоплатеж</div>
|
||||
<div class="subscription-autopay-description" data-i18n="subscription_autopay.subtitle">Автоматически продлеваем подписку перед окончанием срока.</div>
|
||||
</div>
|
||||
<div class="subscription-autopay-status loading" id="subscriptionAutopayStatus">Loading…</div>
|
||||
</div>
|
||||
<div class="subscription-autopay-days hidden" id="subscriptionAutopayDays">
|
||||
<div class="subscription-autopay-days-title" data-i18n="subscription_autopay.days.title">За сколько списывать</div>
|
||||
<div class="subscription-autopay-days-options" id="subscriptionAutopayDaysOptions"></div>
|
||||
</div>
|
||||
<div class="subscription-autopay-hint hidden" id="subscriptionAutopayHint"></div>
|
||||
<div class="subscription-autopay-toggle-group">
|
||||
<button class="subscription-autopay-toggle" id="subscriptionAutopayEnable" type="button" data-i18n="subscription_autopay.action.enable">Включить</button>
|
||||
<button class="subscription-autopay-toggle" id="subscriptionAutopayDisable" type="button" data-i18n="subscription_autopay.action.disable">Отключить</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subscription-renewal-actions">
|
||||
<button class="btn btn-primary" id="subscriptionRenewalSubmit" type="button" data-i18n="subscription_renewal.submit">Продлить</button>
|
||||
</div>
|
||||
@@ -5746,24 +5585,6 @@
|
||||
'trial.activation.error.already_active': 'You already have an active subscription.',
|
||||
'autopay.enabled': 'Enabled',
|
||||
'autopay.disabled': 'Disabled',
|
||||
'subscription_autopay.title': 'Auto-pay',
|
||||
'subscription_autopay.subtitle': 'Automatically renew your subscription before it expires.',
|
||||
'subscription_autopay.status.enabled': 'Enabled',
|
||||
'subscription_autopay.status.disabled': 'Disabled',
|
||||
'subscription_autopay.status.loading': 'Loading…',
|
||||
'subscription_autopay.action.enable': 'Enable',
|
||||
'subscription_autopay.action.disable': 'Disable',
|
||||
'subscription_autopay.days.title': 'Charge before expiry',
|
||||
'subscription_autopay.day.same_day': 'On renewal day',
|
||||
'subscription_autopay.day.before.one': '{count} day before',
|
||||
'subscription_autopay.day.before.few': '{count} days before',
|
||||
'subscription_autopay.day.before.many': '{count} days before',
|
||||
'subscription_autopay.saving': 'Saving…',
|
||||
'subscription_autopay.hint.disabled': 'Enable auto-pay to choose when to charge your balance.',
|
||||
'subscription_autopay.hint.no_options': 'Auto-pay day selection is temporarily unavailable.',
|
||||
'subscription_autopay.error.generic': 'Failed to update auto-pay settings. Please try again later.',
|
||||
'subscription_autopay.error.unauthorized': 'Authorization failed. Please reopen the mini app from Telegram.',
|
||||
'subscription_autopay.error.no_days': 'Select a charge day to enable auto-pay.',
|
||||
'platform.ios': 'iOS',
|
||||
'platform.android': 'Android',
|
||||
'platform.pc': 'PC',
|
||||
@@ -6141,24 +5962,6 @@
|
||||
'trial.activation.error.already_active': 'У вас уже есть активная подписка.',
|
||||
'autopay.enabled': 'Включен',
|
||||
'autopay.disabled': 'Выключен',
|
||||
'subscription_autopay.title': 'Автоплатеж',
|
||||
'subscription_autopay.subtitle': 'Автоматически продлеваем подписку перед окончанием срока.',
|
||||
'subscription_autopay.status.enabled': 'Включен',
|
||||
'subscription_autopay.status.disabled': 'Выключен',
|
||||
'subscription_autopay.status.loading': 'Загружаем…',
|
||||
'subscription_autopay.action.enable': 'Включить',
|
||||
'subscription_autopay.action.disable': 'Отключить',
|
||||
'subscription_autopay.days.title': 'За сколько списывать',
|
||||
'subscription_autopay.day.same_day': 'В день окончания',
|
||||
'subscription_autopay.day.before.one': '{count} день до списания',
|
||||
'subscription_autopay.day.before.few': '{count} дня до списания',
|
||||
'subscription_autopay.day.before.many': '{count} дней до списания',
|
||||
'subscription_autopay.saving': 'Сохраняем…',
|
||||
'subscription_autopay.hint.disabled': 'Включите автоплатеж, чтобы выбрать день списания.',
|
||||
'subscription_autopay.hint.no_options': 'Выбор дня списания временно недоступен.',
|
||||
'subscription_autopay.error.generic': 'Не удалось обновить настройки автоплатежа. Попробуйте позже.',
|
||||
'subscription_autopay.error.unauthorized': 'Ошибка авторизации. Откройте мини-приложение из Telegram и повторите попытку.',
|
||||
'subscription_autopay.error.no_days': 'Выберите день списания, чтобы включить автоплатеж.',
|
||||
'platform.ios': 'iOS',
|
||||
'platform.android': 'Android',
|
||||
'platform.pc': 'ПК',
|
||||
@@ -6353,15 +6156,6 @@
|
||||
periodId: null,
|
||||
};
|
||||
|
||||
let subscriptionAutopayState = {
|
||||
enabled: false,
|
||||
daysBefore: null,
|
||||
defaultDaysBefore: null,
|
||||
options: [],
|
||||
loading: true,
|
||||
saving: false,
|
||||
};
|
||||
|
||||
let trialActivationInProgress = false;
|
||||
|
||||
const PAYMENT_STATUS_INITIAL_DELAY_MS = 2000;
|
||||
@@ -7394,31 +7188,6 @@
|
||||
userData.subscriptionCryptoLink = userData.subscription_crypto_link || null;
|
||||
userData.referral = userData.referral || null;
|
||||
|
||||
if (hasPaidSubscription()) {
|
||||
subscriptionAutopayState.loading = true;
|
||||
subscriptionAutopayState.saving = false;
|
||||
subscriptionAutopayState.defaultDaysBefore = null;
|
||||
subscriptionAutopayState.options = [];
|
||||
ingestAutopayData(
|
||||
payload,
|
||||
payload?.autopay,
|
||||
payload?.autopay_settings,
|
||||
payload?.autopaySettings,
|
||||
payload?.subscription_renewal,
|
||||
payload?.subscriptionRenewal,
|
||||
payload?.subscription_renewal?.autopay,
|
||||
payload?.subscriptionRenewal?.autopay,
|
||||
);
|
||||
} else {
|
||||
subscriptionAutopayState.enabled = false;
|
||||
subscriptionAutopayState.daysBefore = null;
|
||||
subscriptionAutopayState.defaultDaysBefore = null;
|
||||
subscriptionAutopayState.options = [];
|
||||
subscriptionAutopayState.loading = false;
|
||||
subscriptionAutopayState.saving = false;
|
||||
renderSubscriptionAutopay();
|
||||
}
|
||||
|
||||
resetSubscriptionRenewalState(null);
|
||||
prepareSubscriptionRenewalFromUserData();
|
||||
|
||||
@@ -8974,566 +8743,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePluralForm(count, language = preferredLanguage) {
|
||||
const normalizedLang = (language || preferredLanguage || 'en').split('-')[0].toLowerCase();
|
||||
if (normalizedLang === 'ru') {
|
||||
const mod10 = count % 10;
|
||||
const mod100 = count % 100;
|
||||
if (mod10 === 1 && mod100 !== 11) {
|
||||
return 'one';
|
||||
}
|
||||
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'many';
|
||||
}
|
||||
return count === 1 ? 'one' : 'many';
|
||||
}
|
||||
|
||||
function formatAutopayDayLabel(days) {
|
||||
const value = coercePositiveInt(days, null);
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
if (value === 0) {
|
||||
const sameDayKey = 'subscription_autopay.day.same_day';
|
||||
const sameDay = t(sameDayKey);
|
||||
if (sameDay && sameDay !== sameDayKey) {
|
||||
return sameDay;
|
||||
}
|
||||
return preferredLanguage === 'ru' ? 'В день окончания' : 'On renewal day';
|
||||
}
|
||||
const pluralKey = resolvePluralForm(value);
|
||||
const key = `subscription_autopay.day.before.${pluralKey}`;
|
||||
const template = t(key);
|
||||
if (template && template !== key) {
|
||||
return template.replace('{count}', String(value));
|
||||
}
|
||||
if (preferredLanguage === 'ru') {
|
||||
if (pluralKey === 'one') {
|
||||
return `${value} день до списания`;
|
||||
}
|
||||
if (pluralKey === 'few') {
|
||||
return `${value} дня до списания`;
|
||||
}
|
||||
return `${value} дней до списания`;
|
||||
}
|
||||
return pluralKey === 'one'
|
||||
? `${value} day before`
|
||||
: `${value} days before`;
|
||||
}
|
||||
|
||||
function renderSubscriptionAutopay() {
|
||||
const section = document.getElementById('subscriptionAutopaySection');
|
||||
if (!section) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldShow = hasPaidSubscription();
|
||||
section.classList.toggle('hidden', !shouldShow);
|
||||
if (!shouldShow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statusElement = document.getElementById('subscriptionAutopayStatus');
|
||||
const enableButton = document.getElementById('subscriptionAutopayEnable');
|
||||
const disableButton = document.getElementById('subscriptionAutopayDisable');
|
||||
const daysContainer = document.getElementById('subscriptionAutopayDays');
|
||||
const optionsContainer = document.getElementById('subscriptionAutopayDaysOptions');
|
||||
const hintElement = document.getElementById('subscriptionAutopayHint');
|
||||
|
||||
const enabled = Boolean(subscriptionAutopayState.enabled);
|
||||
const saving = Boolean(subscriptionAutopayState.saving);
|
||||
const loading = Boolean(subscriptionAutopayState.loading);
|
||||
const options = Array.isArray(subscriptionAutopayState.options)
|
||||
? subscriptionAutopayState.options.slice().sort((a, b) => a - b)
|
||||
: [];
|
||||
const hasOptions = options.length > 0;
|
||||
|
||||
if (statusElement) {
|
||||
if (saving) {
|
||||
const savingKey = 'subscription_autopay.saving';
|
||||
const savingText = t(savingKey);
|
||||
statusElement.textContent = savingText && savingText !== savingKey ? savingText : 'Saving…';
|
||||
statusElement.className = 'subscription-autopay-status saving';
|
||||
} else if (loading) {
|
||||
const loadingKey = 'subscription_autopay.status.loading';
|
||||
const loadingText = t(loadingKey);
|
||||
statusElement.textContent = loadingText && loadingText !== loadingKey ? loadingText : 'Loading…';
|
||||
statusElement.className = 'subscription-autopay-status loading';
|
||||
} else {
|
||||
const key = enabled
|
||||
? 'subscription_autopay.status.enabled'
|
||||
: 'subscription_autopay.status.disabled';
|
||||
const label = t(key);
|
||||
statusElement.textContent = label && label !== key
|
||||
? label
|
||||
: (enabled ? 'Enabled' : 'Disabled');
|
||||
statusElement.className = `subscription-autopay-status ${enabled ? 'enabled' : 'disabled'}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (enableButton) {
|
||||
enableButton.disabled = saving || loading || enabled;
|
||||
enableButton.classList.toggle('active', enabled && !loading);
|
||||
}
|
||||
|
||||
if (disableButton) {
|
||||
disableButton.disabled = saving || loading || !enabled;
|
||||
disableButton.classList.toggle('active', !enabled && !loading);
|
||||
}
|
||||
|
||||
if (daysContainer) {
|
||||
daysContainer.classList.toggle('hidden', !enabled || !hasOptions);
|
||||
}
|
||||
|
||||
if (hintElement) {
|
||||
if (!enabled) {
|
||||
const hintKey = 'subscription_autopay.hint.disabled';
|
||||
const hint = t(hintKey);
|
||||
hintElement.textContent = hint && hint !== hintKey
|
||||
? hint
|
||||
: (preferredLanguage === 'ru'
|
||||
? 'Включите автоплатеж, чтобы выбрать день списания.'
|
||||
: 'Enable auto-pay to choose when to charge your balance.');
|
||||
hintElement.classList.remove('hidden');
|
||||
} else if (enabled && !hasOptions && !loading) {
|
||||
const hintKey = 'subscription_autopay.hint.no_options';
|
||||
const hint = t(hintKey);
|
||||
hintElement.textContent = hint && hint !== hintKey
|
||||
? hint
|
||||
: (preferredLanguage === 'ru'
|
||||
? 'Выбор дня списания временно недоступен.'
|
||||
: 'Auto-pay day selection is temporarily unavailable.');
|
||||
hintElement.classList.remove('hidden');
|
||||
} else {
|
||||
hintElement.classList.add('hidden');
|
||||
hintElement.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (optionsContainer) {
|
||||
optionsContainer.innerHTML = '';
|
||||
if (enabled && hasOptions) {
|
||||
options.forEach(value => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'subscription-settings-toggle';
|
||||
button.dataset.autopayDays = String(value);
|
||||
button.disabled = saving;
|
||||
|
||||
if (subscriptionAutopayState.daysBefore === value) {
|
||||
button.classList.add('active');
|
||||
}
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'subscription-settings-toggle-label';
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'subscription-settings-toggle-title';
|
||||
title.textContent = formatAutopayDayLabel(value);
|
||||
|
||||
label.appendChild(title);
|
||||
button.appendChild(label);
|
||||
optionsContainer.appendChild(button);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAutopayPayload(raw) {
|
||||
if (!raw || typeof raw !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enabledCandidate = raw.autopay_enabled
|
||||
?? raw.enabled
|
||||
?? raw.is_enabled
|
||||
?? raw.active
|
||||
?? null;
|
||||
let enabledValue;
|
||||
if (typeof enabledCandidate === 'boolean') {
|
||||
enabledValue = enabledCandidate;
|
||||
} else if (enabledCandidate != null) {
|
||||
enabledValue = coerceBoolean(enabledCandidate, undefined);
|
||||
}
|
||||
|
||||
const daysCandidate = raw.autopay_days_before
|
||||
?? raw.days_before
|
||||
?? raw.daysBefore
|
||||
?? raw.days
|
||||
?? raw.value
|
||||
?? null;
|
||||
const daysBefore = daysCandidate != null
|
||||
? coercePositiveInt(daysCandidate, null)
|
||||
: null;
|
||||
|
||||
const optionSet = new Set();
|
||||
const optionSources = [
|
||||
raw.autopay_days_options,
|
||||
raw.autopayDaysOptions,
|
||||
raw.days_options,
|
||||
raw.daysOptions,
|
||||
raw.available_days,
|
||||
raw.availableDays,
|
||||
];
|
||||
|
||||
optionSources.forEach(source => {
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
const normalized = Array.isArray(source)
|
||||
? source
|
||||
: (typeof source === 'object' ? Object.values(source) : [source]);
|
||||
normalized.forEach(item => {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (typeof item === 'object') {
|
||||
const candidate = item.days_before
|
||||
?? item.daysBefore
|
||||
?? item.value
|
||||
?? item.days
|
||||
?? item.amount
|
||||
?? null;
|
||||
const numeric = candidate != null ? coercePositiveInt(candidate, null) : null;
|
||||
if (numeric !== null) {
|
||||
optionSet.add(numeric);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const numeric = coercePositiveInt(item, null);
|
||||
if (numeric !== null) {
|
||||
optionSet.add(numeric);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const options = Array.from(optionSet).sort((a, b) => a - b);
|
||||
if (daysBefore !== null && !optionSet.has(daysBefore)) {
|
||||
options.push(daysBefore);
|
||||
options.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: typeof enabledValue === 'boolean' ? enabledValue : undefined,
|
||||
daysBefore: daysBefore !== null ? daysBefore : null,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_AUTOPAY_DAY_OPTIONS = [1, 3, 7, 14];
|
||||
|
||||
function mergeAutopaySources(...sources) {
|
||||
let hasData = false;
|
||||
let enabledValue;
|
||||
let daysValue = null;
|
||||
const optionSet = new Set();
|
||||
|
||||
DEFAULT_AUTOPAY_DAY_OPTIONS.forEach(value => {
|
||||
const numeric = coercePositiveInt(value, null);
|
||||
if (numeric !== null) {
|
||||
optionSet.add(numeric);
|
||||
}
|
||||
});
|
||||
|
||||
sources.forEach(source => {
|
||||
const normalized = normalizeAutopayPayload(source);
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
hasData = true;
|
||||
if (typeof normalized.enabled === 'boolean') {
|
||||
enabledValue = normalized.enabled;
|
||||
}
|
||||
if (normalized.daysBefore !== null && normalized.daysBefore !== undefined) {
|
||||
daysValue = normalized.daysBefore;
|
||||
}
|
||||
normalized.options.forEach(value => optionSet.add(value));
|
||||
});
|
||||
|
||||
if (!hasData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (daysValue !== null && daysValue !== undefined) {
|
||||
optionSet.add(daysValue);
|
||||
}
|
||||
|
||||
const options = Array.from(optionSet).sort((a, b) => a - b);
|
||||
let resolvedDays = daysValue;
|
||||
if ((resolvedDays === null || resolvedDays === undefined) && options.length) {
|
||||
resolvedDays = options[0];
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: enabledValue,
|
||||
daysBefore: resolvedDays,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
function ingestAutopayData(...sources) {
|
||||
const candidates = sources.filter(Boolean);
|
||||
if (!candidates.length) {
|
||||
subscriptionAutopayState.loading = false;
|
||||
renderSubscriptionAutopay();
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = mergeAutopaySources(...candidates);
|
||||
if (!normalized) {
|
||||
subscriptionAutopayState.loading = false;
|
||||
renderSubscriptionAutopay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof normalized.enabled === 'boolean') {
|
||||
subscriptionAutopayState.enabled = normalized.enabled;
|
||||
}
|
||||
|
||||
if (normalized.daysBefore !== null && normalized.daysBefore !== undefined) {
|
||||
subscriptionAutopayState.daysBefore = normalized.daysBefore;
|
||||
if (subscriptionAutopayState.defaultDaysBefore === null || subscriptionAutopayState.defaultDaysBefore === undefined) {
|
||||
subscriptionAutopayState.defaultDaysBefore = normalized.daysBefore;
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionAutopayState.options = Array.isArray(normalized.options)
|
||||
? normalized.options.slice().sort((a, b) => a - b)
|
||||
: [];
|
||||
|
||||
if ((subscriptionAutopayState.daysBefore === null || subscriptionAutopayState.daysBefore === undefined)
|
||||
&& subscriptionAutopayState.options.length) {
|
||||
subscriptionAutopayState.daysBefore = subscriptionAutopayState.options[0];
|
||||
}
|
||||
|
||||
subscriptionAutopayState.loading = false;
|
||||
renderSubscriptionAutopay();
|
||||
}
|
||||
|
||||
function extractAutopayError(payload, status) {
|
||||
if (status === 401) {
|
||||
return t('subscription_autopay.error.unauthorized');
|
||||
}
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return t('subscription_autopay.error.generic');
|
||||
}
|
||||
if (typeof payload.detail === 'string') {
|
||||
return payload.detail;
|
||||
}
|
||||
if (payload.detail && typeof payload.detail === 'object') {
|
||||
if (typeof payload.detail.message === 'string') {
|
||||
return payload.detail.message;
|
||||
}
|
||||
if (typeof payload.detail.error === 'string') {
|
||||
return payload.detail.error;
|
||||
}
|
||||
}
|
||||
if (typeof payload.message === 'string') {
|
||||
return payload.message;
|
||||
}
|
||||
if (typeof payload.error === 'string') {
|
||||
return payload.error;
|
||||
}
|
||||
return t('subscription_autopay.error.generic');
|
||||
}
|
||||
|
||||
function resolveAutopayErrorMessage(error, fallbackKey = 'subscription_autopay.error.generic') {
|
||||
if (!error) {
|
||||
return t(fallbackKey);
|
||||
}
|
||||
if (typeof error === 'string') {
|
||||
return error;
|
||||
}
|
||||
if (typeof error.message === 'string' && error.message.trim()) {
|
||||
return error.message;
|
||||
}
|
||||
if (error.detail) {
|
||||
if (typeof error.detail === 'string' && error.detail.trim()) {
|
||||
return error.detail;
|
||||
}
|
||||
if (typeof error.detail.message === 'string' && error.detail.message.trim()) {
|
||||
return error.detail.message;
|
||||
}
|
||||
}
|
||||
if (error.status === 401) {
|
||||
return t('subscription_autopay.error.unauthorized');
|
||||
}
|
||||
return t(fallbackKey);
|
||||
}
|
||||
|
||||
function handleAutopayError(error) {
|
||||
const message = resolveAutopayErrorMessage(error);
|
||||
const titleKey = 'subscription_autopay.title';
|
||||
const title = t(titleKey);
|
||||
showPopup(message, title && title !== titleKey ? title : 'Auto-pay');
|
||||
}
|
||||
|
||||
async function submitAutopaySettingsChange(changes = {}) {
|
||||
if (subscriptionAutopayState.saving || subscriptionAutopayState.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasPaidSubscription()) {
|
||||
handleAutopayError(createError('Auto-pay', t('subscription_autopay.error.generic')));
|
||||
return;
|
||||
}
|
||||
|
||||
const initData = tg.initData || '';
|
||||
if (!initData) {
|
||||
handleAutopayError(createError('Authorization Error', t('subscription_autopay.error.unauthorized')));
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriptionId = userData?.subscription_id ?? userData?.subscriptionId ?? null;
|
||||
if (!subscriptionId) {
|
||||
handleAutopayError(createError('Auto-pay', t('subscription_autopay.error.generic')));
|
||||
return;
|
||||
}
|
||||
|
||||
const previousState = {
|
||||
...subscriptionAutopayState,
|
||||
options: [...(subscriptionAutopayState.options || [])],
|
||||
};
|
||||
|
||||
const targetEnabled = typeof changes.enabled === 'boolean'
|
||||
? changes.enabled
|
||||
: subscriptionAutopayState.enabled;
|
||||
|
||||
let targetDays = changes.daysBefore;
|
||||
if (targetDays === undefined || targetDays === null) {
|
||||
targetDays = subscriptionAutopayState.daysBefore;
|
||||
}
|
||||
if (targetEnabled && (targetDays === undefined || targetDays === null)) {
|
||||
if (subscriptionAutopayState.defaultDaysBefore !== null && subscriptionAutopayState.defaultDaysBefore !== undefined) {
|
||||
targetDays = subscriptionAutopayState.defaultDaysBefore;
|
||||
} else if (subscriptionAutopayState.options.length) {
|
||||
targetDays = subscriptionAutopayState.options[0];
|
||||
}
|
||||
}
|
||||
targetDays = targetDays !== undefined && targetDays !== null
|
||||
? coercePositiveInt(targetDays, null)
|
||||
: null;
|
||||
|
||||
if (targetEnabled && (targetDays === null || targetDays === undefined)) {
|
||||
handleAutopayError(createError('Auto-pay', t('subscription_autopay.error.no_days')));
|
||||
renderSubscriptionAutopay();
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptionAutopayState.enabled = targetEnabled;
|
||||
if (targetEnabled) {
|
||||
subscriptionAutopayState.daysBefore = targetDays;
|
||||
if (subscriptionAutopayState.defaultDaysBefore === null || subscriptionAutopayState.defaultDaysBefore === undefined) {
|
||||
subscriptionAutopayState.defaultDaysBefore = targetDays;
|
||||
}
|
||||
}
|
||||
subscriptionAutopayState.saving = true;
|
||||
renderSubscriptionAutopay();
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
initData,
|
||||
subscription_id: subscriptionId,
|
||||
subscriptionId,
|
||||
enabled: targetEnabled,
|
||||
days_before: targetDays,
|
||||
daysBefore: targetDays,
|
||||
};
|
||||
|
||||
const response = await fetch('/miniapp/subscription/autopay', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const body = await parseJsonSafe(response);
|
||||
if (!response.ok || (body && body.success === false)) {
|
||||
const message = extractAutopayError(body, response.status);
|
||||
throw createError('Auto-pay', message, response.status);
|
||||
}
|
||||
|
||||
subscriptionAutopayState.saving = false;
|
||||
|
||||
if (body && typeof body === 'object') {
|
||||
ingestAutopayData(
|
||||
body.autopay
|
||||
|| body.data
|
||||
|| body.subscription
|
||||
|| body.settings
|
||||
|| body,
|
||||
);
|
||||
} else {
|
||||
renderSubscriptionAutopay();
|
||||
}
|
||||
|
||||
await refreshSubscriptionData({ silent: true });
|
||||
await ensureSubscriptionRenewalData({ force: true });
|
||||
} catch (error) {
|
||||
subscriptionAutopayState = {
|
||||
...previousState,
|
||||
options: [...previousState.options],
|
||||
saving: false,
|
||||
};
|
||||
renderSubscriptionAutopay();
|
||||
handleAutopayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleAutopayToggle(enabled) {
|
||||
if (typeof enabled !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
if (subscriptionAutopayState.loading || subscriptionAutopayState.saving) {
|
||||
return;
|
||||
}
|
||||
if (enabled === subscriptionAutopayState.enabled) {
|
||||
return;
|
||||
}
|
||||
submitAutopaySettingsChange({ enabled });
|
||||
}
|
||||
|
||||
function handleAutopayDaySelection(days) {
|
||||
if (subscriptionAutopayState.loading || subscriptionAutopayState.saving) {
|
||||
return;
|
||||
}
|
||||
if (!subscriptionAutopayState.enabled) {
|
||||
return;
|
||||
}
|
||||
const value = coercePositiveInt(days, null);
|
||||
if (value === null || value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (subscriptionAutopayState.daysBefore === value) {
|
||||
return;
|
||||
}
|
||||
if (!subscriptionAutopayState.options.includes(value)) {
|
||||
return;
|
||||
}
|
||||
submitAutopaySettingsChange({ daysBefore: value });
|
||||
}
|
||||
|
||||
function setupSubscriptionAutopayEvents() {
|
||||
document.getElementById('subscriptionAutopayEnable')?.addEventListener('click', () => {
|
||||
handleAutopayToggle(true);
|
||||
});
|
||||
document.getElementById('subscriptionAutopayDisable')?.addEventListener('click', () => {
|
||||
handleAutopayToggle(false);
|
||||
});
|
||||
document.getElementById('subscriptionAutopayDaysOptions')?.addEventListener('click', event => {
|
||||
const target = event.target.closest('button[data-autopay-days]');
|
||||
if (!target || target.disabled) {
|
||||
return;
|
||||
}
|
||||
const value = coercePositiveInt(target.dataset.autopayDays, null);
|
||||
if (value === null || value === undefined) {
|
||||
return;
|
||||
}
|
||||
handleAutopayDaySelection(value);
|
||||
});
|
||||
}
|
||||
|
||||
function isSameSet(a, b) {
|
||||
if (!(a instanceof Set) || !(b instanceof Set)) {
|
||||
return false;
|
||||
@@ -12816,16 +12025,6 @@
|
||||
|
||||
const promoOffer = normalizeRenewalPromoOffer(root.promo_offer ?? root.promoOffer);
|
||||
|
||||
const autopayData = mergeAutopaySources(
|
||||
root.autopay,
|
||||
root.autopay_settings,
|
||||
root.autopaySettings,
|
||||
root,
|
||||
payload.autopay,
|
||||
payload.autopay_settings,
|
||||
payload.autopaySettings,
|
||||
);
|
||||
|
||||
return {
|
||||
subscriptionId,
|
||||
currency,
|
||||
@@ -12837,7 +12036,6 @@
|
||||
promoOffer,
|
||||
missingAmountKopeks,
|
||||
statusMessage,
|
||||
autopay: autopayData,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12855,9 +12053,6 @@
|
||||
if (normalized && Array.isArray(normalized.periods) && normalized.periods.length) {
|
||||
subscriptionRenewalData = normalized;
|
||||
resetSubscriptionRenewalSelection(normalized);
|
||||
if (normalized.autopay) {
|
||||
ingestAutopayData(normalized.autopay);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -12940,9 +12135,6 @@
|
||||
subscriptionRenewalError = null;
|
||||
subscriptionRenewalLoading = false;
|
||||
resetSubscriptionRenewalSelection(inlineNormalized);
|
||||
if (inlineNormalized.autopay) {
|
||||
ingestAutopayData(inlineNormalized.autopay);
|
||||
}
|
||||
renderSubscriptionRenewalCard();
|
||||
return Promise.resolve(inlineNormalized);
|
||||
}
|
||||
@@ -12982,9 +12174,6 @@
|
||||
subscriptionRenewalLoading = false;
|
||||
subscriptionRenewalPromise = null;
|
||||
resetSubscriptionRenewalSelection(normalized);
|
||||
if (normalized.autopay) {
|
||||
ingestAutopayData(normalized.autopay);
|
||||
}
|
||||
renderSubscriptionRenewalCard();
|
||||
return normalized;
|
||||
}).catch(error => {
|
||||
@@ -13511,7 +12700,6 @@
|
||||
renderSubscriptionRenewalMeta(subscriptionRenewalData);
|
||||
renderSubscriptionRenewalOptions(subscriptionRenewalData);
|
||||
renderSubscriptionRenewalSummary(subscriptionRenewalData);
|
||||
renderSubscriptionAutopay();
|
||||
}
|
||||
|
||||
async function confirmSubscriptionRenewal(option, data) {
|
||||
@@ -13643,7 +12831,6 @@
|
||||
}
|
||||
|
||||
function setupSubscriptionRenewalEvents() {
|
||||
setupSubscriptionAutopayEvents();
|
||||
document.getElementById('subscriptionRenewalRetry')?.addEventListener('click', () => {
|
||||
ensureSubscriptionRenewalData({ force: true }).catch(error => {
|
||||
console.warn('Failed to reload renewal options:', error);
|
||||
|
||||
Reference in New Issue
Block a user