feat: implement tiered subscription plans with usage-based pricing and dynamic plan management

Add multi-tier subscription system (Starter, Professional, Enterprise) with per-tier storage and bandwidth pricing, replace static free-tier limits with nullable usage-based fields in SubscriptionPlan model, introduce subscribe/unsubscribe API endpoints with plan validation, update invoice generator to calculate costs based on user's active plan tier, and refactor pricing page to load plans dynamically from backend API instead of hardcoded HTML.
This commit is contained in:
2026-01-22 15:40:16 +00:00
parent a34668941f
commit 619e320780
10 changed files with 445 additions and 148 deletions
+43 -8
View File
@@ -22,14 +22,49 @@ class InvoiceGenerator:
pricing = await PricingConfig.all()
pricing_dict = {p.config_key: p.config_value for p in pricing}
storage_price_per_gb = pricing_dict.get(
"storage_per_gb_month", Decimal("0.0045")
)
bandwidth_price_per_gb = pricing_dict.get(
"bandwidth_egress_per_gb", Decimal("0.009")
)
free_storage_gb = pricing_dict.get("free_tier_storage_gb", Decimal("15"))
free_bandwidth_gb = pricing_dict.get("free_tier_bandwidth_gb", Decimal("15"))
# Get user's subscription plan
user_subscription = await UserSubscription.get_or_none(user=user)
plan_name = "starter" # Default to starter
if user_subscription and user_subscription.plan:
plan_name = user_subscription.plan.name
# Set pricing based on subscription tier
if plan_name == "professional":
storage_price_per_gb = pricing_dict.get(
"storage_per_gb_month_professional", Decimal("0.004")
)
bandwidth_price_per_gb = pricing_dict.get(
"bandwidth_egress_per_gb_professional", Decimal("0.007")
)
elif plan_name == "enterprise":
storage_price_per_gb = pricing_dict.get(
"storage_per_gb_month_enterprise", Decimal("0.003")
)
bandwidth_price_per_gb = pricing_dict.get(
"bandwidth_egress_per_gb_enterprise", Decimal("0.005")
)
# Check if user meets minimum storage requirement for enterprise pricing
enterprise_min_tb = pricing_dict.get("enterprise_min_storage_tb", Decimal("10"))
storage_gb = Decimal(str(usage["storage_gb_avg"]))
if storage_gb < (enterprise_min_tb * Decimal("1024")): # Convert TB to GB
# User doesn't meet enterprise minimum, fall back to professional
storage_price_per_gb = pricing_dict.get(
"storage_per_gb_month_professional", Decimal("0.004")
)
bandwidth_price_per_gb = pricing_dict.get(
"bandwidth_egress_per_gb_professional", Decimal("0.007")
)
else: # starter
storage_price_per_gb = pricing_dict.get(
"storage_per_gb_month_starter", Decimal("0.005")
)
bandwidth_price_per_gb = pricing_dict.get(
"bandwidth_egress_per_gb_starter", Decimal("0.008")
)
# No free tier - charge from first GB
free_storage_gb = Decimal("0")
free_bandwidth_gb = Decimal("0")
tax_rate = pricing_dict.get("tax_rate_default", Decimal("0"))
storage_gb = Decimal(str(usage["storage_gb_avg"]))
+2 -2
View File
@@ -6,8 +6,8 @@ class SubscriptionPlan(models.Model):
name = fields.CharField(max_length=100, unique=True)
display_name = fields.CharField(max_length=255)
description = fields.TextField(null=True)
storage_gb = fields.IntField()
bandwidth_gb = fields.IntField()
storage_gb = fields.IntField(null=True) # null means unlimited/usage-based
bandwidth_gb = fields.IntField(null=True) # null means unlimited/usage-based
price_monthly = fields.DecimalField(max_digits=10, decimal_places=2)
price_yearly = fields.DecimalField(max_digits=10, decimal_places=2, null=True)
stripe_price_id = fields.CharField(max_length=255, null=True)