37 lines
1.2 KiB
Python
37 lines
1.2 KiB
Python
"""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
|