legacy-billing
Troubleshooting
Known gotchas and common misconfigurations.
Troubleshooting
Dependency mismatch: legacy-lib vs community_bridge
The resource manifest lists legacy-lib, ox_lib, and oxmysql as dependencies, but the project's own documentation and example server.cfg list community_bridge instead of legacy-lib for the framework abstraction layer. Check your actual fxmanifest.lua to confirm which is truly required before deploying, and make sure that resource is started before legacy-billing.
Resource fails to start / exports throw errors on boot
ox_lib, oxmysql, and the framework abstraction resource (legacy-lib or community_bridge — see above) must be started with ensure before legacy-billing in server.cfg. Server-side code calls exports like Framework(), Banking(), GetLocale, GetAccentColor, SendWebhook, and CheckVersion at file load time (not lazily) — if the dependency isn't already running, these calls fail immediately.
Bills/data disappeared after a database update
sql/schema.sql starts with DROP TABLE IF EXISTS for all five tables. Re-importing it against a database that already has billing data will permanently wipe legacy_billing_jobs, legacy_bills, legacy_bill_payments, legacy_bill_disputes, and legacy_bill_templates. Never re-run it as an "update" — strip the DROP TABLE lines first, or apply migrations manually.
A job can't send bills
IsJobBiller requires a row in legacy_billing_jobs for the player's current job with can_bill = 1. If a job is missing or can_bill = 0, every job-biller-gated callback (createBill, createBulkBills, getJobBills, getJobStats, templates) will silently return empty/failure rather than an obvious error. Add or enable the job via the admin panel (legacy-billing:addAllowedJob / updateAllowedJob) or directly in SQL.
CreateBill export succeeds for a job that "shouldn't" be able to bill
The CreateBill server export intentionally bypasses the IsJobBiller/can_bill check — it's meant for other resources to invoice players regardless of the job-biller configuration. If jobName doesn't exist in legacy_billing_jobs, the bill still gets created; the label just falls back to the raw jobName string.
Late fees / overdue status never happens
Config.LateFeeInterval controls a background sweep thread. If it's 0 (or negative), the thread never starts — bills will never automatically transition to overdue or accrue late fees, even if legacy_billing_jobs.late_fee_pct is set on a job.
Admins can't access the admin panel
IsAdmin checks the framework's own admin flag or the ace permission group.<name> for each entry in Config.AdminGroups. If your group names don't exactly match your ace/permission setup (without the group. prefix, which is added automatically), admins will be silently locked out with no error shown.
"Nearby" player list shows everyone
Config.NearbyDistance = 0 disables distance filtering entirely, showing all online players in the bulk-billing target picker rather than restricting to nearby players. Set a value greater than 0 if you want actual proximity filtering.
Adding a new payment method does nothing
Config.PaymentMethods only controls what's displayed in the UI. The server's payBill callback hardcodes acceptance of only 'cash' or 'bank' — adding another method to the config list without corresponding server-side handling will cause those payments to be rejected.
Disputes still appear to work when disabled
Config.AllowDisputes = false disables disputes server-side (the callback rejects requests), but the legacy-billing:disputeBill endpoint still exists. Make sure your client UI also respects this flag, otherwise players will see a dispute option that silently fails.
/billing command does nothing when pressed
The keybind for the billing command is registered with an empty default key — there is no default keyboard shortcut. Players must bind it manually in FiveM Settings > Key Bindings, or you should call exports['legacy-billing']:OpenBilling() from your own UI/interaction instead.
New-bill notifications missed for some players
The client only populates its local initData after the billing panel has been opened at least once. If a player has never opened /billing, incoming legacy-billing:notify events (e.g. new_bill) will silently no-op on their client since the handler bails out when initData hasn't loaded.
Stale admin/job-biller UI state
isAdmin and isJobBiller flags on the client are only set when the panel is opened (from getInitData) and are not refreshed automatically. If a player's job or admin status changes while the panel is open, they'll need to close and reopen it to see updated permissions. Note server-side callbacks always re-validate permissions regardless of client state.
Locale text shows raw keys like notify_new_bill
If Config.Locale points to a locale file that's missing or incomplete, or getInitData hasn't loaded yet, the client falls back to displaying the raw locale key instead of translated text. Keep locales/en.json and locales/sv.json (and any custom locale files) in sync key-for-key, and ensure Config.Locale matches an actual filename in locales/.