feat: complete subscription billing system phases 6-10

Phase 6 - Database-driven tiers:
- Update subscription_service to query database first with legacy fallback
- Add get_tier_info() db parameter and _get_tier_from_legacy() method

Phase 7 - Platform health integration:
- Add get_subscription_capacity() for theoretical vs actual capacity
- Include subscription capacity in full health report

Phase 8 - Background subscription tasks:
- Add reset_period_counters() for billing period resets
- Add check_trial_expirations() for trial management
- Add sync_stripe_status() for Stripe synchronization
- Add cleanup_stale_subscriptions() for maintenance
- Add capture_capacity_snapshot() for daily metrics

Phase 10 - Capacity planning & forecasting:
- Add CapacitySnapshot model for historical tracking
- Create capacity_forecast_service with growth trends
- Add /subscription-capacity, /trends, /recommendations endpoints
- Add /snapshot endpoint for manual captures

Also includes billing API enhancements from phase 4:
- Add upcoming-invoice, change-tier, addon purchase/cancel endpoints
- Add UsageSummary schema for billing page
- Enhance billing.js with addon management functions

🤖 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-26 20:51:13 +01:00
parent b717c23787
commit c6e7f4087f
20 changed files with 1895 additions and 29 deletions

View File

@@ -251,6 +251,19 @@ class StripeService:
logger.info(f"Reactivated Stripe subscription {subscription_id}")
return subscription
def cancel_subscription_item(self, subscription_item_id: str) -> None:
"""
Cancel a subscription item (used for add-ons).
Args:
subscription_item_id: Stripe subscription item ID
"""
if not self.is_configured:
raise ValueError("Stripe is not configured")
stripe.SubscriptionItem.delete(subscription_item_id)
logger.info(f"Cancelled Stripe subscription item {subscription_item_id}")
# =========================================================================
# Checkout & Portal
# =========================================================================
@@ -263,6 +276,8 @@ class StripeService:
success_url: str,
cancel_url: str,
trial_days: int | None = None,
quantity: int = 1,
metadata: dict | None = None,
) -> stripe.checkout.Session:
"""
Create a Stripe Checkout session for subscription signup.
@@ -274,6 +289,8 @@ class StripeService:
success_url: URL to redirect on success
cancel_url: URL to redirect on cancel
trial_days: Optional trial period
quantity: Number of items (default 1)
metadata: Additional metadata to store
Returns:
Stripe Checkout Session object
@@ -311,16 +328,21 @@ class StripeService:
subscription.stripe_customer_id = customer_id
db.flush()
# Build metadata
session_metadata = {
"vendor_id": str(vendor.id),
"vendor_code": vendor.vendor_code,
}
if metadata:
session_metadata.update(metadata)
session_data = {
"customer": customer_id,
"line_items": [{"price": price_id, "quantity": 1}],
"line_items": [{"price": price_id, "quantity": quantity}],
"mode": "subscription",
"success_url": success_url,
"cancel_url": cancel_url,
"metadata": {
"vendor_id": str(vendor.id),
"vendor_code": vendor.vendor_code,
},
"metadata": session_metadata,
}
if trial_days: