This commit is contained in:
alorig
2025-12-08 11:18:16 +05:00
parent 4e9d8af768
commit 9764a09a25
5 changed files with 0 additions and 1387 deletions

View File

@@ -1,442 +0,0 @@
# Complete Tenancy System Implementation Plan
## 100% Accurate, Zero-Error Guide
Based on comprehensive analysis of codebase and documentation.
---
## Executive Summary
**Critical Gaps Found:**
1. ❌ No payment_method support (Subscription only has stripe_subscription_id)
2. ❌ API key auth bypasses account/plan validation
3. ❌ Throttling too permissive for authenticated users
4. ❌ Registration doesn't seed initial credits
5. ⚠️ System account logic needs clarity
**Implementation: 10 Phases, ~600 LOC, 7 Days**
---
## Current State Analysis
### ✅ Working
- Account-based filtering ([`AccountModelViewSet`](backend/igny8_core/api/base.py:12))
- Middleware injection ([`AccountContextMiddleware`](backend/igny8_core/auth/middleware.py:19))
- Credit service ([`CreditService`](backend/igny8_core/business/billing/services/credit_service.py:12))
- AI credit checks ([`AIEngine`](backend/igny8_core/ai/engine.py:213-235))
- Login validation ([`AuthViewSet.login()`](backend/igny8_core/auth/views.py:1036-1053))
### ❌ Critical Gaps
**Gap 1: Payment Methods** ([`models.py:192`](backend/igny8_core/auth/models.py:192))
- Subscription.stripe_subscription_id is unique & required
- No payment_method field
- Cannot support bank transfer/PayPal
**Gap 2: API Key Bypass** ([`authentication.py:92`](backend/igny8_core/api/authentication.py:92))
- APIKeyAuthentication sets request.account (line 138)
- Does NOT validate account status/plan
- WordPress bridge can access suspended accounts
**Gap 3: Throttling** ([`throttles.py:46`](backend/igny8_core/api/throttles.py:46))
- Line 46: `authenticated_bypass = True` for ALL users
- No per-account throttling
**Gap 4: Registration** ([`serializers.py:332`](backend/igny8_core/auth/serializers.py:332))
- Creates Account but credits default to 0
- Should set credits = plan.get_effective_credits_per_month()
---
## Implementation Phases
### Phase 1: Add Payment Method Fields (Day 1)
**Migration:** `0007_add_payment_method_fields.py`
```python
operations = [
migrations.AddField('account', 'payment_method', CharField(max_length=30, choices=[...], default='stripe')),
migrations.AddField('subscription', 'payment_method', CharField(max_length=30, choices=[...], default='stripe')),
migrations.AddField('subscription', 'external_payment_id', CharField(max_length=255, null=True, blank=True)),
migrations.AlterField('subscription', 'stripe_subscription_id', CharField(null=True, blank=True)),
migrations.AddIndex('subscription', fields=['payment_method']),
migrations.AddIndex('account', fields=['payment_method']),
]
```
**Update Models** ([`models.py`](backend/igny8_core/auth/models.py)):
- Line 79: Add Account.payment_method
- Line 210: Update Subscription fields
- Line 196: Add 'pending_payment' to STATUS_CHOICES
**Verify:**
```bash
python manage.py makemigrations
python manage.py migrate
# Check Django admin
```
---
### Phase 2: Update Serializers (Day 1)
**Update** [`serializers.py`](backend/igny8_core/auth/serializers.py):
Line 21 - SubscriptionSerializer:
```python
fields = ['id', 'account', 'stripe_subscription_id', 'payment_method', 'external_payment_id', 'status', ...]
```
Line 38 - AccountSerializer:
```python
fields = ['id', 'name', 'slug', 'owner', 'plan', 'credits', 'status', 'payment_method', ...]
```
Line 257 - RegisterSerializer:
```python
payment_method = serializers.ChoiceField(choices=[...], default='bank_transfer', required=False)
```
---
### Phase 3: Extract Validation Helper (Day 2)
**Create** [`auth/utils.py`](backend/igny8_core/auth/utils.py) - add at end:
```python
def validate_account_and_plan(user_or_account):
"""
Validate account exists and has active plan.
Returns: (is_valid: bool, error_msg: str, http_status: int)
"""
from rest_framework import status
from .models import User, Account
if isinstance(user_or_account, User):
account = getattr(user_or_account, 'account', None)
elif isinstance(user_or_account, Account):
account = user_or_account
else:
return (False, 'Invalid object type', status.HTTP_400_BAD_REQUEST)
if not account:
return (False, 'Account not configured. Contact support.', status.HTTP_403_FORBIDDEN)
if hasattr(account, 'status') and account.status in ['suspended', 'cancelled']:
return (False, f'Account is {account.status}', status.HTTP_403_FORBIDDEN)
plan = getattr(account, 'plan', None)
if not plan or (hasattr(plan, 'is_active') and not plan.is_active):
return (False, 'Active subscription required', status.HTTP_402_PAYMENT_REQUIRED)
return (True, None, None)
```
**Update** [`middleware.py:132`](backend/igny8_core/auth/middleware.py:132):
```python
def _validate_account_and_plan(self, request, user):
from .utils import validate_account_and_plan
is_valid, error_message, http_status = validate_account_and_plan(user)
if not is_valid:
return self._deny_request(request, error_message, http_status)
return None
```
---
### Phase 4: Fix API Key Authentication (Day 2)
**Update** [`authentication.py:92`](backend/igny8_core/api/authentication.py:92):
Replace authenticate() method - add after line 119 (before setting request.account):
```python
# CRITICAL FIX: Validate account and plan status
from igny8_core.auth.utils import validate_account_and_plan
is_valid, error_message, http_status = validate_account_and_plan(account)
if not is_valid:
raise AuthenticationFailed(error_message)
```
**Test:**
```bash
# Create API key for suspended account
# Request should return 403
curl -H "Authorization: Bearer <api_key>" http://localhost:8000/api/v1/writer/content/
```
---
### Phase 5: Fix Throttling (Day 3)
**Update** [`throttles.py`](backend/igny8_core/api/throttles.py):
Replace line 44-48:
```python
# Remove blanket authenticated bypass
# Only bypass for DEBUG or public requests
if debug_bypass or env_bypass or public_blueprint_bypass:
return True
# Normal throttling with per-account keying
return super().allow_request(request, view)
```
Add new method:
```python
def get_cache_key(self, request, view):
"""Key by (scope, account.id) instead of user"""
if not self.scope:
return None
account = getattr(request, 'account', None)
if not account and hasattr(request, 'user') and request.user.is_authenticated:
account = getattr(request.user, 'account', None)
account_id = account.id if account else 'anon'
return f'{self.scope}:{account_id}'
```
---
### Phase 6: Bank Transfer Endpoint (Day 3)
**Create** `business/billing/views.py`:
```python
from rest_framework import viewsets, status
from rest_framework.decorators import action
from django.db import transaction
from django.utils import timezone
from datetime import timedelta
from igny8_core.api.response import success_response, error_response
from igny8_core.api.permissions import IsAdminOrOwner
from igny8_core.auth.models import Account, Subscription
from igny8_core.business.billing.services.credit_service import CreditService
from igny8_core.business.billing.models import CreditTransaction
class BillingViewSet(viewsets.GenericViewSet):
permission_classes = [IsAdminOrOwner]
@action(detail=False, methods=['post'], url_path='confirm-bank-transfer')
def confirm_bank_transfer(self, request):
account_id = request.data.get('account_id')
external_payment_id = request.data.get('external_payment_id')
amount = request.data.get('amount')
payer_name = request.data.get('payer_name')
if not all([external_payment_id, amount, payer_name]):
return error_response(error='Missing required fields', status_code=400, request=request)
try:
with transaction.atomic():
account = Account.objects.select_related('plan').get(id=account_id)
# Create/update subscription
subscription, _ = Subscription.objects.get_or_create(
account=account,
defaults={
'payment_method': 'bank_transfer',
'external_payment_id': external_payment_id,
'status': 'active',
'current_period_start': timezone.now(),
'current_period_end': timezone.now() + timedelta(days=30)
}
)
subscription.payment_method = 'bank_transfer'
subscription.external_payment_id = external_payment_id
subscription.status = 'active'
subscription.save()
# Reset credits
monthly_credits = account.plan.get_effective_credits_per_month()
account.payment_method = 'bank_transfer'
account.status = 'active'
account.credits = monthly_credits
account.save()
# Log transaction
CreditTransaction.objects.create(
account=account,
transaction_type='subscription',
amount=monthly_credits,
balance_after=monthly_credits,
description=f'Bank transfer confirmed: {external_payment_id}',
metadata={'payer_name': payer_name, 'amount': str(amount)}
)
return success_response(
data={'account_id': account.id, 'credits': monthly_credits},
message='Payment confirmed',
request=request
)
except Exception as e:
return error_response(error=str(e), status_code=500, request=request)
```
**Add URL:** Update `business/billing/urls.py` and main urls.py
---
### Phase 7: Fix Registration Credits (Day 4)
**Update** [`serializers.py:332`](backend/igny8_core/auth/serializers.py:332):
After creating account (line 332), add:
```python
# Seed initial credits
monthly_credits = plan.get_effective_credits_per_month()
account.credits = monthly_credits
account.save(update_fields=['credits'])
# Log transaction
from igny8_core.business.billing.models import CreditTransaction
CreditTransaction.objects.create(
account=account,
transaction_type='subscription',
amount=monthly_credits,
balance_after=monthly_credits,
description=f'Initial credits from {plan.name} plan',
metadata={'plan_slug': plan.slug, 'registration': True}
)
```
---
### Phase 8: Comprehensive Tests (Day 5)
**Create** `auth/tests/test_tenancy_complete.py`:
```python
from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status
from igny8_core.auth.models import Account, Plan, User
from igny8_core.auth.utils import validate_account_and_plan
class TenancyTests(TestCase):
def setUp(self):
self.plan = Plan.objects.create(
name='Test', slug='test', price=29.99,
included_credits=10000, is_active=True
)
self.client = APIClient()
def test_registration_seeds_credits(self):
response = self.client.post('/api/v1/auth/register/', {
'email': 'test@example.com',
'password': 'Pass123!',
'password_confirm': 'Pass123!',
'plan_id': self.plan.id
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
user = User.objects.get(email='test@example.com')
self.assertEqual(user.account.credits, 10000)
def test_api_key_validates_suspended_account(self):
# Create suspended account with API key
# Test should return 403
pass
def test_bank_transfer_confirmation(self):
# Test admin confirming payment
pass
```
Run: `python manage.py test igny8_core.auth.tests.test_tenancy_complete`
---
### Phase 9: Update Documentation (Day 6)
Update:
- `Final_Flow_Tenancy.md` - Add payment_method flows
- `02-MULTITENANCY-MODEL.md` - Document new fields
- `Tenancy_Audit_Report.md` - Mark issues as resolved
---
### Phase 10: Final Verification (Day 7)
**Checklist:**
- [ ] Migrations applied
- [ ] New accounts get credits
- [ ] Login validates account/plan
- [ ] API key validates account
- [ ] Throttling per-account
- [ ] Bank transfer endpoint works
- [ ] All tests pass
**Test Script:**
```bash
# 1. Register
curl -X POST /api/v1/auth/register/ -d '{"email":"test@ex.com","password":"Pass123!","password_confirm":"Pass123!","plan_id":1}'
# 2. Check credits in DB
python manage.py shell -c "from igny8_core.auth.models import User; u=User.objects.get(email='test@ex.com'); print(u.account.credits)"
# 3. Suspend account and test API key
# Should return 403
# 4. Test bank transfer
curl -X POST /api/v1/billing/confirm-bank-transfer/ -d '{"account_id":1,"external_payment_id":"BT001","amount":"29.99","payer_name":"Test"}'
# 5. Run all tests
python manage.py test
```
---
## Rollback Plan
If issues occur:
```bash
python manage.py migrate igny8_core_auth 0006_soft_delete_and_retention
git revert <commit_hash>
```
Add feature flags to settings.py if needed.
---
## File Change Summary
| File | Changes | Lines |
|------|---------|-------|
| auth/models.py | Add payment_method fields | +30 |
| auth/serializers.py | Update serializers, seed credits | +25 |
| auth/utils.py | Add validation helper | +60 |
| auth/middleware.py | Use helper | +5 |
| api/authentication.py | Add API key validation | +10 |
| api/throttles.py | Per-account throttling | +15 |
| billing/views.py | Bank transfer endpoint | +150 |
| tests/test_tenancy_complete.py | Tests | +250 |
| Migration | DB schema | +80 |
**Total: ~625 lines**
---
## Critical Success Factors
1. **Phase 1-2 must complete together** - DB and serializers
2. **Phase 3-4 are interdependent** - Helper and API key
3. **Phase 7 is critical** - Registration credits
4. **Phase 8 validates everything** - Tests must pass
---
## After Implementation
**You will have:**
- Multi-payment method support
- Secure API key authentication
- Per-account rate limiting
- Automatic credit seeding
- Bank transfer approval flow
- Comprehensive test coverage
- 100% working tenancy system
**This plan ensures zero-error implementation when followed exactly.**

View File

@@ -1,190 +0,0 @@
Nice, this is exactly the sort of thing that cleans up the whole mental model.
Below is a single structured view of flows, split into tight tables and mini text diagrams so you can literally walk them in frontend and API console and tick them off.
I am describing how it is intended to work, aligned with current models and views.
---
## 0. Key objects involved
| Area | Model / Endpoint | Notes |
| ------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Plans | `Plan` model with `price`, `billing_cycle`, `included_credits`, `stripe_*` fields | Defines included monthly credits and Stripe product |
| Accounts | `Account` with `plan`, `credits`, `status` (active, trial, suspended, cancelled) | Tenant root, holds current credits |
| Subscription | `Subscription` with `current_period_start`, `current_period_end`, `status`, `cancel_at_period_end` | Mirrors Stripe subscription state |
| Auth endpoints | `/api/v1/auth/register/`, `/api/v1/auth/login/`, `/api/v1/auth/plans/` allowed without auth | Used before tenant context exists |
| Account/Subscription APIs | `AccountsViewSet`, `SubscriptionsViewSet` under `/api/v1/auth/accounts/` and `/api/v1/auth/subscriptions/` | Manage accounts and subscriptions |
### Payment methods (current state)
- Options shown: Stripe (placeholder), PayPal (placeholder), Bank Transfer (active).
- Stripe/PayPal: keep UI/API options but mark as "coming soon"; no charge attempts yet.
- Bank Transfer: the only active path; capture payer details + plan, mark subscription/account pending payment until confirmed manually.
- No country-specific rules for any method; keep global defaults.
---
## Flow F1 - Sign up, plan selection, initial credits
### Text diagram
```text
Marketing Site → App Auth
1) User chooses plan on pricing page
2) Frontend loads plans from API
3) User fills register form with plan
4) Backend creates User + Account + (optionally) Subscription
5) Account gets plan and starting credits
```
### Step table
| Step | Frontend surface | Backend call | Backend effect (DB) | What to verify |
| ---- | ------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Pricing page (public) | `GET /api/v1/auth/plans/` | Reads active `Plan` records via `PlanSerializer` | Plans visible match Plan rows (name, price, billing_cycle, included_credits) |
| 2 | Register page shows chosen plan | no call or same `GET /auth/plans/` | - | Selected plan id matches an active Plan row |
| 3 | Register submit | `POST /api/v1/auth/register/` (body contains user + account name + `plan_id`) | Creates `User`, `Account(plan=Plan)` and possibly `Subscription` if Stripe created immediately | In DB check: new User, new Account with `plan_id` set and `status` = `trial` or `active` as per your rule; `account.owner` = user |
| 4 | Initial credits seed | same register flow or follow up billing hook | Account.credits should be set to `plan.get_effective_credits_per_month()` (from `included_credits` or legacy `credits_per_month`) | After signup, check `Account.credits` equals the plan credits for this plan |
| 5 | Payment method choice | UI shows Stripe (placeholder), PayPal (placeholder), Bank Transfer (active) | Record `payment_method` on Account/Subscription. For now only Bank Transfer can be marked paid; Stripe/PayPal stay "coming soon" | UI labels make clear Stripe/PayPal are not yet active; bank transfer selection stores choice and blocks entry until marked paid |
For validation you can drive this entirely with Postman and Django admin plus the app UI.
---
## Flow F2 - Login, tenant context, subscription gate
This validates that a user can only enter the app when account and plan are healthy.
### Diagram
```text
Login form
→ POST /auth/login/
→ Validate password
→ Ensure User has Account
→ Ensure Account has active Plan
→ Issue JWT with account_id
→ All subsequent requests:
→ Middleware sets request.account from token
→ Permissions enforce tenant access
```
### Step table
| Step | Frontend surface | API | Backend logic | Verify |
| ---- | -------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| 1 | Login form | `POST /api/v1/auth/login/` | Finds user by email, checks password | Wrong password gives 401 with "Invalid credentials" |
| 2 | Account gate | same login handler | If `user.account` is missing, returns 403 "Account not configured" | Create a user without account and confirm it cannot log in |
| 3 | Plan gate | same | If `account.plan` is missing or inactive, returns 402 "Active subscription required" | Mark a plan inactive, try login, see 402 |
| 4 | Token issue | same | Generates access and refresh tokens embedding `user_id` and `account_id` in JWT, and returns them with expiry timestamps | Decode JWT and check `account_id` == Account.id |
| 5 | Tenant context per request | Any app request after login | `AccountContextMiddleware` reads JWT, fetches User and Account, sets `request.account` and blocks suspended / invalid accounts | Hit a random endpoint, log `request.account.id` and compare to user.account.id |
If this flow passes, your base tenancy gate is sound.
---
## Flow F3 - Normal app workflow and credits consumption
Think of this as the day to day life of a tenant.
### Subflow F3a - Site and sector inside an account
| Step | UI | API | DB objects | Verify tenancy |
| ---- | ----------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| 1 | Sites page | `GET /api/v1/auth/sites/` | Returns `Site` filtered by `account` via `AccountModelViewSet` and `SiteSerializer` | Confirm only sites with `site.account_id = request.account.id` show |
| 2 | Create site | `POST /api/v1/auth/sites/` | Creates `Site` inheriting `account` from `request.account`, counts against `plan.max_sites` | Try creating more sites than `plan.max_sites` and confirm it fails as per your rule |
| 3 | Add sectors | `POST /api/v1/auth/sectors/` or site detail → add sector | Sector belongs to Site, and Site belongs to Account. `Site.get_max_sectors_limit()` uses `account.plan.max_industries` to enforce limit | Adding sectors beyond limit returns validation error; each sector.site.account_id matches account |
This sets the container for Planner / Writer.
---
### Subflow F3b - Planner credits usage
Based on the Planner technical guide.
#### Diagram
```text
Keywords → Auto cluster → Ideas
Each AI operation:
- Calculate credits needed
- Call CreditService to deduct from Account.credits
- Call AICore with account context
- Store results (clusters, ideas, tasks)
```
#### Table
| Stage | Frontend action | Endpoint | Backend behavior | Credits check |
| ------------------ | ------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1. Attach keyword | Keywords page, "Add keyword" | `POST /v1/planner/keywords/` | Validates seed keyword, site, sector, then creates Keyword using `SiteSectorBaseModel` so `account` is set from site | No or minimal credits used, depending on your rule |
| 2. Auto cluster | Select keywords, "Auto Cluster" | `POST /v1/planner/keywords/auto_cluster/` | `auto_cluster` loads keywords, instantiates `ClusteringService`, calls `cluster_keywords(keyword_ids, account, sector_id)` | In `ClusteringService` step 2, it "Check credits - Calculate credits needed and deduct credits from account" . Validate that Account.credits decreases accordingly after clustering |
| 3. Idea generation | Clusters page, "Generate ideas" | Usually `POST /v1/planner/clusters/generate_ideas/` | Service collects cluster data, calls AI via `AICore(account=request.account)` and creates `ContentIdeas` | Confirm one AI call consumes the planned credits and created ideas are tied to same site/sector/account |
To test: run each step for a known account and watch `Account.credits` in admin before and after.
---
### Subflow F3c - Writer credits usage
Pattern mirrors Planner.
| Stage | Frontend | Endpoint | Backend behavior | Credits check |
| -------------------------- | -------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| 1. Create task from idea | Ideas page, "Send to Writer" | `POST /v1/writer/tasks/` | Creates Writer Task linked to idea, site, sector, account | No credits yet or minimal |
| 2. Generate draft | Writer task detail, "Generate Article" | `POST /v1/writer/content/generate/` or similar | Calls AI via `AICore(account=request.account)`, passes structured prompt, receives content, stores `Content` row | Check that for each generation, Account.credits decreases by the expected credit cost per 1k tokens |
| 3. Regenerate sections etc | Same page | e.g. `POST /v1/writer/content/regenerate_section/` | Same AI pipeline, smaller requests | Verify smaller credit deductions and that usage is logged in any Usage / Billing tables if present |
`AICore` always initialises with an account and logs cost per request based on tokens and `MODEL_RATES` . You can cross check AI cost vs credit deduction.
---
## Flow F4 - Subscription renewal and monthly or annual cycle
The renewal flow is Stripe driven, mirrored in your `Subscription` and `Account` rows.
### Diagram
```text
Stripe billing cycle
→ Stripe renews subscription (card charged)
→ Stripe sends webhook to backend
→ Backend updates Subscription row
→ Backend resets/adjusts Account.credits based on Plan.included_credits
```
### Step table
| Step | Source | Backend effect | What to verify | |
| ---- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| 1 | Stripe billing engine / bank transfer confirmation | Stripe (later) marks subscription renewed; today bank transfer confirmation is recorded manually against the subscription | For bank transfer, admin marks payment received and sets renewal date; Stripe dashboard flows remain future work | |
| 2 | Stripe webhook or admin update | Webhook (future) or admin update finds `Subscription` by payment method and updates `status`, `current_period_start`, `current_period_end`, `cancel_at_period_end` | Subscription dates/status match payment confirmation; Stripe webhook path is stubbed until integration | |
| 3 | Credits reset or top up | Same handler/job | Sets `Account.credits` to `plan.get_effective_credits_per_month()` or adds included credits, depending on your policy | At renewal/confirmation, credits jump to intended value and usage across the new period subtracts from that |
| 4 | Account status update | Same billing service | Ensures `Account.status` switches from `trial` or `past_due` back to `active` | After payment, login is allowed again and middleware does not block due to account status |
You can simulate this by editing Subscription dates and status in admin if webhooks are not wired yet, then running the same code path as webhook.
---
## Flow F5 - Cancellation and expiry
Short but necessary to validate health.
| Scenario | Backend state | Effects to validate |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| User cancels at period end | `Subscription.cancel_at_period_end=True` while `status` remains `active` until `current_period_end` | App continues working, credits used until period end; no new credits after end date |
| Subscription ends or payment fails | `Subscription.status` becomes `canceled` or `past_due`. Billing job sets `Account.status` to `suspended` or `cancelled` | Login returns 402 or 403 according to your rule, Planner/Writer calls fail with proper error and do not consume credits |
---
## How to actually use this
You can turn this into a verification pass like:
1. Pick a clean test user and Stripe test customer.
2. Walk F1 then F2 in sequence and confirm all DB fields line up.
3. Run through F3a, F3b, F3c and log credits before and after each AI-heavy action.
4. Simulate renewal and cancellation via Stripe or DB and walk F4 and F5.

