Improve Tribute auto-renew
This commit is contained in:
@@ -32,6 +32,7 @@ from bot.handlers.admin import admin_router_aggregate
|
|||||||
from bot.filters.admin_filter import AdminFilter
|
from bot.filters.admin_filter import AdminFilter
|
||||||
|
|
||||||
from bot.services.notification_service import schedule_subscription_notifications
|
from bot.services.notification_service import schedule_subscription_notifications
|
||||||
|
from bot.services.auto_renew_service import schedule_tribute_autorenew
|
||||||
from bot.services.yookassa_service import YooKassaService
|
from bot.services.yookassa_service import YooKassaService
|
||||||
from bot.services.panel_api_service import PanelApiService
|
from bot.services.panel_api_service import PanelApiService
|
||||||
from bot.services.subscription_service import SubscriptionService
|
from bot.services.subscription_service import SubscriptionService
|
||||||
@@ -116,6 +117,12 @@ async def on_startup_configured(dispatcher: Dispatcher):
|
|||||||
panel_service,
|
panel_service,
|
||||||
async_session_factory,
|
async_session_factory,
|
||||||
)
|
)
|
||||||
|
schedule_tribute_autorenew(
|
||||||
|
settings,
|
||||||
|
scheduler,
|
||||||
|
panel_service,
|
||||||
|
async_session_factory,
|
||||||
|
)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
dispatcher["scheduler"] = scheduler
|
dispatcher["scheduler"] = scheduler
|
||||||
logging.info("STARTUP: APScheduler started.")
|
logging.info("STARTUP: APScheduler started.")
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from config.settings import Settings
|
||||||
|
from bot.services.subscription_service import SubscriptionService
|
||||||
|
from bot.services.panel_api_service import PanelApiService
|
||||||
|
from db.dal import subscription_dal
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_extend_tribute_subscriptions(
|
||||||
|
settings: Settings, panel_service: PanelApiService,
|
||||||
|
async_session_factory: sessionmaker) -> None:
|
||||||
|
logging.info(
|
||||||
|
f"Scheduler job 'auto_extend_tribute_subscriptions' started at {datetime.now(timezone.utc)} UTC."
|
||||||
|
)
|
||||||
|
async with async_session_factory() as session:
|
||||||
|
try:
|
||||||
|
sub_service = SubscriptionService(settings, panel_service)
|
||||||
|
subs = await subscription_dal.get_active_subscriptions_for_autorenew(
|
||||||
|
session, 'tribute', require_skip_flag=False)
|
||||||
|
if not subs:
|
||||||
|
logging.info("No Tribute subscriptions to auto-extend.")
|
||||||
|
return
|
||||||
|
for sub in subs:
|
||||||
|
if not sub.skip_notifications:
|
||||||
|
from db.dal import payment_dal
|
||||||
|
has_pay = await payment_dal.user_has_successful_payment_for_provider(
|
||||||
|
session, sub.user_id, 'tribute')
|
||||||
|
if not has_pay:
|
||||||
|
continue
|
||||||
|
await subscription_dal.set_skip_notifications_for_provider(
|
||||||
|
session, sub.user_id, 'tribute', True)
|
||||||
|
|
||||||
|
months = sub.duration_months or 1
|
||||||
|
bonus_days = months * 30
|
||||||
|
await sub_service.extend_active_subscription_days(
|
||||||
|
session, sub.user_id, bonus_days,
|
||||||
|
reason='tribute_autorenew')
|
||||||
|
await session.commit()
|
||||||
|
logging.info(
|
||||||
|
f"Auto-extended {len(subs)} Tribute subscriptions and committed session.")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"Error during auto_extend_tribute_subscriptions: {e}",
|
||||||
|
exc_info=True)
|
||||||
|
await session.rollback()
|
||||||
|
logging.info("Session rolled back due to error in auto_extend_tribute_subscriptions.")
|
||||||
|
|
||||||
|
|
||||||
|
def schedule_tribute_autorenew(
|
||||||
|
settings: Settings, scheduler: AsyncIOScheduler,
|
||||||
|
panel_service: PanelApiService,
|
||||||
|
async_session_factory: sessionmaker) -> None:
|
||||||
|
|
||||||
|
async def job_wrapper():
|
||||||
|
try:
|
||||||
|
await auto_extend_tribute_subscriptions(
|
||||||
|
settings, panel_service, async_session_factory)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"Unhandled error in scheduled job 'auto_extend_tribute_subscriptions': {e}",
|
||||||
|
exc_info=True)
|
||||||
|
|
||||||
|
scheduler.add_job(
|
||||||
|
job_wrapper,
|
||||||
|
'cron',
|
||||||
|
hour=0,
|
||||||
|
minute=5,
|
||||||
|
name='daily_tribute_autorenew',
|
||||||
|
misfire_grace_time=60 * 15,
|
||||||
|
replace_existing=True)
|
||||||
|
logging.info(
|
||||||
|
"Tribute subscription auto-renew job scheduled daily at 00:05 UTC.")
|
||||||
@@ -96,6 +96,19 @@ async def update_payment_status_by_db_id(
|
|||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
|
||||||
|
async def user_has_successful_payment_for_provider(
|
||||||
|
session: AsyncSession, user_id: int, provider: str) -> bool:
|
||||||
|
"""Check if a user has at least one successful payment for the provider."""
|
||||||
|
|
||||||
|
stmt = (select(Payment.payment_id)
|
||||||
|
.where(Payment.user_id == user_id,
|
||||||
|
Payment.provider == provider,
|
||||||
|
Payment.status == 'succeeded')
|
||||||
|
.limit(1))
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
return result.scalar_one_or_none() is not None
|
||||||
|
|
||||||
|
|
||||||
async def update_payment_status_by_yk_id(session: AsyncSession,
|
async def update_payment_status_by_yk_id(session: AsyncSession,
|
||||||
yookassa_payment_id: str,
|
yookassa_payment_id: str,
|
||||||
new_status: str) -> Optional[Payment]:
|
new_status: str) -> Optional[Payment]:
|
||||||
|
|||||||
@@ -231,3 +231,25 @@ async def set_skip_notifications_for_provider(
|
|||||||
Subscription.provider == provider).values(skip_notifications=skip))
|
Subscription.provider == provider).values(skip_notifications=skip))
|
||||||
result = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
return result.rowcount
|
return result.rowcount
|
||||||
|
|
||||||
|
|
||||||
|
async def get_active_subscriptions_for_autorenew(
|
||||||
|
session: AsyncSession, provider: str,
|
||||||
|
days_threshold: int = 1,
|
||||||
|
require_skip_flag: bool = True) -> List[Subscription]:
|
||||||
|
"""Fetch active subscriptions nearing expiration for auto-renew logic."""
|
||||||
|
|
||||||
|
now_utc = datetime.now(timezone.utc)
|
||||||
|
threshold_date = now_utc + timedelta(days=days_threshold)
|
||||||
|
|
||||||
|
conditions = [
|
||||||
|
Subscription.provider == provider,
|
||||||
|
Subscription.is_active == True,
|
||||||
|
Subscription.end_date <= threshold_date,
|
||||||
|
]
|
||||||
|
if require_skip_flag:
|
||||||
|
conditions.append(Subscription.skip_notifications == True)
|
||||||
|
|
||||||
|
stmt = select(Subscription).where(*conditions)
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
return result.scalars().all()
|
||||||
|
|||||||
Reference in New Issue
Block a user