Files
igny8/IMPLEMENTATION-PLAN-SIGNUP-TO-PAYMENT-WORKFLOW.md
2025-12-08 16:47:27 +00:00

109 KiB

IMPLEMENTATION PLAN: Clean Signup to Payment Workflow

Version: 2.0
Date: December 8, 2025
Status: 🔧 READY FOR IMPLEMENTATION


Table of Contents

  1. Executive Summary
  2. Deep Analysis Findings
  3. Field & Relationship Audit
  4. Simplified Model Architecture
  5. Complete Workflow Diagrams
  6. Implementation Phases
  7. Database Migrations
  8. Testing Plan

Executive Summary

Current State Analysis

Critical Issues Found:

  1. Duplicate Date Fields - Period dates stored in 2 places (Subscription + Invoice)
  2. Payment Method Chaos - Stored in 3 different models with no single source of truth
  3. Missing Relationships - Subscription has no plan field, breaking invoice creation
  4. Unused Payment References - No external_payment_id captured for manual payments
  5. Profile Fields Not Updated - Billing info never synced from account to invoices
  6. Country-Specific Logic Missing - Pakistan payment methods not filtered
  7. Site Industry Not Required - Can create sites without industry, then can't add sectors
  8. Broken Paid Signup Flow - Imports non-existent billing.Subscription

Complexity Issues:

  • 4 global payment methods + 1 country-specific = unnecessary complexity
  • Payment method config table exists but not used in signup flow
  • Manual payment instructions not shown to users
  • No clear workflow for payment confirmation after signup

Target Architecture

Simplified Models:

Account (1) ──────┐
  ├─ plan_id      │
  ├─ credits      │
  └─ status       │
                  │
Subscription (1)  │◄── ONE-TO-ONE relationship
  ├─ account_id   │
  ├─ plan_id      │◄── ADDED (currently missing)
  ├─ period_start │◄── SINGLE source of truth for dates
  ├─ period_end   │
  └─ status       │
                  │
Invoice (many)    │◄── Created from Subscription
  ├─ subscription_id
  ├─ total        │◄── No duplicate period dates
  └─ status       │
                  │
Payment (many)    │◄── Links to Invoice
  ├─ invoice_id   │
  ├─ payment_method
  ├─ external_payment_id  ◄── ADDED for manual payments
  └─ status       │

Simplified Payment Methods:

  • Global (3): Stripe, PayPal, Bank Transfer
  • Country-Specific (1): Local Wallet (Pakistan only)
  • Total: 4 payment methods (removed unnecessary complexity)

Deep Analysis Findings

1. Date Field Redundancy

Problem: Period dates duplicated in multiple places

Current State:

# Subscription model (auth/models.py line 253-254)
current_period_start = models.DateTimeField()
current_period_end = models.DateTimeField()

# Invoice model (billing/models.py line 212-213)
billing_period_start = models.DateTimeField(null=True, blank=True)
billing_period_end = models.DateTimeField(null=True, blank=True)

Issues:

  • Same period stored in 2 places
  • Can become out of sync
  • Invoice fields are nullable but subscription fields are required
  • No automatic sync mechanism

Solution: Keep dates in Subscription only (single source of truth)
Remove billing_period_start/end from Invoice
Access dates via invoice.subscription.current_period_start

Migration Required:

# 1. Remove fields from Invoice model
migrations.RemoveField('invoice', 'billing_period_start')
migrations.RemoveField('invoice', 'billing_period_end')

# No data loss - dates preserved in Subscription

2. Payment Method Field Chaos

Problem: Payment method stored in 3 different models with no clear hierarchy

Current Locations:

# 1. Account (auth/models.py line 87-92)
payment_method = models.CharField(
    max_length=30,
    choices=PAYMENT_METHOD_CHOICES,
    default='stripe'
)

# 2. Subscription (auth/models.py line 240-244)
payment_method = models.CharField(
    max_length=30,
    choices=PAYMENT_METHOD_CHOICES,
    default='stripe'
)

# 3. Payment (billing/models.py line 309)
payment_method = models.CharField(
    max_length=50,
    choices=PAYMENT_METHOD_CHOICES,
    db_index=True
)

# 4. AccountPaymentMethod (billing/models.py line 476) ✓ CORRECT
type = models.CharField(
    max_length=50,
    choices=PAYMENT_METHOD_CHOICES
)

Which is Used?

  • Registration: Sets Account.payment_method and creates AccountPaymentMethod
  • Subscription creation: Copies from account or uses 'bank_transfer' default
  • Payment processing: Uses Payment.payment_method
  • Result: 3 fields that can be out of sync

