Four resources that share one ledger: a bank, a boss office, invoicing and player-owned shops. Every currency movement in all four goes through one bank export and one stored procedure, where the affordability check and the debit are a single statement inside one transaction; a refusal is written to the journal instead of being swallowed.
What is in the box
jr_banking
- Personal accounts: one per character. Deposit, withdraw and transfer are ledger calls, not a Lua read of the balance followed by a second write.
- Organization accounts: created for jobs and gangs listed in
config.lua. The current boss is granted owner; ordinary employees are not added automatically. - Account permissions: owner, manager and member. Managers have per-call and calendar-day caps; members can view and deposit, not withdraw or transfer.
- Account freezes:
/jrbank_freeze and the freezeAccount export, both gated by ACE. A frozen account blocks deposit, withdraw, transfer and role changes; viewing still works. - Append-only journal: every SQL-path success and refusal is a row in
jr_transactions. Refusals keep a machine denied_code. - Access lifetime: leaving the job, losing boss or changing job revokes the auto-owner row. Manual manager and member grants last only while that citizen still belongs to the job or gang.
- Branches and ATMs: configured points. Branches include role management; ATMs do deposit, withdraw and transfer only.
jr_society
- Boss office: one menu per job or gang listed in config, opened only by the framework
isboss grade. The resource has no roles of its own. - Staff actions: hire, fire and set grade on online players. Offline employees are listed read-only; the resource does not write the framework
players table behind its back. - Grade wages: salary per grade per pay period, stored in
jr_society_grades, bounded by config.lua. - Period payroll: the automatic thread and the manual button share one UTC period key, so they cannot name two windows at once.
- Repeat-safe pay: each employee is paid under
payroll:<org>:<period>:<citizenid>. Running the same period again moves no money. - All-or-nothing run: a cashbox that cannot cover the whole roster pays nobody, not the first names in list order. Offline employees are never paid.
- Bonuses and cashbox: one-off bonus, deposit and withdraw go through
jr_banking. A bonus is not a period: two presses pay twice.
jr_billing
- Organization invoices: jobs and gangs listed in config issue bills. There is no player-to-player invoice without an organization.
- Issue templates: ready-made amounts, or a typed amount still capped by config, with a due time.
- Automatic collection: a server thread collects overdue invoices. A console pass uses the same keys as the timer, so it cannot double-charge a scheduled tick.
- Frozen payers: a frozen bank account cannot be charged, by the player or by autopay.
- Fee and tax split: optional employee share and treasury tax, taken from what the organization just received, not from a second debit of the payer.
- One payment key:
invoice:<id>:pay is unique for the life of the invoice. Paying a paid invoice moves nothing. - Cancel, decline, grace: the issuer or a boss can cancel; the payer can decline; after the grace window neither the payer nor autopay may settle.
jr_shops
- Player-owned shops: a character or an organization owns each shop. The till is a
jr_banking account. - Own shelf: stock lives in
jr_shop_stock, not an inventory stash. Taking stock is UPDATE … WHERE qty >= ? inside jr_shop_buy / jr_shop_sell. - Shop clerks: hire staff, optional stock rights, clerk share of each sale (0–50 percent), paid from the till under its own key.
- Player buy-back: a shop can buy items from players when buy-back is on and the item has a buy price.
- Held orders: if the inventory cannot take the goods after payment, the order waits at the counter. It is not refunded.
- Frozen trade: freezing the till in
jr_banking, or the shop itself, stops trade without a second money path. - Integrity check:
stock + handed out + given back == supplied + bought from players, printable from the server console.
Correctness
- One procedure: every currency movement in
jr_banking is a single CALL (jr_ledger_deposit, jr_ledger_withdraw, jr_ledger_transfer). The affordability check and the debit are one UPDATE … WHERE balance >= ? AND is_frozen = 0 joined to access rights, inside one transaction. - Zero-row refusal: not an exception in the console. The same transaction writes the journal row with a
denied_code. - Deterministic keys: payroll, invoices and shop orders reuse a stable
op_id. jr_transactions.op_id is UNIQUE, so a replay returns the first result and does not move money again. - Payroll idempotency: repeating a period — timer, button, restart, or two bosses at once — pays nobody twice.
- Frozen accounts: cannot be deposited to, withdrawn from, transferred from or onto, or charged by billing, including autopay.
- Shelf then till:
jr_shop_buy / jr_shop_sell move the shelf and the order row in one transaction. Money is a jr_banking transfer under a unique key; a bank refusal puts the stock back and writes no currency. - No unguarded fallback: society, billing and shops refuse with
bank_outdated if jr_banking does not export transferAs. They do not fall back to an unguarded payment. - Whole-period payroll: a cashbox that cannot cover everyone pays no one a part. Paid op ids are already spent, so a half-run cannot be finished later.
Tests
- 244 unit tests: Lua 5.4 suites against mocks, shipped with the four resources. They run without FXServer.
- Live MariaDB: integration suites in
tests/integration/ call the real stored procedures, including parallel workers against one account or one last stock unit. - Live FXServer:
tests/live/ on a headless server. Cases that need a connected player are reported SKIP, not PASS. - Outside escrow:
tests/*.lua and tests/**/* are in escrow_ignore on every resource. The test files sit in the package, unencrypted, so you can run them.
Requirements
- Target framework: Qbox (
qbx_core) or QBCore (qb-core), auto-detected. Qbox wins if both are started. ESX is not supported. - Required resources:
oxmysql and ox_lib on all four. ox_inventory for jr_shops only. jr_society, jr_billing and jr_shops require jr_banking 1.0.2 or newer (transferAs). - Database engine: MariaDB 10.5.2+ or MySQL 8.0.16+ (non-negative
CHECK on balances). Stored procedures must be installed; Lua does not fall back to inline UPDATE. - Lua runtime: Lua 5.4 (
lua54 'yes'), fx_version 'cerulean', game 'gta5'. - FiveM build: Legacy. Enhanced is not claimed.
- Editable files:
config.lua, locales (en, ru), SQL and tests are listed in escrow_ignore.
Support
- Support email: hello@joyride.games
- Response target: 72 hours for a complete report.