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