feat: add subscription and billing system with Stripe integration
- Add database models for subscription tiers, vendor subscriptions, add-ons, billing history, and webhook events - Implement BillingService for subscription operations - Implement StripeService for Stripe API operations - Implement StripeWebhookHandler for webhook event processing - Add vendor billing API endpoints for subscription management - Create vendor billing page with Alpine.js frontend - Add limit enforcement for products and team members - Add billing exceptions for proper error handling - Create comprehensive unit tests (40 tests passing) - Add subscription billing documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,63 @@
|
||||
# External webhooks (Stripe, etc.
|
||||
# app/api/v1/shared/webhooks.py
|
||||
"""
|
||||
External webhook endpoints.
|
||||
|
||||
Handles webhooks from:
|
||||
- Stripe (payments and subscriptions)
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Header, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import InvalidWebhookSignatureException, WebhookMissingSignatureException
|
||||
from app.services.stripe_service import stripe_service
|
||||
from app.services.stripe_webhook_handler import stripe_webhook_handler
|
||||
|
||||
router = APIRouter(prefix="/webhooks")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.post("/stripe") # public - Stripe webhooks use signature verification
|
||||
async def stripe_webhook(
|
||||
request: Request,
|
||||
stripe_signature: str = Header(None, alias="Stripe-Signature"),
|
||||
):
|
||||
"""
|
||||
Handle Stripe webhook events.
|
||||
|
||||
Stripe sends events for:
|
||||
- Subscription lifecycle (created, updated, deleted)
|
||||
- Invoice and payment events
|
||||
- Checkout session completion
|
||||
|
||||
The endpoint verifies the webhook signature and processes events idempotently.
|
||||
"""
|
||||
if not stripe_signature:
|
||||
logger.warning("Stripe webhook received without signature")
|
||||
raise WebhookMissingSignatureException()
|
||||
|
||||
# Get raw body for signature verification
|
||||
payload = await request.body()
|
||||
|
||||
try:
|
||||
# Verify and construct event
|
||||
event = stripe_service.construct_event(payload, stripe_signature)
|
||||
except ValueError as e:
|
||||
logger.warning(f"Invalid Stripe webhook: {e}")
|
||||
raise InvalidWebhookSignatureException(str(e))
|
||||
|
||||
# Process the event
|
||||
db = next(get_db())
|
||||
try:
|
||||
result = stripe_webhook_handler.handle_event(db, event)
|
||||
return {"received": True, **result}
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing Stripe webhook: {e}")
|
||||
# Return 200 to prevent Stripe retries for processing errors
|
||||
# The event is marked as failed and can be retried manually
|
||||
return {"received": True, "error": str(e)}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user