Bank Reconciliation Controls

Daily reconciliation between the payment processor and the general ledger, a nightly check that the ledger's processor sub-account equals what the processor holds, hourly payout-bank checks, and the month-end bank reconciliation against statements or the live bank feed.

7 min read Accounting Controls Published Jun 10, 2025

Overview

CommunityPay reconciles money at two layers. Every night, the ReconciliationService compares the general ledger with the payment processor's records in six steps. Every month, a person reconciles the bank account against the statement or the live bank feed, as described in Bank Statement Import and Matching. This page describes the nightly processor reconciliation and the checks that run beside it.

The processor reconciliation is not a monthly close activity. It runs daily as a scheduled task (run_daily_reconciliation, 2:00 AM) over the previous 24 hours, so a discrepancy is detected within a day of its occurrence.


Six-Step Reconciliation Process

Step 1: Payment Reconciliation

For each association with a connected processor account, the service fetches the processor's payments for the window and matches them to database records by provider_transaction_id. Matching is by identity, never by date window. Each processor charge is looked up by its own id, wherever the database row's creation date falls.

For each matched payment, the service creates a ReconciliationItem record with one of three outcomes:

  • Matched: Amount and status align within $0.01 tolerance
  • Amount Mismatch: Database amount differs from the processor's amount
  • Status Mismatch: Database status differs from the processor's status

Critical detection: A payment found at the processor but missing from the database raises a high-severity alert. A payment in the database but missing from the processor is also flagged. Both represent potential data integrity issues.

Step 2: Refund Reconciliation

Refund records (the Refund model) are matched to the processor's refund records. Amount discrepancies are flagged as reconciliation items requiring action.

Step 3: Transfer Reconciliation

Platform fees were retired in favor of subscription pricing, so this step records zero fees collected.

Step 4: Payout Reconciliation

For each connected association, the processor's payouts are read over a window wider than the run, so a payout created yesterday and settled today is refreshed rather than left pending. Each is recorded as a StripePayout record. A failed payout raises a critical-severity alert that includes the failure reason and the affected amount.

For a vendor payout, this step is also the backstop for a missed webhook. It stamps the payout id on the vendor payment record, and a failed vendor payout is applied as a return: its entry reverses, the bill reopens, the record is stamped failed with the bank's reason, and the approval is used up, so paying again takes a new one.

Step 5: Balance Verification

For each association, the service reads its own processor balance and makes three checks.

  • A negative available balance is a critical alert. The account is overdrawn at the processor, and the next vendor payout will fail.
  • An available balance below the association's payout buffer target is a high alert.
  • The ledger's processor sub-account is compared with what the processor holds for the association: the available, pending, and reserved balances, plus payouts created but not yet paid. If the books claim more than the processor holds by more than $0.01, that is a critical alert, because a vendor payout could be drawn against money that is not there. A negative sub-account balance is a critical alert in its own right.

The comparison is one-directional on purpose. The processor holding more than the books show is usually a charge that has settled whose webhook has not yet landed, and the integrity scan's receipt checks own that case. The processor sub-account has no bank statement and sits outside bank reconciliation, so this nightly comparison is the only external check on that balance.

Step 6: Alert Generation

All discrepancy items are reviewed. A payment above $100 missing from the processor is escalated to critical severity. When any discrepancy is found, a summary email goes to the configured payment-alert recipients. It also reports five payment-amount invariants, each of which should read zero: part-paid rows, overpaid rows, receivable-partial obligations, live rows on relieved invoices, and unlinked chargeable rows.

One condition is one alert. If an unresolved alert with the same type, association, and title already exists, the new occurrence is logged against it rather than duplicated.


ReconciliationRun Record

Each execution creates a ReconciliationRun record tracking:

  • Run type (daily, monthly, manual, or processor payout)
  • Date range covered
  • Total payments checked
  • Total processor transactions processed
  • Matched count and discrepancy count
  • Total amount disbursed in payouts, and fees collected (zero, since platform fees were retired)
  • Completion status and any error message

Per-association balances are kept in the daily balance snapshots below.


Daily Balance Tracking

The DailyReconciliation service maintains per-association daily balance snapshots via the DailyBalance model:

  • Opening balance: Previous day's closing balance (chain continuity)
  • Payments received: Sum of the day's paid payments
  • Refunds issued: Sum of completed refunds
  • Payouts sent: Sum of paid payouts
  • Closing balance: Computed from opening + receipts - refunds - payouts

Each snapshot is checked against the processor's available and pending balance for the connected account. Differences exceeding $1.00 are recorded in the balance_discrepancy field. The snapshot is a rollup derived from payments, with a one-dollar tolerance. The exact comparison is the nightly sub-account check in Step 5.


The Payout Bank Check

Each association has one pinned payout bank, and a vendor's bank can never be it. An hourly check, and a check on every bank and payout event, compare the processor's default payout bank with the pin. A mismatch pauses the association's automatic payouts and raises a critical sweep_destination alert. Payouts stay paused until a person restores the default and releases them. The sweep entry in the ledger posts only for a payout to the pinned bank.

Changing the pinned bank takes two people, and the association's bank PIN and a fresh authenticator code from each. A member who holds the Association funds permission asks, and a different board member confirms. The first payout to the new bank is held until a member who holds that permission, other than the requester, releases it, and the hold raises a high alert so staff can see it.


Month-End Bank Reconciliation

A person reconciles each bank account against a statement file or the live bank feed, one statement period at a time. The machine matches a bank transaction to a posted entry only when the amount is exact to the cent and no other entry fits. A person decides everything else, completes the month, and the match never posts an entry. The mechanism is described in Bank Statement Import and Matching.


Alert Severity Levels

Condition Alert Type Severity
Payment at the processor, not in the database missing_payment High
Processor data could not be fetched for an association missing_payment High
High-value payment (> $100) in the database, not at the processor missing_payment Critical
Payout failed at the processor failed_payout Critical
Processor balance negative balance_discrepancy Critical
Ledger processor sub-account negative balance_discrepancy Critical
Ledger claims more processor cash than the processor holds balance_discrepancy Critical
Available balance below the payout buffer target balance_discrepancy High
Processor's default payout bank is not the pinned bank sweep_destination Critical
First payout to a new payout bank held for release sweep_destination High

An amount or status difference on a matched payment is recorded as a reconciliation item that requires action.


Scheduling

Task Schedule Purpose
run_daily_reconciliation Daily, 2:00 AM Full six-step reconciliation for the previous 24 hours
update_all_daily_balances Daily, 3:00 AM Per-association balance snapshots checked against the processor
verify_payout_banks Hourly Compares each association's processor default bank with its pinned bank
bank_feed_sync_all Daily, 6:40 AM Pacific Reads each linked bank account's posted transactions and balance. Posts nothing to the ledger
How CommunityPay Enforces This
  • Six-step processor reconciliation runs daily via a scheduled task
  • Every processor payment is matched to its database record by the processor's transaction id
  • Amount differences over $0.01 are flagged; a payment at the processor missing from the database raises a high alert, and a payment over $100 missing at the processor raises a critical one
  • Every night the ledger's processor sub-account is compared with what the processor holds; the books claiming more than the processor holds is a critical alert
  • Hourly, and on every bank and payout event, the processor's default payout bank is compared with the association's pinned bank; a mismatch pauses automatic payouts
  • Month-end bank reconciliation by statement file or the live bank feed, matched one to one by machine
Login