Integration
Exports, events, and integration guide for legacy-billing
Opening the Billing Panel#
Client Export: OpenBilling#
Opens the billing UI for the current player.
exports['legacy-billing']:OpenBilling()
No parameters are required. The panel automatically detects whether the player is an admin, a job biller, or a regular player, and displays the appropriate tabs.
Creating Bills Programmatically#
Server Export: CreateBill#
Create a bill from any server-side script.
local billId = exports['legacy-billing']:CreateBill(src, targetId, amount, description, jobName, dueDate, metadata)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
src | number | Yes | Server ID of the sending player. Use 0 for system-generated bills |
targetId | string | Yes | Target player's framework identifier |
amount | number | Yes | Bill amount in dollars (must be greater than 0) |
description | string | Yes | Bill description text (max 500 characters) |
jobName | string | Yes | Job name sending the bill. Must exist in the legacy_billing_jobs table |
dueDate | string | No | Due date in SQL timestamp format (e.g. '2025-12-31 23:59:59'). nil for no due date |
metadata | table | No | Custom metadata table stored as JSON on the bill |
Return Value
Returns the new bill ID (number) on success, or nil on failure.
Examples
-- Police issuing a traffic fine
local billId = exports['legacy-billing']:CreateBill(
source,
targetIdentifier,
5000,
'Traffic Fine - Speeding in a school zone',
'police'
)
-- Hospital bill with metadata and due date
local billId = exports['legacy-billing']:CreateBill(
source,
targetIdentifier,
15000,
'Emergency room treatment and medication',
'ambulance',
'2025-06-15 23:59:59',
{ treatmentId = 'ER-4821', doctor = 'Dr. Smith' }
)
-- System-generated bill (no player source)
local billId = exports['legacy-billing']:CreateBill(
0,
playerIdentifier,
2500,
'Monthly property tax',
'government'
)
Server Events#
legacy-billing:billCreated#
Fires when any bill is created (via UI or the CreateBill export).
AddEventHandler('legacy-billing:billCreated', function(data)
print(('Bill #%d created: $%d from %s to %s'):format(
data.billId, data.amount, data.senderId, data.targetId
))
end)
| Field | Type | Description |
|---|---|---|
data.billId | number | The new bill ID |
data.senderId | string | Sender's framework identifier |
data.targetId | string | Target's framework identifier |
data.amount | number | Bill amount |
data.jobName | string | Job that sent the bill |
legacy-billing:billPaid#
Fires when a payment is made on a bill (full or partial).
AddEventHandler('legacy-billing:billPaid', function(data)
print(('Bill #%d: $%d paid via %s'):format(
data.billId, data.amount, data.method
))
end)
| Field | Type | Description |
|---|---|---|
data.billId | number | The bill ID |
data.payerId | string | Identifier of the player who paid |
data.amount | number | Amount paid in this transaction |
data.method | string | Payment method: 'cash' or 'bank' |
legacy-billing:billCancelled#
Fires when an admin cancels a bill.
AddEventHandler('legacy-billing:billCancelled', function(data)
print(('Bill #%d cancelled by %s'):format(data.billId, data.cancelledBy))
end)
| Field | Type | Description |
|---|---|---|
data.billId | number | The bill ID |
data.cancelledBy | string | Admin's framework identifier |
Adding Billing Jobs#
Jobs must be registered before they can send bills. There are three methods:
- Admin Panel -- Open
/billingas an admin and add jobs in the Admin tab. - Database Insert -- Insert directly into the
legacy_billing_jobstable:
INSERT INTO legacy_billing_jobs (job_name, job_label, can_bill, late_fee_pct)
VALUES ('mechanic', 'Mechanic', 1, 0.00);
- Schema Defaults -- The install schema seeds
police(5% late fee),ambulance, andmechanicby default.
Querying Bills from Other Scripts#
You can query the database directly using oxmysql:
-- Get unpaid bills for a player
local bills = MySQL.query.await(
"SELECT * FROM legacy_bills WHERE target_id = ? AND status IN ('unpaid', 'partial', 'overdue')",
{ identifier }
)
-- Get total outstanding amount for a player
local total = MySQL.scalar.await(
"SELECT COALESCE(SUM(amount - amount_paid), 0) FROM legacy_bills WHERE target_id = ? AND status IN ('unpaid', 'partial', 'overdue')",
{ identifier }
)
Bill Lifecycle#
Created (unpaid) --> Partial Payment (partial) --> Fully Paid (paid)
|
|--> Due Date Passes (overdue) --> Late Fee Applied
|
|--> Player Disputes (disputed) --> Admin Resolves
| |--> Cancelled
| |--> Reinstated (unpaid/partial)
|
|--> Admin Cancels (cancelled)
Payment Flow#
- Player opens
/billingand selects a bill - Chooses payment amount (full or partial) and method (cash or bank)
- Server validates funds and deducts from the player's account
- Payment is recorded in the
legacy_bill_paymentstable - Bill status updates to
partialorpaid - Payment amount is deposited into the sending job's bank account via legacy-lib Banking
Late Fee Behavior#
When Config.LateFeeInterval > 0, a background thread checks for overdue bills at the configured interval:
- Finds unpaid/partial bills past their due date where no late fee has been applied
- Looks up the job's
late_fee_pctfrom thelegacy_billing_jobstable - Adds the calculated fee to the bill amount
- Marks the bill as
overduewithlate_fee_applied = 1
Late fees are applied once per bill. The percentage is calculated on the original bill amount.