50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
import logging
|
|
from fastapi import APIRouter, Request, HTTPException, Depends
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.admin_database import get_admin_db
|
|
from app.core.config import settings
|
|
from app.services.billing import BillingService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
|
|
|
|
|
|
@router.post("/stripe")
|
|
async def stripe_webhook(
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_admin_db),
|
|
):
|
|
"""Stripe webhook handler. Public endpoint; signature verification is the
|
|
only gate. Idempotency via stripe_events table.
|
|
|
|
Returns 200 even when Stripe is not configured — keeps the receiver
|
|
permissive for local dev.
|
|
"""
|
|
if not settings.stripe_enabled or not settings.STRIPE_WEBHOOK_SECRET:
|
|
return {"status": "ok", "message": "Stripe not configured, event ignored"}
|
|
|
|
payload = await request.body()
|
|
sig_header = request.headers.get("stripe-signature")
|
|
if not sig_header:
|
|
raise HTTPException(status_code=400, detail="Missing stripe-signature header")
|
|
|
|
try:
|
|
import stripe
|
|
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
event = stripe.Webhook.construct_event(
|
|
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
|
|
)
|
|
except Exception as e:
|
|
logger.warning("stripe webhook bad signature: %s", e)
|
|
raise HTTPException(status_code=400, detail="Invalid signature")
|
|
|
|
applied = await BillingService.apply_subscription_event(
|
|
db,
|
|
event_id=event["id"],
|
|
event_type=event["type"],
|
|
payload={"data": event["data"]},
|
|
)
|
|
return {"status": "ok", "applied": applied}
|