Flow
ServicesBilling

Automatic late fees

Configure approved late fee limits and understand how Billing measures overdue net rent, issues fees, and retries accounting posts.

Billing assesses one automatic late fee per lease and invoice due month. A standalone account has one account-level fee per month. Assessment requires an enabled entity policy with approved jurisdiction limits. No policy means no automatic fee.

Configure a policy

In accounting-app, open Policies → Late fees and choose the legal entity. Controllers and platform admins can schedule policies; accounting staff can read the history. The API accepts the same settings at POST /v1/late-fees/policies, with a command envelope and an Idempotency-Key.

A policy records its effective date, flat amount or percentage of unpaid net rent, grace days, charge day, entity currency and jurisdiction, and an approval reference. Both an absolute cap and a percentage cap of net rent apply. A zero cap prohibits assessment. Grace days cannot be below the approved minimum. The application does not infer legal limits or ship numerical jurisdiction rules: the configured limits must already be approved for that entity's jurisdiction.

Policy rows are append-only. The newest effective policy for an invoice's due date controls its calculation. A currently effective disabled policy stops further assessments. Existing assessments keep their original policy and evidence. Reconciliation uses that original policy even if a later policy is backdated. Policy changes need no code release.

GET /v1/late-fees/policies?legal_entity_code=... returns the policy history. GET /v1/late-fees/assessments?billing_account_id=... returns assessed amounts and their frozen evidence.

Determine overdue rent

The scheduler polls every minute, but a full assessment runs once per legal entity's local day. It reads the legal entity and financial timezone from Accounting before opening a local transaction, then caches that authoritative calendar for the local day. This bounded local copy avoids repeated Accounting calls while evaluating eligibility in the entity's timezone. If a calendar refresh fails, entities with a valid calendar for that day still run; entities needing a refresh remain eligible for retry.

Successful progress is persisted per billing account. A failed account remains eligible for retry. Receipt, payment allocation, credit allocation, deposit application, charge, and invoice changes mark a durable account work queue, so delayed on-time payments are reconciled on the next poll without scanning every account's history. Policy changes mark their entity's accounts too. Each account transaction takes the shared replica lock, rechecks its daily progress, and commits its assessment, progress, and queue acknowledgement together. Derived writes made by that assessment do not mark themselves dirty. Other transactions increment the queue version; acknowledgement removes only the version the assessment read, so concurrent money changes survive. Restarts and multiple scheduler replicas use the same progress and queue state. Currency or jurisdiction mismatches, missing calendars, and invalid timezones prevent assessment for that account and are logged for retry.

A rent charge becomes eligible on the day after its invoice due date plus grace days, and no earlier than the configured charge day in the due month. Day 31 uses the last day of shorter months. For example, rent due October 1 with five grace days first becomes eligible October 7. Financial dates are calendar days; the system clock is converted to the entity's financial timezone before testing eligibility.

A concession can name concession_target_charge_id when created through POST /v1/charges or POST /v1/credits. The target must be a positive charge on the same billing account and lease, and combined concessions cannot exceed it. A tied concession pays that charge. Cash applications explicitly naming that charge are released and can become prepay. Legacy invoice-level cash is kept in place because Billing cannot infer which charge it paid. A tied concession waiting for an unposted target remains held and does not enter the scheduler's allocation queue until that target posts. If a target is cancelled, superseded, or can absorb only part of the concession because other discounts already paid it, Billing withdraws the unusable part through audited invoice cancellation and reissues any applicable portion. A tied concession never becomes an unrelated discount. New credit invoice applications name their target charge; untied credit invoices follow invoice due date and charge order across the account's leases. These allocations supply net charge balances before Billing calculates overdue rent.

Older discounts embedded in mixed invoices have no stored target. Their existing invoice discount remains in that invoice's charge order; Billing does not invent a historical target. Migrating those records to the newer tied or standalone concession requirements remains a separate data classification task. New cash applications name the remaining charge even on a concession invoice. Existing applications without a charge identifier consume net charges in the same order. Classification of a charge as reducing an invoice remains the classification recorded when the invoice was issued, even if its GL mapping later changes.

Payments supplies its original server submission timestamp for online payments and the staff-recorded transaction_date for off-platform payments. Billing compares online submission time in the entity calendar; a recorded calendar date is kept exactly as entered without a timezone conversion. A payment made by the last grace day counts even if staff record it, the processor approves it, or Billing applies it later. Billing includes available held prepay submitted by the deadline, after respecting older due dates, charge order, lease scope, and explicit allocation selections. Money reserved for a selected deposit or invoice that has not posted stays reserved and does not pay unrelated rent. It calculates the fee only from the remaining net rent; other charge types never increase that basis. A percentage fee applies to unpaid net rent; a flat fee is assessed only when some net rent remains unpaid. Both approved caps then limit the result. A fractional cent in the percentage cap rounds down so the fee cannot exceed the approved maximum.

Payments migration services/payments/migrations/0030_receipt_submission_recovery.sql adds the recorded transaction date and separately acknowledged recovery of payment dates. Its delivery worker scans registered, live Payments records, sends stored calendar dates or proven online submission timestamps under stable metadata command keys, and acknowledges only successful deliveries. It does not replay payment outcomes or reallocate existing receipts. Failed recovery remains eligible for retry after a restart.