View File

@@ -1,205 +0,0 @@
## Tenancy Audit — Detailed Report (Dec 2025)
This is a detailed, actionable audit of the tenancy model, controls, and billing/payment gaps in the codebase as of Dec 2025. It includes precise findings with file references, concrete fixes (code + DB migration), recommended tests, rollout/rollback guidance, and prioritised next steps.
Summary of actions already applied
- Removed role/system bypasses from account/site/sector filtering and permissions (changes under `backend/igny8_core/...` and mirrored in `tenant/backend/igny8_core/...`). These changes make tenant isolation strict: queries, creates and updates now require an account context and will fail/return empty if none is present.
- Updated `Final_Flow_Tenancy.md` to present Stripe and PayPal as placeholders and Bank Transfer as the active payment path (document-only change).
Methodology
- Code locations scanned (primary):
- `backend/igny8_core/auth/middleware.py` (account injection & plan validation)
- `backend/igny8_core/api/base.py` (AccountModelViewSet, SiteSectorModelViewSet)
- `backend/igny8_core/api/permissions.py` (HasTenantAccess, role-based perms)
- `backend/igny8_core/api/throttles.py` (DebugScopedRateThrottle)
- `backend/igny8_core/auth/models.py` (User, Account, Plan, Subscription)
- `backend/igny8_core/api/authentication.py` (JWTAuthentication, APIKeyAuthentication)
- `backend/igny8_core/business/billing/services/credit_service.py` & `backend/igny8_core/ai/engine.py` (credits handling)
- `backend/igny8_core/auth/views.py` (Site/Sector ViewSets)
- I searched occurrences of `is_admin_or_developer`, `is_system_account`, `stripe_subscription_id`, `APIKeyAuthentication`, and rate-throttle bypasses and inspected relevant code paths.
Detailed findings (evidence + impact)
1) Subscription / Payment model mismatch (High)
- Evidence:
- `backend/igny8_core/auth/models.py::Subscription` defines `stripe_subscription_id = models.CharField(..., unique=True)` and links subscription to account. There is no `payment_method` or `external_payment_id` field.
- Impact:
- Can't represent non-Stripe payments (bank transfer, PayPal) without schema changes. Current docs (Bank Transfer active) are inaccurate relative to DB constraints.
- Recommended fix:
- DB migration to:
- Add `payment_method` enum/choices to `Subscription` and `Account` (values: `stripe`, `paypal`, `bank_transfer`), default `stripe` for backwards compatibility.
- Add `external_payment_id` (nullable) to record external payment reference (e.g., bank transfer reference number / PayPal transaction id).
- Make `stripe_subscription_id` nullable and remove unique constraint if you must support multiple non-Stripe flows. Migration steps:
1. Add new nullable fields and keep old fields unchanged.
2. Backfill `payment_method='stripe'` where `stripe_subscription_id` exists.
3. Make `stripe_subscription_id` nullable and drop unique index if needed.
- Update serializers, admin forms, and registration/subscription endpoints to accept `payment_method`.
- Files: `backend/igny8_core/auth/models.py`, `backend/igny8_core/api/serializers.py` (where Subscription serializer exists), frontend forms.
2) API-key auth bypasses plan/status validation (High)
- Evidence:
- `backend/igny8_core/api/authentication.py::APIKeyAuthentication` sets `request.account` and `request.site` but does not call `_validate_account_and_plan` from middleware. The middleware (`auth/middleware.py`) runs plan validation only for session/JWT flows.
- Impact:
- WordPress bridge/API-key clients can access tenant-scoped endpoints even if the account is suspended or plan inactive.
- Recommended fix:
- In `APIKeyAuthentication.authenticate()`, after resolving `account`, call a shared validation helper (e.g., factor `_validate_account_and_plan` into `auth/utils.py` and call it here). If validation fails, raise `AuthenticationFailed` or return `None`.
- Files: `backend/igny8_core/api/authentication.py`, `backend/igny8_core/auth/middleware.py` (extract validation helper), `backend/igny8_core/auth/utils.py`.
- Tests: add API-key request tests for suspended/past-due accounts returning 402/403.
3) Role/system overrides removed but system-account function remains (Design decision needed) (Medium)
- Evidence:
- `auth/models.py::Account.is_system_account()` returns slug in `['aws-admin', 'default-account', 'default']`. Many code paths previously used admin/dev/system overrides (these were removed).
- Impact:
- Potential residual special-casing exists (e.g., `is_system_account_user()`), and system accounts may need controlled, explicit privileges rather than silent bypasses.
- Recommended action:
- Decide: keep `system accounts` with tightly-scoped features (e.g., access to global monitoring endpoints only) OR remove hard-coded system slugs and manage via a `features` or `is_system_account` boolean that must be explicitly set.
- Files to update/document: `backend/igny8_core/auth/models.py`, `master-docs/00-system/*`.
4) Throttle bypass broadness (Medium)
- Evidence:
- `backend/igny8_core/api/throttles.py::DebugScopedRateThrottle` currently bypasses throttling for any authenticated user (previously intended for admin/dev/system). This was simplified but remains permissive.
- Impact:
- Authenticated tenants are effectively unthrottled; DoS or excessive usage by a tenant is possible without tenant-specific limits.
- Recommended fix:
- Change to: bypass only when DEBUG or specific env var set OR user is in `system account` or developer role (if you reintroduce that concept intentionally). Otherwise enforce per-tenant/per-scope throttling.
- Add tenant-specific rate bucket logic (e.g., throttle keys by `account.id`).
- Files: `backend/igny8_core/api/throttles.py`.
- Tests: verify throttling triggers for authenticated tenant requests above threshold.
5) Account injection & validation behavior (Medium)
- Evidence:
- `backend/igny8_core/auth/middleware.py::AccountContextMiddleware` skips middleware for `request.path.startswith('/api/v1/auth/')` and `/admin/` and returns immediately in some JWT failure cases (silently allowing `request.account = None`).
- Impact:
- Some endpoints may not get account validated; middleware may "fail silently" and allowing unauthenticated or un-validated access in edge cases.
- Recommended fix:
- Harden error handling: log token decode errors at WARNING/ERROR, and fail requests explicitly where appropriate. For API-key or JWT failure, ensure any downstream endpoints guard properly (permissions should reject).
- Consider removing silent fallback in production; allow session auth to handle user but ensure `request.account` is present for tenant endpoints.
- Files: `backend/igny8_core/auth/middleware.py`.
6) Credit deduction consistent but missing pre-check hooks (Low)
- Evidence:
- `backend/igny8_core/ai/engine.py` automatically deducts credits via `CreditService.deduct_credits_for_operation()` after AI responses.
- Impact:
- Good centralisation; ensure pre-check (sufficient credits) is always called before AI invocation to avoid wasted external API calls.
- Recommended fix:
- Verify `AIEngine.execute()` or `AICore` calls `CreditService.check_credits_legacy()` or similar BEFORE making AI calls. If not, move pre-check earlier and make AI call cancellable if funds insufficient.
- Files: `backend/igny8_core/ai/engine.py`, `backend/igny8_core/business/billing/services/credit_service.py`.
7) Registration & initial credits (Medium)
- Evidence:
- `Final_Flow_Tenancy.md` describes initial credits seeded from `Plan.get_effective_credits_per_month()` on registration.
- Implementation: `auth/views.py::register` (inspected earlier) sets `Account.credits` on creation (confirm serializer/implementation).
- Impact:
- Ensure atomicity: create User + Account + Subscription and seed credits in a transaction to avoid partial creates.
- Recommended fix:
- Wrap registration flow in a DB transaction; move credit seeding into an idempotent service method.
- Files: `backend/igny8_core/auth/views.py`, billing service modules.
8) Billing webhook architecture (Medium)
- Evidence:
- Billing flows refer largely to Stripe webhooks. Bank transfer is manual in doc; no webhook or admin confirmation API exists.
- Impact:
- Manual processes can lead to inconsistent states (account remains suspended when payment received).
- Recommended changes:
- Add `admin` endpoint to confirm bank transfer (secure endpoint, permissioned) which:
- Accepts account_id/subscription_id, `external_payment_id`, payer details, proof (file or URL), amount, and sets `Subscription.status='active'` and updates `Account.credits`.
- Add `payment_proofs` model or attach to `CreditTransaction`.
- Files: new `backend/igny8_core/billing/views.py`, updates to `backend/igny8_core/auth/models.py` (Subscription), `backend/igny8_core/business/billing/services/`.
9) Tests & CI regression gaps (High)
- Evidence:
- No test coverage found for strict tenant filtering removal. No tests for API-key gate or bank-transfer flows.
- Recommended test matrix:
- Unit tests:
- `AccountModelViewSet.get_queryset()` filters by request.account; returns none when `request.account` missing.
- `perform_create()` raises PermissionDenied when account context missing for account-scoped models.
- `APIKeyAuthentication` denies access when account is suspended or plan inactive.
- `DebugScopedRateThrottle` enforces throttle when expected.
- Integration tests:
- Registration -> account created with credits seeded.
- Bank transfer admin confirmation endpoint flow (once implemented).
- Add to CI pipeline; fail on regressions.
Concrete code and DB changes (step-by-step)
1) Add payment fields migration (priority: HIGH)
- Migration pseudo-steps:
- Add `payment_method = models.CharField(max_length=30, choices=PAYMENT_CHOICES, default='stripe')` to `Subscription` and `Account` (nullable initially if you prefer).
- Add `external_payment_id = models.CharField(max_length=255, null=True, blank=True)`.
- Make `stripe_subscription_id` nullable and drop unique constraint if necessary.
- Update serializers and forms to accept `payment_method`.
- Update `register` flow and any subscription creation flows to accept `payment_method`.
2) API-key validation (priority: HIGH)
- Implementation:
- Extract `_validate_account_and_plan()` from `auth/middleware.py` into `auth/utils.py` as `validate_account_and_plan(account_or_user) -> None | (error, status)`.
- Call that helper from `APIKeyAuthentication.authenticate()` immediately after obtaining `account` and before returning `(user, api_key)`. On failure, raise `AuthenticationFailed` with the same message & HTTP semantics as middleware uses (map 402/403 to DRF errors).
- Tests:
- Add tests covering API key access for `active` vs `past_due` / `canceled`.
3) Throttle policy update (priority: MEDIUM)
- Implementation:
- Remove blanket authenticated bypass. Instead:
- Keep `debug_bypass` and env variable bypass.
- Add `if is_system_account_user() or is_developer()` optionally for debug environments only.
- Add per-account throttle key using `account.id` or `request.user.account.id`.
- Files: `backend/igny8_core/api/throttles.py`.
4) Harden middleware & logging (priority: MEDIUM)
- Implementation:
- Ensure token decode errors and other exceptions are logged. Fail louder in production (configurable).
- For JWT absent but APIKey present, ensure account validation runs or permission classes enforce it.
- Files: `backend/igny8_core/auth/middleware.py`.
5) Bank-transfer admin confirmation endpoint (priority: MEDIUM)
- Implementation:
- Endpoint: `POST /api/v1/billing/confirm-bank-transfer/` (permissioned to `IsAdminOrOwner` or similar admin role).
- Accepts: `subscription_id | account_id`, `external_payment_id`, `amount`, `payer_name`, `proof_url` or file upload.
- Actions:
- Mark `Subscription.status='active'`, set `current_period_start/end`.
- Set `Subscription.payment_method='bank_transfer'`, store `external_payment_id`.
- Add `CreditTransaction` entry and top up `Account.credits` to `Plan.get_effective_credits_per_month()` (or configured top-up).
- Files: new `backend/igny8_core/billing/views.py`, `backend/igny8_core/business/billing/services/`.
- Tests: bank transfer happy path; confirm idempotency (double-confirmation should be safe).
6) Tests & CI (priority: HIGH)
- Add unit tests and integration tests described earlier. Run tests locally and include in CI gating.
Documentation & UX changes
- Update `Final_Flow_Tenancy.md` (already done) and `master-docs/00-system/02-MULTITENANCY-MODEL.md` to remove references to admin bypasses and document new account-only model.
- Update admin UI to:
- Show `payment_method` on account/subscription pages.
- Provide "Confirm Bank Transfer" action (with proof upload).
- Mark Stripe/PayPal UI options as "coming soon" where relevant.
Rollout plan (safe incremental)
1. Create DB migrations (add nullable fields) and deploy with code that tolerates nulls. (no downtime)
2. Backfill `payment_method='stripe'` for existing subscriptions where `stripe_subscription_id` exists.
3. Deploy API changes to accept `payment_method` but keep Stripe/PayPal inactive in business logic.
4. Add admin confirmation endpoint and small UI changes; deploy.
5. Gradually switch registration/checkout UI to allow bank transfer (server-side honored immediately).
6. Add tests and enable stricter logging / monitoring.
Rollback guidance
- Revert deployments in order: UI -> backend code -> DB (reverse migration only if safe). Keep feature flags for enabling bank-transfer flows to toggle behavior without DB rollbacks.
Estimated effort & priorities
- Immediate (13 days): Add `payment_method`, `external_payment_id` fields (migration + serializer updates), API-key validation change, unit tests for API-key gating.
- Short (37 days): Implement bank-transfer confirmation endpoint, admin UI changes, and throttling policy adjustments + tests.
- Medium (13 weeks): Full rollout across frontend, integration with Stripe/PayPal placeholders, heavier monitoring, and end-to-end tests.
Appendix — Exact code touchpoints (recommendations)
- `backend/igny8_core/auth/middleware.py` — extract validation helper; improve logging.
- `backend/igny8_core/api/authentication.py` — add validation call in `APIKeyAuthentication`.
- `backend/igny8_core/api/base.py` — already updated to require account; verify all viewsets inherit this correctly.
- `backend/igny8_core/api/permissions.py` — reviewed and cleaned; add tests.
- `backend/igny8_core/api/throttles.py` — tighten bypass rules; add tenant-keyed throttling.
- `backend/igny8_core/auth/models.py` — add `payment_method` + `external_payment_id`, make `stripe_subscription_id` nullable.
- `backend/igny8_core/business/billing/services/credit_service.py` — ensure pre-checks before AI calls.
- New: `backend/igny8_core/billing/views.py` & `backend/igny8_core/billing/serializers.py` — bank transfer admin confirmation endpoint.
Next steps I can take (pick one and I'll implement):
1. Implement DB migration and model/serializer changes for `payment_method` + `external_payment_id` + make `stripe_subscription_id` nullable (create migrations, update code).
2. Implement API-key plan/status validation (small PR).
3. Implement bank-transfer confirmation endpoint and basic admin UI API (POST endpoint only).
4. Produce test cases and a tests PR skeleton (unittests + integration outlines).
If you'd like, I can start with implementing the DB migration and model changes (#1) and update serializers and the admin list/detail views, then add corresponding tests.

View File

@@ -1,212 +0,0 @@
bluesalman: rad the final flow doc, and then the audit done based on that flow doc, and then read teh actual codeabse and find waht is wrong or still nto coevered proeprly and make it a no brainer 0 error 100% accurate gap finding and fixes doc, so that once everything is implmented based onthis audit we ahve complete fully fucntional 100% accuarte working solution only based on this 1 file, for whole tenancey system and user workflow,
GitHub Copilot: I'll analyze the Final Flow document, the Audit Report, and the actual codebase to create a comprehensive, no-brainer gap analysis and fix document.
Created 10 todos
Read [](file:///data/app/igny8/backend/igny8_core/auth/models.py#1-1), lines 1 to 100
Read [](file:///data/app/igny8/backend/igny8_core/auth/middleware.py#1-1), lines 1 to 100
Read [](file:///data/app/igny8/backend/igny8_core/auth/models.py#100-100), lines 100 to 250
Read [](file:///data/app/igny8/backend/igny8_core/auth/middleware.py#100-100), lines 100 to 175
Starting: *Examine API authentication and permissions* (4/10)
Read [](file:///data/app/igny8/backend/igny8_core/api/authentication.py#1-1), lines 1 to 150
Read [](file:///data/app/igny8/backend/igny8_core/api/permissions.py)
Read [](file:///data/app/igny8/backend/igny8_core/api/base.py#1-1), lines 1 to 200
Read [](file:///data/app/igny8/backend/igny8_core/api/base.py#200-200), lines 200 to 402
Starting: *Examine billing and credit services* (6/10)
Read [](file:///data/app/igny8/backend/igny8_core/business/billing/services/credit_service.py#1-1), lines 1 to 200
Read [](file:///data/app/igny8/backend/igny8_core/ai/engine.py#1-1), lines 1 to 200
Read [](file:///data/app/igny8/backend/igny8_core/ai/engine.py#200-200), lines 200 to 400
Searched for regex `def register|class Register` (`**/backend/igny8_core/auth/views.py`), 2 results
Read [](file:///data/app/igny8/backend/igny8_core/auth/views.py#970-970), lines 970 to 1050
Starting: *Examine Planner and Writer credit usage* (8/10)
Searched for regex `auto_cluster|generate_ideas` (`**/backend/igny8_core/modules/planner/**`), 20 results
Searched for regex `generate_content|generate_article` (`**/backend/igny8_core/modules/writer/**`), 18 results
Read [](file:///data/app/igny8/backend/igny8_core/auth/models.py#1-1), lines 1 to 100
Searched for text `class User` (`**/backend/igny8_core/auth/models.py`), 2 results
Read [](file:///data/app/igny8/backend/igny8_core/auth/models.py#560-560), lines 560 to 635
Starting: *Create comprehensive gap analysis document* (10/10)
Now I'll create the comprehensive, no-brainer gap analysis document with 100% accuracy.
bluesalman: @agent Try Again
GitHub Copilot: You have exceeded your premium request allowance. We have automatically switched you to GPT-4.1 which is included with your plan. [Enable additional paid premium requests](command:chat.enablePremiumOverages) to continue using premium models.Here is a comprehensive, zero-error, 100% accurate gap analysis and fixes document for the tenancy and user workflow, based on the Final Flow, Audit Report, and the actual codebase. If you implement every item below, your tenancy system will be fully functional and correct.
---
# Igny8 Tenancy System — Final Gap Analysis & Fixes
## 1. Payment Method & Subscription Model Gaps
**Gap:**
- `Subscription` and `Account` models only support Stripe (`stripe_subscription_id` is required/unique).
- No support for `payment_method` (bank transfer, PayPal) or `external_payment_id`.
- Docs and UI show bank transfer as active, but DB and API do not support it.
**Fixes:**
- Add `payment_method` (enum: `stripe`, `paypal`, `bank_transfer`) to both `Subscription` and `Account` (nullable, default `stripe`).
- Add `external_payment_id` (nullable) for bank transfer/PayPal reference.
- Make `stripe_subscription_id` nullable and drop unique constraint.
- Update serializers, admin forms, registration/subscription endpoints to accept and store `payment_method`.
- Backfill `payment_method='stripe'` for existing Stripe subscriptions.
## 2. API-Key Authentication Bypasses Plan/Status Validation
**Gap:**
- APIKeyAuthentication sets `request.account` but does not validate account/plan status.
- API-key clients (WordPress bridge) can access tenant endpoints even if account is suspended or plan inactive.
**Fixes:**
- Extract `_validate_account_and_plan()` from middleware to a shared helper (e.g., `auth/utils.py`).
- Call this helper in `APIKeyAuthentication.authenticate()` after resolving account.
- On failure, raise `AuthenticationFailed` with correct HTTP status (402/403).
- Add tests for API-key access with suspended/inactive accounts.
## 3. System Account/Role Handling
**Gap:**
- `is_system_account()` and related user methods remain, but bypasses are removed.
- Residual special-casing may exist; system accounts need explicit, controlled privileges.
**Fixes:**
- Decide: keep system accounts with tightly-scoped features, or remove hard-coded slugs and use explicit boolean/feature flags.
- Update code to use explicit privilege checks, not slug-based bypasses.
- Document system account handling in 00-system.
## 4. Throttle Policy
**Gap:**
- `DebugScopedRateThrottle` bypasses throttling for any authenticated user.
- No per-tenant rate limits; DoS risk.
**Fixes:**
- Only bypass throttling in DEBUG or for system/developer roles.
- Add per-account throttle keys (bucket by `account.id`).
- Add tests for throttle enforcement.
## 5. Middleware & Error Handling
**Gap:**
- Middleware skips validation for `/api/v1/auth/` and `/admin/`.
- Silent fallback on JWT decode errors; may allow unvalidated access.
**Fixes:**
- Harden error handling: log token decode errors, fail requests explicitly.
- Ensure all endpoints require validated `request.account` for tenant access.
- Remove silent fallback in production.
## 6. Credit Deduction & Pre-Check
**Gap:**
- Credit deduction is centralized and correct, but pre-check for sufficient credits must always run before AI calls.
**Fixes:**
- Ensure every AI operation (Planner, Writer, etc.) calls `CreditService.check_credits()` before invoking external APIs.
- Add/verify tests for insufficient credits blocking AI calls.
## 7. Registration & Initial Credits
**Gap:**
- Registration flow seeds credits, but atomicity and idempotency must be guaranteed.
**Fixes:**
- Wrap registration in a DB transaction.
- Move credit seeding into an idempotent service method.
- Add tests for atomic User+Account+Subscription creation and credit seeding.
## 8. Bank Transfer Confirmation Endpoint
**Gap:**
- No admin API to confirm bank transfer payments; manual DB edits required.
**Fixes:**
- Add `POST /api/v1/billing/confirm-bank-transfer/` endpoint (admin-only).
- Accepts: `subscription_id|account_id`, `external_payment_id`, `amount`, `payer_name`, `proof_url`/file.
- Marks subscription active, sets renewal dates, stores payment method and reference, tops up credits.
- Add `CreditTransaction` entry and update `Account.credits`.
- Add tests for bank transfer confirmation flow.
## 9. Tests & CI Coverage
**Gap:**
- No test coverage for strict tenant filtering, API-key gating, or bank transfer flows.
**Fixes:**
- Add unit tests for:
- AccountModelViewSet filtering by account.
- PermissionDenied on missing account context.
- APIKeyAuthentication denying access for suspended/inactive accounts.
- Throttle enforcement.
- Add integration tests for:
- Registration and credit seeding.
- Bank transfer confirmation endpoint.
- Add to CI pipeline.
## 10. Documentation & Admin UI
**Gap:**
- Docs and admin UI do not reflect new payment fields or flows.
**Fixes:**
- Update Final_Flow_Tenancy.md and 02-MULTITENANCY-MODEL.md to match new model.
- Update admin UI to show `payment_method` and allow bank transfer confirmation.
- Mark Stripe/PayPal as "coming soon" in UI.
---
## Rollout Plan
1. Create DB migrations for new fields (nullable, default to Stripe).
2. Deploy code that tolerates nulls; backfill Stripe subscriptions.
3. Update API and admin UI to support new payment flows.
4. Add bank transfer confirmation endpoint.
5. Add and run tests; enable stricter logging/monitoring.
6. Gradually enable bank transfer in registration/checkout UI.
## Rollback Guidance
- Revert UI → backend code → DB (reverse migration only if safe).
- Use feature flags for bank transfer flows.
---
## Final Checklist
- [ ] Payment method fields and logic added everywhere (DB, API, UI)
- [ ] API-key validation enforced for plan/status
- [ ] System account logic explicit and documented
- [ ] Throttling per tenant, not blanket bypass
- [ ] Middleware and error handling hardened
- [ ] Credit checks before all AI calls
- [ ] Registration atomic and idempotent
- [ ] Bank transfer admin endpoint implemented
- [ ] Full test coverage for all flows
- [ ] Documentation and admin UI updated
---
**Implement every item above and your tenancy system will be fully correct, secure, and robust.**

View File

@@ -1,338 +0,0 @@
Got you. Lets lock the design first, then walk a very concrete implementation plan.
---
## 1. Design baseline
### 1.1 What is actually enforced vs just marketing
| Area | Enforced in backend | Marketing only / derived text |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Sites per account | Yes, via `Plan.max_sites` and `Site.get_max_sectors_limit()` for industries/sectors | Text on pricing page rephrases this, no extra logic |
| Users per account | Yes, via per plan user limit if present in Plan model (or enforced through Account → User count) | Pricing shows same number, no separate enforcement |
| Credits per month | Yes, `Plan.included_credits` / `Plan.get_effective_credits_per_month()``Account.credits` on signup and renewal | Words per month etc derived from credits only |
| Payment method | Yes, new `payment_method` on Account and Subscription (stripe, paypal, bank_transfer) | UI labels like “Bank transfer” or “Coming soon” |
| Keywords, clusters, ideas | Not enforced for now, only advisory copy | Keep in pricing copy, treat as soft guidance |
| Words per month | Not enforced directly | Approx = credits * X words, shown only on frontend |
| Daily content tasks | Not enforced directly | Approx, shown only on frontend |
| Images per month | Optional: either map to credits or leave as copy | Pricing text only for now |
Decision: backend hard limits are Sites, (Industries/Sectors), Users, Credits. Everything else is just marketing descriptions driven from those hard limits.
---
## 2. Target user flows (one view)
### 2.1 High level text diagram
```text
Pricing page (marketing)
→ Choose plan (Starter / Growth / Scale)
→ Click "Start" → App Register (with plan_id preselected)
Register
→ POST /api/v1/auth/register/ with plan_id + payment_method=bank_transfer
→ Create User + Account(plan) + Subscription(payment_method, status='pending_payment')
→ Seed Account.credits = plan.get_effective_credits_per_month()
→ User can log in and use app while account.status in ['trial', 'active', 'pending_payment'] if you allow pre-payment use
App usage (monthly)
→ Login → JWT → AccountContextMiddleware sets request.account
→ User works in Planner / Writer
→ Every AI call:
- CreditService.check_credits()
- AICore runs model
- CreditService.deduct_credits_for_operation()
Renewal
→ Admin confirms bank transfer for next period
→ Or Stripe webhook in future
→ Subscription period dates updated
→ Account.credits reset to plan monthly credits
→ Account.status set to 'active'
Expiry / non payment
→ Subscription.status becomes 'past_due'/'canceled'
→ Account.status set to 'suspended'
→ Login / AI calls blocked with clear message
```
---
## 3. Phase A Plans and pricing alignment
### A1. Make Plan the single source of truth for limits
**Implementation**
* In `Plan` model ensure we have at least: `slug`, `name`, `price`, `billing_cycle`, `included_credits`, `max_sites`, `max_users`, `max_industries` (or current field names).
* Add helper: `Plan.to_public_dict()` that returns exactly what pricing needs:
* `name`, `slug`, `price`, `billing_cycle`
* `max_sites`, `max_users`, `included_credits`
* optional: `tagline`, `recommended`, `order_index` (pure marketing fields stored on Plan to avoid hardcoded frontend).
* Update `/api/v1/auth/plans/` to return only these fields plus computed soft fields:
* `approx_words_per_month = included_credits * WORDS_PER_CREDIT` (constant, eg 120).
* `approx_images_per_month = included_credits / CREDITS_PER_IMAGE` (if you want).
**Verification**
* Hit `GET /api/v1/auth/plans/` and confirm:
* Each plan matches DB row and has only canonical hard limit fields plus derived estimates.
* No legacy fields leaking like “keywords_limit” unless they truly exist.
* Update pricing page to build cards from this response instead of static JSON.
### A2. Simplify pricing UI to reflect hard limits
**Implementation**
* On `igny8.com/pricing`:
* Show for each plan:
* Sites, users, AI credits per month (clear).
* A line like “Up to ~120K AI generated words per month” using API derived field.
* Keep keywords/clusters/ideas as descriptive bullet points, but not used anywhere in backend.
* Change CTA text from “Start free trial” to “Get started” or keep label but functionally treat as plan signup.
**Verification**
* Disable JS and verify pricing still renders correct values from API.
* Check for each plan: numbers on page match `Plan` entries and included_credits.
---
## 4. Phase B Signup, subscription and payment methods
### B1. Extend models for payment_method and external_payment_id
**Implementation**
* Add migrations as per audit:
* `Account.payment_method` (choices: stripe, paypal, bank_transfer, default stripe).
* `Subscription.payment_method` same choices.
* `Subscription.external_payment_id` nullable.
* Make `stripe_subscription_id` nullable and drop unique if necessary.
* Backfill existing rows: set `payment_method='stripe'` where `stripe_subscription_id` is not null.
**Verification**
* Run migrations in staging:
* Confirm existing subscriptions untouched but now have `payment_method='stripe'`.
* New columns visible in Django admin.
### B2. Wire pricing CTA to register with plan
**Implementation**
* Pricing card button:
* On click, route to `app.igny8.com/register?plan=starter` etc.
* Register page in app:
* On mount, call `/api/v1/auth/plans/` and validate that `plan` query param is a valid `slug`.
* Show selected plan summary in the form.
* Registration form posts:
* `POST /api/v1/auth/register/` with body:
```json
{
"email": "...",
"password": "...",
"account_name": "...",
"plan_slug": "starter",
"payment_method": "bank_transfer"
}
```
* Backend resolves `plan_slug` to `Plan`, sets on Account and Subscription, sets `payment_method`, sets `Subscription.status='pending_payment'` and `Account.status='pending_payment'` or `trial`, and seeds `Account.credits = plan.get_effective_credits_per_month()`.
* Wrap creation in a database transaction as per audit.
**Verification**
* From pricing go through full register flow:
* Check DB: new User, Account with correct Plan, Subscription linked, `payment_method='bank_transfer'`, credits set to plan value.
* Decode JWT after login and verify `account_id` matches created account and account.status is as expected.
### B3. Bank transfer confirmation flow
**Implementation**
* Create admin endpoint `POST /api/v1/billing/confirm-bank-transfer/`:
* Guarded by `IsAdminOrOwner` or separate `IsStaffAdmin`.
* Accepts `subscription_id`, `external_payment_id`, amount, payer details, optional proof URL.
* Inside a transaction:
* Validate subscription belongs to account.
* Set `Subscription.payment_method='bank_transfer'`, `external_payment_id=...`, `status='active'`, and new `current_period_start` and `current_period_end`.
* Set `Account.status='active'`.
* Reset `Account.credits = plan.get_effective_credits_per_month()` and create a `CreditTransaction` log entry.
**Verification**
* In staging, create an account via bank transfer path, then hit admin endpoint:
* Confirm status flips from pending to active.
* Credits reset correctly.
* User can login and use AI features without seeing payment required errors.
---
## 5. Phase C Auth, tenancy and API key correctness
### C1. Central account+plan validation
**Implementation**
* Extract helper `validate_account_and_plan(account)` from `AccountContextMiddleware`.
* Checks:
* `account` exists and not deleted.
* `account.status in allowed set` (trial, active, pending_payment if you allow usage).
* `account.plan` exists and plan active.
* Use this helper:
* In middleware for JWT/session requests.
* In `APIKeyAuthentication.authenticate()` for API key requests.
**Verification**
* Test matrix:
* Account active → both JWT and API key get access.
* Account suspended or plan inactive → both JWT and API key requests receive consistent 402/403.
### C2. Throttling and tenant scoping
**Implementation**
* Update `DebugScopedRateThrottle`:
* Keyed by `(scope, account.id)` instead of user only.
* Only bypass when `DEBUG=True` or a special flag is set.
**Verification**
* Use a script to hit AI endpoints many times:
* Same account should start getting 429 after configured limit.
* Different accounts are throttled independently.
---
## 6. Phase D Credits as single limiting mechanism
### D1. Standardize credit operations
**Implementation**
* In `CreditService` define per operation costs:
* `CLUSTER_COST_PER_KEYWORD`, `IDEA_COST_PER_CLUSTER`, `CONTENT_COST_PER_1K_TOKENS`, `IMAGE_COST_PER_IMAGE`, etc.
* For each AI entry point in Planner and Writer:
* Before calling any AI engine, call `CreditService.check_credits(account, operation_type, units)` and fail fast with a friendly error if credits insufficient.
* After success, call `deduct_credits_for_operation`.
* Ensure `AIEngine.execute` never calls external API without passing through CreditService.
**Verification**
* With a test account:
* Set credits small (for example 10 credits).
* Run a sequence of operations until credits exactly hit zero.
* Verify next AI attempt fails with “not enough credits” and no external API call is made.
### D2. Monthly credits reset on renewal
**Implementation**
* In renewal handler (Stripe webhook or bank transfer confirm):
* Always:
* Set `Account.credits = plan.get_effective_credits_per_month()` as new monthly allowance (no rollover), or apply your chosen policy.
**Verification**
* Manipulate subscription dates to simulate new period, run renewal handler:
* Compare previous credits to new credits and confirm reset logic is correct.
---
## 7. Phase E End to end user flows and QA checklists
For each flow from Final_Flow_Tenancy plus the new marketing connection, create a short verification table. Example:
### E1. Flow P Pricing to first login
| Step | Action | Expected backend state |
| ---- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| P1 | Open pricing page | `GET /auth/plans/` called, numbers match Plan rows |
| P2 | Click “Start” on Growth | Browser navigates to `/register?plan=growth` |
| P3 | Submit registration | User, Account(plan=growth), Subscription(payment_method='bank_transfer', status='pending_payment'), Account.credits set |
| P4 | Login | JWT issued, `request.account` set to new account |
| P5 | Visit Sites page | Only default site or none, request.account matches account.id |
### E2. Flow C Credits and AI usage
| Step | Action | Expected result |
| ---- | ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| C1 | Note current Account.credits | Shows value N from Plan |
| C2 | Run auto cluster for small batch | Credits drop by expected amount, clusters created |
| C3 | Generate content draft | Credits drop again, Content row created |
| C4 | Keep triggering until credits zero | Last allowed request succeeds, next request returns “insufficient credits” without external call |
### E3. Flow R Renewal
| Step | Action | Expected result |
| ---- | --------------------------------------- | ---------------------------------------------------------------------------------- |
| R1 | Admin confirms next month bank transfer | Subscription dates advanced, status active, Account.credits reset to monthly quota |
| R2 | User logs in and runs AI | Requests succeed and credits decrement from new monthly total |
### E4. Flow X Expiry
| Step | Action | Expected result |
| ---- | ----------------------------------------------------------------- | ---------------------------------------------------------------- |
| X1 | Set Subscription.status='past_due' and Account.status='suspended' | Login shows “Active subscription required”, AI endpoints blocked |
| X2 | Confirm payment again | Account.status back to active, login and AI work |
---
## 8. Phase F Tests and CI
Wrap everything with tests so this stays correct.
* Unit tests for:
* Plan API response shape vs Plan model.
* Registration transaction: no partial user or account on failure.
* validate_account_and_plan for different statuses.
* CreditService check and deduct behavior for each operation.
* Integration tests for:
* Pricing → register → login → run AI until credits exhausted.
* Bank transfer confirmation and renewal.
* API key access for active vs suspended accounts.
Once these phases are implemented and the flow tables all pass in staging, you have a fully connected, credit based, monthly subscription system where marketing, signup, tenancy, billing, and AI usage all match the single Final_Flow_Tenancy spec plus the two audits.