KEY INSIGHT
Subscription billing requires mapping customer tiers to entitlements, tracking plan changes mid-cycle, and calculating prorated charges accurately.
Billing transforms usage data into invoices. A well-designed billing system handles recurring subscriptions, usage-based charges, plan changes, and credits.
Subscription tiers define what customers get access to. Each tier has a base price, included usage, and overage rates.
```python
from decimal import Decimal
from enum import Enum
class SubscriptionTier(Enum):
FREE = "free"
STARTER = "starter"
PROFESSIONAL = "professional"
ENTERPRISE = "enterprise"
@dataclass
class TierConfig:
name: str
monthly_price_kobo: int # Monthly subscription in kobo
included_tokens: int # Tokens included in base price
overage_rate_per_1k_kobo: int # Rate for tokens beyond included
max_api_keys: int
allowed_models: list[str]
support_level: str
SUBSCRIPTION_TIERS = {
SubscriptionTier.FREE: TierConfig(
name="Free",
monthly_price_kobo=0,
included_tokens=10000, # 10K tokens free
overage_rate_per_1k_kobo=50, # ₦0.50 per 1K overage
max_api_keys=2,
allowed_models=["gpt-3.5-turbo"],
support_level="community"
),
SubscriptionTier.STARTER: TierConfig(
name="Starter",
monthly_price_kobo=150000, # ₦1,500/month
included_tokens=100000, # 100K tokens
overage_rate_per_1k_kobo=30,
max_api_keys=5,
allowed_models=["gpt-3.5-turbo", "gpt-4o-mini"],
support_level="email"
),
SubscriptionTier.PROFESSIONAL: TierConfig(
name="Professional",
monthly_price_kobo=500000, # ₦5,000/month
included_tokens=500000,
overage_rate_per_1k_kobo=20,
max_api_keys=20,
allowed_models=["gpt-3.5-turbo", "gpt-4o-mini", "gpt-4o"],
support_level="priority"
),
}
class BillingService:
def __init__(self, db: Session):
self.db = db
def calculate_monthly_invoice(
self,
organization_id: str,
period_start: datetime,
period_end: datetime
) -> dict:
"""Calculate monthly invoice for an organization."""
org = self.db.query(Organization).filter_by(id=organization_id).first()
tier_config = SUBSCRIPTION_TIERS.get(org.subscription_tier)
# Get usage for the period
usage = self.db.query(UsageRecord).filter(
UsageRecord.tenant_id == organization_id,
UsageRecord.created_at >= period_start,
UsageRecord.created_at < period_end
).all()
total_tokens = sum(u.total_tokens for u in usage)
total_cost = sum(u.cost_kobo for u in usage)
# Calculate base subscription cost
base_price = tier_config.monthly_price_kobo
# Calculate included tokens value
included_value = (tier_config.included_tokens / 1000) * 20 # Simplified
# Calculate overage
included_tokens_value = included_value
if total_tokens > tier_config.included_tokens:
overage_tokens = total_tokens - tier_config.included_tokens
overage_cost = round((overage_tokens / 1000) * tier_config.overage_rate_per_1k_kobo)
else:
overage_cost = 0
return {
"organization_id": organization_id,
"period_start": period_start,
"period_end": period_end,
"tier": org.subscription_tier.value,
"base_price_kobo": base_price,
"usage": {
"total_tokens": total_tokens,
"included_tokens": tier_config.included_tokens,
"overage_tokens": max(0, total_tokens - tier_config.included_tokens),
"usage_cost_kobo": total_cost,
},
"overage_cost_kobo": overage_cost,
"total_kobo": base_price + overage_cost,
"line_items": [
{"description": f"{tier_config.name} Plan", "amount_kobo": base_price},
{"description": "Usage Overage", "amount_kobo": overage_cost},
]
}
```
Proration handles mid-cycle upgrades and downgrades. When upgrading from Starter to Professional mid-month, charge the prorated difference for the remaining days.