Run the VIP tab settlement
Close out a month of tab spending: sweep credit, charge cards, and handle the failures.
Owned by Admin · 9 steps · about 20 minutes
Why this exists
A VIP tab is not a balance the member owns; it is a limit on what they may owe. Spending against it moves no money at all — it records an obligation. That is why the platform spends real money first: prepaid credit, then earned balance, and only then the tab. The tab is always the last resort, which keeps the amount to be collected as small as it honestly can be.
Settlement is scheduled, not manual. On the first tick of a new calendar month the platform settles the previous month for everybody carrying a tab balance: it sweeps their prepaid credit first, charges their card for whatever remains, and emails an invoice. Doing it on a schedule rather than on demand means nobody has to remember, and nobody chooses who gets chased.
The admin surface exists for the exceptions, and the exceptions are the interesting part. A declined card marks the settlement failed and freezes the tab. The balance is still owed — freezing is a credit decision, not forgiveness. Retry re-attempts the charge. Waive is meant to write it off deliberately and on the record — as of 2026-08-30 it does neither: it relabels the row, posts nothing, and next month's run charges the same card for the same money. Plan 675 is the fix; until it ships, treat a waive as a note to yourself and not as forgiveness. Both are still actions with names, which is better than an admin quietly adjusting a balance to make a problem disappear.
The idempotency guard is a unique constraint on user plus period, not a greyed-out button. Running settlement twice for the same month cannot charge twice even if two admins press it at the same second — which is exactly the property you want in the thing that touches customers' cards.
Before you start
- An admin session. Tab settlement, tab limits and the settlement dashboard are all admin-only.
- A VIP with tab usage to settle (vip@club.test is seeded with $80 used against a $250 limit).
- Know which period you mean. Periods are calendar months and the endpoint takes one.
Practise with
| Persona | Password | Note | |
|---|---|---|---|
| admin | admin@club.test | admin123 | runs settlement, retries, waives and sets limits |
| vip_member | vip@club.test | vip123 | the seeded tab: $250 limit, $80 used |
Steps 1–2 — Admin
their manual →-
1Open the settlement dashboard. It shows one row per user per period, and tells you the last period the scheduler settled.Expected result Recent settlements with status paid, failed or waived, and a default period suggestion of last month.Watch out for The last-settled marker is how the monthly tick knows not to repeat itself. Do not edit that setting by hand — you will make settlement skip or repeat a month.
-
2Set a tab limit for a VIP before the month they will use it, or freeze an existing tab.Expected result The tab row with its new limit or status.Watch out for VIP status alone does not create a tab limit. Granting vip_member gives somebody Zone B and a tab that may have no headroom at all until you set one.
Steps 3 — VIP Member
their manual →It is their tab: the sweep takes their prepaid credit first and their card second.
-
3As the VIP, look at what settlement is going to act on: your prepaid balance — the only pot the run sweeps — your earned balance, and your tab used against its limit.Expected result Three separate numbers plus a combined purchasing power figure.Watch out for Purchasing power adds tab headroom to real money. It is what you can spend, not what you have. Settlement collects the difference — out of prepaid credit first and then the card. It never touches earned or promo balance, so a VIP can be charged while the platform still owes them money.
Steps 4–9 — Admin
their manual →-
4Run settlement for a period. Leave the period out and it settles the previous month.Expected result Per-user results: prepaid swept, card charged for the remainder, invoice emailed. Counts of settled, failed and skipped.Watch out for Idempotent per user and period. Pressing it twice does not charge twice — the unique constraint is the guard, and it holds under concurrency.
-
5Read the results and separate the failures from the successes before you touch anything.Expected result Failed rows for declined cards, alongside paid rows.Watch out for A failed settlement also froze that person's tab. Somebody at the door or the bar will be told they cannot charge, and they will not know why unless you tell them.
-
6Retry a failed settlement once the member has fixed their card.Expected result A fresh charge attempt against the same period, and the tab unfrozen on success.Watch out for 409 SETTLEMENT_NOT_RETRYABLE if it is not in a retryable state. Retry is for failed rows; it is not a way to re-run a month that already settled.
-
7Waive a settlement when the venue has decided to absorb it — a goodwill gesture, a disputed charge, a member who has left.Expected result The settlement row moves to waived. CORRECTED 2026-08-30: that is ALL it does — no ledger transaction is posted, the tab usage is untouched, and next month’s run charges the card for it again. Plan 675.Watch out for The button’s own confirmation says the debt is written off permanently, and that is not true today, so do not repeat it to a member. Nothing records who waived it either — there is no such column. What is still right is the principle: forgiving a balance is a named act, and adjusting the ledger to make the number look nicer is not.
-
8In training, settle a single user for a single period so you can watch one person's mechanics end to end.Expected result The same settlement logic scoped to one user.Watch out for Admin-only debug. The production path is the monthly tick; this is a microscope, not a substitute.
-
9Trace one settlement into the ledger: the prepaid sweep, the card charge and its separate processing fee.Expected result Balanced transactions of kind tab settlement, referencing the settlement.Watch out for Try to edit one and the database itself refuses. The append-only trigger is not a UI convention — it fires from the data suite and from raw SQL exactly the same way.