fix: daily tariff subscriptions stuck in expired/disabled with no resume path

- Keyboard now shows "Возобновить" for disabled/expired daily tariffs
  instead of useless "Приостановить"
- resume_daily_subscription handles EXPIRED→ACTIVE (not only DISABLED)
- Pause handler detects inactive status and calls resume directly
- subscription_extend redirects daily tariffs to subscription info
  (daily tariffs have no period_prices, so extend page was empty)
This commit is contained in:
Fringg
2026-02-15 23:17:45 +03:00
parent e1822800ab
commit 80914c1af7
3 changed files with 33 additions and 13 deletions
+4 -3
View File
@@ -1982,13 +1982,14 @@ async def resume_daily_subscription(
subscription.is_daily_paused = False
# Восстанавливаем статус ACTIVE если подписка была DISABLED (недостаток средств)
if subscription.status == SubscriptionStatus.DISABLED.value:
# Восстанавливаем статус ACTIVE если подписка была DISABLED/EXPIRED
if subscription.status in (SubscriptionStatus.DISABLED.value, SubscriptionStatus.EXPIRED.value):
previous_status = subscription.status
subscription.status = SubscriptionStatus.ACTIVE.value
# Обновляем время последнего списания для корректного расчёта следующего
subscription.last_daily_charge_at = datetime.utcnow()
subscription.end_date = datetime.utcnow() + timedelta(days=1)
logger.info(f'✅ Суточная подписка {subscription.id} восстановлена из DISABLED в ACTIVE')
logger.info(f'✅ Суточная подписка {subscription.id} восстановлена из {previous_status} в ACTIVE')
await db.commit()
await db.refresh(subscription)
+21 -8
View File
@@ -1537,6 +1537,15 @@ async def handle_extend_subscription(callback: types.CallbackQuery, db_user: Use
# В режиме тарифов проверяем наличие tariff_id
if settings.is_tariffs_mode():
if subscription.tariff_id:
# Проверяем, суточный ли тариф — у суточных нет period_prices, продление через resume
from app.database.crud.tariff import get_tariff_by_id
tariff = getattr(subscription, 'tariff', None) or await get_tariff_by_id(db, subscription.tariff_id)
if tariff and getattr(tariff, 'is_daily', False):
# Суточный тариф: перенаправляем на страницу подписки (там кнопка «Возобновить»)
await show_subscription_info(callback, db_user, db)
return
# У подписки есть тариф - перенаправляем на продление по тарифу
from .tariff_purchase import show_tariff_extend
@@ -3111,11 +3120,15 @@ async def handle_toggle_daily_subscription_pause(callback: types.CallbackQuery,
# Прикрепляем тариф к подписке для CRUD функций
subscription.tariff = tariff
# Переключаем статус паузы
# Определяем, нужно ли возобновление: пауза пользователя ИЛИ остановка системой (disabled/expired)
from app.database.models import SubscriptionStatus
was_paused = getattr(subscription, 'is_daily_paused', False)
is_inactive = subscription.status in (SubscriptionStatus.DISABLED.value, SubscriptionStatus.EXPIRED.value)
needs_resume = was_paused or is_inactive
# При возобновлении проверяем баланс
if was_paused:
if needs_resume:
daily_price = getattr(tariff, 'daily_price_kopeks', 0)
if daily_price > 0 and db_user.balance_kopeks < daily_price:
await callback.answer(
@@ -3127,10 +3140,11 @@ async def handle_toggle_daily_subscription_pause(callback: types.CallbackQuery,
)
return
subscription = await toggle_daily_subscription_pause(db, subscription)
if needs_resume:
# Принудительный resume: снимаем паузу + восстанавливаем статус ACTIVE
from app.database.crud.subscription import resume_daily_subscription
if was_paused:
# Была пауза, теперь возобновили
subscription = await resume_daily_subscription(db, subscription)
message = texts.t('DAILY_SUBSCRIPTION_RESUMED', '▶️ Подписка возобновлена!')
# Синхронизируем с Remnawave - активируем пользователя
try:
@@ -3147,10 +3161,9 @@ async def handle_toggle_daily_subscription_pause(callback: types.CallbackQuery,
except Exception as e:
logger.error(f'Ошибка синхронизации с Remnawave при возобновлении: {e}')
else:
# Была активна, теперь на паузе
# Подписка активна, ставим на паузу
subscription = await toggle_daily_subscription_pause(db, subscription)
message = texts.t('DAILY_SUBSCRIPTION_PAUSED', '⏸️ Подписка приостановлена!')
# При паузе можно отключить пользователя в Remnawave (опционально)
# Пока оставляем активным, т.к. пауза - это только остановка списания
await callback.answer(message, show_alert=True)
+8 -2
View File
@@ -1092,9 +1092,15 @@ def get_subscription_keyboard(
is_daily_tariff = tariff and getattr(tariff, 'is_daily', False)
if is_daily_tariff:
# Для суточного тарифа показываем кнопку паузы/возобновления
# Для суточного тарифа: проверяем статус подписки
from app.database.models import SubscriptionStatus
sub_status = getattr(subscription, 'status', None)
is_paused = getattr(subscription, 'is_daily_paused', False)
if is_paused:
is_inactive = sub_status in (SubscriptionStatus.DISABLED.value, SubscriptionStatus.EXPIRED.value)
if is_inactive or is_paused:
# Подписка остановлена (системой или пользователем) — показываем «Возобновить»
pause_text = texts.t('RESUME_DAILY_BUTTON', '▶️ Возобновить подписку')
else:
pause_text = texts.t('PAUSE_DAILY_BUTTON', '⏸️ Приостановить подписку')