- 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>
96 lines
2.9 KiB
Python
96 lines
2.9 KiB
Python
# 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",
|
|
)
|