Files
igny8/final-tenancy-accounts-payments/tenancy-implementation-plan.md
IGNY8 VPS (Salman) 191287829f asdasd
2025-12-08 05:53:32 +00:00

15 KiB
Raw Blame History

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

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:

      {
        "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.