Browse docs

Sky Billing

Create player and society invoices, including automated invoices without an online issuer.

Sky Billing is the default billing provider in Sky Base. It stores invoices in sky_jobs_base and records society income after a payment has actually reached the society account. External billing providers remain optional through the Sky Base billing setting.

Players view their invoices with /billing. Staff use File Explorer → Billing or their job's billing action in the radial menu. Configure the command, optional key binding and payment rules under Jobs Base → Administration → Billing in /jobconfig.

Payment rules

ModeBehaviour
On-site (onsite)An online issuer presents a payment request. Player recipients must be nearby, in the same routing bucket. Unpaid requests expire after 120 seconds by default.
Pay Later (later)The invoice is payable until and after its stored deadline. The default payment term is seven days.
Immediate (immediate, trusted server exports only)Record the invoice and attempt collection immediately, for example for an automatic speed-camera fine. Manual invoice forms cannot request a forced debit.

Automatic overdue payment is enabled by default. Disable it to leave overdue Pay Later invoices open for manual payment. Insufficient funds leave an invoice open; automatic collection retries later. Offline player invoices wait until the character is online because the current bank adapter cannot safely debit offline accounts. Society accounts can be collected without an online manager.

Changing the payment term affects new invoices only. Expired on-site requests and older invoices without a deadline are not automatically collected. Explicit external providers retain their own payment rules and capabilities.

Immediate invoices collect independently of the automatic overdue-payment setting. Offline recipients or insufficient funds can leave them unpaid for a later attempt. CreateInvoice returning success = true confirms creation; inspect data.status and paymentError to determine whether the immediate payment succeeded.

Employee shares

Under Jobs Base → Administration → Billing → Employee shares by job, approve a percentage for each eligible job. No job receives a share by default. With /jobconfig disabled, the equivalent configuration is:

employeeShares = {
    police = 10,    -- A paid $500 fine gives the officer $50 and the society $450.
    sheriff = 5,    -- A paid $500 fine gives the deputy $25 and the society $475.
    ambulance = 10, -- A paid $200 treatment invoice gives the medic $20 and the society $180.
},

Only society → player invoices in bank/cash currencies with society deposits enabled qualify. The real issuing employee must belong to the issuing job and differ from the recipient. Integrations identify that employee with createdBy = { source = issuer_source }. Society-to-society invoices, private player invoices and automatic invoices with only a system name receive no employee share.

The percentage is stored when the invoice is issued. Only confirmed payments earn a share; partial payments use the cumulative paid amount, rounded down to whole currency units. The society receives the remainder and its income history records that net amount. The employee receives their share in the bank. If they are offline, it is saved and paid after their next login. Changing the configured percentage affects new invoices only.

Create an invoice

These are server exports. Your resource must validate its prices, permissions and recipient identifiers. Do not forward unvalidated client input to CreateInvoice.

This speed-camera example needs no issuer source ID and also accepts an offline recipient:

local result = exports["sky_jobs_base"]:CreateInvoice({
    from = { type = "society", job = "police" },
    to = {
        type = "player",
        identifier = character_identifier,
        name = character_name,
    },
    createdBy = { name = "Speed camera - Elgin Avenue" },
    amount = 450,
    reason = "Exceeding the speed limit",
    paymentMode = "later",
    sourceType = "speed_camera",
    reference = "my_speedcams:" .. tostring(detection_id),
})

if not result.success then
    print(("[my_speedcams] Invoice failed: %s"):format(result.error))
end

Use a unique, stable reference for each event. Repeating the same invoice request returns the existing invoice; changing its parties, amount, reason, currency, account or payment mode returns reference_conflict.

Parties

PartyValue
Online player{ type = "player", source = player_source } — the server resolves the character and name.
Offline player{ type = "player", identifier = character_identifier, name = character_name }
Society or business{ type = "society", job = "police" } — use the registered job key.

Supported combinations include society → player, society → society and player → society. from receives the payment and to owes it. For example, DOJ invoices a business by setting from.job = "doj" and to = { type = "society", job = "mechanic" }.

A private player beneficiary must be online when payment settles. Otherwise the invoice stays open with beneficiary_offline.

Optional fields

  • currency: invoice currency; defaults to the configured currency (bank by default).
  • depositToSociety: whether a successful payment credits the issuing society; enabled by default.
  • paymentMode: later by default; onsite requires createdBy = { source = issuer_source }. Trusted automatic integrations may use immediate.
  • paymentTermDays: a trusted override greater than zero and no more than 365 days.
  • sourceType: your integration name, such as speed_camera.

Read, pay and cancel

ExportResult and access
CreateInvoice(request){ success = true, data = invoice } or { success = false, error = code }.
GetInvoice(id)The same response shape; trusted server read.
PayInvoice(source, id, options?)Requires the matching player or an authorized society manager. Trusted options support partial amount and bank/cash currency for monetary invoices.
CancelInvoice(id)Cancels an unpaid open invoice. Your calling resource must authorize cancellation.
GetCitizenInvoices(identifier, page?, limit?){ success = true, data = { invoices, page, hasMore } }; trusted server read.
GetBillingAccess(source)Returns canViewJob, canIssue, canCancel and canPayJob.

Invoice data includes number, from, to, createdBy, amount, amountPaid, balanceDue, status, paymentMode and dueAt (Unix milliseconds). employeeSharePercent, employeeShareEarned and employeeSharePaid report the saved allocation and confirmed employee payout.

Listen to the server event sky_jobs_base:billing:changed for an invoice's updated public data. Only a confirmed payment is income; issuing an invoice is not a deposit. An uncertain account-provider result keeps the invoice in processing for reconciliation. Do not manually reset it and retry, since money may already have moved.

The client export exports["sky_jobs_base"]:OpenBilling(invoiceId) opens personal billing. The invoice ID is optional, and reads always check the current recipient's access.

Existing job billing

Police, Ambulance and Fire reach the selected provider through their framework billing mode. Existing saved mode overrides remain in effect. Mechanic service orders keep their own invoice and fulfilment flow; do not create a second collectible invoice for the same order.

Existing integrations may continue using Sky.Functions.SendBill. With the Sky provider, its optional eighth argument accepts { paymentMode = "onsite" } or { paymentMode = "later" }. External providers are not given capabilities they do not support, and their existing invoices are not automatically imported into Sky Billing.