From 09f0de78c6b09216dc47f9ed2ad3bc7fd695c58b Mon Sep 17 00:00:00 2001 From: machka-pasla Date: Wed, 3 Sep 2025 10:22:02 +0300 Subject: [PATCH] Refactor subscription renewal process to utilize panel webhook - Removed the recurring billing task from the bot, shifting the auto-renew functionality to the panel webhook service, which now triggers renewals 24 hours before expiry. - Updated service dependencies to wire the subscription service with the panel webhook for seamless renewal handling. - Adjusted the Subscription model to enable auto-renew by default, enhancing subscription management. --- bot/app/factories/build_services.py | 9 ++++++++ bot/main_bot.py | 31 +-------------------------- bot/services/panel_webhook_service.py | 20 +++++++++++++++++ bot/services/subscription_service.py | 1 + db/models.py | 2 +- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/bot/app/factories/build_services.py b/bot/app/factories/build_services.py index 8e84968..68b770b 100644 --- a/bot/app/factories/build_services.py +++ b/bot/app/factories/build_services.py @@ -54,6 +54,15 @@ def build_core_services( settings_obj=settings, ) + # Wire services that depend on each other + try: + # Attach YooKassa to subscription service for auto-renew charges + setattr(subscription_service, "yookassa_service", yookassa_service) + # Allow panel webhook to trigger renewals through subscription service + setattr(panel_webhook_service, "subscription_service", subscription_service) + except Exception: + pass + return { "panel_service": panel_service, "subscription_service": subscription_service, diff --git a/bot/main_bot.py b/bot/main_bot.py index 3f1f11b..e25a0dc 100644 --- a/bot/main_bot.py +++ b/bot/main_bot.py @@ -293,36 +293,7 @@ async def run_bot(settings_param: Settings): main_tasks.append(asyncio.create_task(web_server_task(), name="AIOHTTPServerTask")) - async def recurring_billing_task(): - # Run periodic check to bill 1 day before expiry - async_session_factory = dp.get("async_session_factory") - subscription_service = dp.get("subscription_service") - if not async_session_factory or not subscription_service: - logging.warning("Recurring billing task: dependencies missing; task not started") - return - while True: - try: - async with async_session_factory() as session: - # Find subscriptions ending in 1 day - subs = await subscription_service.get_subscriptions_ending_soon(session, 1) - # We need actual Subscription objects; reuse DAL directly - from db.dal import subscription_dal - subs_models = await subscription_dal.get_subscriptions_near_expiration(session, 1) - handled = 0 - for sub in subs_models: - try: - ok = await subscription_service.charge_subscription_renewal(session, sub) - handled += 1 if ok else 0 - except Exception: - logging.exception("Auto-renew attempt failed") - if handled: - await session.commit() - except Exception: - logging.exception("Recurring billing iteration failed") - # Sleep 1 hour between scans - await asyncio.sleep(3600) - - main_tasks.append(asyncio.create_task(recurring_billing_task(), name="RecurringBillingTask")) + # Recurring billing moved to panel webhook (24h before expiry). No periodic task needed here. logging.info("Starting bot in Webhook mode with AIOHTTP server...") logging.info(f"Starting bot with main tasks: {[task.get_name() for task in main_tasks]}") diff --git a/bot/services/panel_webhook_service.py b/bot/services/panel_webhook_service.py index cee7ada..fcef89a 100644 --- a/bot/services/panel_webhook_service.py +++ b/bot/services/panel_webhook_service.py @@ -185,6 +185,26 @@ class PanelWebhookService: if event_name in EVENT_MAP: days_left, msg_key = EVENT_MAP[event_name] + if days_left == 1: + # Trigger auto-renew via SubscriptionService (wired in at factory) + try: + subscription_service = getattr(self, "subscription_service", None) + if subscription_service: + async with self.async_session_factory() as session: + from db.dal import subscription_dal + sub = await subscription_dal.get_active_subscription_by_user_id(session, user_id) + if sub and sub.auto_renew_enabled and sub.provider != 'tribute': + try: + ok = await subscription_service.charge_subscription_renewal(session, sub) + if ok: + await session.commit() + else: + await session.rollback() + except Exception: + await session.rollback() + logging.exception("Auto-renew attempt (24h) failed") + except Exception: + logging.exception("Auto-renew trigger (24h) failed pre-check") if days_left <= self.settings.SUBSCRIPTION_NOTIFY_DAYS_BEFORE: await self._send_message( user_id, diff --git a/bot/services/subscription_service.py b/bot/services/subscription_service.py index 3fb6c46..206eea7 100644 --- a/bot/services/subscription_service.py +++ b/bot/services/subscription_service.py @@ -509,6 +509,7 @@ class SubscriptionService: "traffic_limit_bytes": self.settings.user_traffic_limit_bytes, "provider": provider, "skip_notifications": provider == "tribute" and self.settings.TRIBUTE_SKIP_NOTIFICATIONS, + "auto_renew_enabled": True, } try: new_or_updated_sub = await subscription_dal.upsert_subscription( diff --git a/db/models.py b/db/models.py index c53e23a..cc36312 100644 --- a/db/models.py +++ b/db/models.py @@ -72,7 +72,7 @@ class Subscription(Base): last_notification_sent = Column(DateTime(timezone=True), nullable=True) provider = Column(String, nullable=True) skip_notifications = Column(Boolean, default=False) - auto_renew_enabled = Column(Boolean, default=False, index=True) + auto_renew_enabled = Column(Boolean, default=True, index=True) user = relationship("User", back_populates="subscriptions")