The ledger, and the rules that make it trustworthy.
This is the controller's page. Every claim on it is enforced in the database, not in a policy document — and there is a test that proves the guardrail rejects the bad case.
A posted entry is permanent.
The ledger is append-only, and that is enforced by database constraints and triggers
rather than only in application code. A posted entry cannot be edited or deleted by
anyone — including a superuser sitting at a psql prompt. Corrections are
reversing entries, which is what an auditor wants to see anyway.
The reason to put the rule in the database rather than the application is that a future integration, a migration script or a well-meant data fix cannot quietly write an unbalanced entry. There is an entire destructive test suite whose only job is to attempt the bad case and confirm the database refuses.
Money is a signed integer in minor units. Positive is a debit, negative a credit. No floating point, so nothing rounds itself into a discrepancy over a year of dining charges.
Every module posts through the same intake.
Poolside, the beverage cart, the pro shop, valet, shoe shine, locker supply, the wedding deposit, the dues run — all of them post through one idempotent charge intake. Idempotent matters on a golf course: a cart terminal that loses signal and retries must not charge the member twice.
The practical consequence is what happens when the club opens something new. Adding
the cigar lounge is a revenue_mapping row, not a code change and not a
release. The chart of accounts grows; the backbone does not move.
Why this is the whole architecture. If each module owned its own posting logic, then each module would have its own way of being subtly wrong, and month-end would be the place you found out. One door means one thing to get right.
Cash posts when it settles, not when the member clicks.
A payment is recorded first
Recording a payment has no ledger impact at all. A card authorisation is a promise, and ACH can fail days later.
Then it settles
On settlement, cash posts, the receivable falls, and allocation runs. That is the moment the club actually has the money.
The fee is an expense
The processor's fee is the club's cost of taking cards — never a quietly smaller payment. The member's balance falls by exactly what they paid.
Payments allocate FIFO against open items, because that is what ages a receivable correctly and what a member expects — July clears before August. Overpaying is allowed and shows as unapplied cash on a report, not as an error somebody has to go and fix.
Money leaves a member account four ways.
Each posts differently, on purpose. Systems that treat all four as "an adjustment" are the reason a club's revenue line and its receivable disagree.
| Type | Where it lands | Why |
|---|---|---|
| Payment | DR cash, DR merchant fees, CR receivable | The fee is a cost of taking cards, not a discount on the payment |
| Credit memo | DR revenue, CR receivable | A comped dinner was never revenue |
| Write-off | DR bad debt expense, CR receivable | The sale happened; the collection did not |
| Transfer | Both legs on the control account | The club is owed exactly as much afterwards |
Preview, then commit.
The dues run, the statement run and the chart import all work the same way: a controller looks at four hundred charges before they become four hundred immutable entries.
- Every membership appears in the runIncluding the ones not being billed, and the reason each one was skipped. A silent omission is how a club loses a year of dues on a category nobody noticed.
- Proration is by days servedNot by a half-month convention that nobody can reconcile back to a join date.
- Dues past the charge month are deferredAnd earned month by month through the same engine as initiation fees.
- Prepaid dues, gift certificates and capital assessmentsRecognise through that same engine, so there is one earn-out to audit rather than three.
The ladder moves one rung per run.
current → past_due → suspended → collections, and never two rungs at
once, because every rung is a letter the member had a chance to answer.
Paying in full reinstates the member in the same transaction — not on the next nightly job. A member who settles at the desk and is still refused at the bar an hour later calls the general manager, and that call is the product failing.
A collections hold takes an account out of both the ladder and the late-fee run, so an account already with an attorney does not keep accruing fees nobody will collect.
Autopay queues pending payments and charges nothing itself; the
gateway does that, and settlement arrives by webhook. The processor sits behind a
PaymentGateway interface with a manual gateway as the default — a club
taking cheques at the desk needs no processor at all, which is still how most clubs of
a certain age collect most of their money.
The things a general accounting package cannot model.
These are first-class in the schema, not workarounds a consultant bolted on during implementation. They are also, in practice, the reason a club leaves its incumbent.
F&B minimums
Configurable qualifying spend and configurable forfeiture. Which outlets count, what happens to the shortfall, and whether an unspent balance rolls or is forfeited — all per club, all posted through the same intake as everything else.
Initiation fees: equity, deferred, or split
The highest-impact accounting question a club answers, and it is answered per membership category — a club can be equity for Full Golf and deferred for Corporate, which is common where corporate memberships carry no vote.
Prepaid dues, recognised monthly
The schedule is stored so an auditor can read the whole plan; the individual entries materialise as each period is recognised. Running recognition twice in a month recognises nothing twice.
Departmental P&L
Golf, F&B, grounds, pro shop — separated from the first entry rather than allocated backwards at year end from a spreadsheet of guesses.
| Treatment | What the member gets | Where the money goes |
|---|---|---|
| Equity | Ownership rights — certificate, vote, claim on liquidation | Capital contribution. Never revenue, ever. |
| Deferred revenue | No ownership rights | A liability, earned into revenue over the expected tenure |
| Split | Mixed rights | A set share to equity, the remainder deferred |
Choosing wrong here is expensive. A club that books equity initiations as revenue overstates its income statement by millions; one that books deferred fees as equity keeps millions off it. Neither error is visible from a dashboard, and both are visible to an auditor.
The club keeps its own chart.
Import from Jonas, Clubessential, Northstar, QuickBooks or a spreadsheet. The import stages, validates, then commits — never partially, so a failed import leaves the club exactly where it started rather than half-migrated.
Their account numbers survive as permanent aliases, and every export carries both numbers. A controller who has read account 4100 as dining revenue for eleven years keeps reading 4100 as dining revenue.
We import the chart of accounts from those systems. We do not integrate with them, and that is a deliberate choice rather than a gap — the migration page explains why.
Keep your books in QuickBooks if you want to.
The ledger backend is pluggable per club. Native BetterClub GL is the default and where every club starts. QuickBooks Online is the same journal, pushed on the club's own interval. It is a per-club setting and it is invisible to every module.
Posting a journal entry enqueues it by trigger — there is no code path that can write to the ledger and forget to tell the adapter.
- Idempotency keys, unique per clubChecked against QuickBooks before posting, so a retried batch cannot double-post.
- A worker claims a batch under a leaseRather than holding a transaction open across a slow third-party call. A worker that dies releases its work instead of pinning it.
- Failures back off and surface1, 3, 9, 27 minutes, capped at eight hours, with the reason attached on a reconciliation screen — rather than blocking the module that generated them.
- Nothing syncs while a category is unmappedAdding the cigar lounge to the chart makes it appear as something to map, rather than as a surprise at month end. A visible stall beats revenue landing in a suspense account in somebody else's ledger.
What is never pluggable is accounts receivable. The beverage cart authorises against a member's balance, credit limit and charge privileges in the moment of the swipe. No hourly sync to a third party can answer that, and a cart that waits on someone else's API is a cart that stops selling when the Wi-Fi drops. QuickBooks also has no concept of an F&B minimum with configurable qualifying spend, a statement cycle, or a spouse who may charge but not be billed — and those mechanics are exactly why the club is leaving its incumbent.
So a QuickBooks club gets a clean periodic journal in its accountant's own system and keeps every club-specific mechanic here.
QuickBooks Desktop is not supported. Daily-summary roll-up is not built — the worker sends per entry.
Built for other markets from day one.
17 countries pre-registered
Each with its tax regime, statutory chart, e-invoicing and fiscalization flags. Adding a market is one SQL file — tax accounts, codes and components, the wiring to revenue categories, document sequences and translations. No schema change.
Three tax shapes, correctly
VAT quoted inside the price. US sales tax added on top. Quebec's QST compounding on GST. All effective-dated, so a rate change never rewrites history.
Gapless numbering, hash-chained receipts
Document numbering is gapless per year and fiscal receipts are hash-chained — which is what Germany, France, Portugal and Saudi Arabia each require, in their own dialect.
The currency exponent sizes every amount, so 1234 is $12.34, ¥1,234 or 1.234 KWD without special cases. Certified fiscalization devices and e-invoicing transport — Peppol, SdI, KSeF, ZATCA — are not built. The schema holds the fields they need, so they can be scoped when a deal in that market is actually in sight.
Bring your controller.
We will run a dues preview against a club chart of accounts, post a charge, settle a payment, and show you the trial balance afterwards.