Store

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

ParameterTypeRequiredDescription
srcnumberYesServer ID of the sending player. Use 0 for system-generated bills
targetIdstringYesTarget player's framework identifier
amountnumberYesBill amount in dollars (must be greater than 0)
descriptionstringYesBill description text (max 500 characters)
jobNamestringYesJob name sending the bill. Must exist in the legacy_billing_jobs table
dueDatestringNoDue date in SQL timestamp format (e.g. '2025-12-31 23:59:59'). nil for no due date
metadatatableNoCustom 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)
FieldTypeDescription
data.billIdnumberThe new bill ID
data.senderIdstringSender's framework identifier
data.targetIdstringTarget's framework identifier
data.amountnumberBill amount
data.jobNamestringJob 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)
FieldTypeDescription
data.billIdnumberThe bill ID
data.payerIdstringIdentifier of the player who paid
data.amountnumberAmount paid in this transaction
data.methodstringPayment 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)
FieldTypeDescription
data.billIdnumberThe bill ID
data.cancelledBystringAdmin's framework identifier

Adding Billing Jobs#

Jobs must be registered before they can send bills. There are three methods:

  1. Admin Panel -- Open /billing as an admin and add jobs in the Admin tab.
  2. Database Insert -- Insert directly into the legacy_billing_jobs table:
INSERT INTO legacy_billing_jobs (job_name, job_label, can_bill, late_fee_pct)
VALUES ('mechanic', 'Mechanic', 1, 0.00);
  1. Schema Defaults -- The install schema seeds police (5% late fee), ambulance, and mechanic by 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#

  1. Player opens /billing and selects a bill
  2. Chooses payment amount (full or partial) and method (cash or bank)
  3. Server validates funds and deducts from the player's account
  4. Payment is recorded in the legacy_bill_payments table
  5. Bill status updates to partial or paid
  6. 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:

  1. Finds unpaid/partial bills past their due date where no late fee has been applied
  2. Looks up the job's late_fee_pct from the legacy_billing_jobs table
  3. Adds the calculated fee to the bill amount
  4. Marks the bill as overdue with late_fee_applied = 1

Late fees are applied once per bill. The percentage is calculated on the original bill amount.