esx_billing
esx_billing lets ESX Legacy jobs send invoices and fines that players pay from cash or bank. Config, sendBill event, exports, callbacks and security limits.
esx_billing adds invoices to ESX Legacy. Job resources such as esx_policejob, esx_taxijob and esx_mechanicjob use it to fine or bill players. Players open their unpaid bills with F7 and pay them from cash or bank, and the money goes to the society account of the issuing job.
Overview
| Resource | esx_billing (version 1.0) |
| Side | Client and server |
| Dependencies | esx_lib, es_extended, oxmysql; society bills need esx_addonaccount |
| SQL file | esx_billing.sql (table billing) |
| Command | showbills (key mapping F7), console command billingpayments |
| Server exports | BillPlayer, BillPlayerByIdentifier |
Installation
Import the SQL
Run esx_billing.sql to create the billing table. On first start the resource also creates a billing_payments journal table and checks that billing uses InnoDB. If the migration fails, payments are disabled and the console prints [esx_billing] Schema migration failed; payments disabled.
Ensure the resource
ensure es_extended
ensure esx_addonaccount
ensure esx_billingCreate society accounts
Every job that bills needs a shared society_<job> row in addon_account. See esx_addonaccount.
Configuration
All keys live in config.lua.
| Key | Default | Description |
|---|---|---|
Config.Locale | GetConvar('esx:locale', 'en') | Language for notifications |
Config.BillingDistance | 10.0 | Max distance between sender and target, checked on the server |
Config.BillingCooldown | 3000 | Cooldown in ms between two bills from the same player |
Config.MinimumBillingGrade | 1 | Minimum job grade required to send bills |
Config.BillingMinimumGrades | {} | Per-job override, e.g. { police = 0, taxi = 2 } |
Config.MaxBillAmount | 100000 | Highest amount a single bill may have |
Config.MaxDailyBillAmount | 250000 | Total a single sender may bill per day. 0 disables the limit |
Config.MaxBillLabelLength | 80 | Labels are trimmed to this length. Empty labels become Invoice |
Config.HighBillConfirmationAmount | 50000 | Bills at or above this amount must be accepted by the target |
Config.HighBillConfirmationTimeout | 30000 | Time in ms the target has to accept a high bill |
Config.BillsPageSize | 50 | Bills per page in the menu (clamped to 1-100) |
Config.BillsReadCooldown | 1000 | Rate limit in ms for loading the bill list |
Config.BillPaymentCooldown | 500 | Rate limit in ms for payment attempts |
The daily total is kept in memory per sender identifier and calendar day, so it resets on a resource restart.
How payments work
- The player selects a bill in the
F7menu. - The server takes the money from
money(cash) if the player has enough, otherwise frombank. - Society bills (
target_type = 'society') credit the shared account named in the bill. - Player bills (
target_type = 'player') credit the sender's same account type. The sender must be online, otherwise the payment is refused. - The bill is moved to
billing_paymentsin a transaction and markedappliedonce both players and the account are saved. Failed credits are rolled back.
Server events
esx_billing:sendBill
TriggerServerEvent('esx_billing:sendBill', targetId, sharedAccountName, label, amount)| Name | Type | Description |
|---|---|---|
targetId | number | Server id of the player who receives the bill |
sharedAccountName | string | Society account of the sender's job, e.g. society_taxi |
label | string | Bill text shown to the target |
amount | number | Amount, floored to a whole number, 1 to Config.MaxBillAmount |
Client-triggerable
Any client can fire this net event, so the server enforces several checks. The bill is silently dropped unless all of these pass: sharedAccountName matches society_<sender job>, the sender's grade meets the minimum, the target is not the sender, the target is within Config.BillingDistance, the cooldown has passed and the daily quota is not exceeded. Keep these limits tight on live servers.
local player, distance = ESX.Game.GetClosestPlayer()
if player ~= -1 and distance <= 3.0 then
TriggerServerEvent('esx_billing:sendBill', GetPlayerServerId(player), 'society_mechanic', 'Engine repair', 750)
else
ESX.ShowNotification('No player nearby')
endesx_billing:sendBillToIdentifier
Local server event (not networked). It only runs when triggered by another resource and bills an identifier with sender server.
TriggerEvent('esx_billing:sendBillToIdentifier', targetIdentifier, sharedAccountName, label, amount)Use it with a society account. A non-society bill with sender server can never be paid, because player bills require the sender to be online.
esx_billing:paidBill
Fired after a bill was paid.
| Side | Arguments |
|---|---|
| Server (local event) | source, billId |
| Client (local event) | billId |
AddEventHandler('esx_billing:paidBill', function(playerId, billId)
print(('Player %s paid bill #%s'):format(playerId, billId))
end)Server exports
BillPlayer
exports.esx_billing:BillPlayer(targetId, senderIdentifier, sharedAccountName, label, amount)| Name | Type | Description |
|---|---|---|
targetId | number | Server id of an online player |
senderIdentifier | string | Identifier stored as sender |
sharedAccountName | string? | society_<job> for a society bill. Anything else creates a player bill paid to senderIdentifier |
label | string | Bill text |
amount | number | Amount, 1 to Config.MaxBillAmount |
Returns the inserted bill id, or false. The export skips the job, grade, distance and confirmation checks of the net event, but still counts toward the daily quota.
local xTarget = ESX.GetPlayerFromId(targetId)
local xSender = ESX.GetPlayerFromId(source)
local billId = exports.esx_billing:BillPlayer(xTarget.source, xSender.identifier, 'society_police', 'Speeding', 500)BillPlayerByIdentifier
exports.esx_billing:BillPlayerByIdentifier(targetIdentifier, senderIdentifier, sharedAccountName, label, amount)Same as BillPlayer, but targets an identifier, so the player can be offline. Returns the bill id or false.
Callbacks
Registered with xLib.callback.registerCompat, so they work with ESX.TriggerServerCallback and xLib.callback.
| Name | Arguments | Returns | Notes |
|---|---|---|---|
esx_billing:getBills | afterId? | bills, hasNext | Own bills (id, label, amount), paged |
esx_billing:getTargetBills | target, afterId? | bills, hasNext | Only for job police, target must be nearby |
esx_billing:payBill | billId | boolean | Pays one of the caller's own bills |
esx_billing:respondHighBill | token, accepted | boolean | Answer to a high-bill confirmation |
ESX.TriggerServerCallback('esx_billing:getTargetBills', function(bills)
for _, bill in ipairs(bills) do
print(bill.label, bill.amount)
end
end, GetPlayerServerId(closestPlayer))Console command
billingpayments works only from the server console and helps with stuck payments:
billingpayments list
billingpayments show <operation_id>
billingpayments retry <operation_id>list prints up to 20 receipts still in state claimed.
Common issues
- Bills never arrive: check the sender's job grade against
Config.MinimumBillingGrade, the distance and thatsharedAccountNameis exactlysociety_<job>. Failed authorization printsattempted to send an unauthorized billin the console. - "Attempted to send bill from invalid society": the
society_<job>row is missing inaddon_account. - Paying a player bill fails: the original sender is offline.
- Large fines need an extra click: bills of
Config.HighBillConfirmationAmountor more open a confirmation menu on the target.
Related
Frequently asked questions
Independent documentation
Last updated on
esx_basicneeds
esx_basicneeds adds hunger and thirst to ESX Legacy. Learn how to add food and drink items, the /heal command, events, config keys and how statuses drain.
esx_boat
esx_boat adds a boat shop, a boat license and six boat garages to ESX Legacy. Covers setup, every config key, callbacks, the getGarages export and pitfalls.