feat(billing): add BillingService.start_trial; wire into /auth/register
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
36
backend/app/services/billing.py
Normal file
36
backend/app/services/billing.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user