The platform

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.

433automated tests, against a real database with no mocks
299SQL invariant assertions across six suites
134end-to-end API tests at the HTTP layer
0floating-point numbers anywhere near the ledger
Immutability

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.

One door in

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.

Settlement, not authorisation

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.

Getting paid

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.

TypeWhere it landsWhy
PaymentDR cash, DR merchant fees, CR receivableThe fee is a cost of taking cards, not a discount on the payment
Credit memoDR revenue, CR receivableA comped dinner was never revenue
Write-offDR bad debt expense, CR receivableThe sale happened; the collection did not
TransferBoth legs on the control accountThe club is owed exactly as much afterwards
The runs

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.
Collections

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.

Club-specific mechanics

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.

Initiation treatments, set per membership category
TreatmentWhat the member getsWhere the money goes
EquityOwnership rights — certificate, vote, claim on liquidationCapital contribution. Never revenue, ever.
Deferred revenueNo ownership rightsA liability, earned into revenue over the expected tenure
SplitMixed rightsA 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.

Chart of accounts

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.

Where the books live

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.

More than one country

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.