Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""
|
|
Webhook endpoints for external service integrations.
|
|
|
|
- Stripe payment webhooks
|
|
- Future: Other payment providers, notification services, etc.
|
|
"""
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from fastapi import APIRouter, HTTPException, Request, Header, status
|
|
|
|
from app.database import get_db
|
|
from app.services.stripe_service import StripeService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/stripe/test")
|
|
async def test_stripe_webhook():
|
|
"""
|
|
Test endpoint to verify webhook route is accessible.
|
|
|
|
Use this to verify the webhook URL is correct.
|
|
The actual Stripe webhook should POST to /api/v1/webhooks/stripe
|
|
"""
|
|
return {
|
|
"status": "ok",
|
|
"message": "Stripe webhook endpoint is accessible",
|
|
"endpoint": "/api/v1/webhooks/stripe",
|
|
"method": "POST",
|
|
"stripe_configured": StripeService.is_configured(),
|
|
"webhook_secret_set": bool(os.getenv("STRIPE_WEBHOOK_SECRET")),
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
}
|
|
|
|
|
|
@router.post("/stripe")
|
|
async def stripe_webhook(
|
|
request: Request,
|
|
stripe_signature: str = Header(None, alias="Stripe-Signature"),
|
|
):
|
|
"""
|
|
Handle Stripe webhook events.
|
|
|
|
This endpoint receives events from Stripe when:
|
|
- Payment succeeds or fails
|
|
- Subscription is updated or cancelled
|
|
- Invoice is created or paid
|
|
|
|
The webhook must be configured in Stripe Dashboard to point to:
|
|
https://pounce.ch/api/v1/webhooks/stripe
|
|
|
|
Required Header:
|
|
- Stripe-Signature: Stripe's webhook signature for verification
|
|
"""
|
|
logger.info("🔔 Stripe webhook received")
|
|
|
|
if not stripe_signature:
|
|
logger.error("❌ Missing Stripe-Signature header")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Missing Stripe-Signature header",
|
|
)
|
|
|
|
if not StripeService.is_configured():
|
|
logger.error("❌ Stripe not configured")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Stripe not configured",
|
|
)
|
|
|
|
# Get raw body for signature verification
|
|
payload = await request.body()
|
|
|
|
logger.info(f" Payload size: {len(payload)} bytes")
|
|
logger.info(f" Signature: {stripe_signature[:50]}...")
|
|
|
|
try:
|
|
async for db in get_db():
|
|
result = await StripeService.handle_webhook(
|
|
payload=payload,
|
|
sig_header=stripe_signature,
|
|
db=db,
|
|
)
|
|
logger.info(f"✅ Webhook processed successfully: {result}")
|
|
return result
|
|
|
|
except ValueError as e:
|
|
logger.error(f"❌ Webhook validation error: {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e),
|
|
)
|
|
except Exception as e:
|
|
logger.exception(f"❌ Webhook processing error: {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Webhook processing failed",
|
|
)
|
|
|