-- 007_refunds.sql -- Refund tracking for failed filings that were not the customer's fault. -- Covers: state portal errors, payment processing failures, name collisions -- discovered after payment, state rejections, etc. BEGIN; CREATE TABLE IF NOT EXISTS refunds ( id SERIAL PRIMARY KEY, refund_number TEXT UNIQUE NOT NULL, -- REF-2026-XXXXXX -- What's being refunded order_type TEXT NOT NULL CHECK (order_type IN ('formation', 'service')), order_id INTEGER, -- FK to formation_orders.id or orders.id order_number TEXT NOT NULL, -- PW-2026-XXXX or ORD-2026-XXXX -- Who customer_name TEXT NOT NULL, customer_email TEXT NOT NULL, -- Amount original_amount_cents INTEGER NOT NULL, -- what was originally charged refund_amount_cents INTEGER NOT NULL, -- what we're refunding (may be partial) refund_type TEXT NOT NULL CHECK (refund_type IN ('full', 'partial', 'state_fee_only', 'service_fee_only')), -- Why reason_category TEXT NOT NULL CHECK (reason_category IN ( 'state_portal_error', -- state portal down, rejected filing incorrectly 'payment_failed', -- card charged but filing didn't go through 'name_collision', -- name became unavailable between search and filing 'state_rejected', -- state rejected the filing after payment 'automation_error', -- our automation failed and couldn't recover 'duplicate_charge', -- accidentally charged twice 'customer_request', -- customer changed mind (discretionary) 'other' )), reason_detail TEXT, -- free-text explanation -- State fee recovery state_fee_recoverable BOOLEAN DEFAULT FALSE, -- can we get the state fee back? state_fee_recovered BOOLEAN DEFAULT FALSE, state_fee_recovery_notes TEXT, -- Processing status TEXT DEFAULT 'pending' CHECK (status IN ( 'pending', -- refund requested, not yet approved 'approved', -- admin approved, ready to process 'processing', -- refund being sent via Relay 'sent', -- ACH/card refund sent 'confirmed', -- customer confirmed receipt 'denied', -- refund request denied (with reason) 'cancelled' )), -- Approval requested_by TEXT, -- 'system', 'admin', 'customer' requested_at TIMESTAMPTZ DEFAULT now(), approved_by INTEGER, -- admin_users.id approved_at TIMESTAMPTZ, denied_reason TEXT, -- Payment refund_method TEXT CHECK (refund_method IN ('relay_ach', 'card_reversal', 'check', 'credit')), relay_transaction_id TEXT, sent_at TIMESTAMPTZ, confirmed_at TIMESTAMPTZ, -- Metadata admin_notes TEXT, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_refunds_order ON refunds(order_type, order_id); CREATE INDEX IF NOT EXISTS idx_refunds_status ON refunds(status); CREATE INDEX IF NOT EXISTS idx_refunds_number ON refunds(refund_number); CREATE INDEX IF NOT EXISTS idx_refunds_email ON refunds(customer_email); -- Add refund tracking to formation_orders ALTER TABLE formation_orders ADD COLUMN IF NOT EXISTS refund_id INTEGER REFERENCES refunds(id); ALTER TABLE formation_orders ADD COLUMN IF NOT EXISTS refunded BOOLEAN DEFAULT FALSE; -- Add refund tracking to general orders ALTER TABLE orders ADD COLUMN IF NOT EXISTS refund_id INTEGER REFERENCES refunds(id); ALTER TABLE orders ADD COLUMN IF NOT EXISTS refunded BOOLEAN DEFAULT FALSE; COMMIT;