| 1 | DROP TRIGGER IF EXISTS trg_prevent_overpaid_invoice ON public.payments;
|
|---|
| 2 | CREATE OR REPLACE FUNCTION public.trg_fn_prevent_overpaid_invoice()
|
|---|
| 3 | RETURNS trigger
|
|---|
| 4 | LANGUAGE plpgsql
|
|---|
| 5 | AS $$
|
|---|
| 6 | DECLARE
|
|---|
| 7 | v_invoice_total numeric;
|
|---|
| 8 | v_invoice_status text;
|
|---|
| 9 | v_total_paid numeric;
|
|---|
| 10 | BEGIN
|
|---|
| 11 | IF NEW.invoice_id IS NULL OR NEW.status <> 'completed' THEN
|
|---|
| 12 | RETURN NEW;
|
|---|
| 13 | END IF;
|
|---|
| 14 |
|
|---|
| 15 | SELECT total_amount, status
|
|---|
| 16 | INTO v_invoice_total, v_invoice_status
|
|---|
| 17 | FROM public.invoices
|
|---|
| 18 | WHERE invoice_id = NEW.invoice_id
|
|---|
| 19 | AND account_id = NEW.account_id
|
|---|
| 20 | FOR UPDATE;
|
|---|
| 21 |
|
|---|
| 22 | IF NOT FOUND THEN
|
|---|
| 23 | RAISE EXCEPTION 'Invoice % does not belong to account %.', NEW.invoice_id, NEW.account_id;
|
|---|
| 24 | END IF;
|
|---|
| 25 | IF v_invoice_status = 'cancelled' THEN
|
|---|
| 26 | RAISE EXCEPTION 'Payment rejected: invoice % is cancelled.', NEW.invoice_id;
|
|---|
| 27 | END IF;
|
|---|
| 28 |
|
|---|
| 29 | SELECT COALESCE(SUM(amount), 0)
|
|---|
| 30 | INTO v_total_paid
|
|---|
| 31 | FROM public.payments
|
|---|
| 32 | WHERE invoice_id = NEW.invoice_id
|
|---|
| 33 | AND status = 'completed'
|
|---|
| 34 | AND payment_id IS DISTINCT FROM NEW.payment_id;
|
|---|
| 35 |
|
|---|
| 36 | IF v_total_paid >= v_invoice_total - 0.01 THEN
|
|---|
| 37 | RAISE EXCEPTION 'Payment rejected: invoice % is already fully paid.', NEW.invoice_id;
|
|---|
| 38 | END IF;
|
|---|
| 39 | IF v_total_paid + NEW.amount > v_invoice_total + 0.01 THEN
|
|---|
| 40 | RAISE EXCEPTION 'Payment rejected: invoice % has remaining balance %, attempted payment is %.',
|
|---|
| 41 | NEW.invoice_id, v_invoice_total - v_total_paid, NEW.amount;
|
|---|
| 42 | END IF;
|
|---|
| 43 |
|
|---|
| 44 | RETURN NEW;
|
|---|
| 45 | END;
|
|---|
| 46 | $$;
|
|---|
| 47 |
|
|---|
| 48 | CREATE TRIGGER trg_prevent_overpaid_invoice
|
|---|
| 49 | BEFORE INSERT OR UPDATE ON public.payments
|
|---|
| 50 | FOR EACH ROW
|
|---|
| 51 | EXECUTE FUNCTION public.trg_fn_prevent_overpaid_invoice();
|
|---|
| 52 | COMMENT ON FUNCTION public.trg_fn_prevent_overpaid_invoice() IS
|
|---|
| 53 | 'Locks the invoice row and prevents completed payments from exceeding its total, including concurrent attempts.';
|
|---|