# app/modules/billing/definition.py """ Billing module definition. Defines the billing module including its features, menu items, route configurations, and scheduled tasks. """ import logging from typing import Any from app.modules.base import ( MenuItemDefinition, MenuSectionDefinition, ModuleDefinition, PermissionDefinition, ScheduledTask, ) from app.modules.enums import FrontendType logger = logging.getLogger(__name__) # ============================================================================= # Context Providers # ============================================================================= def _get_platform_context(request: Any, db: Any, platform: Any) -> dict[str, Any]: """ Provide billing context for platform/marketing pages. Returns pricing tier data for the marketing pricing page. """ from app.core.config import settings from app.modules.billing.models import SubscriptionTier, TierCode tiers_db = ( db.query(SubscriptionTier) .filter( SubscriptionTier.is_active == True, # noqa: E712 SubscriptionTier.is_public == True, # noqa: E712 ) .order_by(SubscriptionTier.display_order) .all() ) tiers = [] for tier in tiers_db: feature_codes = sorted(tier.get_feature_codes()) tiers.append({ "code": tier.code, "name": tier.name, "price_monthly": tier.price_monthly_cents / 100, "price_annual": (tier.price_annual_cents / 100) if tier.price_annual_cents else None, "feature_codes": feature_codes, "products_limit": tier.get_limit_for_feature("products_limit"), "orders_per_month": tier.get_limit_for_feature("orders_per_month"), "team_members": tier.get_limit_for_feature("team_members"), "is_popular": tier.code == TierCode.PROFESSIONAL.value, "is_enterprise": tier.code == TierCode.ENTERPRISE.value, }) return { "tiers": tiers, "trial_days": settings.stripe_trial_days, "stripe_publishable_key": settings.stripe_publishable_key, } # ============================================================================= # Router Lazy Imports # ============================================================================= def _get_admin_router(): """Lazy import of admin router to avoid circular imports.""" from app.modules.billing.routes.api.admin import admin_router return admin_router def _get_store_router(): """Lazy import of store router to avoid circular imports.""" from app.modules.billing.routes.api.store import store_router return store_router def _get_metrics_provider(): """Lazy import of metrics provider to avoid circular imports.""" from app.modules.billing.services.billing_metrics import billing_metrics_provider return billing_metrics_provider def _get_feature_provider(): """Lazy import of feature provider to avoid circular imports.""" from app.modules.billing.services.billing_features import billing_feature_provider return billing_feature_provider # Billing module definition billing_module = ModuleDefinition( code="billing", name="Billing & Subscriptions", description=( "Core subscription management, tier limits, store billing, and invoice history. " "Provides tier-based feature gating used throughout the platform. " "Uses the payments module for actual payment processing." ), version="1.0.0", requires=["payments"], # Depends on payments module (also core) for payment processing features=[ "subscription_management", # Manage subscription tiers "billing_history", # View invoices and payment history "invoice_generation", # Generate and download invoices "subscription_analytics", # Subscription stats and metrics "trial_management", # Manage store trial periods "limit_overrides", # Override tier limits per store ], # Module-driven permissions permissions=[ PermissionDefinition( id="billing.view_tiers", label_key="billing.permissions.view_tiers", description_key="billing.permissions.view_tiers_desc", category="billing", ), PermissionDefinition( id="billing.manage_tiers", label_key="billing.permissions.manage_tiers", description_key="billing.permissions.manage_tiers_desc", category="billing", ), PermissionDefinition( id="billing.view_subscriptions", label_key="billing.permissions.view_subscriptions", description_key="billing.permissions.view_subscriptions_desc", category="billing", ), PermissionDefinition( id="billing.manage_subscriptions", label_key="billing.permissions.manage_subscriptions", description_key="billing.permissions.manage_subscriptions_desc", category="billing", ), PermissionDefinition( id="billing.view_invoices", label_key="billing.permissions.view_invoices", description_key="billing.permissions.view_invoices_desc", category="billing", ), ], menu_items={ FrontendType.ADMIN: [ "subscription-tiers", # Manage tier definitions "subscriptions", # View/manage store subscriptions "billing-history", # View all invoices ], FrontendType.STORE: [ "billing", # Store billing dashboard "invoices", # Store invoice history ], FrontendType.MERCHANT: [ "subscriptions", # Merchant subscriptions "invoices", # Merchant billing history ], }, # New module-driven menu definitions menus={ FrontendType.ADMIN: [ MenuSectionDefinition( id="billing", label_key="billing.menu.billing_subscriptions", icon="credit-card", order=50, items=[ MenuItemDefinition( id="subscription-tiers", label_key="billing.menu.subscription_tiers", icon="tag", route="/admin/subscription-tiers", order=10, ), MenuItemDefinition( id="subscriptions", label_key="billing.menu.merchant_subscriptions", icon="credit-card", route="/admin/subscriptions", order=20, ), MenuItemDefinition( id="billing-history", label_key="billing.menu.billing_history", icon="document-text", route="/admin/billing-history", order=30, ), ], ), ], FrontendType.MERCHANT: [ MenuSectionDefinition( id="billing", label_key="billing.menu.billing_subscriptions", icon="credit-card", order=50, items=[ MenuItemDefinition( id="subscriptions", label_key="billing.menu.subscriptions", icon="clipboard-list", route="/merchants/billing/subscriptions", order=10, is_mandatory=True, ), MenuItemDefinition( id="invoices", label_key="billing.menu.billing_history", icon="currency-euro", route="/merchants/billing/invoices", order=20, ), ], ), ], FrontendType.STORE: [ MenuSectionDefinition( id="sales", label_key="billing.menu.sales_orders", icon="currency-euro", order=20, items=[ MenuItemDefinition( id="invoices", label_key="billing.menu.invoices", icon="currency-euro", route="/store/{store_code}/invoices", order=30, requires_permission="billing.view_invoices", ), ], ), MenuSectionDefinition( id="account", label_key="billing.menu.account_settings", icon="credit-card", order=900, items=[ MenuItemDefinition( id="billing", label_key="billing.menu.billing", icon="credit-card", route="/store/{store_code}/billing", order=30, requires_permission="billing.view_subscriptions", ), ], ), ], }, is_core=True, # Core module - tier limits and subscription management are fundamental # Context providers for dynamic page context context_providers={ FrontendType.PLATFORM: _get_platform_context, }, # ========================================================================= # Self-Contained Module Configuration # ========================================================================= is_self_contained=True, services_path="app.modules.billing.services", models_path="app.modules.billing.models", schemas_path="app.modules.billing.schemas", exceptions_path="app.modules.billing.exceptions", tasks_path="app.modules.billing.tasks", migrations_path="migrations", # ========================================================================= # Scheduled Tasks # ========================================================================= scheduled_tasks=[ ScheduledTask( name="billing.reset_period_counters", task="app.modules.billing.tasks.subscription.reset_period_counters", schedule="5 0 * * *", # Daily at 00:05 options={"queue": "scheduled"}, ), ScheduledTask( name="billing.check_trial_expirations", task="app.modules.billing.tasks.subscription.check_trial_expirations", schedule="0 1 * * *", # Daily at 01:00 options={"queue": "scheduled"}, ), ScheduledTask( name="billing.sync_stripe_status", task="app.modules.billing.tasks.subscription.sync_stripe_status", schedule="30 * * * *", # Hourly at :30 options={"queue": "scheduled"}, ), ScheduledTask( name="billing.cleanup_stale_subscriptions", task="app.modules.billing.tasks.subscription.cleanup_stale_subscriptions", schedule="0 3 * * 0", # Weekly on Sunday at 03:00 options={"queue": "scheduled"}, ), ], # Feature provider for feature flags feature_provider=_get_feature_provider, # Metrics provider for subscription metrics metrics_provider=_get_metrics_provider, ) def get_billing_module_with_routers() -> ModuleDefinition: """ Get billing module with routers attached. This function attaches the routers lazily to avoid circular imports during module initialization. """ billing_module.admin_router = _get_admin_router() billing_module.store_router = _get_store_router() return billing_module __all__ = ["billing_module", "get_billing_module_with_routers"]