Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
78 lines
3.8 KiB
PL/PgSQL
78 lines
3.8 KiB
PL/PgSQL
-- 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;
|