# esx_banking (/docs/addons/esx_banking)

esx_banking is the official bank and ATM resource for ESX Legacy. Players walk up to a bank counter or an ATM, press `E` and use a web UI to deposit, withdraw, transfer money to other players and set an ATM PIN. Every transaction is validated on the server and written to a history table.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_banking` (version `1.0.1`) |
| Side | Client, server and NUI |
| Dependencies | `esx_lib`, `es_extended`, `oxmysql` |
| SQL file | `banking.sql` (table `banking`, adds `users.pincode`) |
| Interaction | `E` near a bank or ATM, shown with [esx_textui](/docs/core/esx_textui) |
| Server export | `logTransaction` |

## Installation [#installation]

<Steps>
<Step>
### Import the SQL [#import-the-sql]

Run `banking.sql`. It creates the `banking` history table and adds a nullable `pincode` column to `users`. If you skip it, the resource runs the same migration on start, but it requires both tables to use InnoDB. On failure the console prints `Schema migration failed; banking disabled`.
</Step>
<Step>
### Ensure the resource [#ensure-the-resource]

```bash title="server.cfg"
ensure es_extended
ensure esx_banking
```
</Step>
</Steps>

## Configuration [#configuration]

| Key | Default | Description |
| --- | --- | --- |
| `Debug` | `false` | Prints debug messages on the server |
| `DrawMarker` | `10` | Distance at which bank markers are drawn (only with `ShowMarker`) |
| `Locale` | `GetConvar('esx:locale', 'en')` | Language |
| `EnablePeds` | `true` | Spawns the networked bank clerk peds from `Peds` |
| `ShowMarker` | `false` | Draws a marker at each bank position |
| `InteractionDistance` | `1.5` | Max distance to a bank `Position`, client and server |
| `AtmInteractionDistance` | `2.0` | Max distance to an ATM, checked on the server |
| `ClientAtmFallback` | `false` | Trusts ATM coords sent by the client, but only if they match an `AtmLocations` entry |
| `ClientAtmFallbackDistance` | `1.75` | Distance used for that fallback check |
| `RequireAtmPin` | `true` | ATM sessions need the 4-digit PIN before money can move |
| `SessionDuration` | `90` | Seconds a bank/ATM session stays valid, refreshed by each action |
| `TransactionCooldown` | `1250` | Rate limit in ms between transactions |
| `PinAttemptCooldown` | `1500` | Rate limit in ms between PIN attempts |
| `MaxTransactionAmount` | `100000000` | Highest amount per transaction |
| `HistoryLimit` | `20` | Rows shown in the history (max 100) |
| `HistoryDays` | `30` | Only rows from the last N days are shown |
| `LogRetentionDays` | `60` | Rows older than this are deleted on start. `0` keeps everything |
| `LogValueMaxLength` | `80` | Max length of label and type in a log row |
| `AtmModels` | `prop_fleeca_atm`, `prop_atm_01`, `prop_atm_02`, `prop_atm_03` | ATM prop hashes |
| `AtmLocations` | 70 `vector3` positions | Known ATM positions used for server-side distance checks |
| `Banks` | 8 entries | Bank counters, see below |
| `Peds` | 8 entries | Clerk peds, see below |

Each `Banks` entry:

```lua
{
    Position = vector4(149.91, -1040.74, 29.374, 160),
    Blip = { Enabled = true, Color = 69, Label = 'Bank', Sprite = 108, Scale = 0.7 }
}
```

Each `Peds` entry has `Position` (`vector4`), `Model` (hash, default `` `U_M_M_BankMan` ``) and `Scenario` (default `'WORLD_HUMAN_CLIPBOARD'`).

<Callout type="info" title="Custom ATMs">
The server only opens an ATM session when the player stands near an `AtmLocations` entry or a networked ATM object. If you add ATMs with a map, add their positions to `AtmLocations`, otherwise the UI will refuse to open.
</Callout>

## Security model [#security-model]

The client only sends requests. The server:

- starts a session in `esx_banking:getPlayerData` only when the player is near a bank or ATM,
- re-checks the distance on every action and expires sessions after `SessionDuration`,
- allows transfers and PIN changes only in bank sessions,
- requires the PIN for ATM sessions when `RequireAtmPin` is on,
- rate limits opening (750 ms), transactions and PIN attempts, and locks one transaction per player at a time.

## Server export [#server-export]

### logTransaction [#logtransaction]

Adds a row to a player's bank history. It does not move money. Use it when another resource charges the bank account and you want it to show up in the banking UI.

```lua
exports.esx_banking:logTransaction(playerId, label, key, amount)
```

| Name | Type | Description |
| --- | --- | --- |
| `playerId` | `number` | Server id of an online player |
| `label` | `string` | Text shown in the history, e.g. `CAR PURCHASE` |
| `key` | `string` | Transaction type, e.g. `WITHDRAW` or `DEPOSIT`. Required, stored upper-case |
| `amount` | `number` | Positive amount up to `MaxTransactionAmount` |

```lua title="server/main.lua"
local xPlayer = ESX.GetPlayerFromId(source)

if xPlayer.getAccount('bank').money >= price then
    xPlayer.removeAccountMoney('bank', price, 'Vehicle purchase')
    exports.esx_banking:logTransaction(source, 'CAR PURCHASE', 'WITHDRAW', price)
end
```

## Events and callbacks [#events-and-callbacks]

| Name | Type | Description |
| --- | --- | --- |
| `esx_banking:doingType` | Net event (client to server) | Deposit, withdraw, transfer or PIN change. Payload `{ action = 'deposit', amount = 500 }`, `{ action = 'transfer', target = id, amount = n }`, `{ action = 'pincode', pin = '1234' }` |
| `esx_banking:getPlayerData` | Callback | Opens a session. Argument `{ accessType = 'bank' or 'atm', atm = ... }` |
| `esx_banking:checkPincode` | Callback | Unlocks an ATM session with a 4-digit PIN |
| `esx_banking:closebanking` | Client event | Closes the banking UI. Useful when you cuff or kill a player |
| `esx_banking:updateMoneyInUI` | Client event | Server pushes fresh balances to the UI |
| `esx_banking:pedHandler` | Client event | Server sends the network ids of the clerk peds |

<Callout type="warn" title="Client-triggerable">
`esx_banking:doingType` and the callbacks can be called by any client. They are safe only because of the session and distance checks above. Do not loosen `ClientAtmFallback` or the distances without a reason.
</Callout>

Close the UI from another resource:

```lua title="server/main.lua"
TriggerClientEvent('esx_banking:closebanking', targetId)
```

## Common issues [#common-issues]

- **`E` prompt shows but the UI does not open**: the server distance check failed. Usually a custom ATM not listed in `AtmLocations`.
- **"Payments disabled" or "banking disabled" in the console**: `users` or `banking` is not InnoDB, or the `identifier` column is not `VARCHAR`.
- **Old scripts call `TriggerServerEvent('esx_banking:logTransaction', ...)`**: that event was removed. Move the call to the server export.

## Related [#related]

- [Money and accounts guide](/docs/guides/money-and-accounts)
- [xPlayer](/docs/es_extended/server/xplayer)
- [esx_billing](/docs/addons/esx_billing)
- [esx_textui](/docs/core/esx_textui)
