Spaces:
Running
Running
| """Author RAG — Billing webhook routes (R-173).""" | |
| import json | |
| from fastapi import APIRouter, Header, HTTPException, Request | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from app.config import get_settings | |
| from app.dependencies import get_db | |
| from app.services.billing_service import BillingService, verify_stripe_signature | |
| from fastapi import Depends | |
| router = APIRouter() | |
| cfg = get_settings() | |
| async def stripe_webhook( | |
| request: Request, | |
| stripe_signature: str = Header(default="", alias="Stripe-Signature"), | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """Handle Stripe checkout.session.completed events.""" | |
| secret = getattr(cfg, "STRIPE_WEBHOOK_SECRET", None) or "" | |
| body = await request.body() | |
| if secret and not verify_stripe_signature(body, stripe_signature, secret): | |
| raise HTTPException(400, "Invalid webhook signature") | |
| event = json.loads(body) | |
| if event.get("type") != "checkout.session.completed": | |
| return {"received": True, "handled": False} | |
| session = event.get("data", {}).get("object", {}) | |
| email = (session.get("customer_details") or {}).get("email") or session.get("customer_email") | |
| plan = (session.get("metadata") or {}).get("plan", "monthly") | |
| if not email: | |
| raise HTTPException(400, "Missing customer email") | |
| result = await BillingService(db).grant_from_checkout( | |
| customer_email=email, | |
| plan=plan, | |
| external_id=session.get("id", ""), | |
| ) | |
| return {"received": True, "handled": True, **result} | |