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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user