Subscription plans are now differentiated by check limits per month rather than features:
- Personal (£9.99/month): 5 checks/month
- Trade (£29.99/month): 100 checks/month
- Business (£99.99/month): Unlimited checks
All plans include: Dashboard access, PDF downloads, check history
CREATE TABLE monthly_usage (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) REFERENCES users(id) ON DELETE CASCADE,
subscription_id VARCHAR(36) REFERENCES subscriptions(id) ON DELETE CASCADE,
month DATE NOT NULL, -- First day of the month (e.g., 2025-01-01)
checks_used INTEGER DEFAULT 0 NOT NULL,
UNIQUE(user_id, subscription_id, month),
INDEX idx_monthly_usage_user_month (user_id, month),
INDEX idx_monthly_usage_subscription (subscription_id)
);MonthlyUsagemodel with relationships to User and Subscription- Properties to calculate check limits, remaining checks, and validation
- Plan limits hardcoded in
check_limitproperty:- Personal: 5 checks
- Trade: 100 checks
- Business: -1 (unlimited)
File: /backend/app/crud/usage_tracking.py
async def get_or_create_current_usage(db, user_id, subscription_id):
"""Get or create usage record for current month."""
async def increment_usage(db, user_id):
"""Increment check count for current month."""
async def reset_monthly_usage(db, subscription_id):
"""Reset usage at billing cycle (called by Stripe webhooks)."""
async def get_remaining_checks(db, user_id):
"""Get remaining checks for user's active subscription."""File: /backend/app/api/checks.py
Before creating a check:
- Check if user is authenticated
- If authenticated, check if they have an active subscription
- If subscription exists, validate usage limits:
usage = await usage_tracking_crud.get_or_create_current_usage(db, user_id, sub_id) if not usage.can_run_check: raise HTTPException( status_code=429, detail=f"Monthly check limit reached. You have {usage.check_limit} checks per month." )
- After check creation, increment usage counter
File: /backend/app/api/v1/endpoints/dashboard.py
Add new endpoint:
@router.get("/usage")
async def get_usage_stats(current_user: User, db: AsyncSession):
"""Get current month's usage statistics."""
subscription = await subscription_crud.get_active_subscription(db, current_user.id)
if not subscription:
return {"has_subscription": False}
usage = await usage_tracking_crud.get_or_create_current_usage(
db, current_user.id, subscription.id
)
return {
"plan_type": subscription.plan_type.value,
"check_limit": usage.check_limit,
"checks_used": usage.checks_used,
"remaining_checks": usage.remaining_checks,
"is_unlimited": usage.is_unlimited,
"resets_at": subscription.current_period_end
}File: /backend/app/api/v1/payments.py
In Stripe webhook handler for customer.subscription.updated:
async def handle_subscription_updated(subscription, db):
# ... existing code ...
# If new billing period started, reset usage
if subscription.current_period_start changed:
await usage_tracking_crud.reset_monthly_usage(db, subscription_id)File: /frontend/app/dashboard/page.tsx
Add usage stats display:
<div className="bg-white rounded-lg shadow p-6">
<h3 className="font-semibold mb-2">Usage This Month</h3>
{usageStats.is_unlimited ? (
<p className="text-2xl font-bold text-green-600">Unlimited</p>
) : (
<>
<div className="flex items-baseline gap-2 mb-2">
<span className="text-3xl font-bold">{usageStats.remaining_checks}</span>
<span className="text-gray-600">/ {usageStats.check_limit} remaining</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{width: `${(usageStats.checks_used / usageStats.check_limit) * 100}%`}}
/>
</div>
</>
)}
<p className="text-sm text-gray-500 mt-2">
Resets on {new Date(usageStats.resets_at).toLocaleDateString()}
</p>
</div>cd /root/clone-check/backend
poetry run alembic revision --autogenerate -m "add_monthly_usage_table"
poetry run alembic upgrade headCreate /backend/app/crud/usage_tracking.py with CRUD operations
Modify /backend/app/api/checks.py to validate limits before creating checks
Modify /backend/app/api/v1/payments.py to reset usage on billing cycle
Add usage endpoint to /backend/app/api/v1/endpoints/dashboard.py
Modify /frontend/app/dashboard/page.tsx to display usage stats
The Stripe price IDs remain the same. The mapping works like this:
- User subscribes to a plan (Personal/Trade/Business)
- Webhook creates
Subscriptionrecord in database - First check creates
MonthlyUsagerecord for current month - Each check increments
checks_used - Validation happens before each check creation
- When
customer.subscription.updatedwebhook fires with new billing period:- Create new
MonthlyUsagerecord for new month - Old month's data is preserved for history
- Create new
-
Manual Test Subscription Creation:
- Create test user
- Manually insert subscription record with Personal plan
- Manually insert MonthlyUsage record
- Try to run 6 checks (should fail on 6th)
-
Test Usage Reset:
- Update
monthfield to previous month - Create new MonthlyUsage for current month
- Verify checks work again
- Update
-
Test Unlimited:
- Change plan_type to BUSINESS_MONTHLY
- Verify can run unlimited checks
You need to decide if you want me to:
- Continue with full implementation now - Create all the CRUD, update endpoints, run migrations
- Test first - Give you SQL to manually create a subscription and test the basic flow
- Pause and review - Review this plan and suggest changes before proceeding
The pricing page is already updated to show the new structure clearly!