# esx_billing (/docs/addons/esx_billing)

esx_billing adds invoices to ESX Legacy. Job resources such as [esx_policejob](/docs/addons/esx_policejob), [esx_taxijob](/docs/addons/esx_taxijob) and [esx_mechanicjob](/docs/addons/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 [#overview]

| | |
| --- | --- |
| Resource | `esx_billing` (version `1.0`) |
| Side | Client and server |
| Dependencies | `esx_lib`, `es_extended`, `oxmysql`; society bills need [esx_addonaccount](/docs/addons/esx_addonaccount) |
| SQL file | `esx_billing.sql` (table `billing`) |
| Command | `showbills` (key mapping `F7`), console command `billingpayments` |
| Server exports | `BillPlayer`, `BillPlayerByIdentifier` |

## Installation [#installation]

<Steps>
<Step>
### Import the SQL [#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`.
</Step>
<Step>
### Ensure the resource [#ensure-the-resource]

```bash title="server.cfg"
ensure es_extended
ensure esx_addonaccount
ensure esx_billing
```
</Step>
<Step>
### Create society accounts [#create-society-accounts]

Every job that bills needs a shared `society_<job>` row in `addon_account`. See [esx_addonaccount](/docs/addons/esx_addonaccount).
</Step>
</Steps>

## Configuration [#configuration]

All keys live in `config.lua`.

| Key | Default | Description |
| --- | --- | --- |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Language for notifications |
| `Config.BillingDistance` | `10.0` | Max distance between sender and target, checked on the server |
| `Config.BillingCooldown` | `3000` | Cooldown in ms between two bills from the same player |
| `Config.MinimumBillingGrade` | `1` | Minimum job grade required to send bills |
| `Config.BillingMinimumGrades` | `{}` | Per-job override, e.g. `{ police = 0, taxi = 2 }` |
| `Config.MaxBillAmount` | `100000` | Highest amount a single bill may have |
| `Config.MaxDailyBillAmount` | `250000` | Total a single sender may bill per day. `0` disables the limit |
| `Config.MaxBillLabelLength` | `80` | Labels are trimmed to this length. Empty labels become `Invoice` |
| `Config.HighBillConfirmationAmount` | `50000` | Bills at or above this amount must be accepted by the target |
| `Config.HighBillConfirmationTimeout` | `30000` | Time in ms the target has to accept a high bill |
| `Config.BillsPageSize` | `50` | Bills per page in the menu (clamped to 1-100) |
| `Config.BillsReadCooldown` | `1000` | Rate limit in ms for loading the bill list |
| `Config.BillPaymentCooldown` | `500` | Rate 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 [#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 [#server-events]

### esx_billing:sendBill [#esx_billingsendbill]

```lua
TriggerServerEvent('esx_billing:sendBill', targetId, sharedAccountName, label, amount)
```

| Name | Type | Description |
| --- | --- | --- |
| `targetId` | `number` | Server id of the player who receives the bill |
| `sharedAccountName` | `string` | Society account of the sender's job, e.g. `society_taxi` |
| `label` | `string` | Bill text shown to the target |
| `amount` | `number` | Amount, floored to a whole number, 1 to `Config.MaxBillAmount` |

<Callout type="warn" title="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.
</Callout>

```lua title="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 [#esx_billingsendbilltoidentifier]

Local server event (not networked). It only runs when triggered by another resource and bills an identifier with sender `server`.

```lua
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 [#esx_billingpaidbill]

Fired after a bill was paid.

| Side | Arguments |
| --- | --- |
| Server (local event) | `source, billId` |
| Client (local event) | `billId` |

```lua title="server/main.lua"
AddEventHandler('esx_billing:paidBill', function(playerId, billId)
    print(('Player %s paid bill #%s'):format(playerId, billId))
end)
```

## Server exports [#server-exports]

### BillPlayer [#billplayer]

```lua
exports.esx_billing:BillPlayer(targetId, senderIdentifier, sharedAccountName, label, amount)
```

| Name | Type | Description |
| --- | --- | --- |
| `targetId` | `number` | Server id of an online player |
| `senderIdentifier` | `string` | Identifier stored as sender |
| `sharedAccountName` | `string?` | `society_<job>` for a society bill. Anything else creates a player bill paid to `senderIdentifier` |
| `label` | `string` | Bill text |
| `amount` | `number` | Amount, 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.

```lua title="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 [#billplayerbyidentifier]

```lua
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 [#callbacks]

Registered with `xLib.callback.registerCompat`, so they work with `ESX.TriggerServerCallback` and `xLib.callback`.

| Name | Arguments | Returns | Notes |
| --- | --- | --- | --- |
| `esx_billing:getBills` | `afterId?` | `bills, hasNext` | Own bills (`id`, `label`, `amount`), paged |
| `esx_billing:getTargetBills` | `target, afterId?` | `bills, hasNext` | Only for job `police`, target must be nearby |
| `esx_billing:payBill` | `billId` | `boolean` | Pays one of the caller's own bills |
| `esx_billing:respondHighBill` | `token, accepted` | `boolean` | Answer to a high-bill confirmation |

```lua title="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 [#console-command]

`billingpayments` works only from the server console and helps with stuck payments:

```bash
billingpayments list
billingpayments show <operation_id>
billingpayments retry <operation_id>
```

`list` prints up to 20 receipts still in state `claimed`.

## Common issues [#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.

## Related [#related]

- [esx_addonaccount](/docs/addons/esx_addonaccount)
- [esx_society](/docs/addons/esx_society)
- [esx_policejob](/docs/addons/esx_policejob)
- [Server callbacks guide](/docs/guides/callbacks)
- [Create a job](/docs/guides/create-a-job)
