Skip to content
CogniYukti

Billing

Customers do not pay invoices. They pay amounts.

One transfer against four invoices with a reference naming two of them. Half an invoice now and the rest next month. A round number that does not match anything.

Every one of those is ordinary, and every one is where an invoice ledger hands the work back to a person with a spreadsheet.

Splitting

It proposes, explains, and waits.

Three rules in order. Any invoice named in the payment reference — matched longest-first, so a full number beats a prefix of itself. Then, only if nothing matched yet, an invoice whose balance is exactly the amount, and only when there is precisely one. Then oldest first.

Each proposed line comes back with why it was proposed. Nothing is applied until somebody accepts, and what is left over stays as credit rather than being pushed onto an invoice that did not earn it.

One receipt, proposed splitref: “settling 0184 and the older ones”£29,000.00
  • INV-2026-0184£12,400.00
    exactnamed in the payment reference
  • INV-2026-0179£8,150.00
    exactthe only open invoice for that exact amount
  • INV-2026-0166£6,000.00
    oldest firstoldest still open
  • INV-2026-0171£2,450.00
    oldest firstnext oldest — partially filled
Nothing is applied until somebody accepts it, and every row says why it was proposed. What is left over comes back as credit rather than being forced onto an invoice.
Illustrative
  • Applying many at once is one act

    Duplicate rows are folded, and any invoice belonging to a different customer than the payment is refused rather than quietly allocated.

  • Overpaying an invoice is refused

    Checked against a freshly recomputed balance, not a cached one.

  • Un-allocating reverses properly

    And restores the invoice to the state it was actually in — sent if it had been sent, rather than blindly back to finalised.

The guarantee

The database refuses money that does not exist.

Allocating more of a payment than the payment is worth is not prevented by careful code. It is refused by the database, which re-sums every allocation on write and rejects the transaction if the total exceeds the payment. The same guard exists for credit notes against their own total.

Which means a concurrency bug in a web handler — two clicks, two workers, a retry — cannot produce an over-allocated payment. The invariant does not depend on the code path being right.

  • A receipt number is minted on capture, not on attempt

    A declined card leaves no gap in the sequence.

  • Gateway payments cannot be recorded twice

    Enforced on the provider's own reference — and offline payments, which have no such reference, are exempt rather than blocked.

  • A missed notification is caught overnight

    A nightly pass replays the provider's own record of the last day through exactly the same path. It cannot double-count, because the same guard applies.

  • The incoming notification identifies its own workspace

    By which stored secret validates the signature — the provider does not know who you are, so the signature answers it.

Which gateways?

Two, with payment links, incoming notifications and the overnight reconciliation behind both.

Can a customer pay part of an invoice?

Yes — it is the normal case rather than a special one. The invoice moves to partly paid and the balance is maintained by the database.

Is there a payment schedule or instalment plan?

No. Partial payments are supported as they arrive; there is no agreed schedule object, and no automatic interest or late fee. Dunning escalates and can place a hold — it never adds money to the balance.

Is there a standalone payments register?

Payments are read on the invoice they belong to. There is no separate ledger screen today.