Correcting a paid invoice
Unapply payments, cancel an incorrect invoice, and issue new charges without replacing the original payment.
Staff correct a paid invoice through three separate actions in the account's invoice detail: Unapply payments, Cancel invoice, and Create corrected invoice. The original payment stays recorded. No refund or new payment is created, and invoices have no replacement status.
Unapply and pause allocation
POST /v1/invoices/{id}/unapply reverses the invoice's live payment applications and pauses
further allocation to that invoice in the same transaction. Each removed application emits
invoice.payment_reversed; the invoice emits invoice.allocation_paused. Application history
remains visible through payment_applications on the invoice response.
Freed funds return to the receipt's available balance. The scheduler reconciles unused funds from verified payment journals to the prepay liability: Dr Accounts Receivable, Cr Prepay. When those funds are applied again it posts Dr Prepay, Cr Accounts Receivable. These postings are asynchronous and retryable; the unapply request itself commits Billing's application changes. Receipts without a posted payment journal or verified funding cannot produce a prepay posting. The allocation pass applies available funds to other eligible charges in the normal order. They are not reserved exclusively for the corrected invoice. With no other open charges, correcting a $50 invoice paid in full to $30 leaves $20 in prepay; correcting it to $70 leaves $20 owed.
The pause excludes the invoice from payment and credit allocation. Database triggers also reject
new payment, credit, or deposit applications from callers using older allocation queries.
Credits with only paused invoice targets are excluded from the scheduler's candidate batch, so
they do not delay credits on other accounts. Late-fee assessment waits for a lease/month with
paused rent invoices to be corrected or resumed, so it does not charge a fee against a temporary
correction balance or freeze a partial monthly calculation. Pausing and resuming mark the account
for reassessment; existing fees are retained during the pause. Database triggers also stop older
schedulers from inserting evaluations or changing fees for a paused rent cycle.
POST /v1/invoices/{id}/allocation/resume removes the pause when the operator decides to keep the
issued invoice. It emits invoice.allocation_resumed.
Cancel the original invoice
POST /v1/invoices/{id}/void requires a reason and refuses while payment or deposit cover remains.
The screen lists the live payments and any deposit cover that must be removed first. Cancellation
keeps the invoice number, original charges, amounts, and application history. Its charges become
cancelled and cannot be issued again. Staff see Cancelled, the reason, actor, and time; the API
retains the existing void status.
Cancellation clears the temporary allocation pause. A payment whose preferred invoice or charge was issued and then cancelled remains available for other eligible charges in the normal allocation order. Money directed to a draft invoice or an unissued charge stays held until that target issues; cancelling an unissued target does not redirect its reserved payment.
The correction migration refuses rollback while cancellation actors or dates are recorded, so rolling back cannot silently discard that audit history.
Cancellation releases applied credit through the existing credit reversal path. The scheduler reverses the original invoice posting and stops its deferral schedules. A separately corrected charge still blocks a full invoice cancellation, preventing a second ledger reversal of that charge.
Accounting supplies the entity's cancellation day and current Open month. Billing verifies that month is not before the original posting month, and records the cancellation day for the reversal. The action returns 503 if Accounting cannot verify these dates; committed retries still replay their original response. This action uses the current Open month and does not offer a manual post-month selector.
Issue corrected charges
POST /v1/invoices accepts an account, optional lease, billing and due dates, and explicit charge
lines. It creates those charges and issues only those lines in one transaction by default. Set
status: draft to save without an invoice number, ledger posting, or payment allocation. Unrelated
scheduled charges are left for their normal issuance. Refundable deposits cannot be mixed with other charges.
Existing charge validation, account and lease checks, and journal construction also apply here.
The new manual invoice's due date must be on or after its issue day, supplied by Accounting in the
legal entity's time zone. The transaction date does not set that minimum. The form leaves the due
date empty for staff to choose afresh; the API verifies it again and returns 503 if the issue day
cannot be verified. Drafts can be saved without a calendar lookup; their due date is checked when
issued. Committed retries still replay without a calendar lookup. Manual transaction dates are kept
as entered, including future dates. The scheduler still waits for scheduled charges' billing dates.
This is a general manual-invoice endpoint: it does not require or link to a cancelled invoice. The correction screen uses it to issue the edited copy, with the same create-charge and issue-invoice permissions as other manual invoices.
The form copies the cancelled invoice's charge details and transaction date for editing. Staff can
choose Draft invoice to save, then reopen it with Edit or issue draft. The default is to issue.
POST /v1/invoices/{id}/draft replaces a draft's dates and charge lines, or issues it when status is
issued (the default). The account and lease cannot change. Empty drafts can be saved, but issuing
requires at least one valid charge. Saving emits the internal invoice.draft_saved event; issuing
emits invoice.issued once. An issued invoice cannot use the draft editing route. Drafts are visible
only through the staff/service invoice API and are excluded from account balances, automatic
issuance, posting and allocation. Draft edits retain the invoice ID, retained line IDs and audit
snapshots. A line's id identifies an existing draft line; omit it to add a line. Referenced lines
cannot be removed while another record still depends on them.
Issuing creates a separate invoice from the cancelled original; it does not edit or relabel the original. Normal allocation applies available funds up to the amount owed, leaves excess available, and leaves any shortfall outstanding.
Requirements alignment
The invoice requirements include behavior beyond the correction workflow currently implemented:
- Invoice cancellation: separately corrected charges still block full cancellation. The required cancellation of tied negative charges exactly once is not implemented by that path.
- Cancellation post month: staff cannot select another permitted month; cancellation uses the current Open month.
Permissions and retries
Unapply and resume use billing::ApplyPayment; cancellation uses billing::VoidInvoice. Creating
an invoice requires both billing::CreateCharge and billing::IssueInvoice. Every command requires
an Idempotency-Key; retries with the same payload replay, while reuse with different input is
rejected. The staff UI retains the key when retrying an unchanged submission.
Source: services/billing/internal/handlers/invoice_correction.go, services/billing/internal/handlers/create_invoice.go, services/billing/internal/voiding/void.go and services/billing/migrations/0058_invoice_correction.sql.