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:
95
app/exceptions/billing.py
Normal file
95
app/exceptions/billing.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# app/exceptions/billing.py
|
||||
"""
|
||||
Billing and subscription related exceptions.
|
||||
|
||||
This module provides exceptions for:
|
||||
- Payment system configuration issues
|
||||
- Subscription management errors
|
||||
- Tier-related errors
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ServiceUnavailableException
|
||||
|
||||
|
||||
class PaymentSystemNotConfiguredException(ServiceUnavailableException):
|
||||
"""Raised when the payment system (Stripe) is not configured."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(message="Payment system not configured")
|
||||
|
||||
|
||||
class TierNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a subscription tier is not found."""
|
||||
|
||||
def __init__(self, tier_code: str):
|
||||
super().__init__(
|
||||
resource_type="SubscriptionTier",
|
||||
identifier=tier_code,
|
||||
message=f"Subscription tier '{tier_code}' not found",
|
||||
error_code="TIER_NOT_FOUND",
|
||||
)
|
||||
self.tier_code = tier_code
|
||||
|
||||
|
||||
class StripePriceNotConfiguredException(BusinessLogicException):
|
||||
"""Raised when Stripe price is not configured for a tier."""
|
||||
|
||||
def __init__(self, tier_code: str):
|
||||
super().__init__(
|
||||
message=f"Stripe price not configured for tier '{tier_code}'",
|
||||
error_code="STRIPE_PRICE_NOT_CONFIGURED",
|
||||
details={"tier_code": tier_code},
|
||||
)
|
||||
self.tier_code = tier_code
|
||||
|
||||
|
||||
class NoActiveSubscriptionException(BusinessLogicException):
|
||||
"""Raised when no active subscription exists for an operation that requires one."""
|
||||
|
||||
def __init__(self, message: str = "No active subscription found"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="NO_ACTIVE_SUBSCRIPTION",
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionNotCancelledException(BusinessLogicException):
|
||||
"""Raised when trying to reactivate a subscription that is not cancelled."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Subscription is not cancelled",
|
||||
error_code="SUBSCRIPTION_NOT_CANCELLED",
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionAlreadyCancelledException(BusinessLogicException):
|
||||
"""Raised when trying to cancel an already cancelled subscription."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Subscription is already cancelled",
|
||||
error_code="SUBSCRIPTION_ALREADY_CANCELLED",
|
||||
)
|
||||
|
||||
|
||||
class InvalidWebhookSignatureException(BusinessLogicException):
|
||||
"""Raised when Stripe webhook signature verification fails."""
|
||||
|
||||
def __init__(self, message: str = "Invalid webhook signature"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INVALID_WEBHOOK_SIGNATURE",
|
||||
)
|
||||
|
||||
|
||||
class WebhookMissingSignatureException(BusinessLogicException):
|
||||
"""Raised when Stripe webhook is missing the signature header."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Missing Stripe-Signature header",
|
||||
error_code="WEBHOOK_MISSING_SIGNATURE",
|
||||
)
|
||||
Reference in New Issue
Block a user