The billing account page displays a warning and the affected payment IDs when receipts needed for assessment have unknown submission dates. This includes refunded payments that may establish coverage at an earlier deadline. GET /v1/late-fees/submission-status?billing_account_id=... exposes those missing dates; blocked: false establishes submission provenance only, not policy or calendar eligibility. Refresh the account after recovery to clear the warning.

Historical receipts without a verified online submission time or recorded transaction date are not treated as late. They block automatic assessment on their account and appear in the account warning. A persisted processor attempt establishes that an old payment was initiated online, so recovery can use its immutable creation clock. The payment method label alone does not establish that. An old cheque, cash or wire record without a transaction date remains unknown until an authoritative date is supplied; creation, approval, allocation and delivery times are never substituted. New off-platform payment requests require the real calendar date. Recovery does not provide a payment-date correction operation.

Discounts use their financial date when calculating coverage at the deadline. A concession or credit granted later, or a deposit released later, does not reduce a fee that was valid then. Correcting a wrong rent charge does reduce the basis, even when that correction is recorded later. Allocation still uses the current charge balance; the historical fee calculation does not change live allocation rules.

Issue, post, and retry

The first eligible evaluation records the cycle’s policy and evidence, including when the fee is zero. A zero evaluation creates no charge or invoice. When a fee is owed, one local transaction locks the account, creates the latefee charge, issues its invoice, records the monthly assessment, and writes audit events. Unique cycle constraints prevent duplicate evaluations and assessments across concurrent workers and restarts. The fee participates in ordinary payment allocation by invoice due date and charge order.

The configured latefee mapping must debit the receivable and credit earned fee income; it cannot be a refundable deposit or a deferred charge. Before assessment, Billing verifies that both mapped accounts belong to the legal entity's Accounting chart. The daily calendar cache records the validated account IDs. A mapping change invalidates that proof immediately, and the account transaction rechecks the mapping under the shared mapping lock before creating a fee. Missing accounts, mismatched charts, or an unavailable validation response defer assessment without creating a charge or marking the account assessed.

The invoice's frozen journal request goes through the existing accounting posting queue. Accounting outages leave the invoice pending or failed for retry. Permanent posting failures become blocked invoices and use the normal invoice repost operation after correction. The assessment is not created again to retry a journal post.

Corrections and returned payments

Every scheduler pass reconciles existing fees using the original payment dates and the cycle’s original policy. An on-time payment delivered late can reduce the fee. If that payment is returned, the fee is recalculated without its coverage and any difference is reinstated. A voluntary refund after the deadline does not retroactively make the original on-time payment late. Repeated deliveries produce the same result regardless of whether the return arrives before or after assessment.

A reduction creates a negative charge on the latefee code, linked by correction_target_charge_id. A full correction is labelled Reversal and a partial correction Adjustment. Reinstatement creates a positive adjustment linked to the same monthly assessment. Both use the original fee’s frozen posting accounts and the adjustment’s entity-local date; Accounting selects the current open posting month. The original charge and invoice remain unchanged. A later policy or account-mapping change does not rewrite an existing assessment. An operator’s waiver or cancellation is not automatically undone.

Negative adjustments pay their target fee charges. Displaced cash becomes available as prepay; displaced ordinary credit becomes available again with audited reversal and reapplication of any retained portion. Credit naming the corrected charge is released first. Invoice-level credit is released only up to that charge’s share, preserving other charges’ coverage. Corrections spanning several fee charges are checked before any adjustment is written.

When a completed deposit disposition paid the corrected fee, Billing releases only the displaced deposit coverage into prepay. The original disposition, statement, refund and posted journals remain in the audit history. A recorded release offset reduces the funds available to later disposition versions, so reopening and completing the disposition cannot apply or refund that amount again.

The negative fee correction posts first. A separate release then debits its frozen receivable account and credits Prepay. A failed or interrupted posting remains pending and retries with the same identifiers. The account shows that funding is pending until activation succeeds. See Prepaid rent for when the funds become available and how the receipt is protected.

Missing or inconsistent posting evidence defers a correction without guessing its accounts. correction_blocked_reason explains the problem on the assessment and the account view. A later successful correction clears the warning once any required deposit funding is available.

Corrections wait for the original invoice to finish posting. An imported legacy_posted invoice also qualifies when its original journal reference and frozen posting payload are available. Missing historical evidence prevents correction; Billing never reconstructs those accounts from today’s charge-code mapping.

The original assessment identity, amount and evidence remain available. current_fee_cents shows the recalculated fee; current_charge_id and current_invoice_id keep identifying the original fee. Each adjustment records its signed delta and emits charge.late_fee_corrected with the previous and current amounts, adjustment charge and invoice IDs, evidence and reason. Restoring a payment after a mistaken return reduces the fee again through audited negative adjustments. Replays do not create a second assessment or duplicate adjustment. Journal posting retries use the existing durable invoice queue.

The charge event feed includes charge.late_fee_evaluated, charge.late_fee_policy_scheduled and charge.late_fee_assessed. Evidence records the rent charges, deadlines, net and unpaid rent, policy reference, timezone, actor and creation time.

Tied concessions are checked for cancellation even when the account has no outstanding positive invoice. An unused concession for a cancelled or fully discounted target cannot remain as free-standing credit. Concessions reserved for future scheduled targets remain dormant.

Dirty tracking records financial, allocation, submission, and charge/date changes. Posting references, posting-status updates, cancellation retry stamps, and unchanged input values do not enqueue reassessment. Inserts and deletes still record work.

On this page