diff --git a/miniapp/index.html b/miniapp/index.html
index 1023062b..6e53995b 100644
--- a/miniapp/index.html
+++ b/miniapp/index.html
@@ -2041,6 +2041,174 @@
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);
@@ -4544,9 +4712,42 @@
Device Limit
-
-
-
Auto-Pay
-
-
+
+
+
+
+
+
+
+
+
@@ -5134,6 +5335,7 @@
setupSubscriptionSettingsEvents();
setupSubscriptionPurchaseEvents();
setupSubscriptionRenewalEvents();
+ setupAutopayControlsEvents();
const themeToggle = document.getElementById('themeToggle');
const THEME_STORAGE_KEY = 'remnawave-miniapp-theme';
@@ -5585,6 +5787,18 @@
'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',
@@ -5962,6 +6176,18 @@
'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': 'ПК',
@@ -6117,6 +6343,15 @@
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;
@@ -7254,6 +7489,7 @@
detectPlatform();
setActivePlatformButton();
+ syncAutopayStateFromUserData(userData);
refreshAfterLanguageChange();
animateCardsOnce();
@@ -7446,6 +7682,8 @@
: autopayLabel;
}
+ renderAutopayControls();
+
renderSubscriptionMissingCard();
renderSubscriptionPurchaseCard();
renderSubscriptionRenewalCard();
@@ -7463,6 +7701,553 @@
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) {