| """ |
| Streamlit integration for subscription services. |
| """ |
| import os |
| import asyncio |
| import pandas as pd |
| from typing import List, Dict, Any, Optional, Union |
| from datetime import datetime |
|
|
| import stripe |
| from sqlalchemy.ext.asyncio import AsyncSession |
|
|
| from src.models.subscription import ( |
| SubscriptionPlan, UserSubscription, PaymentHistory, |
| SubscriptionTier, BillingPeriod, SubscriptionStatus, PaymentStatus |
| ) |
| from src.api.services.subscription_service import ( |
| get_subscription_plans, get_subscription_plan_by_id, get_subscription_plan_by_tier, |
| create_subscription_plan, update_subscription_plan, |
| get_user_subscription, get_user_subscription_by_id, |
| create_user_subscription, cancel_user_subscription |
| ) |
|
|
| from src.streamlit_database import run_async, get_db_session |
|
|
| |
| STRIPE_PUBLISHABLE_KEY = os.environ.get("STRIPE_PUBLISHABLE_KEY") |
|
|
| def get_subscription_plans_df(active_only: bool = True) -> pd.DataFrame: |
| """ |
| Get all subscription plans as a DataFrame. |
| |
| Args: |
| active_only: If True, only return active plans |
| |
| Returns: |
| DataFrame containing subscription plans |
| """ |
| session = get_db_session() |
| |
| if not session: |
| return pd.DataFrame() |
| |
| plans = run_async(get_subscription_plans(session, active_only)) |
| |
| if not plans: |
| return pd.DataFrame() |
| |
| data = [] |
| for plan in plans: |
| data.append({ |
| "id": plan.id, |
| "name": plan.name, |
| "tier": plan.tier.value if plan.tier else None, |
| "description": plan.description, |
| "price_monthly": plan.price_monthly, |
| "price_annually": plan.price_annually, |
| "max_alerts": plan.max_alerts, |
| "max_reports": plan.max_reports, |
| "max_searches_per_day": plan.max_searches_per_day, |
| "max_monitoring_keywords": plan.max_monitoring_keywords, |
| "max_data_retention_days": plan.max_data_retention_days, |
| "supports_api_access": plan.supports_api_access, |
| "supports_live_feed": plan.supports_live_feed, |
| "supports_dark_web_monitoring": plan.supports_dark_web_monitoring, |
| "supports_export": plan.supports_export, |
| "supports_advanced_analytics": plan.supports_advanced_analytics, |
| "is_active": plan.is_active, |
| }) |
| |
| return pd.DataFrame(data) |
|
|
|
|
| def get_subscription_plan(plan_id: int) -> Optional[Dict[str, Any]]: |
| """ |
| Get a subscription plan by ID. |
| |
| Args: |
| plan_id: ID of the plan to get |
| |
| Returns: |
| Dictionary containing plan details or None if not found |
| """ |
| session = get_db_session() |
| |
| if not session: |
| return None |
| |
| plan = run_async(get_subscription_plan_by_id(session, plan_id)) |
| |
| if not plan: |
| return None |
| |
| return { |
| "id": plan.id, |
| "name": plan.name, |
| "tier": plan.tier.value if plan.tier else None, |
| "description": plan.description, |
| "price_monthly": plan.price_monthly, |
| "price_annually": plan.price_annually, |
| "max_alerts": plan.max_alerts, |
| "max_reports": plan.max_reports, |
| "max_searches_per_day": plan.max_searches_per_day, |
| "max_monitoring_keywords": plan.max_monitoring_keywords, |
| "max_data_retention_days": plan.max_data_retention_days, |
| "supports_api_access": plan.supports_api_access, |
| "supports_live_feed": plan.supports_live_feed, |
| "supports_dark_web_monitoring": plan.supports_dark_web_monitoring, |
| "supports_export": plan.supports_export, |
| "supports_advanced_analytics": plan.supports_advanced_analytics, |
| "is_active": plan.is_active, |
| "stripe_product_id": plan.stripe_product_id, |
| "stripe_monthly_price_id": plan.stripe_monthly_price_id, |
| "stripe_annual_price_id": plan.stripe_annual_price_id, |
| } |
|
|
|
|
| def get_user_current_subscription(user_id: int) -> Optional[Dict[str, Any]]: |
| """ |
| Get a user's current subscription. |
| |
| Args: |
| user_id: ID of the user |
| |
| Returns: |
| Dictionary containing subscription details or None if not found |
| """ |
| session = get_db_session() |
| |
| if not session: |
| return None |
| |
| subscription = run_async(get_user_subscription(session, user_id)) |
| |
| if not subscription: |
| return None |
| |
| plan = subscription.plan |
| |
| return { |
| "id": subscription.id, |
| "user_id": subscription.user_id, |
| "plan_id": subscription.plan_id, |
| "plan_name": plan.name if plan else None, |
| "plan_tier": plan.tier.value if plan and plan.tier else None, |
| "status": subscription.status.value if subscription.status else None, |
| "billing_period": subscription.billing_period.value if subscription.billing_period else None, |
| "current_period_start": subscription.current_period_start, |
| "current_period_end": subscription.current_period_end, |
| "stripe_subscription_id": subscription.stripe_subscription_id, |
| "stripe_customer_id": subscription.stripe_customer_id, |
| "created_at": subscription.created_at, |
| "canceled_at": subscription.canceled_at, |
| } |
|
|
|
|
| def create_new_subscription_plan( |
| name: str, |
| tier: str, |
| description: str, |
| price_monthly: float, |
| price_annually: float, |
| max_alerts: int = 10, |
| max_reports: int = 5, |
| max_searches_per_day: int = 20, |
| max_monitoring_keywords: int = 10, |
| max_data_retention_days: int = 30, |
| supports_api_access: bool = False, |
| supports_live_feed: bool = False, |
| supports_dark_web_monitoring: bool = False, |
| supports_export: bool = False, |
| supports_advanced_analytics: bool = False, |
| create_stripe_product: bool = True |
| ) -> Optional[Dict[str, Any]]: |
| """ |
| Create a new subscription plan. |
| |
| Args: |
| name: Name of the plan |
| tier: Tier of the plan (must be one of "free", "basic", "professional", "enterprise") |
| description: Description of the plan |
| price_monthly: Monthly price of the plan |
| price_annually: Annual price of the plan |
| max_alerts: Maximum number of alerts allowed |
| max_reports: Maximum number of reports allowed |
| max_searches_per_day: Maximum number of searches per day |
| max_monitoring_keywords: Maximum number of monitoring keywords |
| max_data_retention_days: Maximum number of days to retain data |
| supports_api_access: Whether the plan supports API access |
| supports_live_feed: Whether the plan supports live feed |
| supports_dark_web_monitoring: Whether the plan supports dark web monitoring |
| supports_export: Whether the plan supports data export |
| supports_advanced_analytics: Whether the plan supports advanced analytics |
| create_stripe_product: Whether to create a Stripe product for this plan |
| |
| Returns: |
| Dictionary containing plan details or None if creation failed |
| """ |
| session = get_db_session() |
| |
| if not session: |
| return None |
| |
| try: |
| |
| tier_enum = SubscriptionTier(tier.lower()) |
| except ValueError: |
| return None |
| |
| plan = run_async(create_subscription_plan( |
| db=session, |
| name=name, |
| tier=tier_enum, |
| description=description, |
| price_monthly=price_monthly, |
| price_annually=price_annually, |
| max_alerts=max_alerts, |
| max_reports=max_reports, |
| max_searches_per_day=max_searches_per_day, |
| max_monitoring_keywords=max_monitoring_keywords, |
| max_data_retention_days=max_data_retention_days, |
| supports_api_access=supports_api_access, |
| supports_live_feed=supports_live_feed, |
| supports_dark_web_monitoring=supports_dark_web_monitoring, |
| supports_export=supports_export, |
| supports_advanced_analytics=supports_advanced_analytics, |
| create_stripe_product=create_stripe_product |
| )) |
| |
| if not plan: |
| return None |
| |
| return { |
| "id": plan.id, |
| "name": plan.name, |
| "tier": plan.tier.value if plan.tier else None, |
| "description": plan.description, |
| "price_monthly": plan.price_monthly, |
| "price_annually": plan.price_annually, |
| "max_alerts": plan.max_alerts, |
| "max_reports": plan.max_reports, |
| "max_searches_per_day": plan.max_searches_per_day, |
| "max_monitoring_keywords": plan.max_monitoring_keywords, |
| "max_data_retention_days": plan.max_data_retention_days, |
| "supports_api_access": plan.supports_api_access, |
| "supports_live_feed": plan.supports_live_feed, |
| "supports_dark_web_monitoring": plan.supports_dark_web_monitoring, |
| "supports_export": plan.supports_export, |
| "supports_advanced_analytics": plan.supports_advanced_analytics, |
| "is_active": plan.is_active, |
| "stripe_product_id": plan.stripe_product_id, |
| "stripe_monthly_price_id": plan.stripe_monthly_price_id, |
| "stripe_annual_price_id": plan.stripe_annual_price_id, |
| } |
|
|
|
|
| def subscribe_user_to_plan( |
| user_id: int, |
| plan_id: int, |
| billing_period: str = "monthly", |
| create_stripe_subscription: bool = True, |
| payment_method_id: Optional[str] = None |
| ) -> Optional[Dict[str, Any]]: |
| """ |
| Subscribe a user to a plan. |
| |
| Args: |
| user_id: ID of the user |
| plan_id: ID of the plan |
| billing_period: Billing period ("monthly" or "annually") |
| create_stripe_subscription: Whether to create a Stripe subscription |
| payment_method_id: ID of the payment method to use (required if create_stripe_subscription is True) |
| |
| Returns: |
| Dictionary containing subscription details or None if creation failed |
| """ |
| session = get_db_session() |
| |
| if not session: |
| return None |
| |
| try: |
| |
| billing_period_enum = BillingPeriod(billing_period.lower()) |
| except ValueError: |
| return None |
| |
| subscription = run_async(create_user_subscription( |
| db=session, |
| user_id=user_id, |
| plan_id=plan_id, |
| billing_period=billing_period_enum, |
| create_stripe_subscription=create_stripe_subscription, |
| payment_method_id=payment_method_id |
| )) |
| |
| if not subscription: |
| return None |
| |
| plan = subscription.plan |
| |
| return { |
| "id": subscription.id, |
| "user_id": subscription.user_id, |
| "plan_id": subscription.plan_id, |
| "plan_name": plan.name if plan else None, |
| "plan_tier": plan.tier.value if plan and plan.tier else None, |
| "status": subscription.status.value if subscription.status else None, |
| "billing_period": subscription.billing_period.value if subscription.billing_period else None, |
| "current_period_start": subscription.current_period_start, |
| "current_period_end": subscription.current_period_end, |
| "stripe_subscription_id": subscription.stripe_subscription_id, |
| "stripe_customer_id": subscription.stripe_customer_id, |
| "created_at": subscription.created_at, |
| } |
|
|
|
|
| def cancel_subscription( |
| subscription_id: int, |
| cancel_stripe_subscription: bool = True |
| ) -> Optional[Dict[str, Any]]: |
| """ |
| Cancel a subscription. |
| |
| Args: |
| subscription_id: ID of the subscription to cancel |
| cancel_stripe_subscription: Whether to cancel the Stripe subscription |
| |
| Returns: |
| Dictionary containing subscription details or None if cancellation failed |
| """ |
| session = get_db_session() |
| |
| if not session: |
| return None |
| |
| subscription = run_async(cancel_user_subscription( |
| db=session, |
| subscription_id=subscription_id, |
| cancel_stripe_subscription=cancel_stripe_subscription |
| )) |
| |
| if not subscription: |
| return None |
| |
| plan = subscription.plan |
| |
| return { |
| "id": subscription.id, |
| "user_id": subscription.user_id, |
| "plan_id": subscription.plan_id, |
| "plan_name": plan.name if plan else None, |
| "plan_tier": plan.tier.value if plan and plan.tier else None, |
| "status": subscription.status.value if subscription.status else None, |
| "billing_period": subscription.billing_period.value if subscription.billing_period else None, |
| "current_period_start": subscription.current_period_start, |
| "current_period_end": subscription.current_period_end, |
| "stripe_subscription_id": subscription.stripe_subscription_id, |
| "stripe_customer_id": subscription.stripe_customer_id, |
| "created_at": subscription.created_at, |
| "canceled_at": subscription.canceled_at, |
| } |
|
|
|
|
| def initialize_default_plans(): |
| """Initialize default subscription plans if they don't exist.""" |
| |
| plans_df = get_subscription_plans_df(active_only=False) |
| |
| if not plans_df.empty: |
| |
| return |
| |
| |
| |
| create_new_subscription_plan( |
| name="Free", |
| tier="free", |
| description="Basic access to the platform with limited features. Perfect for individuals or small teams starting with OSINT.", |
| price_monthly=0.0, |
| price_annually=0.0, |
| max_alerts=5, |
| max_reports=2, |
| max_searches_per_day=10, |
| max_monitoring_keywords=5, |
| max_data_retention_days=7, |
| supports_api_access=False, |
| supports_live_feed=False, |
| supports_dark_web_monitoring=False, |
| supports_export=False, |
| supports_advanced_analytics=False, |
| create_stripe_product=False |
| ) |
| |
| |
| create_new_subscription_plan( |
| name="Basic", |
| tier="basic", |
| description="Enhanced access with more features. Ideal for small businesses and security teams requiring regular threat intelligence.", |
| price_monthly=29.99, |
| price_annually=299.99, |
| max_alerts=20, |
| max_reports=10, |
| max_searches_per_day=50, |
| max_monitoring_keywords=25, |
| max_data_retention_days=30, |
| supports_api_access=False, |
| supports_live_feed=True, |
| supports_dark_web_monitoring=True, |
| supports_export=True, |
| supports_advanced_analytics=False |
| ) |
| |
| |
| create_new_subscription_plan( |
| name="Professional", |
| tier="professional", |
| description="Comprehensive access for professional users. Perfect for medium-sized organizations requiring advanced threat intelligence capabilities.", |
| price_monthly=99.99, |
| price_annually=999.99, |
| max_alerts=100, |
| max_reports=50, |
| max_searches_per_day=200, |
| max_monitoring_keywords=100, |
| max_data_retention_days=90, |
| supports_api_access=True, |
| supports_live_feed=True, |
| supports_dark_web_monitoring=True, |
| supports_export=True, |
| supports_advanced_analytics=True |
| ) |
| |
| |
| create_new_subscription_plan( |
| name="Enterprise", |
| tier="enterprise", |
| description="Full access to all features with unlimited usage. Designed for large organizations with sophisticated threat intelligence requirements.", |
| price_monthly=249.99, |
| price_annually=2499.99, |
| max_alerts=0, |
| max_reports=0, |
| max_searches_per_day=0, |
| max_monitoring_keywords=0, |
| max_data_retention_days=365, |
| supports_api_access=True, |
| supports_live_feed=True, |
| supports_dark_web_monitoring=True, |
| supports_export=True, |
| supports_advanced_analytics=True |
| ) |