Solution: Use AccountPaymentMethod as single source of truth
Deprecate Account.payment_method (make it read-only derived property)
Deprecate Subscription.payment_method (get from account's default method)
Keep Payment.payment_method for historical record

New Pattern:

# Account model
@property
def payment_method(self):
    """Get default payment method from AccountPaymentMethod"""
    default_method = self.accountpaymentmethod_set.filter(
        is_default=True, 
        is_enabled=True
    ).first()
    return default_method.type if default_method else 'stripe'

# Subscription model - remove field, use property
@property
def payment_method(self):
    return self.account.payment_method

3. Missing Subscription.plan Relationship

Problem: Subscription has no plan field, breaking invoice logic

Current State:

# Subscription model (auth/models.py line 218-260)
class Subscription(models.Model):
    account = models.OneToOneField('Account')
    # ❌ NO PLAN FIELD
    stripe_subscription_id = CharField
    payment_method = CharField
    status = CharField
    current_period_start = DateTimeField
    current_period_end = DateTimeField

Code Expects Plan:

# InvoiceService.create_subscription_invoice()
subscription.plan  # ❌ AttributeError: 'Subscription' object has no attribute 'plan'

Why It's Missing:

  • Plan stored in Account.plan only
  • Assumption: Get plan via subscription.account.plan
  • Problem: If account changes plan mid-subscription, historical invoices show wrong plan

Solution: Add Subscription.plan field
Set from Account.plan at subscription creation
Keep plan locked for subscription duration (historical accuracy)
New subscription created when plan changes

Migration:

migrations.AddField(
    model_name='subscription',
    name='plan',
    field=models.ForeignKey(
        'igny8_core_auth.Plan',
        on_delete=models.PROTECT,
        related_name='subscriptions',
        null=True  # Temporarily nullable
    ),
)

# Data migration: Copy from account
def copy_plan_from_account(apps, schema_editor):
    Subscription = apps.get_model('igny8_core_auth', 'Subscription')
    for sub in Subscription.objects.all():
        sub.plan = sub.account.plan
        sub.save()

# Make required
migrations.AlterField(
    model_name='subscription',
    name='plan',
    field=models.ForeignKey(
        'igny8_core_auth.Plan',
        on_delete=models.PROTECT,
        related_name='subscriptions'
    ),
)

4. Missing Payment References for Manual Payments

Problem: No way to track bank transfer references or local wallet transaction IDs

Current State:

# Payment model has these fields:
stripe_payment_intent_id = CharField  # ✓ For Stripe
stripe_charge_id = CharField          # ✓ For Stripe
paypal_order_id = CharField           # ✓ For PayPal
paypal_capture_id = CharField         # ✓ For PayPal

# For manual payments:
manual_reference = CharField(blank=True)  # ✓ EXISTS but not used
transaction_reference = CharField(blank=True)  # ⚠️ DUPLICATE field

Issues:

  • Two fields for same purpose (manual_reference and transaction_reference)
  • Neither field is populated during signup
  • No validation requiring reference for manual payments
  • Admin approval has no reference number to verify

Solution: Keep manual_reference only (remove transaction_reference)
Require manual_reference for bank_transfer and local_wallet payments
Show reference field in payment confirmation form
Display reference in admin approval interface

Validation:

class Payment(models.Model):
    def clean(self):
        # Require manual_reference for manual payment methods
        if self.payment_method in ['bank_transfer', 'local_wallet']:
            if not self.manual_reference:
                raise ValidationError({
                    'manual_reference': 'Reference number required for manual payments'
                })

5. Billing Profile Fields Not Updated

Problem: Account billing fields exist but never populated or synced

Current Account Fields:

# Account model (auth/models.py line 100-110)
billing_email = models.EmailField(blank=True, null=True)
billing_address_line1 = CharField(max_length=255, blank=True)
billing_address_line2 = CharField(max_length=255, blank=True)
billing_city = CharField(max_length=100, blank=True)
billing_state = CharField(max_length=100, blank=True)
billing_postal_code = CharField(max_length=20, blank=True)
billing_country = CharField(max_length=2, blank=True)
tax_id = CharField(max_length=100, blank=True)

When Are They Set? Not set during registration
Not set during payment
No form to update them
Invoice has duplicate billing_email field

Solution: Add billing form during paid plan signup (before payment)
Update account billing fields when user confirms payment
Snapshot billing info to Invoice.metadata at invoice creation
Remove duplicate Invoice.billing_email field

Signup Flow Update:

1. User selects paid plan
2. User fills registration form
3. User fills billing info form ← NEW STEP
4. User selects payment method
5. Payment instructions shown
6. Account created with billing info
7. Invoice created with billing snapshot

6. Payment Method Country Logic Missing

Problem: Pakistan-specific payment method not filtered by country

Current Setup:

# PaymentMethodConfig model exists (billing/models.py line 410)
class PaymentMethodConfig(models.Model):
    country_code = CharField  # e.g., 'PK'
    payment_method = CharField  # e.g., 'local_wallet'
    is_enabled = BooleanField
    instructions = TextField
    wallet_type = CharField  # e.g., 'JazzCash', 'Easypaisa'

Issues:

  • Config exists but NOT used in signup flow
  • Frontend shows all 4 payment methods to everyone
  • No country detection
  • No filtering logic

Solution: Detect user country from IP or billing_country
Query PaymentMethodConfig for available methods
Show only enabled methods for user's country
Create default configs for global methods

Default Configurations:

# Global methods (available everywhere)
PaymentMethodConfig.objects.create(
    country_code='*',  # Wildcard for all countries
    payment_method='stripe',
    is_enabled=True,
    display_name='Credit/Debit Card (Stripe)',
    sort_order=1
)

PaymentMethodConfig.objects.create(
    country_code='*',
    payment_method='paypal',
    is_enabled=True,
    display_name='PayPal',
    sort_order=2
)

PaymentMethodConfig.objects.create(
    country_code='*',
    payment_method='bank_transfer',
    is_enabled=True,
    display_name='Bank Transfer',
    instructions='Transfer to: Account 123456...',
    sort_order=3
)

# Pakistan-specific
PaymentMethodConfig.objects.create(
    country_code='PK',
    payment_method='local_wallet',
    is_enabled=True,
    display_name='JazzCash / Easypaisa',
    wallet_type='JazzCash',
    wallet_id='03001234567',
    instructions='Send payment to JazzCash: 03001234567',
    sort_order=4
)

API Endpoint:

# New endpoint: GET /v1/billing/payment-methods/?country=PK
def list_payment_methods(request):
    country = request.GET.get('country', '*')
    
    # Get country-specific + global methods
    methods = PaymentMethodConfig.objects.filter(
        Q(country_code=country) | Q(country_code='*'),
        is_enabled=True
    ).order_by('sort_order')
    
    return Response(PaymentMethodConfigSerializer(methods, many=True).data)

7. Site.industry Field Not Required

Problem: Industry is nullable but required for sector creation

Current State:

# Site model (auth/models.py line 280-286)
industry = models.ForeignKey(
    'Industry',
    on_delete=models.PROTECT,
    related_name='sites',
    null=True,  # ❌ NULLABLE
    blank=True,
    help_text="Industry this site belongs to"
)

Serializer:

# SiteSerializer (auth/serializers.py line 60-82)
class Meta:
    fields = ['industry', ...]
    # ❌ industry not in required_fields

Impact:

  • Sites created without industry
  • Sector creation fails: if self.industry_sector.industry != self.site.industry
  • If site.industry is None, comparison always fails
  • User can't add any sectors

Solution: Make Site.industry required (not nullable)
Update migration to set default industry for existing NULL sites
Update serializer to require industry
Update frontend to show industry selector in site creation form

Migration:

# 1. Set default industry for existing sites
def set_default_industry(apps, schema_editor):
    Site = apps.get_model('igny8_core_auth', 'Site')
    Industry = apps.get_model('igny8_core_auth', 'Industry')
    
    default_industry = Industry.objects.filter(
        slug='technology'
    ).first()
    
    if default_industry:
        Site.objects.filter(industry__isnull=True).update(
            industry=default_industry
        )

migrations.RunPython(set_default_industry)

# 2. Make field required
migrations.AlterField(
    model_name='site',
    name='industry',
    field=models.ForeignKey(
        'Industry',
        on_delete=models.PROTECT,
        related_name='sites'
        # Removed: null=True, blank=True
    ),
)

8. Broken Paid Signup Import

Problem: Imports non-existent billing.Subscription

Current Code:

# auth/serializers.py line 291
from igny8_core.business.billing.models import Subscription  # ❌ DOES NOT EXIST

Fix:

# Use existing auth.Subscription
from igny8_core.auth.models import Subscription  # ✓ EXISTS

Field & Relationship Audit

Complete Field Inventory

Subscription Model - BEFORE Cleanup

class Subscription(models.Model):
    # Relationships
    account = OneToOneField('Account')          # ✓ KEEP
    # ❌ MISSING: plan field
    
    # Payment tracking
    stripe_subscription_id = CharField          # ✓ KEEP
    payment_method = CharField                  # ❌ REMOVE (use AccountPaymentMethod)
    external_payment_id = CharField             # ✓ KEEP
    
    # Status & dates
    status = CharField                          # ✓ KEEP
    current_period_start = DateTimeField        # ✓ KEEP (source of truth)
    current_period_end = DateTimeField          # ✓ KEEP (source of truth)
    cancel_at_period_end = BooleanField         # ✓ KEEP
    
    # Audit
    created_at = DateTimeField                  # ✓ KEEP
    updated_at = DateTimeField                  # ✓ KEEP

Subscription Model - AFTER Cleanup

class Subscription(models.Model):
    # Relationships
    account = OneToOneField('Account')          # ✓ Tenant isolation
    plan = ForeignKey('Plan')                   # ✅ ADDED - historical plan tracking
    
    # Payment tracking
    stripe_subscription_id = CharField          # ✓ Stripe reference
    external_payment_id = CharField             # ✓ Manual payment reference
    
    # Status & dates (SINGLE SOURCE OF TRUTH)
    status = CharField                          # ✓ active/past_due/canceled
    current_period_start = DateTimeField        # ✓ Billing cycle start
    current_period_end = DateTimeField          # ✓ Billing cycle end
    cancel_at_period_end = BooleanField         # ✓ Cancellation flag
    
    # Audit
    created_at = DateTimeField
    updated_at = DateTimeField
    
    # Properties (derived, not stored)
    @property
    def payment_method(self):
        """Get from AccountPaymentMethod"""
        return self.account.payment_method

Changes Summary:

  • Added: plan field (FK to Plan)
  • Removed: payment_method field (use property instead)
  • Field count: 11 → 11 (same, but cleaner relationships)

Invoice Model - BEFORE Cleanup

class Invoice(AccountBaseModel):
    # Relationships
    account = ForeignKey('Account')             # ✓ KEEP (via AccountBaseModel)
    subscription = ForeignKey('Subscription')   # ✓ KEEP
    
    # Amounts
    subtotal = DecimalField                     # ✓ KEEP
    tax = DecimalField                          # ✓ KEEP
    total = DecimalField                        # ✓ KEEP
    currency = CharField                        # ✓ KEEP
    
    # Status & dates
    status = CharField                          # ✓ KEEP
    invoice_number = CharField                  # ✓ KEEP
    invoice_date = DateField                    # ✓ KEEP
    due_date = DateField                        # ✓ KEEP
    paid_at = DateTimeField                     # ✓ KEEP
    
    # DUPLICATE FIELDS
    billing_period_start = DateTimeField        # ❌ REMOVE (in Subscription)
    billing_period_end = DateTimeField          # ❌ REMOVE (in Subscription)
    billing_email = EmailField                  # ❌ REMOVE (use metadata snapshot)
    payment_method = CharField                  # ✓ KEEP (historical record)
    
    # Data
    line_items = JSONField                      # ✓ KEEP
    notes = TextField                           # ✓ KEEP
    metadata = JSONField                        # ✓ KEEP (billing snapshot goes here)
    
    # Stripe
    stripe_invoice_id = CharField               # ✓ KEEP
    
    # Audit
    created_at = DateTimeField                  # ✓ KEEP
    updated_at = DateTimeField                  # ✓ KEEP

Invoice Model - AFTER Cleanup

class Invoice(AccountBaseModel):
    # Relationships
    account = ForeignKey('Account')             # ✓ Tenant isolation
    subscription = ForeignKey('Subscription')   # ✓ Links to subscription
    
    # Invoice identity
    invoice_number = CharField(unique=True)     # ✓ INV-2025-001
    
    # Amounts
    subtotal = DecimalField                     # ✓ Pre-tax amount
    tax = DecimalField                          # ✓ Tax amount
    total = DecimalField                        # ✓ Total payable
    currency = CharField(default='USD')         # ✓ Currency code
    
    # Status & dates
    status = CharField                          # ✓ draft/pending/paid/void
    invoice_date = DateField                    # ✓ Issue date
    due_date = DateField                        # ✓ Payment deadline
    paid_at = DateTimeField(null=True)          # ✓ Payment timestamp
    
    # Historical tracking
    payment_method = CharField                  # ✓ Payment method used
    
    # Data
    line_items = JSONField(default=list)        # ✓ Invoice items
    notes = TextField(blank=True)               # ✓ Admin notes
    metadata = JSONField(default=dict)          # ✓ Billing snapshot
    
    # Stripe integration
    stripe_invoice_id = CharField               # ✓ Stripe reference
    
    # Audit
    created_at = DateTimeField
    updated_at = DateTimeField
    
    # Properties (access via relationship)
    @property
    def billing_period_start(self):
        """Get from subscription"""
        return self.subscription.current_period_start if self.subscription else None
    
    @property
    def billing_period_end(self):
        """Get from subscription"""
        return self.subscription.current_period_end if self.subscription else None
    
    @property
    def billing_email(self):
        """Get from metadata snapshot or account"""
        return self.metadata.get('billing_email') or self.account.billing_email

Changes Summary:

  • Removed: billing_period_start (get from subscription)
  • Removed: billing_period_end (get from subscription)
  • Removed: billing_email (use metadata snapshot)
  • Field count: 21 → 18 (3 fields removed, cleaner)

Simplified Model Architecture

Core Models Summary

Model Fields (Before) Fields (After) Changes
Account 26 fields 26 fields Convert payment_method to property
Subscription 11 fields 11 fields Add plan, Remove payment_method field
Invoice 21 fields 18 fields Remove 3 duplicate fields
Payment 26 fields 25 fields Remove transaction_reference
AccountPaymentMethod 11 fields 11 fields No changes (already correct)
PaymentMethodConfig 14 fields 14 fields No changes (needs implementation)

Total Field Reduction: 109 fields → 105 fields (4 fewer fields, cleaner relationships)


Relationship Map - BEFORE Cleanup

Account (1)
  ├─ plan_id ──────────────────► Plan
  ├─ payment_method (field)     ⚠️ Can be out of sync
  │
  ├─ Subscription (1:1)
  │    ├─ payment_method (field)  ⚠️ Duplicate #2
  │    ├─ period_start
  │    ├─ period_end
  │    └─ ❌ NO plan field
  │
  ├─ Invoice (1:many)
  │    ├─ subscription_id
  │    ├─ period_start           ⚠️ Duplicate dates
  │    ├─ period_end             ⚠️ Duplicate dates
  │    ├─ billing_email          ⚠️ Duplicate field
  │    └─ payment_method (field) ⚠️ Duplicate #3
  │
  ├─ Payment (1:many)
  │    ├─ invoice_id
  │    ├─ payment_method (field) ⚠️ Duplicate #4
  │    ├─ manual_reference
  │    └─ transaction_reference   ⚠️ Duplicate field
  │
  └─ AccountPaymentMethod (1:many)  ✓ Correct design
       ├─ type
       ├─ is_default
       └─ is_enabled

Problems:

  • 🔴 4 payment_method fields (which is source of truth?)
  • 🔴 Period dates duplicated (Subscription + Invoice)
  • 🔴 Billing email duplicated (Account + Invoice)
  • 🔴 Payment references duplicated (manual_reference + transaction_reference)
  • 🔴 No plan in Subscription (can't track plan changes)

Relationship Map - AFTER Cleanup

Account (1)
  ├─ plan_id ──────────────────► Plan
  │  @property payment_method → get from AccountPaymentMethod
  │
  ├─ Subscription (1:1)
  │    ├─ ✅ plan_id ──────────► Plan (added)
  │    ├─ period_start          ◄─── SINGLE SOURCE
  │    ├─ period_end            ◄─── SINGLE SOURCE
  │    └─ @property payment_method → get from Account
  │
  ├─ Invoice (1:many)
  │    ├─ subscription_id ─────► Subscription
  │    ├─ @property period_start  → via subscription
  │    ├─ @property period_end    → via subscription
  │    ├─ @property billing_email → via metadata
  │    └─ payment_method (field)  ✓ Historical record
  │
  ├─ Payment (1:many)
  │    ├─ invoice_id ──────────► Invoice
  │    ├─ payment_method (field)  ✓ Historical record
  │    └─ manual_reference        ✓ Single field
  │
  └─ AccountPaymentMethod (1:many) ◄─── PRIMARY SOURCE
       ├─ type                    ◄─── Source of truth
       ├─ is_default
       ├─ is_enabled
       └─ instructions

PaymentMethodConfig (global)
  ├─ country_code
  ├─ payment_method
  ├─ is_enabled
  └─ instructions

Solutions:

  • AccountPaymentMethod is single source of truth
  • Subscription has plan_id for historical tracking
  • Period dates only in Subscription (Invoice uses properties)
  • Billing email only in Account (Invoice snapshots to metadata)
  • Single payment reference field
  • Clear hierarchy: Config → AccountPaymentMethod → Historical records

Complete Workflow Diagrams

1. FREE TRIAL SIGNUP FLOW

┌────────────────────────────────────────────────────────────────┐
│ USER: Clicks "Start Free Trial" on homepage                   │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: /signup (no plan parameter)                         │
│                                                                 │
│  Form Fields:                                                   │
│  ├─ First Name*                                                │
│  ├─ Last Name*                                                 │
│  ├─ Email*                                                     │
│  ├─ Password*                                                  │
│  ├─ Account Name (optional)                                   │
│  └─ ☑ Agree to Terms                                          │
│                                                                 │
│  [Create Account] button                                       │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ API: POST /v1/auth/register/                                   │
│                                                                 │
│  Request Body:                                                  │
│  {                                                              │
│    "email": "user@example.com",                                │
│    "password": "SecurePass123!",                               │
│    "first_name": "John",                                       │
│    "last_name": "Doe",                                         │
│    "account_name": "John's Business"                           │
│    // plan_slug not provided = defaults to 'free'             │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ BACKEND: RegisterSerializer.create()                           │
│                                                                 │
│  1. Resolve Plan                                               │
│     plan = Plan.objects.get(slug='free')                      │
│     status = 'trial'                                           │
│     credits = 1000 (plan.included_credits)                    │
│                                                                 │
│  2. Generate Username                                          │
│     username = email.split('@')[0]                            │
│     (ensure unique with counter if needed)                     │
│                                                                 │
│  3. Create User (without account first)                        │
│     user = User.objects.create_user(                          │
│       username=username,                                       │
│       email=email,                                             │
│       password=hashed_password,                                │
│       role='owner',                                            │
│       account=None  ← Will be set later                       │
│     )                                                           │
│                                                                 │
│  4. Generate Account Slug                                      │
│     slug = slugify(account_name)                              │
│     (ensure unique with counter)                               │
│                                                                 │
│  5. Create Account                                             │
│     account = Account.objects.create(                         │
│       name=account_name,                                       │
│       slug=slug,                                               │
│       owner=user,                                              │
│       plan=plan,                                               │
│       credits=1000,                                            │
│       status='trial'                                           │
│     )                                                           │
│                                                                 │
│  6. Link User to Account                                       │
│     user.account = account                                     │
│     user.save()                                                │
│                                                                 │
│  7. Log Credit Transaction                                     │
│     CreditTransaction.objects.create(                         │
│       account=account,                                         │
│       transaction_type='subscription',                        │
│       amount=1000,                                             │
│       balance_after=1000,                                      │
│       description='Free plan credits'                         │
│     )                                                           │
│                                                                 │
│  8. Return User Object                                         │
│     return user                                                │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: Check account status                                 │
│                                                                 │
│  if (user.account.status === 'trial') {                        │
│    navigate('/sites')  ← Go to dashboard                      │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ USER: Lands on /sites (Sites page)                            │
│                                                                 │
│  - Can create 1 site (plan.max_sites = 1)                     │
│  - Has 1000 credits to use                                     │
│  - Status: Free Trial                                          │
└────────────────────────────────────────────────────────────────┘

Database State After Free Trial Signup:

-- igny8_tenants (Accounts)
INSERT INTO igny8_tenants VALUES (
  1, 'Johns Business', 'johns-business', 1, 1000, 'trial', NULL
);
-- id, name, slug, plan_id, credits, status, owner_id

-- igny8_users
INSERT INTO igny8_users VALUES (
  1, 'john@example.com', 'john-doe', 'owner', 1
);
-- id, email, username, role, tenant_id

-- igny8_credit_transactions
INSERT INTO igny8_credit_transactions VALUES (
  1, 1, 'subscription', 1000, 1000, 'Free plan credits'
);
-- id, tenant_id, type, amount, balance_after, description

-- igny8_subscriptions: NO RECORD (free trial has no subscription)
-- igny8_invoices: NO RECORD
-- igny8_payments: NO RECORD

2. PAID PLAN SIGNUP FLOW (With Manual Payment)

┌────────────────────────────────────────────────────────────────┐
│ USER: Clicks "Get Started" on Starter plan pricing card       │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: /signup?plan=starter                                 │
│                                                                 │
│  Page loads with plan details shown:                           │
│  ┌──────────────────────────────────────┐                     │
│  │ 📋 Starter Plan                       │                     │
│  │ $29/month                             │                     │
│  │ ✓ 5,000 credits                      │                     │
│  │ ✓ 3 sites                             │                     │
│  │ ✓ 3 users                             │                     │
│  └──────────────────────────────────────┘                     │
│                                                                 │
│  Form Fields:                                                   │
│  ├─ First Name*                                                │
│  ├─ Last Name*                                                 │
│  ├─ Email*                                                     │
│  ├─ Password*                                                  │
│  ├─ Account Name*                                              │
│  └─ ☑ Agree to Terms                                          │
│                                                                 │
│  [Continue to Billing] button                                  │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: /signup/billing (New Step)                          │
│                                                                 │
│  Billing Information Form:                                      │
│  ├─ Billing Email*                                             │
│  ├─ Country* (dropdown with flag icons)                       │
│  ├─ Address Line 1                                             │
│  ├─ Address Line 2                                             │
│  ├─ City                                                       │
│  ├─ State/Province                                             │
│  ├─ Postal Code                                                │
│  └─ Tax ID (optional)                                          │
│                                                                 │
│  [Continue to Payment] button                                  │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: Fetch Available Payment Methods                     │
│                                                                 │
│  API: GET /v1/billing/payment-methods/?country=PK             │
│                                                                 │
│  Response:                                                      │
│  [                                                              │
│    {                                                            │
│      "payment_method": "stripe",                               │
│      "display_name": "Credit/Debit Card",                     │
│      "sort_order": 1                                           │
│    },                                                           │
│    {                                                            │
│      "payment_method": "paypal",                               │
│      "display_name": "PayPal",                                 │
│      "sort_order": 2                                           │
│    },                                                           │
│    {                                                            │
│      "payment_method": "bank_transfer",                        │
│      "display_name": "Bank Transfer",                          │
│      "instructions": "Transfer to: Account 123456789...",     │
│      "sort_order": 3                                           │
│    },                                                           │
│    {                                                            │
│      "payment_method": "local_wallet",                         │
│      "display_name": "JazzCash / Easypaisa",                  │
│      "wallet_type": "JazzCash",                                │
│      "wallet_id": "03001234567",                               │
│      "instructions": "Send to JazzCash: 03001234567",         │
│      "sort_order": 4                                           │
│    }                                                            │
│  ]                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: /signup/payment                                      │
│                                                                 │
│  Payment Method Selection:                                      │
│  ○ Credit/Debit Card (Stripe)                                 │
│  ○ PayPal                                                      │
│  ● Bank Transfer ← User selects                               │
│  ○ JazzCash / Easypaisa                                        │
│                                                                 │
│  [Conditional: If Bank Transfer selected]                      │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ 📋 Bank Transfer Instructions                             │ │
│  │                                                            │ │
│  │ Account Name: IGNY8 Inc                                   │ │
│  │ Account Number: 123456789                                 │ │
│  │ Bank Name: ABC Bank                                       │ │
│  │ SWIFT: ABCPKKA                                            │ │
│  │                                                            │ │
│  │ Amount: $29.00 USD                                        │ │
│  │                                                            │ │
│  │ ⚠️ Important:                                             │ │
│  │ - Make payment to above account                           │ │
│  │ - Keep transaction reference                              │ │
│  │ - You'll confirm payment in next step                     │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                                 │
│  [Create Account & Continue] button                            │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ API: POST /v1/auth/register/                                   │
│                                                                 │
│  Request Body:                                                  │
│  {                                                              │
│    "email": "user@example.com",                                │
│    "password": "SecurePass123!",                               │
│    "first_name": "Ahmad",                                      │
│    "last_name": "Khan",                                        │
│    "account_name": "Ahmad Tech",                               │
│    "plan_slug": "starter",                                     │
│    "payment_method": "bank_transfer",                          │
│    "billing_email": "billing@ahmad.com",                       │
│    "billing_country": "PK",                                    │
│    "billing_address_line1": "123 Main St",                     │
│    "billing_city": "Karachi"                                   │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ BACKEND: RegisterSerializer.create() - PAID PLAN PATH         │
│                                                                 │
│  1. Resolve Plan                                               │
│     plan = Plan.objects.get(slug='starter')                   │
│     status = 'pending_payment'                                 │
│     credits = 0  ← No credits until payment confirmed         │
│                                                                 │
│  2. Calculate Period Dates                                     │
│     period_start = timezone.now()                              │
│     period_end = period_start + 30 days                        │
│                                                                 │
│  3. Create User                                                │
│     user = User.objects.create_user(...)                      │
│                                                                 │
│  4. Create Account WITH Billing Info                           │
│     account = Account.objects.create(                         │
│       name="Ahmad Tech",                                       │
│       slug="ahmad-tech",                                       │
│       owner=user,                                              │
│       plan=plan,                                               │
│       credits=0,                                               │
│       status='pending_payment',                                │
│       billing_email="billing@ahmad.com",                       │
│       billing_country="PK",                                    │
│       billing_address_line1="123 Main St",                     │
│       billing_city="Karachi"                                   │
│     )                                                           │
│                                                                 │
│  5. Link User to Account                                       │
│     user.account = account                                     │
│     user.save()                                                │
│                                                                 │
│  6. Create Subscription ✅ WITH PLAN                           │
│     subscription = Subscription.objects.create(                │
│       account=account,                                         │
│       plan=plan,  ← ADDED FIELD                               │
│       status='pending_payment',                                │
│       current_period_start=period_start,                       │
│       current_period_end=period_end,                           │
│       cancel_at_period_end=False                               │
│     )                                                           │
│                                                                 │
│  7. Create Invoice                                             │
│     invoice = InvoiceService.create_subscription_invoice(      │
│       subscription=subscription,                               │
│       billing_period_start=period_start,                       │
│       billing_period_end=period_end                            │
│     )                                                           │
│     # Invoice fields:                                          │
│     # - invoice_number: "INV-2025-001"                         │
│     # - total: 29.00                                           │
│     # - status: "pending"                                      │
│     # - metadata: { billing snapshot }                         │
│                                                                 │
│  8. Create AccountPaymentMethod                                │
│     AccountPaymentMethod.objects.create(                      │
│       account=account,                                         │
│       type='bank_transfer',                                    │
│       display_name='Bank Transfer (Manual)',                   │
│       is_default=True,                                         │
│       is_enabled=True,                                         │
│       instructions='See invoice for details'                   │
│     )                                                           │
│                                                                 │
│  9. NO CREDIT TRANSACTION (not yet paid)                       │
│                                                                 │
│  10. Return User Object                                        │
│      return user                                               │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: Check account status                                 │
│                                                                 │
│  if (user.account.status === 'pending_payment') {              │
│    navigate('/account/plans')  ← Go to payment confirmation   │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ USER: Lands on /account/plans (Account & Plans Page)          │
│                                                                 │
│  Account Status Bar:                                            │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ ⚠️  Payment Pending                                       │ │
│  │ Your account is pending payment confirmation              │ │
│  │ [Confirm Payment] button                                  │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                                 │
│  Current Plan: Starter Plan ($29/month)                        │
│  Status: Pending Payment                                       │
│  Credits: 0 / 5,000                                            │
│                                                                 │
│  Invoices Tab:                                                  │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ Invoice #INV-2025-001                                     │ │
│  │ Date: Dec 8, 2025                                         │ │
│  │ Amount: $29.00                                            │ │
│  │ Status: Pending                                           │ │
│  │ Payment Method: Bank Transfer                             │ │
│  │ [Confirm Payment] button                                  │ │
│  └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘

Database State After Paid Signup (Before Payment Confirmation):

-- igny8_tenants
INSERT INTO igny8_tenants VALUES (
  2, 'Ahmad Tech', 'ahmad-tech', 2, 0, 'pending_payment', 
  'billing@ahmad.com', 'PK', '123 Main St', NULL, 'Karachi', NULL, NULL, NULL
);
-- id, name, slug, plan_id, credits, status, billing_email, billing_country, address...

-- igny8_users
INSERT INTO igny8_users VALUES (
  2, 'user@example.com', 'ahmad-khan', 'owner', 2
);

-- igny8_subscriptions ✅ WITH PLAN NOW
INSERT INTO igny8_subscriptions VALUES (
  2, 2, 2, NULL, 'pending_payment', '2025-12-08', '2026-01-08', FALSE
);
-- id, tenant_id, plan_id, stripe_id, status, period_start, period_end, cancel_at_end

-- igny8_invoices
INSERT INTO igny8_invoices VALUES (
  2, 2, 2, 'INV-2025-002', 29.00, 0, 29.00, 'USD', 'pending', '2025-12-08', '2025-12-15'
);
-- id, tenant_id, subscription_id, invoice_number, subtotal, tax, total, currency, status, invoice_date, due_date

-- igny8_account_payment_methods
INSERT INTO igny8_account_payment_methods VALUES (
  2, 2, 'bank_transfer', 'Bank Transfer (Manual)', TRUE, TRUE, FALSE
);
-- id, tenant_id, type, display_name, is_default, is_enabled, is_verified

-- igny8_payments: NO RECORD YET
-- igny8_credit_transactions: NO RECORD YET

3. PAYMENT CONFIRMATION FLOW (Manual Payment)

┌────────────────────────────────────────────────────────────────┐
│ USER: Clicks "Confirm Payment" button                          │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: Payment Confirmation Modal/Page                      │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ 💳 Confirm Payment                                        │ │
│  │                                                            │ │
│  │ Invoice: #INV-2025-002                                    │ │
│  │ Amount: $29.00 USD                                        │ │
│  │ Payment Method: Bank Transfer                             │ │
│  │                                                            │ │
│  │ Payment Reference Number:*                                │ │
│  │ ┌──────────────────────────────────────────────────────┐ │ │
│  │ │ [Enter transaction reference]                         │ │ │
│  │ └──────────────────────────────────────────────────────┘ │ │
│  │                                                            │ │
│  │ Payment Date:*                                            │ │
│  │ ┌──────────────────────────────────────────────────────┐ │ │
│  │ │ [Select date]                                         │ │ │
│  │ └──────────────────────────────────────────────────────┘ │ │
│  │                                                            │ │
│  │ Additional Notes:                                         │ │
│  │ ┌──────────────────────────────────────────────────────┐ │ │
│  │ │ [Optional notes about payment]                        │ │ │
│  │ └──────────────────────────────────────────────────────┘ │ │
│  │                                                            │ │
│  │ Upload Proof (Optional):                                  │ │
│  │ ┌──────────────────────────────────────────────────────┐ │ │
│  │ │ [Upload receipt/screenshot]                           │ │ │
│  │ └──────────────────────────────────────────────────────┘ │ │
│  │                                                            │ │
│  │ ⚠️ Your payment will be reviewed by our team within 24hrs│ │
│  │                                                            │ │
│  │ [Cancel]  [Submit for Approval]                          │ │
│  └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ API: POST /v1/billing/payments/confirm/                        │
│                                                                 │
│  Request Body:                                                  │
│  {                                                              │
│    "invoice_id": 2,                                            │
│    "payment_method": "bank_transfer",                          │
│    "manual_reference": "BT-20251208-12345",                    │
│    "manual_notes": "Paid via ABC Bank on Dec 8",              │
│    "amount": "29.00",                                          │
│    "proof_url": "https://s3.../receipt.jpg"                    │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ BACKEND: Create Payment Record - Pending Approval              │
│                                                                 │
│  Payment.objects.create(                                        │
│    account=account,                                            │
│    invoice=invoice,                                            │
│    amount=29.00,                                               │
│    currency='USD',                                             │
│    status='pending_approval',  ← Awaiting admin approval      │
│    payment_method='bank_transfer',                             │
│    manual_reference='BT-20251208-12345',  ← User provided     │
│    manual_notes='Paid via ABC Bank on Dec 8',                 │
│    metadata={                                                  │
│      'proof_url': 'https://s3.../receipt.jpg'                 │
│    }                                                            │
│  )                                                              │
│                                                                 │
│  # Invoice remains "pending" until payment approved            │
│  # Account remains "pending_payment" until payment approved    │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: Success Message                                      │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ ✅ Payment Submitted                                      │ │
│  │                                                            │ │
│  │ Your payment confirmation has been submitted for review.  │ │
│  │ We'll verify your payment and activate your account       │ │
│  │ within 24 hours.                                          │ │
│  │                                                            │ │
│  │ Reference: BT-20251208-12345                              │ │
│  │ Status: Pending Review                                    │ │
│  │                                                            │ │
│  │ You'll receive an email once approved.                    │ │
│  │                                                            │ │
│  │ [Go to Dashboard]                                         │ │
│  └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ USER: Dashboard shows "Payment under review" status            │
│                                                                 │
│  - Can browse platform but can't create sites                 │
│  - Credits still 0 until approved                              │
│  - Status shows "Pending Approval"                             │
└────────────────────────────────────────────────────────────────┘

Database State After Payment Submission:

-- igny8_payments (NEW RECORD)
INSERT INTO igny8_payments VALUES (
  1, 2, 2, 29.00, 'USD', 'pending_approval', 'bank_transfer',
  NULL, NULL, NULL, NULL,
  'BT-20251208-12345', 'Paid via ABC Bank on Dec 8',
  NULL, NULL, NULL, NULL, NULL, NULL,
  '{"proof_url": "https://s3.../receipt.jpg"}'
);
-- id, tenant_id, invoice_id, amount, currency, status, payment_method,
-- stripe fields, paypal fields,
-- manual_reference, manual_notes,
-- admin_notes, approved_by, approved_at, processed_at, failed_at, refunded_at,
-- metadata

4. ADMIN APPROVAL FLOW

┌────────────────────────────────────────────────────────────────┐
│ ADMIN: Receives notification of pending payment                │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ ADMIN PANEL: /admin/billing/payment/                           │
│                                                                 │
│  Pending Payments List:                                         │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ Payment #1                                                │ │
│  │ Account: Ahmad Tech                                       │ │
│  │ Invoice: INV-2025-002                                     │ │
│  │ Amount: $29.00                                            │ │
│  │ Method: Bank Transfer                                     │ │
│  │ Reference: BT-20251208-12345                              │ │
│  │ Notes: Paid via ABC Bank on Dec 8                        │ │
│  │ Proof: [View Receipt]                                     │ │
│  │                                                            │ │
│  │ [Approve] [Reject]                                        │ │
│  └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ ADMIN: Clicks "Approve" button                                 │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ API: POST /v1/billing/payments/1/approve/                      │
│                                                                 │
│  Request Body:                                                  │
│  {                                                              │
│    "admin_notes": "Verified payment in bank statement"         │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ BACKEND: Approve Payment & Activate Account                    │
│                                                                 │
│  with transaction.atomic():                                     │
│    # 1. Update Payment                                         │
│    payment.status = 'succeeded'                                │
│    payment.approved_by = admin_user                            │
│    payment.approved_at = timezone.now()                        │
│    payment.processed_at = timezone.now()                       │
│    payment.admin_notes = 'Verified payment...'                 │
│    payment.save()                                              │
│                                                                 │
│    # 2. Update Invoice                                         │
│    invoice.status = 'paid'                                     │
│    invoice.paid_at = timezone.now()                            │
│    invoice.save()                                              │
│                                                                 │
│    # 3. Update Subscription                                    │
│    subscription.status = 'active'                              │
│    subscription.external_payment_id = payment.manual_reference │
│    subscription.save()                                         │
│                                                                 │
│    # 4. Update Account                                         │
│    account.status = 'active'                                   │
│    account.save()                                              │
│                                                                 │
│    # 5. Add Credits                                            │
│    CreditService.add_credits(                                  │
│      account=account,                                          │
│      amount=subscription.plan.included_credits,  # 5000        │
│      description=f'Starter plan credits - {invoice.invoice_number}',│
│      metadata={                                                │
│        'subscription_id': subscription.id,                     │
│        'invoice_id': invoice.id,                               │
│        'payment_id': payment.id                                │
│      }                                                          │
│    )                                                            │
│    # This creates:                                             │
│    # - Updates account.credits = 5000                          │
│    # - Creates CreditTransaction record                        │
│                                                                 │
│    # 6. Send Activation Email                                  │
│    send_account_activated_email(account, subscription)        │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ USER: Receives email "Account Activated"                       │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ USER: Logs in and sees activated account                       │
│                                                                 │
│  Account Status: ✅ Active                                     │
│  Plan: Starter Plan                                            │
│  Credits: 5,000 / 5,000                                        │
│  Sites: 0 / 3                                                  │
│                                                                 │
│  [Create Your First Site] button                               │
└────────────────────────────────────────────────────────────────┘

Database State After Admin Approval:

-- igny8_tenants (UPDATED)
UPDATE igny8_tenants SET status = 'active', credits = 5000 WHERE id = 2;

-- igny8_subscriptions (UPDATED)
UPDATE igny8_subscriptions SET status = 'active', external_payment_id = 'BT-20251208-12345' WHERE id = 2;

-- igny8_invoices (UPDATED)
UPDATE igny8_invoices SET status = 'paid', paid_at = '2025-12-08 15:30:00' WHERE id = 2;

-- igny8_payments (UPDATED)
UPDATE igny8_payments SET 
  status = 'succeeded', 
  approved_by = 1, 
  approved_at = '2025-12-08 15:30:00',
  processed_at = '2025-12-08 15:30:00',
  admin_notes = 'Verified payment in bank statement'
WHERE id = 1;

-- igny8_credit_transactions (NEW RECORD)
INSERT INTO igny8_credit_transactions VALUES (
  2, 2, 'subscription', 5000, 5000, 'Starter plan credits - INV-2025-002',
  '{"subscription_id": 2, "invoice_id": 2, "payment_id": 1}'
);

5. SITE CREATION FLOW (After Account Activated)

┌────────────────────────────────────────────────────────────────┐
│ USER: Clicks "Create Your First Site" button                   │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: /sites/create                                        │
│                                                                 │
│  Site Creation Form:                                            │
│  ├─ Site Name*                                                 │
│  │  [Enter site name]                                          │
│  │                                                             │
│  ├─ Domain/URL                                                 │
│  │  [https://example.com]                                      │
│  │                                                             │
│  ├─ Industry* ✅ REQUIRED FIELD                                │
│  │  [Select industry ▼]                                        │
│  │   - Technology                                              │
│  │   - Healthcare                                              │
│  │   - Finance                                                 │
│  │   - E-commerce                                              │
│  │   ...                                                       │
│  │                                                             │
│  ├─ Description                                                │
│  │  [Describe your site...]                                    │
│  │                                                             │
│  └─ Site Type                                                  │
│     ○ Marketing Site                                           │
│     ○ Blog                                                     │
│     ○ E-commerce                                               │
│     ○ Corporate                                                │
│                                                                 │
│  [Cancel] [Create Site]                                        │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ API: POST /v1/auth/sites/                                      │
│                                                                 │
│  Request Body:                                                  │
│  {                                                              │
│    "name": "Tech News Hub",                                    │
│    "domain": "https://technewshub.com",                        │
│    "industry": 2,  ← REQUIRED (Technology industry ID)        │
│    "description": "Latest technology news and tutorials",      │
│    "site_type": "blog"                                         │
│  }                                                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ BACKEND: SiteViewSet.create()                                  │
│                                                                 │
│  1. Validate Account Plan Limits                               │
│     max_sites = account.plan.max_sites  # 3 for Starter       │
│     current_sites = account.sites.filter(is_active=True).count()│
│     if current_sites >= max_sites:                             │
│       raise ValidationError("Site limit reached")              │
│                                                                 │
│  2. Validate Industry Required ✅                              │
│     if not validated_data.get('industry'):                     │
│       raise ValidationError("Industry is required")            │
│                                                                 │
│  3. Generate Unique Slug                                       │
│     slug = slugify("Tech News Hub")  # "tech-news-hub"        │
│     # Ensure unique per account                                │
│                                                                 │
│  4. Normalize Domain                                           │
│     domain = "https://technewshub.com"                         │
│     # Ensure https:// prefix                                   │
│                                                                 │
│  5. Create Site                                                │
│     site = Site.objects.create(                               │
│       account=request.account,  # Auto from middleware        │
│       name="Tech News Hub",                                    │
│       slug="tech-news-hub",                                    │
│       domain="https://technewshub.com",                        │
│       industry_id=2,  # Technology                            │
│       description="Latest technology news...",                 │
│       site_type="blog",                                        │
│       hosting_type="igny8_sites",                              │
│       status="active",                                         │
│       is_active=True                                           │
│     )                                                           │
│                                                                 │
│  6. Create SiteUserAccess ✅ NEW STEP                          │
│     SiteUserAccess.objects.create(                            │
│       user=request.user,                                       │
│       site=site,                                               │
│       granted_by=request.user                                  │
│     )                                                           │
│                                                                 │
│  7. Return Site Object                                         │
│     return site                                                │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ FRONTEND: Redirect to /sites/{site_id}/sectors                │
│                                                                 │
│  Success message: "Site created! Now add sectors to organize   │
│                    your content."                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│ USER: Add Sectors to Site                                      │
│                                                                 │
│  Available Sectors for Technology Industry:                     │
│  □ Web Development                                             │
│  □ AI & Machine Learning                                       │
│  □ Cybersecurity                                               │
│  □ Cloud Computing                                             │
│  □ Mobile Development                                          │
│                                                                 │
│  Select up to 5 sectors (Starter plan limit)                   │
│  [Add Selected Sectors]                                        │
└────────────────────────────────────────────────────────────────┘

Database State After Site Creation:

-- igny8_sites (NEW RECORD)
INSERT INTO igny8_sites VALUES (
  1, 2, 'Tech News Hub', 'tech-news-hub', 'https://technewshub.com',
  'Latest technology news and tutorials', 2, TRUE, 'active',
  'blog', 'igny8_sites'
);
-- id, tenant_id, name, slug, domain, description, industry_id, is_active, status, site_type, hosting_type

-- igny8_site_user_access (NEW RECORD) ✅
INSERT INTO igny8_site_user_access VALUES (
  1, 2, 1, 2, '2025-12-08 16:00:00'
);
-- id, user_id, site_id, granted_by_id, granted_at

Implementation Phases

PHASE 1: Critical Fixes (URGENT - 1 Day)

Priority: 🔴 Blocking Production

1.1 Fix Subscription Import

# File: backend/igny8_core/auth/serializers.py
# Line 291

# BEFORE:
from igny8_core.business.billing.models import Subscription  # ❌

# AFTER:
from igny8_core.auth.models import Subscription  # ✅

1.2 Add Subscription.plan Field

cd backend
python manage.py makemigrations --name add_subscription_plan_field

# Migration file created: 0XXX_add_subscription_plan_field.py
# Migration content:
from django.db import migrations, models

def copy_plan_from_account(apps, schema_editor):
    """Copy plan from account to subscription"""
    Subscription = apps.get_model('igny8_core_auth', 'Subscription')
    for sub in Subscription.objects.select_related('account__plan'):
        sub.plan = sub.account.plan
        sub.save(update_fields=['plan'])

class Migration(migrations.Migration):
    dependencies = [
        ('igny8_core_auth', '0XXX_previous_migration'),
    ]
    
    operations = [
        # 1. Add field as nullable
        migrations.AddField(
            model_name='subscription',
            name='plan',
            field=models.ForeignKey(
                'igny8_core_auth.Plan',
                on_delete=models.PROTECT,
                related_name='subscriptions',
                null=True
            ),
        ),
        # 2. Copy data
        migrations.RunPython(
            copy_plan_from_account,
            reverse_code=migrations.RunPython.noop
        ),
        # 3. Make non-nullable
        migrations.AlterField(
            model_name='subscription',
            name='plan',
            field=models.ForeignKey(
                'igny8_core_auth.Plan',
                on_delete=models.PROTECT,
                related_name='subscriptions'
            ),
        ),
    ]
# Apply migration
python manage.py migrate

1.3 Make Site.industry Required

python manage.py makemigrations --name make_site_industry_required
def set_default_industry(apps, schema_editor):
    """Set default industry for sites without one"""
    Site = apps.get_model('igny8_core_auth', 'Site')
    Industry = apps.get_model('igny8_core_auth', 'Industry')
    
    default = Industry.objects.filter(slug='technology').first()
    if default:
        Site.objects.filter(industry__isnull=True).update(industry=default)

class Migration(migrations.Migration):
    operations = [
        migrations.RunPython(set_default_industry),
        migrations.AlterField(
            model_name='site',
            name='industry',
            field=models.ForeignKey(
                'igny8_core_auth.Industry',
                on_delete=models.PROTECT,
                related_name='sites'
            ),
        ),
    ]

1.4 Update Free Plan Credits

docker exec igny8_backend python manage.py shell
from igny8_core.auth.models import Plan
plan = Plan.objects.get(slug='free')
plan.included_credits = 1000
plan.save()
print(f"Updated free plan credits: {plan.included_credits}")

Testing Phase 1:

# Test 1: Free trial signup
curl -X POST http://localhost:8000/api/v1/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "Test123!",
    "password_confirm": "Test123!",
    "first_name": "Test",
    "last_name": "User"
  }'
# Expected: ✅ Success, 1000 credits

# Test 2: Paid signup (should not crash)
curl -X POST http://localhost:8000/api/v1/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "paid@example.com",
    "password": "Test123!",
    "password_confirm": "Test123!",
    "first_name": "Paid",
    "last_name": "User",
    "plan_slug": "starter"
  }'
# Expected: ✅ Success, subscription created with plan_id

PHASE 2: Model Cleanup (2-3 Days)

Priority: 🟡 Important

2.1 Remove Duplicate Fields from Invoice

python manage.py makemigrations --name remove_duplicate_invoice_fields
class Migration(migrations.Migration):
    operations = [
        migrations.RemoveField('invoice', 'billing_period_start'),
        migrations.RemoveField('invoice', 'billing_period_end'),
        migrations.RemoveField('invoice', 'billing_email'),
    ]

2.2 Add Properties to Invoice Model

# File: backend/igny8_core/business/billing/models.py

class Invoice(AccountBaseModel):
    # ... existing fields ...
    
    @property
    def billing_period_start(self):
        """Get from subscription"""
        return self.subscription.current_period_start if self.subscription else None
    
    @property
    def billing_period_end(self):
        """Get from subscription"""
        return self.subscription.current_period_end if self.subscription else None
    
    @property
    def billing_email(self):
        """Get from metadata or account"""
        snapshot = self.metadata.get('billing_snapshot', {})
        return snapshot.get('email') or self.account.billing_email

2.3 Remove Duplicate payment_method from Subscription

python manage.py makemigrations --name remove_subscription_payment_method
class Migration(migrations.Migration):
    operations = [
        migrations.RemoveField('subscription', 'payment_method'),
    ]

2.4 Add payment_method Property to Subscription

# File: backend/igny8_core/auth/models.py

class Subscription(models.Model):
    # ... existing fields ...
    
    @property
    def payment_method(self):
        """Get from account's default payment method"""
        return self.account.payment_method

2.5 Convert Account.payment_method to Property

# File: backend/igny8_core/auth/models.py

class Account(SoftDeletableModel):
    # Keep field for backward compatibility but make it read-only in forms
    # Eventually remove via migration
    
    @property
    def default_payment_method(self):
        """Get default payment method from AccountPaymentMethod"""
        method = self.accountpaymentmethod_set.filter(
            is_default=True,
            is_enabled=True
        ).first()
        return method.type if method else 'stripe'

2.6 Remove transaction_reference from Payment

python manage.py makemigrations --name remove_duplicate_payment_reference
class Migration(migrations.Migration):
    operations = [
        migrations.RemoveField('payment', 'transaction_reference'),
    ]

PHASE 3: New Features (3-5 Days)

Priority: 🟢 Enhancement

3.1 Create PaymentMethodConfig Default Data

docker exec igny8_backend python manage.py shell
from igny8_core.business.billing.models import PaymentMethodConfig

# Global methods (available everywhere)
PaymentMethodConfig.objects.get_or_create(
    country_code='*',
    payment_method='stripe',
    defaults={
        'is_enabled': True,
        'display_name': 'Credit/Debit Card (Stripe)',
        'sort_order': 1
    }
)

PaymentMethodConfig.objects.get_or_create(
    country_code='*',
    payment_method='paypal',
    defaults={
        'is_enabled': True,
        'display_name': 'PayPal',
        'sort_order': 2
    }
)

PaymentMethodConfig.objects.get_or_create(
    country_code='*',
    payment_method='bank_transfer',
    defaults={
        'is_enabled': True,
        'display_name': 'Bank Transfer',
        'instructions': '''
            Bank Name: ABC Bank
            Account Name: IGNY8 Inc
            Account Number: 123456789
            SWIFT: ABCPKKA
            
            Please transfer the exact invoice amount and keep the transaction reference.
        ''',
        'sort_order': 3
    }
)

# Pakistan-specific
PaymentMethodConfig.objects.get_or_create(
    country_code='PK',
    payment_method='local_wallet',
    defaults={
        'is_enabled': True,
        'display_name': 'JazzCash / Easypaisa',
        'wallet_type': 'JazzCash',
        'wallet_id': '03001234567',
        'instructions': '''
            Send payment to:
            JazzCash: 03001234567
            Account Name: IGNY8
            
            Please keep the transaction ID and confirm payment after sending.
        ''',
        'sort_order': 4
    }
)

print("Payment method configurations created!")

3.2 Create Payment Methods API Endpoint

# File: backend/igny8_core/business/billing/views.py

@action(detail=False, methods=['get'], url_path='payment-methods')
def list_payment_methods(self, request):
    """Get available payment methods for user's country"""
    country = request.GET.get('country', '*')
    
    # Get country-specific + global methods
    from django.db.models import Q
    methods = PaymentMethodConfig.objects.filter(
        Q(country_code=country) | Q(country_code='*'),
        is_enabled=True
    ).order_by('sort_order')
    
    from .serializers import PaymentMethodConfigSerializer
    return Response(PaymentMethodConfigSerializer(methods, many=True).data)

3.3 Create Payment Confirmation Endpoint

# File: backend/igny8_core/business/billing/views.py

@action(detail=False, methods=['post'], url_path='payments/confirm')
def confirm_payment(self, request):
    """User confirms manual payment with reference"""
    invoice_id = request.data.get('invoice_id')
    manual_reference = request.data.get('manual_reference')
    manual_notes = request.data.get('manual_notes', '')
    proof_url = request.data.get('proof_url')
    
    if not invoice_id or not manual_reference:
        return error_response(
            error='invoice_id and manual_reference required',
            status_code=400,
            request=request
        )
    
    try:
        invoice = Invoice.objects.get(
            id=invoice_id,
            account=request.account
        )
        
        payment = Payment.objects.create(
            account=request.account,
            invoice=invoice,
            amount=invoice.total,
            currency=invoice.currency,
            status='pending_approval',
            payment_method=invoice.payment_method or 'bank_transfer',
            manual_reference=manual_reference,
            manual_notes=manual_notes,
            metadata={'proof_url': proof_url} if proof_url else {}
        )
        
        # Send notification to admin
        send_payment_confirmation_notification(payment)
        
        return success_response(
            data={'payment_id': payment.id, 'status': 'pending_approval'},
            message='Payment confirmation submitted for review',
            request=request
        )
    except Invoice.DoesNotExist:
        return error_response(
            error='Invoice not found',
            status_code=404,
            request=request
        )

3.4 Update RegisterSerializer for Billing Fields

# File: backend/igny8_core/auth/serializers.py

class RegisterSerializer(serializers.ModelSerializer):
    # ... existing fields ...
    
    # Add billing fields
    billing_email = serializers.EmailField(required=False, allow_blank=True)
    billing_address = serializers.CharField(required=False, allow_blank=True)
    billing_city = serializers.CharField(required=False, allow_blank=True)
    billing_country = serializers.CharField(required=False, allow_blank=True)
    payment_method = serializers.CharField(required=False, default='stripe')
    
    class Meta:
        model = User
        fields = [
            'email', 'password', 'password_confirm', 'first_name', 'last_name',
            'billing_email', 'billing_address', 'billing_city', 'billing_country',
            'payment_method', 'plan_slug'
        ]
    
    def create(self, validated_data):
        # ... existing account/user creation ...
        
        # Update billing fields if provided
        billing_email = validated_data.pop('billing_email', None)
        billing_address = validated_data.pop('billing_address', None)
        billing_city = validated_data.pop('billing_city', None)
        billing_country = validated_data.pop('billing_country', None)
        payment_method = validated_data.pop('payment_method', 'stripe')
        
        if billing_email:
            account.billing_email = billing_email
        if billing_address:
            account.billing_address = billing_address
        if billing_city:
            account.billing_city = billing_city
        if billing_country:
            account.billing_country = billing_country
        
        account.save(update_fields=[
            'billing_email', 'billing_address', 'billing_city', 'billing_country'
        ])
        
        # Create AccountPaymentMethod if not free trial
        if not is_free_trial:
            from igny8_core.business.billing.models import AccountPaymentMethod
            AccountPaymentMethod.objects.create(
                account=account,
                type=payment_method,
                is_default=True,
                is_enabled=True
            )
        
        # ... rest of creation logic ...

3.5 Frontend: Add Billing Form Step

// File: frontend/src/components/auth/SignUpForm.tsx

interface BillingFormData {
  billing_email: string;
  billing_address: string;
  billing_city: string;
  billing_country: string;
  payment_method: string;
}

const [step, setStep] = useState<'signup' | 'billing'>('signup');
const [billingData, setBillingData] = useState<BillingFormData>({
  billing_email: '',
  billing_address: '',
  billing_city: '',
  billing_country: '',
  payment_method: 'stripe'
});

// After initial form submission
const handleSignupSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  
  // If paid plan, show billing form
  if (planSlug && planSlug !== 'free') {
    setStep('billing');
  } else {
    // Free trial, submit directly
    await submitRegistration();
  }
};

// Billing form submission
const handleBillingSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  await submitRegistration();
};

const submitRegistration = async () => {
  const payload = {
    email,
    password,
    password_confirm: passwordConfirm,
    first_name: firstName,
    last_name: lastName,
    plan_slug: planSlug,
    ...(step === 'billing' ? billingData : {})
  };
  
  await register(payload);
};

// Render billing form
{step === 'billing' && (
  <div className="billing-form">
    <h3>Billing Information</h3>
    
    <Input
      label="Billing Email"
      type="email"
      value={billingData.billing_email}
      onChange={(e) => setBillingData({
        ...billingData,
        billing_email: e.target.value
      })}
      required
    />
    
    <Input
      label="Address"
      value={billingData.billing_address}
      onChange={(e) => setBillingData({
        ...billingData,
        billing_address: e.target.value
      })}
      required
    />
    
    <CountrySelect
      label="Country"
      value={billingData.billing_country}
      onChange={(country) => setBillingData({
        ...billingData,
        billing_country: country
      })}
    />
    
    <PaymentMethodSelect
      country={billingData.billing_country}
      value={billingData.payment_method}
      onChange={(method) => setBillingData({
        ...billingData,
        payment_method: method
      })}
    />
    
    <Button onClick={handleBillingSubmit}>
      Complete Signup
    </Button>
  </div>
)}

3.6 Admin Payment Approval Interface

# File: backend/igny8_core/business/billing/admin.py

from django.contrib import admin
from .models import Payment

@admin.register(Payment)
class PaymentAdmin(admin.ModelAdmin):
    list_display = ['id', 'account', 'amount', 'status', 'payment_method', 'created_at']
    list_filter = ['status', 'payment_method', 'created_at']
    search_fields = ['account__name', 'manual_reference', 'external_payment_id']
    
    actions = ['approve_payments', 'reject_payments']
    
    def approve_payments(self, request, queryset):
        """Approve selected payments"""
        count = 0
        for payment in queryset.filter(status='pending_approval'):
            try:
                # Call existing approval endpoint
                from .views import BillingViewSet
                viewset = BillingViewSet()
                viewset.request = request
                viewset.approve_payment_internal(payment)
                count += 1
            except Exception as e:
                self.message_user(
                    request,
                    f"Error approving payment {payment.id}: {str(e)}",
                    level='ERROR'
                )
        
        self.message_user(
            request,
            f"Successfully approved {count} payment(s)"
        )
    
    approve_payments.short_description = "Approve selected payments"
    
    def reject_payments(self, request, queryset):
        """Reject selected payments"""
        count = queryset.filter(
            status='pending_approval'
        ).update(status='failed', metadata={'rejected_by': request.user.id})
        
        self.message_user(request, f"Rejected {count} payment(s)")
    
    reject_payments.short_description = "Reject selected payments"

PHASE 4: Testing & Validation (1-2 Days)

Priority: 🔵 Quality Assurance

4.1 Test: Free Trial Signup End-to-End

# 1. Frontend: Go to /signup
# 2. Fill form: email, password, name
# 3. Submit
# 4. Verify: Account created with 1000 credits
# 5. Verify: Subscription.plan_id = 1 (Free plan)
# 6. Verify: Can create 1 site
# 7. Verify: Site requires industry selection
# 8. Verify: Can access site dashboard

Database Verification:

SELECT 
  a.id, a.email, a.status, a.credits,
  s.plan_id, s.status, s.current_period_start,
  p.slug, p.included_credits
FROM igny8_tenants a
JOIN igny8_subscriptions s ON s.tenant_id = a.id
JOIN igny8_plans p ON s.plan_id = p.id
WHERE a.email = 'test@example.com';
-- Expected: credits=1000, plan_id=1, plan.slug='free'

4.2 Test: Paid Signup with Bank Transfer

# 1. Frontend: /signup?plan=starter
# 2. Fill signup form
# 3. Step 2: Fill billing form
#    - billing_email: billing@company.com
#    - billing_country: PK
#    - payment_method: bank_transfer
# 4. Submit
# 5. Verify: Account created with status='pending_payment'
# 6. Verify: Invoice created with total=5000
# 7. Verify: Payment method instructions shown
# 8. User submits payment with reference: BT-12345
# 9. Verify: Payment status='pending_approval'
# 10. Admin approves payment
# 11. Verify: Account status='active', credits=5000
# 12. User can now create 3 sites

API Test:

# Step 1: Register with paid plan
curl -X POST http://localhost:8000/api/v1/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "paid@test.com",
    "password": "Test123!",
    "password_confirm": "Test123!",
    "first_name": "Paid",
    "last_name": "User",
    "plan_slug": "starter",
    "billing_email": "billing@test.com",
    "billing_country": "PK",
    "payment_method": "bank_transfer"
  }'

# Expected response:
# {
#   "success": true,
#   "message": "Account created. Please complete payment.",
#   "data": {
#     "account_id": 2,
#     "invoice_id": 2,
#     "amount": 5000,
#     "payment_method": "bank_transfer",
#     "instructions": "Bank transfer details..."
#   }
# }

# Step 2: Confirm payment
curl -X POST http://localhost:8000/api/v1/billing/payments/confirm/ \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "invoice_id": 2,
    "manual_reference": "BT-20251208-12345",
    "manual_notes": "Transferred via ABC Bank on Dec 8"
  }'

# Expected:
# {
#   "success": true,
#   "message": "Payment confirmation submitted for review",
#   "data": {"payment_id": 1, "status": "pending_approval"}
# }

4.3 Test: Site Creation with Industry

# After account activated, create site
curl -X POST http://localhost:8000/api/v1/auth/sites/ \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Tech Blog",
    "domain": "https://mytechblog.com",
    "industry": 2,
    "site_type": "blog"
  }'

# Expected: ✅ Site created

# Test without industry (should fail)
curl -X POST http://localhost:8000/api/v1/auth/sites/ \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Blog",
    "domain": "https://myblog.com"
  }'

# Expected: ❌ 400 Bad Request, "industry is required"

4.4 Test: Payment Method Filtering by Country

# Get payment methods for Pakistan
curl http://localhost:8000/api/v1/billing/payment-methods/?country=PK

# Expected:
# [
#   {"type": "stripe", "display_name": "Credit/Debit Card (Stripe)"},
#   {"type": "paypal", "display_name": "PayPal"},
#   {"type": "bank_transfer", "display_name": "Bank Transfer", "instructions": "..."},
#   {"type": "local_wallet", "display_name": "JazzCash / Easypaisa", "instructions": "..."}
# ]

# Get payment methods for USA
curl http://localhost:8000/api/v1/billing/payment-methods/?country=US

# Expected (only global methods):
# [
#   {"type": "stripe", "display_name": "Credit/Debit Card (Stripe)"},
#   {"type": "paypal", "display_name": "PayPal"},
#   {"type": "bank_transfer", "display_name": "Bank Transfer"}
# ]

Summary: Complete Workflow After Implementation

FREE TRIAL PATH:
User → /signup → Fill form → Submit → Account created (1000 credits) → 
Create site (industry required) → Add sectors → Start using

PAID PLAN PATH:
User → /signup?plan=starter → Fill signup form → Fill billing form → 
Submit → Account created (pending_payment) → Invoice generated → 
User sees payment instructions → Transfer payment → Submit confirmation → 
Admin receives notification → Admin approves → Account activated (5000 credits) → 
Create sites (up to 3) → Add sectors → Start using

Key Improvements: Subscription.plan field tracks historical plan No duplicate date fields (Invoice uses Subscription dates) Single source of truth for payment methods (AccountPaymentMethod) Billing fields captured and saved during signup Country-specific payment methods shown Manual payment confirmation workflow Admin approval triggers atomic updates across all tables Site.industry is required SiteUserAccess created automatically Free plan has correct 1000 credits

Database Reduction:

  • Before: 109 fields across models
  • After: 105 fields (-4 duplicate fields)
  • Relationships cleaner: payment_method in 1 place instead of 4

Testing Coverage:

  • Free trial signup (frontend → API → database)
  • Paid plan signup with billing form
  • Payment confirmation flow
  • Admin approval workflow
  • Site creation with industry requirement
  • Payment method filtering by country
  • Credit allocation and transaction logging

Implementation Timeline

Phase Duration Tasks
Phase 1 1 day Fix import, add Subscription.plan, make Site.industry required, update free plan credits
Phase 2 2-3 days Remove duplicate fields, add properties, consolidate payment methods
Phase 3 3-5 days Create payment methods API, payment confirmation, billing form, admin interface
Phase 4 1-2 days End-to-end testing, validation, bug fixes
Total 7-11 days Complete implementation with testing

Critical Path:

  1. Day 1: Phase 1 (fixes blocking production)
  2. Day 2-4: Phase 2 (cleanup duplicate fields)
  3. Day 5-9: Phase 3 (new features)
  4. Day 10-11: Phase 4 (testing)

Appendix: Quick Reference

Model Changes Summary

# Subscription
+ plan = FK(Plan)  # NEW
- payment_method  # REMOVE (use property)

# Invoice
- billing_period_start  # REMOVE (use property from subscription)
- billing_period_end  # REMOVE (use property from subscription)
- billing_email  # REMOVE (use property from metadata/account)

# Payment
- transaction_reference  # REMOVE (duplicate of manual_reference)

# Site
! industry = FK(Industry, null=False)  # MAKE REQUIRED

# Account (no changes, keep payment_method for backward compatibility)

API Endpoints Created

GET  /v1/billing/payment-methods/?country=PK
POST /v1/billing/payments/confirm/
POST /v1/auth/register/  (enhanced with billing fields)

Database Queries for Verification

-- Verify subscription has plan
SELECT s.id, s.tenant_id, s.plan_id, p.slug
FROM igny8_subscriptions s
JOIN igny8_plans p ON s.plan_id = p.id;

-- Verify no duplicate payment methods
SELECT 
  a.id AS account_id,
  apm.type AS default_method,
  apm.is_default
FROM igny8_tenants a
LEFT JOIN igny8_account_payment_methods apm 
  ON apm.tenant_id = a.id AND apm.is_default = TRUE;

-- Verify all sites have industry
SELECT id, name, industry_id 
FROM igny8_sites 
WHERE industry_id IS NULL;
-- Expected: 0 rows

-- Verify credit transactions match account credits
SELECT 
  a.id,
  a.credits AS account_total,
  COALESCE(SUM(ct.amount), 0) AS transaction_total
FROM igny8_tenants a
LEFT JOIN igny8_credit_transactions ct ON ct.tenant_id = a.id
GROUP BY a.id, a.credits
HAVING a.credits != COALESCE(SUM(ct.amount), 0);
-- Expected: 0 rows (all match)