19 KiB
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
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
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
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
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:
- Pick a clean test user and Stripe test customer.
- Walk F1 then F2 in sequence and confirm all DB fields line up.
- Run through F3a, F3b, F3c and log credits before and after each AI-heavy action.
- Simulate renewal and cancellation via Stripe or DB and walk F4 and F5.