DEV Community

Tack k
Tack k

Posted on

I Built a Custom Billing System for My QBCore Server — Here's What I Learned

Why I built my own

My QBCore server had a very specific need: mechanics needed to bill players for multiple repair items in a single invoice, with each item having a variable quantity. Something like "Engine repair × 3, Tire replacement × 4" — calculated on the fly, not pre-defined in config.

I also wanted job-based revenue splitting baked in — so when a bill gets paid, the money automatically goes to both the employee and the company society account at the right ratio.

I couldn't find an existing solution that did exactly what I wanted, so I built gg_billing2 from scratch using Claude Opus as my coding partner.


What gg_billing2 does

Quantity-aware billing. Players select items from a job-specific list and input quantities. The total is calculated live. No need to pre-define every price combination.

Job-based revenue split. Each job has a configurable company/personal share ratio. Payment is automatically distributed at the moment the bill is settled.

Config.JobShares = {
    police    = 0.5,   -- 50% company, 50% personal
    mechanic  = 0.7,   -- 70% company, 30% personal
    ambulance = 0.5,
}
Enter fullscreen mode Exit fullscreen mode

Due dates and overdue handling. Bills expire after a configurable number of days per job. You can choose to allow late payment, block it, or charge a late fee percentage.

Config.OverdueBehavior = 'late_fee'

Config.LateFee = {
    type  = 'percent',
    value = 0.10,        -- 10% late fee
    toCompanyOnly = true
}
Enter fullscreen mode Exit fullscreen mode

Repeat overdue notifications. Debtors get notified on a configurable interval while their bill remains unpaid and overdue. Also triggers on login.

Mass invoicing. Send the same bill to multiple players at once — handy for group fines after large RP events.

Dual banking support. Works with both okokBanking and qb-banking via a single config flag.

Config.useOkokBanking = true  -- false = qb-banking
Enter fullscreen mode Exit fullscreen mode

The trickiest part: split calculation

Revenue splitting sounds simple, but there's a subtle edge case: what if the config changes between when a bill is created and when it's paid? To handle this, gg_billing2 saves the job share at invoice creation time and uses that saved value when calculating the split — regardless of what the current config says.

-- Uses the share saved at invoice creation time
local jobShare = tonumber(invoice.companyShareUsed) or GetJobShare(jobName)
local companyAmount  = math.floor(payBase * jobShare)
local personalAmount = payBase - companyAmount

xOwner.Functions.AddMoney('bank', personalAmount, 'billing-receive-personal')
AddCompanyMoney(jobName, companyAmount, 'billing-receive-company')
Enter fullscreen mode Exit fullscreen mode

Built with AI, designed by a non-coder

I designed the entire system — the data model, UX flow, and edge cases — and implemented it using Claude Opus as my coding partner. I don't write Lua myself; my job is systems thinking and design.

This is Vol.1 of my FiveM Dev Diaries series, documenting 2+ years of custom scripts built for my QBCore server.

Next up: Vol.2 — A Police Reward Distribution System with PED-based interaction UI.


Questions about the implementation? Drop a comment — happy to share config snippets or dig into specifics.

Top comments (0)