-- ============================================================ -- FreightDesk — Migration 006: Payment Escrow System -- Escrow payments, transactions, settlements, payouts -- ============================================================ -- ============================================================ -- 1. ESCROW ACCOUNTS (one per user — shipper or driver) -- ============================================================ CREATE TABLE IF NOT EXISTS escrow_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL, role TEXT NOT NULL CHECK (role IN ('shipper', 'driver')), balance INTEGER DEFAULT 0, -- available balance in paise held_balance INTEGER DEFAULT 0, -- funds in escrow (in paise) total_deposited INTEGER DEFAULT 0, total_withdrawn INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, role) ); CREATE INDEX IF NOT EXISTS idx_escrow_user ON escrow_accounts(user_id); -- ============================================================ -- 2. ESCROW TRANSACTIONS -- ============================================================ CREATE TABLE IF NOT EXISTS escrow_transactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), escrow_account_id UUID REFERENCES escrow_accounts(id), load_id UUID REFERENCES loads(id), bid_id UUID REFERENCES bids(id), type TEXT NOT NULL CHECK (type IN ( 'deposit', -- shipper deposits funds 'hold', -- funds moved to escrow hold 'release', -- funds released to driver 'refund', -- funds refunded to shipper 'payout', -- driver withdraws to bank 'platform_fee', -- FreightDesk commission 'adjustment' -- manual admin adjustment )), amount INTEGER NOT NULL, -- in paise status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'failed', 'reversed')), reference_id TEXT, -- external payment reference metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), completed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_escrow_tx_account ON escrow_transactions(escrow_account_id); CREATE INDEX IF NOT EXISTS idx_escrow_tx_load ON escrow_transactions(load_id); CREATE INDEX IF NOT EXISTS idx_escrow_tx_type ON escrow_transactions(type); CREATE INDEX IF NOT EXISTS idx_escrow_tx_status ON escrow_transactions(status); -- ============================================================ -- 3. PAYOUT REQUESTS (driver withdrawal requests) -- ============================================================ CREATE TABLE IF NOT EXISTS payout_requests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL, driver_id UUID REFERENCES vehicles(id), amount INTEGER NOT NULL, status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'processed')), bank_name TEXT, account_number TEXT, ifsc_code TEXT, upi_id TEXT, processed_by UUID, processed_at TIMESTAMP WITH TIME ZONE, notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_payout_driver ON payout_requests(driver_id); CREATE INDEX IF NOT EXISTS idx_payout_status ON payout_requests(status); -- ============================================================ -- 4. PLATFORM CONFIG (fee settings) -- ============================================================ CREATE TABLE IF NOT EXISTS platform_config ( key TEXT PRIMARY KEY, value TEXT NOT NULL, description TEXT, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Default fee settings INSERT INTO platform_config (key, value, description) VALUES ('escrow.platform_fee_percent', '5', 'Platform commission percentage'), ('escrow.min_deposit_amount', '10000', 'Minimum deposit amount in paise (₹100)'), ('escrow.hold_period_hours', '72', 'Hours to hold funds after delivery before auto-release'), ('escrow.payout_min_amount', '50000', 'Minimum payout request in paise (₹500)'), ('escrow.payout_fee', '0', 'Payout processing fee in paise') ON CONFLICT (key) DO NOTHING; -- ============================================================ -- 5. LOAD PAYMENT STATUS (tracks payment state per load) -- ============================================================ ALTER TABLE loads ADD COLUMN IF NOT EXISTS payment_status TEXT DEFAULT 'none' CHECK (payment_status IN ('none', 'deposited', 'in_escrow', 'released', 'refunded', 'disputed')); ALTER TABLE loads ADD COLUMN IF NOT EXISTS escrow_amount INTEGER; ALTER TABLE loads ADD COLUMN IF NOT EXISTS platform_fee INTEGER; ALTER TABLE loads ADD COLUMN IF NOT EXISTS settled_at TIMESTAMP WITH TIME ZONE; -- ============================================================ -- 6. DISPUTES TABLE -- ============================================================ CREATE TABLE IF NOT EXISTS disputes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), load_id UUID NOT NULL REFERENCES loads(id), raised_by UUID NOT NULL, raised_against UUID NOT NULL, reason TEXT NOT NULL, status TEXT DEFAULT 'open' CHECK (status IN ('open', 'under_review', 'resolved', 'closed')), resolution TEXT, resolved_by UUID, resolved_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_disputes_load ON disputes(load_id); CREATE INDEX IF NOT EXISTS idx_disputes_status ON disputes(status);