-- Migration 979: partial UNIQUE on refunds.hyperswitch_refund_id (v1.0.6.1 hotfix) -- -- The v1.0.6 migration 978 used a plain UNIQUE constraint on -- hyperswitch_refund_id. That broke when two refunds in the same DB -- stayed in their post-Phase-1 / pre-Phase-2 state: both rows have -- hyperswitch_refund_id='' (empty string, because Go's zero-value for -- string writes '' rather than NULL), and PostgreSQL treats two empty -- strings as colliding under a regular UNIQUE constraint. -- -- Surfaced by the v1.0.6 refund smoke test (scenario S4, triggered -- after the S3 PSP-error path left one row with refund_id=''): the -- second refund attempt on a different order got a UNIQUE violation -- at INSERT time. -- -- Fix: make the UNIQUE partial — only enforce uniqueness on rows that -- have actually received a PSP-assigned refund_id. Empty strings and -- NULLs are ignored. This preserves the load-bearing idempotency -- guarantee for successful refunds (duplicate webhook lands on the -- same row) without rejecting legitimate second attempts after a PSP -- failure on a different order. ALTER TABLE public.refunds DROP CONSTRAINT IF EXISTS refunds_hyperswitch_refund_id_key; DROP INDEX IF EXISTS refunds_hyperswitch_refund_id_unique; CREATE UNIQUE INDEX refunds_hyperswitch_refund_id_unique ON public.refunds (hyperswitch_refund_id) WHERE hyperswitch_refund_id IS NOT NULL AND hyperswitch_refund_id <> '';