"""Single billing service module. Stripe is the only impl — no provider abstraction. Account row is canonical local state; Stripe is canonical remote state; the webhook handler bridges the two.""" from datetime import datetime, timezone, timedelta from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.subscription import Subscription TRIAL_DAYS = 14 class BillingService: @staticmethod async def start_trial(db: AsyncSession, account_id) -> Subscription: """Idempotent. Creates a trialing Subscription on Pro for the account if one doesn't exist; otherwise returns the existing row.""" result = await db.execute( select(Subscription).where(Subscription.account_id == account_id) ) existing = result.scalar_one_or_none() if existing is not None: return existing sub = Subscription( account_id=account_id, plan="pro", status="trialing", current_period_start=datetime.now(timezone.utc), current_period_end=datetime.now(timezone.utc) + timedelta(days=TRIAL_DAYS), ) db.add(sub) await db.commit() await db.refresh(sub) return sub