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:
2025-12-25 20:29:44 +01:00
parent b98c7c553b
commit 9d8d5e7138
27 changed files with 4558 additions and 23 deletions

View File

@@ -20,11 +20,11 @@ from app.core.permissions import get_preset_permissions
from app.exceptions import (
CannotRemoveOwnerException,
InvalidInvitationTokenException,
MaxTeamMembersReachedException,
TeamInvitationAlreadyAcceptedException,
TeamMemberAlreadyExistsException,
UserNotFoundException,
)
from app.services.subscription_service import TierLimitExceededException
from middleware.auth import AuthManager
from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
@@ -37,7 +37,6 @@ class VendorTeamService:
def __init__(self):
self.auth_manager = AuthManager()
self.max_team_members = 50 # Configure as needed
def invite_team_member(
self,
@@ -68,21 +67,10 @@ class VendorTeamService:
Dict with invitation details
"""
try:
# Check team size limit
current_team_size = (
db.query(VendorUser)
.filter(
VendorUser.vendor_id == vendor.id,
VendorUser.is_active == True,
)
.count()
)
# Check team size limit from subscription
from app.services.subscription_service import subscription_service
if current_team_size >= self.max_team_members:
raise MaxTeamMembersReachedException(
self.max_team_members,
vendor.vendor_code,
)
subscription_service.check_team_limit(db, vendor.id)
# Check if user already exists
user = db.query(User).filter(User.email == email).first()
@@ -187,7 +175,7 @@ class VendorTeamService:
"existing_user": user.is_active,
}
except (TeamMemberAlreadyExistsException, MaxTeamMembersReachedException):
except (TeamMemberAlreadyExistsException, TierLimitExceededException):
raise
except Exception as e:
logger.error(f"Error inviting team member: {str(e)}")