-- ============================================ -- TEMPLATE-SAAS: Invoices & Payments -- Schema: billing -- Version: 1.0.0 -- ============================================ CREATE TABLE billing.invoices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, subscription_id UUID REFERENCES billing.subscriptions(id), -- Invoice number invoice_number VARCHAR(50) UNIQUE NOT NULL, -- Stripe stripe_invoice_id VARCHAR(255) UNIQUE, -- Status status billing.invoice_status DEFAULT 'draft' NOT NULL, -- Amounts subtotal DECIMAL(10, 2) NOT NULL DEFAULT 0, tax DECIMAL(10, 2) DEFAULT 0, discount DECIMAL(10, 2) DEFAULT 0, total DECIMAL(10, 2) NOT NULL DEFAULT 0, amount_paid DECIMAL(10, 2) DEFAULT 0, amount_due DECIMAL(10, 2) NOT NULL DEFAULT 0, currency VARCHAR(3) DEFAULT 'USD', -- Dates invoice_date DATE NOT NULL DEFAULT CURRENT_DATE, due_date DATE, paid_at TIMESTAMPTZ, -- Period period_start DATE, period_end DATE, -- PDF invoice_pdf_url VARCHAR(500), hosted_invoice_url VARCHAR(500), -- Customer info at time of invoice customer_name VARCHAR(255), customer_email VARCHAR(255), billing_address JSONB, -- Metadata metadata JSONB DEFAULT '{}'::jsonb, notes TEXT, -- Audit created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL ); -- Invoice line items CREATE TABLE billing.invoice_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE, -- Description description VARCHAR(500) NOT NULL, quantity INT DEFAULT 1, -- Pricing unit_amount DECIMAL(10, 2) NOT NULL, amount DECIMAL(10, 2) NOT NULL, currency VARCHAR(3) DEFAULT 'USD', -- Period (for subscription items) period_start DATE, period_end DATE, -- Stripe stripe_invoice_item_id VARCHAR(255), -- Metadata metadata JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL ); -- Payments CREATE TABLE billing.payments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, invoice_id UUID REFERENCES billing.invoices(id), -- Stripe stripe_payment_intent_id VARCHAR(255) UNIQUE, stripe_charge_id VARCHAR(255), -- Amount amount DECIMAL(10, 2) NOT NULL, currency VARCHAR(3) DEFAULT 'USD', -- Status status billing.payment_status DEFAULT 'pending' NOT NULL, -- Payment method payment_method_type VARCHAR(50), -- 'card', 'bank_transfer', etc. payment_method_last4 VARCHAR(4), payment_method_brand VARCHAR(50), -- Failure info failure_code VARCHAR(100), failure_message TEXT, -- Dates created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, succeeded_at TIMESTAMPTZ, failed_at TIMESTAMPTZ, refunded_at TIMESTAMPTZ, -- Refund info refund_amount DECIMAL(10, 2), refund_reason VARCHAR(500) ); -- Indexes CREATE INDEX idx_invoices_tenant ON billing.invoices(tenant_id); CREATE INDEX idx_invoices_subscription ON billing.invoices(subscription_id); CREATE INDEX idx_invoices_status ON billing.invoices(status); CREATE INDEX idx_invoices_stripe ON billing.invoices(stripe_invoice_id); CREATE INDEX idx_invoice_items_invoice ON billing.invoice_items(invoice_id); CREATE INDEX idx_payments_tenant ON billing.payments(tenant_id); CREATE INDEX idx_payments_invoice ON billing.payments(invoice_id); CREATE INDEX idx_payments_stripe ON billing.payments(stripe_payment_intent_id); -- RLS ALTER TABLE billing.invoices ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.invoice_items ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.payments ENABLE ROW LEVEL SECURITY; CREATE POLICY invoices_tenant_isolation ON billing.invoices USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY invoice_items_tenant_isolation ON billing.invoice_items USING (invoice_id IN ( SELECT id FROM billing.invoices WHERE tenant_id = current_setting('app.current_tenant_id', true)::UUID )); CREATE POLICY payments_tenant_isolation ON billing.payments USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Trigger CREATE TRIGGER trg_invoices_updated_at BEFORE UPDATE ON billing.invoices FOR EACH ROW EXECUTE FUNCTION billing.update_updated_at(); CREATE TRIGGER trg_payments_updated_at BEFORE UPDATE ON billing.payments FOR EACH ROW EXECUTE FUNCTION billing.update_updated_at(); -- Invoice number sequence CREATE SEQUENCE billing.invoice_number_seq START 1000; -- Function to generate invoice number CREATE OR REPLACE FUNCTION billing.generate_invoice_number() RETURNS VARCHAR AS $$ BEGIN RETURN 'INV-' || TO_CHAR(NOW(), 'YYYYMM') || '-' || LPAD(nextval('billing.invoice_number_seq')::TEXT, 6, '0'); END; $$ LANGUAGE plpgsql; -- Comments COMMENT ON TABLE billing.invoices IS 'Invoices generated for billing'; COMMENT ON TABLE billing.invoice_items IS 'Line items within an invoice'; COMMENT ON TABLE billing.payments IS 'Payment records and attempts';