mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-22 12:21:26 +00:00
Merge pull request #1197 from Fr1ngg/revert-1196-qbc98z-bedolaga/redesign-autopayment-feature-in-miniapp/index.html
Revert "Add autopay toggle controls to miniapp UI"
This commit is contained in:
@@ -2041,174 +2041,6 @@
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.info-item--autopay {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-item--autopay:hover {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.info-item--autopay .info-item-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.autopay-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.autopay-controls.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autopay-toggle {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.autopay-toggle-btn {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.autopay-toggle-btn:hover:not(:disabled) {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
box-shadow: 0 2px 6px rgba(var(--primary-rgb), 0.1);
|
||||
}
|
||||
|
||||
.autopay-toggle-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.autopay-toggle-btn.active {
|
||||
background: rgba(var(--primary-rgb), 0.12);
|
||||
border-color: rgba(var(--primary-rgb), 0.6);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.autopay-slider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.autopay-slider.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autopay-slider-caption {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.autopay-slider-input {
|
||||
width: 100%;
|
||||
appearance: none;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: var(--border-color);
|
||||
outline: none;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.autopay-slider-input::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
border: 2px solid var(--bg-primary);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(var(--primary-rgb), 0.3);
|
||||
transition: transform 0.2s ease, background 0.3s ease;
|
||||
}
|
||||
|
||||
.autopay-slider-input::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
border: 2px solid var(--bg-primary);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(var(--primary-rgb), 0.3);
|
||||
transition: transform 0.2s ease, background 0.3s ease;
|
||||
}
|
||||
|
||||
.autopay-slider-input:disabled::-webkit-slider-thumb,
|
||||
.autopay-slider-input:disabled::-moz-range-thumb {
|
||||
background: var(--border-color);
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.autopay-slider-input::-webkit-slider-thumb:hover,
|
||||
.autopay-slider-input::-moz-range-thumb:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.autopay-slider-marks {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.autopay-slider-mark {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding-top: 6px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.autopay-slider-mark::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--border-color);
|
||||
transform: translateX(-50%);
|
||||
transition: background 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.autopay-slider-mark.active {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.autopay-slider-mark.active::before {
|
||||
background: var(--primary);
|
||||
transform: translateX(-50%) scale(1.2);
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
@@ -4712,42 +4544,9 @@
|
||||
<span class="info-label" data-i18n="info.device_limit">Device Limit</span>
|
||||
<span class="info-value" id="deviceLimit">-</span>
|
||||
</div>
|
||||
<div class="info-item info-item--autopay" id="autopayInfoItem">
|
||||
<div class="info-item-header">
|
||||
<span class="info-label" data-i18n="info.autopay">Auto-Pay</span>
|
||||
<span class="info-value" id="autopayStatus">-</span>
|
||||
</div>
|
||||
<div class="autopay-controls hidden" id="autopayControls">
|
||||
<div class="autopay-toggle">
|
||||
<button
|
||||
class="autopay-toggle-btn"
|
||||
type="button"
|
||||
id="autopayEnableBtn"
|
||||
data-i18n="autopay.toggle.enable"
|
||||
>Enable auto-pay</button>
|
||||
<button
|
||||
class="autopay-toggle-btn"
|
||||
type="button"
|
||||
id="autopayDisableBtn"
|
||||
data-i18n="autopay.toggle.disable"
|
||||
>Disable auto-pay</button>
|
||||
</div>
|
||||
<div class="autopay-slider hidden" id="autopaySliderWrapper">
|
||||
<div class="autopay-slider-caption" id="autopaySliderLabel">—</div>
|
||||
<input
|
||||
class="autopay-slider-input"
|
||||
id="autopaySlider"
|
||||
type="range"
|
||||
min="0"
|
||||
max="0"
|
||||
step="1"
|
||||
value="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="0"
|
||||
/>
|
||||
<div class="autopay-slider-marks" id="autopaySliderMarks"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label" data-i18n="info.autopay">Auto-Pay</span>
|
||||
<span class="info-value" id="autopayStatus">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5335,7 +5134,6 @@
|
||||
setupSubscriptionSettingsEvents();
|
||||
setupSubscriptionPurchaseEvents();
|
||||
setupSubscriptionRenewalEvents();
|
||||
setupAutopayControlsEvents();
|
||||
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const THEME_STORAGE_KEY = 'remnawave-miniapp-theme';
|
||||
@@ -5787,18 +5585,6 @@
|
||||
'trial.activation.error.already_active': 'You already have an active subscription.',
|
||||
'autopay.enabled': 'Enabled',
|
||||
'autopay.disabled': 'Disabled',
|
||||
'autopay.toggle.enable': 'Enable auto-pay',
|
||||
'autopay.toggle.disable': 'Disable auto-pay',
|
||||
'autopay.update_in_progress': 'Updating…',
|
||||
'autopay.slider.caption': 'Charge {count} days before renewal',
|
||||
'autopay.slider.caption.one': 'Charge {count} day before renewal',
|
||||
'autopay.slider.hint': 'Choose when to charge automatically.',
|
||||
'autopay.days.label': '{count} days',
|
||||
'autopay.days.label.one': '{count} day',
|
||||
'autopay.error.generic': 'Failed to update auto-pay settings. Please try again later.',
|
||||
'autopay.success.enabled': 'Auto-pay enabled.',
|
||||
'autopay.success.disabled': 'Auto-pay disabled.',
|
||||
'autopay.success.updated': 'Auto-pay schedule updated.',
|
||||
'platform.ios': 'iOS',
|
||||
'platform.android': 'Android',
|
||||
'platform.pc': 'PC',
|
||||
@@ -6176,18 +5962,6 @@
|
||||
'trial.activation.error.already_active': 'У вас уже есть активная подписка.',
|
||||
'autopay.enabled': 'Включен',
|
||||
'autopay.disabled': 'Выключен',
|
||||
'autopay.toggle.enable': 'Включить автоплатеж',
|
||||
'autopay.toggle.disable': 'Отключить автоплатеж',
|
||||
'autopay.update_in_progress': 'Обновляем…',
|
||||
'autopay.slider.caption': 'Списывать за {count} дн. до окончания',
|
||||
'autopay.slider.caption.one': 'Списывать за {count} день до окончания',
|
||||
'autopay.slider.hint': 'Выберите, за сколько дней до окончания списывать оплату автоматически.',
|
||||
'autopay.days.label': '{count} дн.',
|
||||
'autopay.days.label.one': '{count} день',
|
||||
'autopay.error.generic': 'Не удалось обновить автоплатеж. Попробуйте позже.',
|
||||
'autopay.success.enabled': 'Автоплатеж включен.',
|
||||
'autopay.success.disabled': 'Автоплатеж отключен.',
|
||||
'autopay.success.updated': 'Настройки автоплатежа обновлены.',
|
||||
'platform.ios': 'iOS',
|
||||
'platform.android': 'Android',
|
||||
'platform.pc': 'ПК',
|
||||
@@ -6343,15 +6117,6 @@
|
||||
const activePaymentMonitors = new Map();
|
||||
let paymentStatusPollTimer = null;
|
||||
let isPaymentStatusPolling = false;
|
||||
const autopayState = {
|
||||
enabled: false,
|
||||
daysBefore: null,
|
||||
options: [],
|
||||
previewDays: null,
|
||||
updating: false,
|
||||
pendingAction: null,
|
||||
available: false,
|
||||
};
|
||||
let subscriptionSettingsData = null;
|
||||
let subscriptionSettingsPromise = null;
|
||||
let subscriptionSettingsError = null;
|
||||
@@ -7489,7 +7254,6 @@
|
||||
|
||||
detectPlatform();
|
||||
setActivePlatformButton();
|
||||
syncAutopayStateFromUserData(userData);
|
||||
refreshAfterLanguageChange();
|
||||
|
||||
animateCardsOnce();
|
||||
@@ -7682,8 +7446,6 @@
|
||||
: autopayLabel;
|
||||
}
|
||||
|
||||
renderAutopayControls();
|
||||
|
||||
renderSubscriptionMissingCard();
|
||||
renderSubscriptionPurchaseCard();
|
||||
renderSubscriptionRenewalCard();
|
||||
@@ -7701,553 +7463,6 @@
|
||||
updateActionButtons();
|
||||
}
|
||||
|
||||
function resolveAutopayString(key, fallback) {
|
||||
const value = t(key);
|
||||
if (value && value !== key) {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function normalizeAutopaySourceValues(source) {
|
||||
if (Array.isArray(source)) {
|
||||
return source;
|
||||
}
|
||||
if (source === undefined || source === null) {
|
||||
return [];
|
||||
}
|
||||
if (typeof source === 'number') {
|
||||
return [source];
|
||||
}
|
||||
if (typeof source === 'string') {
|
||||
return source
|
||||
.split(/[,;\s]+/)
|
||||
.map(entry => entry.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
if (typeof source === 'object') {
|
||||
const collected = [];
|
||||
if (Array.isArray(source.options)) {
|
||||
collected.push(...source.options);
|
||||
}
|
||||
if (Array.isArray(source.values)) {
|
||||
collected.push(...source.values);
|
||||
}
|
||||
if (Array.isArray(source.days)) {
|
||||
collected.push(...source.days);
|
||||
}
|
||||
if (Array.isArray(source.list)) {
|
||||
collected.push(...source.list);
|
||||
}
|
||||
['min', 'max', 'default', 'value', 'days_before', 'daysBefore'].forEach(key => {
|
||||
if (source[key] !== undefined && source[key] !== null) {
|
||||
collected.push(source[key]);
|
||||
}
|
||||
});
|
||||
return collected;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function resolveAutopayOptions(data = userData) {
|
||||
const sources = [
|
||||
data?.autopay_days_options,
|
||||
data?.autopayDaysOptions,
|
||||
data?.autopay_warning_days,
|
||||
data?.autopayWarningDays,
|
||||
data?.autopay?.days_before_options,
|
||||
data?.autopay?.daysBeforeOptions,
|
||||
data?.autopay?.options,
|
||||
data?.autopay?.values,
|
||||
appsConfig?.autopay_days_options,
|
||||
appsConfig?.autopayDaysOptions,
|
||||
appsConfig?.autopay_warning_days,
|
||||
appsConfig?.autopayWarningDays,
|
||||
];
|
||||
const values = new Set();
|
||||
sources.forEach(source => {
|
||||
normalizeAutopaySourceValues(source).forEach(entry => {
|
||||
const number = coercePositiveInt(entry, null);
|
||||
if (number !== null && number > 0) {
|
||||
values.add(number);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (values.size === 0) {
|
||||
return [1, 3];
|
||||
}
|
||||
return Array.from(values).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
function extractAutopayDaysValue(data = userData) {
|
||||
const candidates = [
|
||||
data?.autopay_days_before,
|
||||
data?.autopayDaysBefore,
|
||||
data?.autopay?.days_before,
|
||||
data?.autopay?.daysBefore,
|
||||
data?.user?.autopay_days_before,
|
||||
data?.user?.autopayDaysBefore,
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const value = coercePositiveInt(candidate, null);
|
||||
if (value !== null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function syncAutopayStateFromUserData(data = userData) {
|
||||
if (!data) {
|
||||
autopayState.available = false;
|
||||
autopayState.enabled = false;
|
||||
autopayState.options = [];
|
||||
autopayState.daysBefore = null;
|
||||
autopayState.previewDays = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriptionMissing = Boolean(data.subscription_missing ?? data.subscriptionMissing);
|
||||
const hasSubscription = Boolean(data.subscription_id ?? data.subscriptionId);
|
||||
const available = hasSubscription && !subscriptionMissing && hasPaidSubscription();
|
||||
autopayState.available = available;
|
||||
|
||||
const enabled = Boolean(data.autopay_enabled ?? data.autopayEnabled);
|
||||
autopayState.enabled = enabled;
|
||||
|
||||
const options = resolveAutopayOptions(data);
|
||||
const daysValue = extractAutopayDaysValue(data);
|
||||
|
||||
const normalizedOptions = Array.isArray(options) ? options.slice() : [];
|
||||
if (daysValue !== null && !normalizedOptions.includes(daysValue)) {
|
||||
normalizedOptions.push(daysValue);
|
||||
}
|
||||
normalizedOptions.sort((a, b) => a - b);
|
||||
autopayState.options = normalizedOptions;
|
||||
|
||||
if (daysValue !== null) {
|
||||
autopayState.daysBefore = daysValue;
|
||||
} else if (!autopayState.daysBefore || !normalizedOptions.includes(autopayState.daysBefore)) {
|
||||
autopayState.daysBefore = normalizedOptions.length
|
||||
? normalizedOptions[normalizedOptions.length - 1]
|
||||
: null;
|
||||
}
|
||||
|
||||
if (!available) {
|
||||
autopayState.previewDays = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getAutopayActiveDays() {
|
||||
if (autopayState.previewDays !== null && autopayState.previewDays !== undefined) {
|
||||
return autopayState.previewDays;
|
||||
}
|
||||
if (autopayState.daysBefore !== null && autopayState.daysBefore !== undefined) {
|
||||
return autopayState.daysBefore;
|
||||
}
|
||||
if (autopayState.options.length) {
|
||||
return autopayState.options[autopayState.options.length - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatAutopayDaysLabel(days) {
|
||||
const value = coercePositiveInt(days, null);
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
const key = value === 1 ? 'autopay.days.label.one' : 'autopay.days.label';
|
||||
const template = t(key);
|
||||
if (template && template !== key) {
|
||||
return template.replace('{count}', String(value));
|
||||
}
|
||||
return value === 1 ? `${value} day` : `${value} days`;
|
||||
}
|
||||
|
||||
function formatAutopayCaption(days) {
|
||||
const value = coercePositiveInt(days, null);
|
||||
if (value === null) {
|
||||
return resolveAutopayString('autopay.slider.hint', 'Choose when to charge automatically.');
|
||||
}
|
||||
const key = value === 1 ? 'autopay.slider.caption.one' : 'autopay.slider.caption';
|
||||
const template = t(key);
|
||||
if (template && template !== key) {
|
||||
return template.replace('{count}', String(value));
|
||||
}
|
||||
return value === 1
|
||||
? `Charge ${value} day before renewal`
|
||||
: `Charge ${value} days before renewal`;
|
||||
}
|
||||
|
||||
function updateAutopaySliderTrack(slider, activeIndex) {
|
||||
if (!slider) {
|
||||
return;
|
||||
}
|
||||
const hasMultiple = autopayState.options.length > 1;
|
||||
if (!autopayState.enabled || !hasMultiple) {
|
||||
slider.style.background = `linear-gradient(90deg, var(--border-color) 0%, var(--border-color) 100%)`;
|
||||
return;
|
||||
}
|
||||
const maxIndex = autopayState.options.length - 1;
|
||||
const safeIndex = Math.max(0, Math.min(maxIndex, activeIndex || 0));
|
||||
const percentage = (safeIndex / maxIndex) * 100;
|
||||
slider.style.background = `linear-gradient(90deg, rgba(var(--primary-rgb), 0.7) 0%, rgba(var(--primary-rgb), 0.7) ${percentage}%, var(--border-color) ${percentage}%, var(--border-color) 100%)`;
|
||||
}
|
||||
|
||||
function renderAutopayControls() {
|
||||
const controls = document.getElementById('autopayControls');
|
||||
if (!controls) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldShowControls = autopayState.available;
|
||||
controls.classList.toggle('hidden', !shouldShowControls);
|
||||
if (!shouldShowControls) {
|
||||
return;
|
||||
}
|
||||
|
||||
const enableBtn = document.getElementById('autopayEnableBtn');
|
||||
const disableBtn = document.getElementById('autopayDisableBtn');
|
||||
const sliderWrapper = document.getElementById('autopaySliderWrapper');
|
||||
const slider = document.getElementById('autopaySlider');
|
||||
const marksContainer = document.getElementById('autopaySliderMarks');
|
||||
const caption = document.getElementById('autopaySliderLabel');
|
||||
|
||||
const activeDays = getAutopayActiveDays();
|
||||
const options = autopayState.options;
|
||||
const hasSlider = autopayState.enabled && options.length > 0;
|
||||
|
||||
if (enableBtn) {
|
||||
const key = autopayState.updating && autopayState.pendingAction === 'enable'
|
||||
? 'autopay.update_in_progress'
|
||||
: 'autopay.toggle.enable';
|
||||
const fallback = autopayState.updating && autopayState.pendingAction === 'enable'
|
||||
? 'Updating…'
|
||||
: 'Enable auto-pay';
|
||||
enableBtn.textContent = resolveAutopayString(key, fallback);
|
||||
enableBtn.disabled = autopayState.updating || autopayState.enabled;
|
||||
enableBtn.classList.toggle('active', autopayState.enabled);
|
||||
enableBtn.setAttribute('aria-pressed', autopayState.enabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (disableBtn) {
|
||||
const key = autopayState.updating && autopayState.pendingAction === 'disable'
|
||||
? 'autopay.update_in_progress'
|
||||
: 'autopay.toggle.disable';
|
||||
const fallback = autopayState.updating && autopayState.pendingAction === 'disable'
|
||||
? 'Updating…'
|
||||
: 'Disable auto-pay';
|
||||
disableBtn.textContent = resolveAutopayString(key, fallback);
|
||||
disableBtn.disabled = autopayState.updating || !autopayState.enabled;
|
||||
disableBtn.classList.toggle('active', !autopayState.enabled);
|
||||
disableBtn.setAttribute('aria-pressed', autopayState.enabled ? 'false' : 'true');
|
||||
}
|
||||
|
||||
if (sliderWrapper) {
|
||||
sliderWrapper.classList.toggle('hidden', !hasSlider);
|
||||
}
|
||||
|
||||
if (caption) {
|
||||
caption.textContent = hasSlider && activeDays !== null
|
||||
? formatAutopayCaption(activeDays)
|
||||
: resolveAutopayString('autopay.slider.hint', 'Choose when to charge automatically.');
|
||||
}
|
||||
|
||||
if (slider) {
|
||||
const maxIndex = Math.max(options.length - 1, 0);
|
||||
slider.setAttribute('aria-valuemin', '0');
|
||||
slider.setAttribute('aria-valuemax', String(maxIndex));
|
||||
slider.disabled = autopayState.updating || !autopayState.enabled || options.length <= 1;
|
||||
|
||||
if (options.length > 0) {
|
||||
let activeIndex = options.findIndex(value => value === activeDays);
|
||||
if (activeIndex < 0) {
|
||||
activeIndex = options.length - 1;
|
||||
}
|
||||
slider.max = String(Math.max(options.length - 1, 0));
|
||||
slider.value = String(Math.max(activeIndex, 0));
|
||||
slider.setAttribute('aria-valuenow', activeDays !== null ? String(activeDays) : '0');
|
||||
slider.setAttribute('aria-label', formatAutopayCaption(activeDays));
|
||||
updateAutopaySliderTrack(slider, activeIndex);
|
||||
} else {
|
||||
slider.max = '0';
|
||||
slider.value = '0';
|
||||
slider.setAttribute('aria-valuenow', '0');
|
||||
slider.setAttribute('aria-label', resolveAutopayString('autopay.slider.hint', 'Choose when to charge automatically.'));
|
||||
slider.style.background = `linear-gradient(90deg, var(--border-color) 0%, var(--border-color) 100%)`;
|
||||
}
|
||||
}
|
||||
|
||||
if (marksContainer) {
|
||||
marksContainer.innerHTML = '';
|
||||
if (hasSlider && options.length) {
|
||||
const activeValue = activeDays;
|
||||
options.forEach((value, index) => {
|
||||
const mark = document.createElement('div');
|
||||
mark.className = 'autopay-slider-mark';
|
||||
if (value === activeValue) {
|
||||
mark.classList.add('active');
|
||||
}
|
||||
mark.textContent = formatAutopayDaysLabel(value);
|
||||
if (!slider?.disabled) {
|
||||
mark.style.cursor = 'pointer';
|
||||
mark.addEventListener('click', () => {
|
||||
if (autopayState.updating || !autopayState.enabled) {
|
||||
return;
|
||||
}
|
||||
const sliderElement = document.getElementById('autopaySlider');
|
||||
if (!sliderElement) {
|
||||
return;
|
||||
}
|
||||
sliderElement.value = String(index);
|
||||
sliderElement.dispatchEvent(new Event('input'));
|
||||
sliderElement.dispatchEvent(new Event('change'));
|
||||
});
|
||||
}
|
||||
marksContainer.appendChild(mark);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleAutopayToggle(enabled) {
|
||||
if (autopayState.updating || !autopayState.available) {
|
||||
return;
|
||||
}
|
||||
if (autopayState.enabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousEnabled = autopayState.enabled;
|
||||
const previousDays = autopayState.daysBefore;
|
||||
const targetDays = autopayState.daysBefore
|
||||
?? (autopayState.options.length ? autopayState.options[autopayState.options.length - 1] : null);
|
||||
|
||||
autopayState.enabled = enabled;
|
||||
autopayState.previewDays = null;
|
||||
renderAutopayControls();
|
||||
|
||||
submitAutopayUpdate({
|
||||
enabled,
|
||||
daysBefore: targetDays,
|
||||
action: enabled ? 'enable' : 'disable',
|
||||
}).catch(error => {
|
||||
console.warn('Failed to toggle autopay:', error);
|
||||
autopayState.enabled = previousEnabled;
|
||||
autopayState.daysBefore = previousDays;
|
||||
renderAutopayControls();
|
||||
});
|
||||
}
|
||||
|
||||
function handleAutopaySliderInput(event) {
|
||||
if (!autopayState.options.length) {
|
||||
return;
|
||||
}
|
||||
const index = Math.round(Number(event.target.value) || 0);
|
||||
const safeIndex = Math.max(0, Math.min(autopayState.options.length - 1, index));
|
||||
autopayState.previewDays = autopayState.options[safeIndex];
|
||||
renderAutopayControls();
|
||||
}
|
||||
|
||||
function handleAutopaySliderChange(event) {
|
||||
if (!autopayState.options.length) {
|
||||
return;
|
||||
}
|
||||
const index = Math.round(Number(event.target.value) || 0);
|
||||
const safeIndex = Math.max(0, Math.min(autopayState.options.length - 1, index));
|
||||
const selected = autopayState.options[safeIndex];
|
||||
autopayState.previewDays = null;
|
||||
|
||||
if (selected === autopayState.daysBefore) {
|
||||
renderAutopayControls();
|
||||
return;
|
||||
}
|
||||
|
||||
const previousDays = autopayState.daysBefore;
|
||||
autopayState.daysBefore = selected;
|
||||
renderAutopayControls();
|
||||
|
||||
if (!autopayState.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitAutopayUpdate({
|
||||
enabled: true,
|
||||
daysBefore: selected,
|
||||
action: 'days',
|
||||
}).catch(error => {
|
||||
console.warn('Failed to update autopay days:', error);
|
||||
autopayState.daysBefore = previousDays;
|
||||
renderAutopayControls();
|
||||
});
|
||||
}
|
||||
|
||||
async function submitAutopayUpdate(options) {
|
||||
const { enabled, daysBefore, action } = options || {};
|
||||
const initData = tg.initData || '';
|
||||
if (!initData) {
|
||||
const error = createError('Authorization Error', t('subscription_settings.error.unauthorized'));
|
||||
showPopup(error.message, resolveAutopayString('info.autopay', 'Auto-pay'));
|
||||
throw error;
|
||||
}
|
||||
|
||||
const subscriptionId = userData?.subscription_id ?? userData?.subscriptionId ?? null;
|
||||
if (!subscriptionId) {
|
||||
const fallback = resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.');
|
||||
const error = createError('Autopay Error', fallback);
|
||||
showPopup(error.message, resolveAutopayString('info.autopay', 'Auto-pay'));
|
||||
throw error;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
initData,
|
||||
subscription_id: subscriptionId,
|
||||
subscriptionId,
|
||||
enabled,
|
||||
autopay_enabled: enabled,
|
||||
autopayEnabled: enabled,
|
||||
};
|
||||
|
||||
const daysValue = coercePositiveInt(daysBefore, null);
|
||||
if (daysValue !== null) {
|
||||
payload.days_before = daysValue;
|
||||
payload.daysBefore = daysValue;
|
||||
payload.autopay_days_before = daysValue;
|
||||
payload.autopayDaysBefore = daysValue;
|
||||
}
|
||||
|
||||
autopayState.updating = true;
|
||||
autopayState.pendingAction = action || (enabled ? 'enable' : 'disable');
|
||||
renderAutopayControls();
|
||||
|
||||
try {
|
||||
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('Autopay update error', message, response.status);
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
userData = {};
|
||||
}
|
||||
userData.autopay_enabled = enabled;
|
||||
userData.autopayEnabled = enabled;
|
||||
if (daysValue !== null) {
|
||||
userData.autopay_days_before = daysValue;
|
||||
userData.autopayDaysBefore = daysValue;
|
||||
}
|
||||
|
||||
autopayState.enabled = enabled;
|
||||
if (daysValue !== null) {
|
||||
autopayState.daysBefore = daysValue;
|
||||
}
|
||||
autopayState.previewDays = null;
|
||||
|
||||
const successKey = action === 'days'
|
||||
? 'autopay.success.updated'
|
||||
: enabled
|
||||
? 'autopay.success.enabled'
|
||||
: 'autopay.success.disabled';
|
||||
const successFallback = action === 'days'
|
||||
? 'Auto-pay schedule updated.'
|
||||
: enabled
|
||||
? 'Auto-pay enabled.'
|
||||
: 'Auto-pay disabled.';
|
||||
showPopup(resolveAutopayString(successKey, successFallback), resolveAutopayString('info.autopay', 'Auto-pay'));
|
||||
|
||||
renderUserData();
|
||||
|
||||
try {
|
||||
await refreshSubscriptionData({ silent: true });
|
||||
} catch (refreshError) {
|
||||
console.warn('Failed to refresh subscription after autopay update:', refreshError);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = resolveAutopayErrorMessage(error);
|
||||
showPopup(message, resolveAutopayString('info.autopay', 'Auto-pay'));
|
||||
throw error;
|
||||
} finally {
|
||||
autopayState.updating = false;
|
||||
autopayState.pendingAction = null;
|
||||
renderAutopayControls();
|
||||
}
|
||||
}
|
||||
|
||||
function extractAutopayError(payload, status) {
|
||||
if (status === 401) {
|
||||
return t('subscription_settings.error.unauthorized');
|
||||
}
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.');
|
||||
}
|
||||
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 resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.');
|
||||
}
|
||||
|
||||
function resolveAutopayErrorMessage(error) {
|
||||
if (!error) {
|
||||
return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.');
|
||||
}
|
||||
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) {
|
||||
const unauthorized = t('subscription_settings.error.unauthorized');
|
||||
if (unauthorized && unauthorized !== 'subscription_settings.error.unauthorized') {
|
||||
return unauthorized;
|
||||
}
|
||||
}
|
||||
return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.');
|
||||
}
|
||||
|
||||
function setupAutopayControlsEvents() {
|
||||
document.getElementById('autopayEnableBtn')?.addEventListener('click', () => {
|
||||
handleAutopayToggle(true);
|
||||
});
|
||||
document.getElementById('autopayDisableBtn')?.addEventListener('click', () => {
|
||||
handleAutopayToggle(false);
|
||||
});
|
||||
const slider = document.getElementById('autopaySlider');
|
||||
if (slider) {
|
||||
slider.addEventListener('input', handleAutopaySliderInput);
|
||||
slider.addEventListener('change', handleAutopaySliderChange);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSubscriptionMissingCard() {
|
||||
const card = document.getElementById('subscriptionMissingCard');
|
||||
if (!card) {
|
||||
|
||||
Reference in New Issue
Block a user