asdasd
This commit is contained in:
338
final-tenancy-accounts-payments/tenancy-implementation-plan.md
Normal file
338
final-tenancy-accounts-payments/tenancy-implementation-plan.md
Normal file
@@ -0,0 +1,338 @@
|
||||
Got you. Let’s 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.
|
||||
Reference in New Issue
Block a user