# IGNY8 Multi-Tenancy Data Flow Diagrams

**Last Updated:** December 9, 2024  
**Document Purpose:** Visual representation of all critical workflows in IGNY8 multi-tenancy system  
**Companion Document:** TENANCY-IMPLEMENTATION-GUIDE.md

---

## Table of Contents

1. [Free Trial Signup Flow](#1-free-trial-signup-flow)
2. [Paid Signup Flow](#2-paid-signup-flow)
3. [Payment Confirmation Flow](#3-payment-confirmation-flow)
4. [Payment Approval Flow (Admin)](#4-payment-approval-flow-admin)
5. [Site Creation Flow](#5-site-creation-flow)
6. [Sector Selection Flow](#6-sector-selection-flow)
7. [Credit Allocation Flow](#7-credit-allocation-flow)
8. [Currency Conversion Flow](#8-currency-conversion-flow)
9. [Authentication Flow](#9-authentication-flow)
10. [Complete End-to-End Journey](#10-complete-end-to-end-journey)

---

## 1. Free Trial Signup Flow

### Flow Diagram

```
User Visits Signup Page
    ↓
Enters User Details
- Email
- Password
- First Name
- Last Name
- Country Selection
    ↓
Selects "Free Trial" Plan
    ↓
Frontend → POST /v1/auth/register/
    ↓
Backend: RegisterView.post()
    ├─ Validate Email Uniqueness
    ├─ Validate Password Strength
    └─ Check Country Code
    ↓
Database Transaction Starts
    ↓
Create Account Record
- Generate UUID
- billing_country = selected country
- account_status = 'trial'
- credits = 0 (initially)
    ↓
Create User Record
- Set account FK
- Set role = 'owner'
- hash password
- is_active = True
    ↓
Fetch Free Trial Plan
- Filter: plan_type='trial'
- Get default trial plan
    ↓
Create Subscription
- account FK
- plan FK
- status = 'active'
- current_period_start = now
- current_period_end = now + 7 days
- auto_renew = False
    ↓
Allocate Trial Credits
- Create CreditTransaction
  - type = 'subscription'
  - amount = plan.credits (e.g., 500)
  - description = "Free trial credits"
- Update account.credits = 500
    ↓
Generate JWT Tokens
- Access token (15 min expiry)
- Refresh token (7 day expiry)
- Include account_id in claims
    ↓
Commit Transaction
    ↓
Response to Frontend
{
    user: {...},
    account: {...},
    subscription: {...},
    tokens: {
        access: "...",
        refresh: "..."
    }
}
    ↓
Frontend: authStore.setAuth()
- Store tokens in localStorage
- Set user state in Zustand
- Set account state
    ↓
Redirect to Dashboard
- Free trial banner visible
- 500 credits available
- 7 days remaining
```

### Key Decision Points

**Q: Email already exists?**
- Yes → Return 400 error "Email already registered"
- No → Continue

**Q: Country has payment methods?**
- Yes → Store billing_country
- No → Default to 'US', store billing_country

**Q: Free trial plan exists?**
- Yes → Use existing plan
- No → Return 500 error "Free trial plan not configured"

### State Changes

| Entity | Before | After |
|--------|--------|-------|
| Account | Does not exist | account_status='trial', credits=500 |
| User | Does not exist | is_active=True, role='owner' |
| Subscription | Does not exist | status='active', 7-day period |
| Credits | N/A | 500 credits from plan |

---

## 2. Paid Signup Flow

### Flow Diagram

```
User Visits Signup Page
    ↓
Enters User Details
- Email, Password, Name, Country
    ↓
Selects Paid Plan
- Basic ($29/month)
- Pro ($79/month)
- Enterprise ($199/month)
    ↓
Frontend → POST /v1/auth/register/
    ↓
Backend: RegisterView.post()
    ├─ Validate User Data
    └─ Detect plan_type = 'paid'
    ↓
Database Transaction Starts
    ↓
Create Account Record
- billing_country = selected country
- account_status = 'pending_payment'
- credits = 0
    ↓
Create User Record
- Set account FK
- role = 'owner'
- is_active = True
    ↓
Convert Plan Price to Local Currency
- Fetch plan.price_usd (e.g., $29)
- Get currency multiplier for country
- Calculate: local_amount = price_usd × multiplier
- Example: $29 × 278 = PKR 8,062
    ↓
Create Subscription
- account FK
- plan FK
- status = 'incomplete'
- current_period_start = None
- current_period_end = None
- auto_renew = True (for paid plans)
    ↓
Create Invoice
- account FK
- subscription FK
- amount = local_amount (PKR 8,062)
- currency = local currency code ('PKR')
- status = 'pending'
- due_date = now + 7 days
- Metadata:
  - usd_price: $29
  - exchange_rate: 278
  - plan_name: "Basic Plan"
    ↓
NO Credit Allocation Yet
(Credits allocated after payment approval)
    ↓
Generate JWT Tokens
- Access token
- Refresh token
    ↓
Commit Transaction
    ↓
Response to Frontend
{
    user: {...},
    account: {
        account_status: 'pending_payment',
        credits: 0
    },
    subscription: {
        status: 'incomplete'
    },
    invoice: {
        invoice_number: "INV-123",
        total: 8062.00,
        currency: "PKR",
        status: "pending"
    },
    tokens: {...}
}
    ↓
Frontend: authStore.setAuth()
- Store all data
- Detect pending_payment status
    ↓
Redirect to Payment Instructions
- Show invoice details
- Display payment methods for country
- Request manual payment confirmation
```

### Critical Differences from Free Trial

| Aspect | Free Trial | Paid Signup |
|--------|-----------|-------------|
| Account Status | 'trial' | 'pending_payment' |
| Subscription Status | 'active' | 'incomplete' |
| Credits on Signup | Allocated immediately | 0 (allocated after payment) |
| Invoice Created | No | Yes |
| Period Dates | Set immediately | null until payment |

---

## 3. Payment Confirmation Flow

### Flow Diagram

```
User Completes Manual Payment
(Bank Transfer or Mobile Wallet)
    ↓
User Opens Payment Modal
- Frontend fetches invoice
- GET /v1/billing/invoices/
    ↓
Display Invoice Details
- Amount: PKR 8,062.00
- Currency: PKR
- Plan: Basic Plan
    ↓
User Selects Payment Method
- Dropdown shows methods for billing_country
- GET /v1/billing/admin/payment-methods/?country=PK
- Returns: [JazzCash, EasyPaisa, Bank Transfer]
    ↓
User Enters Payment Details
- Selected method: JazzCash
- Transaction ID: JC20241209123456
- Notes: "Paid via JazzCash mobile app"
- Amount: 8062.00 (auto-filled, read-only)
    ↓
Frontend → POST /v1/billing/admin/payments/confirm/
{
    invoice_id: 123,
    payment_method_id: 45,
    manual_reference: "JC20241209123456",
    manual_notes: "Paid via JazzCash mobile app",
    amount: 8062.00
}
    ↓
Backend: AdminPaymentView.confirm_payment()
    ├─ Validate invoice exists
    ├─ Validate invoice belongs to user's account
    ├─ Check invoice status = 'pending'
    └─ Validate amount matches invoice.total exactly
    ↓
Database Transaction Starts
    ↓
Create Payment Record
- invoice FK
- account FK
- payment_method FK
- amount = 8062.00
- currency = 'PKR'
- status = 'pending_approval'
- manual_reference = "JC20241209123456"
- manual_notes = user's notes
- confirmed_at = now()
    ↓
Update Invoice Status
- status = 'pending' → 'pending_approval'
- Indicates payment under review
    ↓
Commit Transaction
    ↓
Response to Frontend
{
    payment: {
        id: 789,
        status: 'pending_approval',
        amount: 8062.00,
        manual_reference: "JC20241209123456"
    },
    message: "Payment submitted for approval"
}
    ↓
Frontend Shows Success
- "Payment submitted successfully"
- "Admin will review within 24 hours"
- Close modal
- Update invoice list
    ↓
User Waits for Admin Approval
```

### Validation Rules

| Field | Validation |
|-------|-----------|
| invoice_id | Must exist, belong to user's account |
| payment_method_id | Must exist, be active for user's country |
| amount | Must exactly match invoice.total |
| manual_reference | Required, max 255 chars |
| manual_notes | Optional, max 1000 chars |

### State Changes

| Entity | Before | After |
|--------|--------|-------|
| Invoice | status='pending' | status='pending_approval' |
| Payment | Does not exist | status='pending_approval' |
| Account | account_status='pending_payment' | No change yet |
| Subscription | status='incomplete' | No change yet |

---

## 4. Payment Approval Flow (Admin)

### Flow Diagram

```
Admin Logs into Django Admin
    ↓
Navigates to Billing → Payments
    ↓
Sees Payment List
- Filters by status='pending_approval'
- Shows all pending manual payments
    ↓
Admin Reviews Payment
- Checks manual_reference (transaction ID)
- Verifies amount matches invoice
- Reviews manual_notes from user
- Confirms payment received in bank/wallet
    ↓
Admin Approves Payment

Option A: Bulk Action
    ├─ Selects payment checkboxes
    ├─ Chooses "Approve selected manual payments"
    ├─ Clicks "Go"
    └─ Calls approve_manual_payments() action

Option B: Individual Edit
    ├─ Clicks payment to open
    ├─ Changes Status → "Succeeded"
    ├─ Adds admin notes (optional)
    ├─ Clicks "Save"
    └─ Calls PaymentAdmin.save_model()

    ↓
Backend Detects Status Change
- old_status = 'pending_approval'
- new_status = 'succeeded'
- Change detected
    ↓
Database Transaction Starts (Atomic)
    ↓
Update Payment Record
- status = 'succeeded'
- approved_by = admin user
- approved_at = now()
    ↓
Fetch Related Invoice
- Get payment.invoice
    ↓
Update Invoice
- status = 'pending_approval' → 'paid'
- paid_at = now()
    ↓
Fetch Subscription
- Get invoice.subscription
    ↓
Update Subscription
- status = 'incomplete' → 'active'
- current_period_start = now()
- current_period_end = now() + 1 month (for monthly plans)
    ↓
Fetch Account
- Get subscription.account
    ↓
Update Account Status
- account_status = 'pending_payment' → 'active'
    ↓
Allocate Credits (via CreditService)
    ├─ Get plan.credits (e.g., 5000 for Basic)
    ├─ Create CreditTransaction
    │   - type = 'subscription'
    │   - amount = 5000
    │   - description = "Credits for Basic Plan subscription"
    │   - metadata = {payment_id, invoice_id}
    │   - balance_after = 5000
    ├─ Update account.credits = 5000
    └─ Return transaction record
    ↓
Check for Duplicate Allocations
- Query CreditTransaction
- Filter by payment_id in metadata
- If already exists: Skip allocation
- Prevents double-credits on repeated saves
    ↓
Commit Transaction
    ↓
Admin Sees Success Message
"Payment approved successfully. Account activated with 5000 credits."
    ↓
User Can Now Access Full Platform
- account_status = 'active'
- subscription.status = 'active'
- 5000 credits available
- All features unlocked
```

### Approval Decision Tree

```
Payment Status = 'pending_approval'
    ↓
Admin Reviews Evidence
    ├─ Transaction ID valid? ────→ No → Reject Payment
    ├─ Amount matches invoice? ──→ No → Reject Payment
    ├─ Payment received? ────────→ No → Keep pending
    └─ All checks pass? ─────────→ Yes → Approve Payment
                                            ↓
                                    Automatic Workflow
                                            ↓
                                    Account Activated
```

### Critical Atomic Updates

All these must succeed together or all fail:

1. Payment status → succeeded
2. Invoice status → paid
3. Subscription status → active, dates set
4. Account status → active
5. Credits allocated
6. Transaction recorded

If any step fails: Entire transaction rolled back

---

## 5. Site Creation Flow

### Flow Diagram

```
User Navigates to Dashboard
    ↓
Clicks "Add Site" Button
    ↓
Opens Site Creation Wizard
    ↓
Step 1: Basic Information
- Enter site name (e.g., "Tech Blog")
- Enter domain (optional, e.g., "https://techblog.com")
- Enter description (optional)
    ↓
Step 2: Industry Selection
- Dropdown shows all industries
- User selects (e.g., "Technology")
- Frontend stores industry.id
    ↓
Step 3: Additional Settings
- Select hosting type (WordPress/Custom/Static)
- Select site type (Blog/Ecommerce/Corporate)
- Toggle is_active (default: true)
    ↓
User Clicks "Create Site"
    ↓
Frontend → POST /v1/auth/sites/
{
    name: "Tech Blog",
    domain: "https://techblog.com",
    description: "A blog about technology",
    industry: 5,
    hosting_type: "wordpress",
    site_type: "blog",
    is_active: true
}
    ↓
Backend: SiteViewSet.create()
    ↓
Middleware Sets request.account
- Extract from JWT token
- Attach to request object
    ↓
Check Site Limit
    ├─ Get account.plan.max_sites (e.g., 3 for Basic)
    ├─ Count active sites for account
    ├─ Current count >= limit?
    │   ├─ Yes → Return 400 "Site limit reached for your plan"
    │   └─ No → Continue
    ↓
Auto-Generate Slug
    ├─ slugify(name) → "tech-blog"
    ├─ Check uniqueness within account
    ├─ If exists: Append number "tech-blog-2"
    └─ Final slug: "tech-blog"
    ↓
Validate and Format Domain
    ├─ domain = "https://techblog.com"
    ├─ Strip whitespace
    ├─ If empty string → Set to None
    ├─ If starts with http:// → Replace with https://
    ├─ If no protocol → Add https://
    └─ Validate URL format
    ↓
Database Transaction Starts
    ↓
Create Site Record
- account FK (from request.account)
- name = "Tech Blog"
- slug = "tech-blog"
- domain = "https://techblog.com"
- description = text
- industry FK = 5
- hosting_type = "wordpress"
- site_type = "blog"
- is_active = True
- created_by = request.user
    ↓
Check User Role
    ├─ role in ['owner', 'admin']?
    │   ├─ Yes → Grant Access
    │   │   └─ Create SiteUserAccess
    │   │       - user FK
    │   │       - site FK
    │   │       - granted_by = request.user
    │   └─ No → Skip access grant
    ↓
Commit Transaction
    ↓
Response to Frontend
{
    id: 123,
    name: "Tech Blog",
    slug: "tech-blog",
    domain: "https://techblog.com",
    industry: {
        id: 5,
        name: "Technology",
        slug: "technology"
    },
    sectors_count: 0,
    is_active: true
}
    ↓
Frontend Updates Site List
- Add new site to state
- Close wizard modal
- Show success message
    ↓
Redirect to Sector Selection
- Prompt: "Select up to 5 sectors for your site"
```

### Site Creation Validations

| Validation | Rule | Error Message |
|-----------|------|---------------|
| Plan Limit | active_sites < max_sites | "Site limit reached for your plan" |
| Name | Required, max 255 chars | "Site name is required" |
| Slug | Unique within account | Auto-appends number if duplicate |
| Domain | Valid URL or None | "Invalid domain format" |
| Industry | Must exist | "Invalid industry selected" |

### State Changes

| Entity | Before | After |
|--------|--------|-------|
| Site | Does not exist | is_active=True, sectors_count=0 |
| SiteUserAccess | Does not exist | Created for owner/admin |
| Account | site_count = n | site_count = n+1 |

---

## 6. Sector Selection Flow

### Flow Diagram

```
User on Site Details Page
    ↓
Clicks "Add Sectors" Button
    ↓
Frontend Fetches Available Sectors
GET /v1/auth/industries/{industry_slug}/sectors/
    ↓
Response: List of IndustrySectors
[
    {id: 1, name: "AI & Machine Learning", slug: "ai-ml"},
    {id: 2, name: "Web Development", slug: "web-dev"},
    {id: 3, name: "Mobile Apps", slug: "mobile-apps"},
    ...
]
    ↓
Display Sector Selection UI
- Show checkboxes for each sector
- Limit: "Select up to 5 sectors"
- Track selected count
    ↓
User Selects Sectors
- Checks 3 sectors:
  - AI & Machine Learning
  - Web Development
  - Cloud Computing
    ↓
User Clicks "Save Sectors"
    ↓
Frontend → POST /v1/auth/sites/{site_id}/select_sectors/
{
    industry_slug: "technology",
    sector_slugs: ["ai-ml", "web-dev", "cloud-computing"]
}
    ↓
Backend: SiteViewSet.select_sectors()
    ↓
Validate Site Ownership
    ├─ site.account == request.account?
    │   ├─ Yes → Continue
    │   └─ No → Return 403 "Access denied"
    ↓
Validate Industry Match
    ├─ Fetch industry by slug
    ├─ industry == site.industry?
    │   ├─ Yes → Continue
    │   └─ No → Return 400 "Industry mismatch"
    ↓
Check Sector Limit
    ├─ Max sectors per site = 5
    ├─ Count current active sectors
    ├─ Current count = 0
    ├─ Requested count = 3
    ├─ Available slots = 5 - 0 = 5
    ├─ 3 <= 5?
    │   ├─ Yes → Continue
    │   └─ No → Return 400 "Sector limit exceeded"
    ↓
Fetch IndustrySectors
    ├─ Query by slugs: ["ai-ml", "web-dev", "cloud-computing"]
    ├─ Filter by industry
    ├─ Validate all 3 exist
    └─ All found → Continue
    ↓
Database Transaction Starts
    ↓
For Each IndustrySector:
    ↓
Check if Sector Already Exists
    ├─ Query Sector by site + industry_sector
    ├─ Found existing sector?
    │   ├─ Yes → Update
    │   │   └─ Set is_active = True
    │   │   └─ updated_sectors++
    │   └─ No → Create
    │       └─ New Sector Record
    │           - site FK
    │           - industry_sector FK
    │           - account FK (from site.account)
    │           - name = industry_sector.name
    │           - slug = industry_sector.slug
    │           - is_active = True
    │           - created_by = request.user
    │       └─ created_sectors++
    ↓
Commit Transaction
    ↓
Response to Frontend
{
    created: 3,
    updated: 0,
    sectors: [
        {
            id: 501,
            name: "AI & Machine Learning",
            slug: "ai-ml",
            is_active: true
        },
        {
            id: 502,
            name: "Web Development",
            slug: "web-dev",
            is_active: true
        },
        {
            id: 503,
            name: "Cloud Computing",
            slug: "cloud-computing",
            is_active: true
        }
    ]
}
    ↓
Frontend Updates UI
- Display selected sectors
- Update sectors_count = 3
- Show success message
- Enable "Next" button in wizard
```

### Sector Limit Logic

```
Max Sectors = 5 (Hard-coded)
    ↓
Count Active Sectors for Site
    ↓
Available Slots = Max - Current
    ↓
Validate Request
    ├─ Requested <= Available?
    │   ├─ Yes → Allow
    │   └─ No → Reject
```

### Example Scenarios

**Scenario 1: New Site (0 sectors)**
- Request: 3 sectors
- Current: 0
- Available: 5
- Result: ✅ Allow, create 3 new sectors

**Scenario 2: Site with 3 Sectors**
- Request: 2 more sectors
- Current: 3
- Available: 2
- Result: ✅ Allow, create 2 new sectors

**Scenario 3: Site with 5 Sectors**
- Request: 1 more sector
- Current: 5
- Available: 0
- Result: ❌ Reject "Sector limit reached"

**Scenario 4: Reactivating Deleted Sector**
- Request: sector-a (previously deleted)
- Query finds existing Sector with is_active=False
- Result: ✅ Update is_active=True (no new creation)

---

## 7. Credit Allocation Flow

### Flow Diagram

```
Trigger Event
    ├─ Payment Approval (Admin)
    ├─ Free Trial Signup
    ├─ Manual Credit Adjustment (Admin)
    └─ Subscription Renewal (Auto)
    ↓
Call: CreditService.add_credits()
Parameters:
- account: Account object
- amount: Integer (e.g., 5000)
- transaction_type: String ('subscription', 'adjustment', 'refund')
- description: String (human-readable)
- metadata: Dict (payment_id, invoice_id, etc.)
    ↓
Validate Amount
    ├─ amount > 0?
    │   ├─ Yes → Continue
    │   └─ No → Raise ValueError "Amount must be positive"
    ↓
Get Current Balance
- current_balance = account.credits
- Example: current_balance = 0
    ↓
Calculate New Balance
- new_balance = current_balance + amount
- Example: new_balance = 0 + 5000 = 5000
    ↓
Database Transaction Starts
    ↓
Create CreditTransaction Record
- account FK
- type = transaction_type ('subscription')
- amount = 5000 (positive for additions)
- balance_after = 5000
- description = "Credits for Basic Plan subscription"
- metadata = {
    payment_id: 789,
    invoice_id: 123,
    plan_name: "Basic Plan"
  }
- created_at = now()
    ↓
Update Account Credits
- account.credits = new_balance (5000)
- account.save()
    ↓
Commit Transaction
    ↓
Return CreditTransaction Object
    ↓
Credits Available for Use
```

### Credit Deduction Flow

```
Content Generation Request
    ↓
Check Required Credits
- Feature: Blog post generation
- Cost: 100 credits
    ↓
Call: CreditService.deduct_credits()
Parameters:
- account: Account object
- amount: 100
- transaction_type: 'content_generation'
- description: "Blog post: How to Start a Business"
- metadata: {content_id: 456, feature: 'blog'}
    ↓
Validate Sufficient Balance
    ├─ account.credits >= amount?
    │   ├─ Yes → Continue
    │   └─ No → Raise InsufficientCreditsError
    ↓
Calculate New Balance
- current_balance = 5000
- new_balance = 5000 - 100 = 4900
    ↓
Database Transaction Starts
    ↓
Create CreditTransaction Record
- account FK
- type = 'content_generation'
- amount = -100 (negative for deductions)
- balance_after = 4900
- description = "Blog post: How to Start a Business"
- metadata = {content_id: 456}
    ↓
Update Account Credits
- account.credits = 4900
- account.save()
    ↓
Commit Transaction
    ↓
Return CreditTransaction Object
    ↓
Credits Deducted, Content Generated
```

### Transaction History View

```
User Views Credit History
GET /v1/billing/credit-transactions/
    ↓
Backend Returns List
[
    {
        id: 1001,
        type: "subscription",
        amount: 5000,
        balance_after: 5000,
        description: "Credits for Basic Plan subscription",
        created_at: "2024-12-09T10:00:00Z"
    },
    {
        id: 1002,
        type: "content_generation",
        amount: -100,
        balance_after: 4900,
        description: "Blog post: How to Start a Business",
        created_at: "2024-12-09T11:00:00Z"
    },
    {
        id: 1003,
        type: "content_generation",
        amount: -50,
        balance_after: 4850,
        description: "Social media post batch",
        created_at: "2024-12-09T12:00:00Z"
    }
]
    ↓
Frontend Displays Timeline
- Shows running balance
- Color codes: Green for additions, Red for deductions
- Filterable by type
```

---

## 8. Currency Conversion Flow

### Flow Diagram

```
User Signs Up from Pakistan
    ↓
Frontend Detects Country
- navigator.language or IP geolocation
- country_code = 'PK'
    ↓
Sends to Backend
POST /v1/auth/register/
{
    ...user data,
    country: "PK"
}
    ↓
Backend Receives Request
    ↓
User Selects Plan
- Plan: Basic
- plan.price_usd = 29.00
    ↓
Call: convert_usd_to_local(29.00, 'PK')
    ↓
Lookup Currency Multiplier
```python
CURRENCY_MULTIPLIERS = {
    'PK': 278.0,
    'IN': 83.0,
    'GB': 0.79,
    ...
}
multiplier = CURRENCY_MULTIPLIERS.get('PK', 1.0)
# multiplier = 278.0
```
    ↓
Calculate Local Amount
- local_amount = 29.00 × 278.0
- local_amount = 8,062.00
    ↓
Get Currency Code
- Call: get_currency_for_country('PK')
- Returns: 'PKR'
    ↓
Create Invoice with Local Currency
- amount = 8062.00
- currency = 'PKR'
- metadata = {
    usd_price: 29.00,
    exchange_rate: 278.0,
    country: 'PK'
  }
    ↓
Return to Frontend
{
    invoice: {
        total: 8062.00,
        currency: "PKR",
        ...
    }
}
    ↓
Frontend Displays
"Amount Due: PKR 8,062.00"
```

### Multi-Currency Support Table

| Country | Currency | Rate | $29 Plan | $79 Plan | $199 Plan |
|---------|----------|------|----------|----------|-----------|
| Pakistan | PKR | 278.0 | ₨8,062 | ₨21,962 | ₨55,322 |
| India | INR | 83.0 | ₹2,407 | ₹6,557 | ₹16,517 |
| UK | GBP | 0.79 | £22.91 | £62.41 | £157.21 |
| Europe | EUR | 0.92 | €26.68 | €72.68 | €183.08 |
| Canada | CAD | 1.36 | C$39.44 | C$107.44 | C$270.64 |
| Australia | AUD | 1.52 | A$44.08 | A$120.08 | A$302.48 |
| USA | USD | 1.0 | $29.00 | $79.00 | $199.00 |

### Currency Display Formatting

```
Call: format_currency(8062.00, 'PKR')
    ↓
Returns: "PKR 8,062.00"

Call: format_currency(29.00, 'USD')
    ↓
Returns: "$29.00"

Call: format_currency(2407.00, 'INR')
    ↓
Returns: "₹2,407.00"
```

---

## 9. Authentication Flow

### Registration with JWT Token Generation

```
User Submits Registration
POST /v1/auth/register/
    ↓
Backend: RegisterView.post()
    ↓
Create User & Account
(See Free Trial or Paid Signup Flow)
    ↓
Generate Access Token
```python
from igny8_core.auth.utils import generate_access_token

access_token = generate_access_token(user)
# Creates JWT with:
# - user_id
# - account_id
# - email
# - role
# - exp: 15 minutes from now
```
    ↓
Generate Refresh Token
```python
from igny8_core.auth.utils import generate_refresh_token

refresh_token = generate_refresh_token(user)
# Creates JWT with:
# - user_id
# - exp: 7 days from now
```
    ↓
Return Tokens in Response
{
    user: {...},
    account: {...},
    tokens: {
        access: "eyJhbGciOi...",
        refresh: "eyJhbGciOi..."
    }
}
    ↓
Frontend Receives Response
    ↓
authStore.setAuth() Called
    ├─ Extract tokens from response
    │   └─ Check multiple paths:
    │       - tokens?.access
    │       - data?.tokens?.access
    │       - data?.data?.tokens?.access
    ├─ Store in localStorage (synchronous)
    │   └─ localStorage.setItem('access_token', token)
    │   └─ localStorage.setItem('refresh_token', token)
    └─ Update Zustand state
        └─ set({ user, account, isAuthenticated: true })
    ↓
User Authenticated and Logged In
```

**Key Fix Applied (Dec 9, 2024):**

Problem: Users logged out immediately after signup
- RegisterView didn't generate JWT tokens
- Frontend couldn't find tokens in response
- authStore set user but no tokens
- Subsequent requests failed authentication

Solution: Generate tokens like LoginView
- Added generate_access_token(user) call
- Added generate_refresh_token(user) call
- Return tokens in response data
- Frontend extracts with fallback paths

---

### Login Flow

```
User Enters Credentials
POST /v1/auth/login/
{
    email: "user@example.com",
    password: "SecurePass123"
}
    ↓
Backend: LoginView.post()
    ↓
Validate Credentials
    ├─ User exists with email?
    ├─ Password hash matches?
    ├─ User is_active = True?
    │   ├─ All pass → Continue
    │   └─ Any fail → Return 401 "Invalid credentials"
    ↓
Check Account Status
    ├─ account.account_status in ['active', 'trial']?
    │   ├─ Yes → Continue
    │   └─ No → Return 403 "Account not active"
    ↓
Generate Tokens
- Access token (15 min)
- Refresh token (7 days)
    ↓
Return Response
{
    user: {...},
    account: {...},
    tokens: {
        access: "...",
        refresh: "..."
    }
}
    ↓
Frontend Stores Tokens
- localStorage persistence
- Zustand state update
    ↓
User Logged In
```

### Token Refresh Flow

```
Access Token Expires (15 min)
    ↓
API Request Fails with 401
    ↓
Frontend Detects Expiration
    ↓
Check Refresh Token
    ├─ Refresh token exists?
    ├─ Refresh token not expired?
    │   ├─ Yes → Refresh
    │   └─ No → Force logout
    ↓
POST /v1/auth/refresh/
{
    refresh: "eyJhbGciOi..."
}
    ↓
Backend Validates Refresh Token
    ├─ Token signature valid?
    ├─ Token not expired?
    ├─ User still exists and active?
    │   ├─ All pass → Continue
    │   └─ Any fail → Return 401
    ↓
Generate New Access Token
- Fresh 15-minute token
- Same claims as before
    ↓
Return Response
{
    access: "eyJhbGciOi...",
    refresh: "eyJhbGciOi..." (same or new)
}
    ↓
Frontend Updates Token
- Replace access_token in localStorage
- Retry failed API request
    ↓
Request Succeeds
```

### Middleware Flow (Every Request)

```
API Request with Token
Authorization: Bearer eyJhbGciOi...
    ↓
Django Middleware Pipeline
    ↓
JWTAuthenticationMiddleware
    ├─ Extract token from header
    ├─ Decode and validate JWT
    ├─ Extract user_id from claims
    ├─ Fetch User object
    ├─ Attach to request.user
    └─ Continue to next middleware
    ↓
TenantMiddleware
    ├─ Get account_id from JWT claims
    ├─ Fetch Account object
    ├─ Attach to request.account
    ├─ Set thread-local for query filtering
    └─ Continue to view
    ↓
View Executes
- Has access to request.user
- Has access to request.account
- All queries auto-filtered by account
```

---

## 10. Complete End-to-End Journey

### Paid User Journey (Bank Transfer Payment)

```
┌─────────────────────────────────────────────────────────────┐
│                    Day 1: User Signup                        │
└─────────────────────────────────────────────────────────────┘
                            ↓
User Visits Landing Page (Pakistan)
                            ↓
Clicks "Get Started" → Signup Form
                            ↓
Fills Form:
- Email: owner@business.pk
- Password: SecurePass123
- Name: Ahmad Khan
- Country: Pakistan
                            ↓
Selects Plan: Basic ($29/month)
                            ↓
POST /v1/auth/register/
                            ↓
Backend Creates:
├─ Account (account_status='pending_payment')
├─ User (role='owner')
├─ Subscription (status='incomplete')
└─ Invoice (PKR 8,062.00, status='pending')
                            ↓
JWT Tokens Generated and Returned
                            ↓
Frontend Redirects to Payment Instructions
                            ↓
User Sees:
- Invoice #INV-001
- Amount: PKR 8,062.00
- Payment methods for Pakistan
- Instructions for bank transfer


┌─────────────────────────────────────────────────────────────┐
│              Day 1: User Makes Payment                       │
└─────────────────────────────────────────────────────────────┘
                            ↓
User Opens Banking App
                            ↓
Transfers PKR 8,062 to Company Account
- Transaction ID: TXN20241209001
- Takes screenshot of receipt
                            ↓
Returns to Platform
                            ↓
Clicks "Confirm Payment" Button
                            ↓
Payment Modal Opens
- Shows invoice amount: PKR 8,062.00
- Dropdown: Selects "Bank Transfer"
- Enters Transaction ID: TXN20241209001
- Enters Notes: "Paid via HBL mobile banking"
                            ↓
POST /v1/billing/admin/payments/confirm/
                            ↓
Backend Creates Payment:
- status='pending_approval'
- manual_reference='TXN20241209001'
                            ↓
Invoice Updated:
- status='pending' → 'pending_approval'
                            ↓
User Sees Success Message:
"Payment submitted. We'll review within 24 hours."
                            ↓
User Waits (Limited Access)
- Can browse dashboard
- Cannot create sites/content
- account_status='pending_payment'


┌─────────────────────────────────────────────────────────────┐
│            Day 2: Admin Approves Payment                     │
└─────────────────────────────────────────────────────────────┘
                            ↓
Admin Logs into Django Admin
                            ↓
Navigates: Billing → Payments
                            ↓
Filters: Status = Pending Approval
                            ↓
Sees Payment:
- Account: Ahmad Khan (owner@business.pk)
- Amount: PKR 8,062.00
- Method: Bank Transfer
- Reference: TXN20241209001
- Notes: "Paid via HBL mobile banking"
                            ↓
Admin Verifies:
✓ Checks bank account
✓ Sees PKR 8,062 received
✓ Transaction ID matches
✓ Amount correct
                            ↓
Admin Clicks Payment → Opens Edit
                            ↓
Changes Status: "Pending Approval" → "Succeeded"
                            ↓
Clicks "Save"
                            ↓
Automatic Workflow Triggers:
├─ Payment.status = 'succeeded'
├─ Payment.approved_by = admin
├─ Invoice.status = 'paid'
├─ Subscription.status = 'active'
├─ Subscription.current_period_end = +30 days
├─ Account.account_status = 'active'
└─ Credits allocated: +5000
    └─ CreditTransaction created
                            ↓
Admin Sees Success Message:
"Payment approved. Account activated with 5000 credits."


┌─────────────────────────────────────────────────────────────┐
│          Day 2: User Gets Activated (Automatic)              │
└─────────────────────────────────────────────────────────────┘
                            ↓
User Refreshes Dashboard
                            ↓
API Calls Return Updated Data:
- account.account_status = 'active'
- account.credits = 5000
- subscription.status = 'active'
                            ↓
UI Changes:
- Payment pending banner disappears
- "Create Site" button enabled
- Credit balance shows 5000
- All features unlocked
                            ↓
User Clicks "Create Site"
                            ↓
Opens Site Creation Wizard
                            ↓
Fills Form:
- Name: "Digital Marketing Blog"
- Domain: https://digitalmktg.pk
- Industry: Marketing
- Type: Blog
                            ↓
POST /v1/auth/sites/
                            ↓
Backend:
├─ Checks plan limit (3 sites for Basic)
├─ Current sites: 0
├─ Creates site
└─ Grants owner access
                            ↓
User Sees New Site Created
                            ↓
Proceeds to Select Sectors:
- Selects 3 sectors:
  ├─ Content Marketing
  ├─ Social Media
  └─ SEO
                            ↓
POST /v1/auth/sites/123/select_sectors/
                            ↓
Backend Creates 3 Sector Records
                            ↓
Site Setup Complete
                            ↓
User Navigates to Content Generation
                            ↓
Generates First Blog Post
- Cost: 100 credits
- Deduction: 5000 → 4900
                            ↓
Credits Deducted, Content Created
                            ↓
User Actively Using Platform
- 4900 credits remaining
- 29 days left in subscription
- Full access to all features


┌─────────────────────────────────────────────────────────────┐
│        Day 32: Subscription Renewal (Future Feature)         │
└─────────────────────────────────────────────────────────────┘
                            ↓
Celery Task: check_expiring_subscriptions
                            ↓
Detects subscription.current_period_end approaching
                            ↓
If auto_renew=True:
├─ Create new invoice
├─ Send payment reminder email
└─ User repeats payment flow
                            ↓
If auto_renew=False:
├─ Subscription expires
├─ account_status = 'trial' or 'inactive'
└─ User must renew manually
```

---

**End of Data Flow Diagrams Document**

This document provides visual representation of all critical workflows in the IGNY8 multi-tenancy system as implemented on December 9, 2024.
