CREATE TYPE aftersale_kind AS ENUM ('refund_only', 'return_refund'); CREATE TYPE aftersale_status AS ENUM ( 'pending', 'approved', 'rejected', 'buyer_shipping', 'merchant_confirmed', 'refunded', 'cancelled' ); -- Authoritative per-order refund total; every increment is a guarded UPDATE. ALTER TABLE orders ADD COLUMN refund_total_minor BIGINT NOT NULL DEFAULT 0 CHECK (refund_total_minor >= 0); CREATE TABLE aftersales ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), order_id UUID NOT NULL REFERENCES orders (id), order_item_id UUID NOT NULL REFERENCES order_items (id), shop_id UUID NOT NULL REFERENCES shops (id), user_id UUID NOT NULL REFERENCES users (id), kind aftersale_kind NOT NULL, status aftersale_status NOT NULL DEFAULT 'pending', reason JSONB NOT NULL, amount_minor BIGINT NOT NULL CHECK (amount_minor > 0), evidence JSONB NOT NULL DEFAULT '[]', reopened BOOLEAN NOT NULL DEFAULT false, return_carrier TEXT, return_tracking_no TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- At most one active (non-terminal) aftersale per order item. CREATE UNIQUE INDEX aftersales_one_active_per_item ON aftersales (order_item_id) WHERE status NOT IN ('refunded', 'rejected', 'cancelled'); CREATE INDEX idx_aftersales_user ON aftersales (user_id); CREATE INDEX idx_aftersales_shop ON aftersales (shop_id); CREATE INDEX idx_aftersales_order ON aftersales (order_id); -- Append-only bilateral message log; no UPDATE/DELETE paths exist. CREATE TABLE aftersale_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), aftersale_id UUID NOT NULL REFERENCES aftersales (id) ON DELETE CASCADE, author_role TEXT NOT NULL CHECK (author_role IN ('buyer', 'merchant', 'platform')), author_id UUID NOT NULL REFERENCES users (id), content JSONB NOT NULL, evidence JSONB NOT NULL DEFAULT '[]', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_aftersale_messages_aftersale ON aftersale_messages (aftersale_id);