The same cheque can't be recorded twice

A payment with the same reference and amount on the same invoice is refused by the database, with a message that says it was not added a second time.

What it is

A unique index over (invoice_id, lowercased trimmed reference, amount) on live payment rows. Two real cheques never share a number, so a reference is a hard identity. Payments with no reference — cash, 'other' — are deliberately not constrained, because the same customer paying the same round sum twice is ordinary.

Also called: entered the payment twice · same cheque number · double payment entry · overpaid invoice

See it
The same cheque can't be recorded twice
Prepared for
Delia Yoder
1140 Ridge Line Rd · Fair Play
Shell package$35,365
Concrete & site$10,267
Doors & windows$6,845
Trim & finish$4,563
Total
$57,040
Signature
The payment form still open with the message 'Cheque 8616 for $10,205.06 is already recorded on this invoice. It was not added a second time.' src/components/invoices/InvoiceViewer.tsx. Sample data — no customer information appears here.
How it works
  1. 1recordPayment surfaces Postgres 23505 as a duplicate rather than a generic failure.
  2. 2The message names the cheque and the amount and states it was not added again.
  3. 3Reversed rows are excluded from the index so a payment can be backed out and re-entered correctly.
  4. 4The entry form stays open with the explanation, because closing it would look like the entry went through.
Why we built it

The same cheque was recorded twice against one invoice eight seconds apart, leaving $20,410.12 taken on a $10,205.06 bill while the balance still read zero, so nothing on screen looked wrong. Recording a payment that matches one already on the invoice now stops and shows the one it matched, with the option to confirm it is genuinely a second payment. It never offers to try again, because inviting a retry is exactly how the first duplicate got in.

The problem
  • Double-entered cheques inflating an invoice's paid amount.
  • A 'try again' message causing the exact second entry that creates the problem.
Sound familiar?
What you get
Payment entry is safe to retry.
The refusal explains exactly which payment already exists.

See it on your own jobs

Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.

or keep browsing features →