-- Synor Economics Database Schema -- Phase 12: Economics & Billing Infrastructure -- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Accounts table CREATE TABLE IF NOT EXISTS accounts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), account_id VARCHAR(255) UNIQUE NOT NULL, tier VARCHAR(50) NOT NULL DEFAULT 'free', prepaid_balance DECIMAL(38, 18) NOT NULL DEFAULT 0, credit_balance DECIMAL(38, 18) NOT NULL DEFAULT 0, billing_cycle_start TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -- Price history table CREATE TABLE IF NOT EXISTS price_history ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), token VARCHAR(20) NOT NULL, quote VARCHAR(20) NOT NULL, price DECIMAL(38, 18) NOT NULL, source VARCHAR(50) NOT NULL, confidence DECIMAL(5, 4) NOT NULL DEFAULT 1.0, timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -- Create index for price lookups CREATE INDEX IF NOT EXISTS idx_price_history_pair_time ON price_history(token, quote, timestamp DESC); -- Usage events table CREATE TABLE IF NOT EXISTS usage_events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), event_id VARCHAR(100) UNIQUE NOT NULL, account_id VARCHAR(255) NOT NULL REFERENCES accounts(account_id), service_id VARCHAR(255), service_type VARCHAR(50) NOT NULL, resource_unit VARCHAR(50) NOT NULL, amount DECIMAL(38, 18) NOT NULL, cost DECIMAL(38, 18), timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), metadata JSONB DEFAULT '{}' ); -- Create indexes for usage queries CREATE INDEX IF NOT EXISTS idx_usage_events_account ON usage_events(account_id, timestamp DESC); CREATE INDEX IF NOT EXISTS idx_usage_events_service ON usage_events(service_type, timestamp DESC); -- Invoices table CREATE TABLE IF NOT EXISTS invoices ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), invoice_id VARCHAR(100) UNIQUE NOT NULL, invoice_number VARCHAR(50) NOT NULL, account_id VARCHAR(255) NOT NULL REFERENCES accounts(account_id), status VARCHAR(20) NOT NULL DEFAULT 'draft', period_start TIMESTAMP WITH TIME ZONE NOT NULL, period_end TIMESTAMP WITH TIME ZONE NOT NULL, subtotal DECIMAL(38, 18) NOT NULL DEFAULT 0, discount DECIMAL(38, 18) NOT NULL DEFAULT 0, discount_description TEXT, tax DECIMAL(38, 18) NOT NULL DEFAULT 0, total DECIMAL(38, 18) NOT NULL DEFAULT 0, due_date TIMESTAMP WITH TIME ZONE NOT NULL, paid_at TIMESTAMP WITH TIME ZONE, payment_id VARCHAR(100), notes TEXT, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -- Create indexes for invoice queries CREATE INDEX IF NOT EXISTS idx_invoices_account ON invoices(account_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status); -- Invoice line items table CREATE TABLE IF NOT EXISTS invoice_line_items ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), invoice_id VARCHAR(100) NOT NULL REFERENCES invoices(invoice_id), description TEXT NOT NULL, service_type VARCHAR(50) NOT NULL, quantity DECIMAL(38, 18) NOT NULL, unit_price DECIMAL(38, 18) NOT NULL, amount DECIMAL(38, 18) NOT NULL ); -- Payments table CREATE TABLE IF NOT EXISTS payments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), payment_id VARCHAR(100) UNIQUE NOT NULL, account_id VARCHAR(255) NOT NULL REFERENCES accounts(account_id), invoice_id VARCHAR(100) REFERENCES invoices(invoice_id), amount DECIMAL(38, 18) NOT NULL, method VARCHAR(50) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', transaction_hash VARCHAR(100), block_number BIGINT, failure_reason TEXT, metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), completed_at TIMESTAMP WITH TIME ZONE ); -- Create indexes for payment queries CREATE INDEX IF NOT EXISTS idx_payments_account ON payments(account_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status); -- Credits table CREATE TABLE IF NOT EXISTS credits ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), credit_id VARCHAR(100) UNIQUE NOT NULL, account_id VARCHAR(255) NOT NULL REFERENCES accounts(account_id), original_amount DECIMAL(38, 18) NOT NULL, remaining_amount DECIMAL(38, 18) NOT NULL, used_amount DECIMAL(38, 18) NOT NULL DEFAULT 0, credit_type VARCHAR(50) NOT NULL, reason TEXT NOT NULL, reference_id VARCHAR(255), approved_by VARCHAR(255), is_active BOOLEAN NOT NULL DEFAULT true, expires_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -- Create indexes for credit queries CREATE INDEX IF NOT EXISTS idx_credits_account ON credits(account_id, is_active); -- Discounts table CREATE TABLE IF NOT EXISTS discounts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), code VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, discount_type VARCHAR(50) NOT NULL, value DECIMAL(38, 18) NOT NULL, min_spend DECIMAL(38, 18), max_discount DECIMAL(38, 18), service_types TEXT[], account_ids TEXT[], start_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), end_date TIMESTAMP WITH TIME ZONE, max_uses INTEGER, current_uses INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -- Pricing tiers table CREATE TABLE IF NOT EXISTS pricing_tiers ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(50) UNIQUE NOT NULL, display_name VARCHAR(100) NOT NULL, monthly_fee DECIMAL(38, 18) NOT NULL DEFAULT 0, discount_percentage DECIMAL(5, 2) NOT NULL DEFAULT 0, priority_support BOOLEAN NOT NULL DEFAULT false, sla_percentage DECIMAL(5, 2) NOT NULL DEFAULT 95.00, custom_domain_limit INTEGER NOT NULL DEFAULT 1, api_rate_limit INTEGER NOT NULL DEFAULT 100, features TEXT[] NOT NULL DEFAULT '{}', min_commitment_months INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -- Insert default pricing tiers INSERT INTO pricing_tiers (name, display_name, monthly_fee, discount_percentage, priority_support, sla_percentage, custom_domain_limit, api_rate_limit, features, min_commitment_months) VALUES ('free', 'Free', 0, 0, false, 95.00, 1, 100, ARRAY['0.5 GB Storage', '1 GB Hosting Bandwidth', '1M Database Queries', '100 CPU Core-Hours', 'Community Support'], 0), ('standard', 'Standard', 10, 10, false, 99.00, 5, 1000, ARRAY['Everything in Free', '10% Usage Discount', '5 Custom Domains', 'Email Support', '99% SLA Guarantee'], 0), ('premium', 'Premium', 50, 20, true, 99.90, 20, 5000, ARRAY['Everything in Standard', '20% Usage Discount', '20 Custom Domains', 'Priority Support', '99.9% SLA Guarantee', 'Advanced Analytics'], 0), ('enterprise', 'Enterprise', 500, 30, true, 99.99, 0, 0, ARRAY['Everything in Premium', '30%+ Usage Discount', 'Unlimited Custom Domains', 'Dedicated Support', '99.99% SLA Guarantee', 'Custom Integrations', 'Volume Pricing', 'Invoice Billing'], 12) ON CONFLICT (name) DO NOTHING; -- Aggregated usage view CREATE OR REPLACE VIEW usage_summary AS SELECT account_id, service_type, DATE_TRUNC('day', timestamp) as usage_date, SUM(amount) as total_amount, SUM(cost) as total_cost, COUNT(*) as event_count FROM usage_events GROUP BY account_id, service_type, DATE_TRUNC('day', timestamp); -- Outstanding invoices view CREATE OR REPLACE VIEW outstanding_invoices AS SELECT i.*, a.prepaid_balance, a.credit_balance, (a.prepaid_balance + a.credit_balance) >= i.total as can_auto_pay FROM invoices i JOIN accounts a ON i.account_id = a.account_id WHERE i.status IN ('pending', 'overdue') ORDER BY i.due_date ASC; -- Function to update updated_at timestamp CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- Add triggers for updated_at CREATE TRIGGER update_accounts_updated_at BEFORE UPDATE ON accounts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Grant permissions GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO synor; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO synor;