FiveM Docs
Addons

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

Resourceesx_billing (version 1.0)
SideClient and server
Dependenciesesx_lib, es_extended, oxmysql; society bills need esx_addonaccount
SQL fileesx_billing.sql (table billing)
Commandshowbills (key mapping F7), console command billingpayments
Server exportsBillPlayer, 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

server.cfg
ensure es_extended
ensure esx_addonaccount
ensure esx_billing

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

KeyDefaultDescription
Config.LocaleGetConvar('esx:locale', 'en')Language for notifications
Config.BillingDistance10.0Max distance between sender and target, checked on the server
Config.BillingCooldown3000Cooldown in ms between two bills from the same player
Config.MinimumBillingGrade1Minimum job grade required to send bills
Config.BillingMinimumGrades{}Per-job override, e.g. { police = 0, taxi = 2 }
Config.MaxBillAmount100000Highest amount a single bill may have
Config.MaxDailyBillAmount250000Total a single sender may bill per day. 0 disables the limit
Config.MaxBillLabelLength80Labels are trimmed to this length. Empty labels become Invoice
Config.HighBillConfirmationAmount50000Bills at or above this amount must be accepted by the target
Config.HighBillConfirmationTimeout30000Time in ms the target has to accept a high bill
Config.BillsPageSize50Bills per page in the menu (clamped to 1-100)
Config.BillsReadCooldown1000Rate limit in ms for loading the bill list
Config.BillPaymentCooldown500Rate 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

  1. The player selects a bill in the F7 menu.
  2. The server takes the money from money (cash) if the player has enough, otherwise from bank.
  3. Society bills (target_type = 'society') credit the shared account named in the bill.
  4. Player bills (target_type = 'player') credit the sender's same account type. The sender must be online, otherwise the payment is refused.
  5. The bill is moved to billing_payments in a transaction and marked applied once 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)
NameTypeDescription
targetIdnumberServer id of the player who receives the bill
sharedAccountNamestringSociety account of the sender's job, e.g. society_taxi
labelstringBill text shown to the target
amountnumberAmount, 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.

client/main.lua
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')
end

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

SideArguments
Server (local event)source, billId
Client (local event)billId
server/main.lua
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)
NameTypeDescription
targetIdnumberServer id of an online player
senderIdentifierstringIdentifier stored as sender
sharedAccountNamestring?society_<job> for a society bill. Anything else creates a player bill paid to senderIdentifier
labelstringBill text
amountnumberAmount, 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.

server/main.lua
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.

NameArgumentsReturnsNotes
esx_billing:getBillsafterId?bills, hasNextOwn bills (id, label, amount), paged
esx_billing:getTargetBillstarget, afterId?bills, hasNextOnly for job police, target must be nearby
esx_billing:payBillbillIdbooleanPays one of the caller's own bills
esx_billing:respondHighBilltoken, acceptedbooleanAnswer to a high-bill confirmation
client/main.lua
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 that sharedAccountName is exactly society_<job>. Failed authorization prints attempted to send an unauthorized bill in the console.
  • "Attempted to send bill from invalid society": the society_<job> row is missing in addon_account.
  • Paying a player bill fails: the original sender is offline.
  • Large fines need an extra click: bills of Config.HighBillConfirmationAmount or more open a confirmation menu on the target.

Frequently asked questions

Independent documentation

esx-fivem.com is a community project and is not affiliated with the ESX Framework team. Facts on this page were checked against the upstream source. Official resources: docs.esx-framework.org and github.com/esx-framework.
Edit on GitHub

Last updated on

On this page