# esx_barbershop (/docs/addons/esx_barbershop)

esx_barbershop places barber shops on the map where players pay to change their hairstyle, beard, eyebrows, makeup, lipstick and ear accessories. It reuses the [esx_skin](/docs/core/esx_skin) menu in restricted mode and saves the result server-side, with an automatic refund if saving fails.

## Overview [#overview]

| | |
|---|---|
| Resource | `esx_barbershop` |
| Side | Client + server |
| Dependencies | `esx_lib`, `es_extended`, `esx_skin` |
| Database | None (uses the skin column via `esx_skin`) |
| Payment | Cash (`xPlayer.removeMoney`) |

In-game flow:

1. The player walks into one of the 7 default shop markers and presses `E`.
2. The esx_skin menu opens, limited to the fields listed below.
3. After confirming with "Yes", the server checks distance and money, charges `Config.Price` and saves the skin.
4. "No", leaving the marker or a failed payment reloads the previous look.

Editable fields: `hair_1`, `hair_2`, `hair_color_1`, `hair_color_2`, `beard_1` to `beard_4`, `eyebrows_1` to `eyebrows_4`, `makeup_1` to `makeup_4`, `lipstick_1` to `lipstick_4`, `ears_1`, `ears_2`.

## Installation [#installation]

There is no SQL file. Make sure `esx_skin` (which exports `SaveSkin`) starts first:

```bash title="server.cfg"
ensure es_extended
ensure esx_lib
ensure skinchanger
ensure esx_skin
ensure esx_barbershop
```

## Configuration [#configuration]

All keys live in `config.lua`.

| Key | Default | Description |
|---|---|---|
| `Config.Price` | `100` | Cash price per visit. |
| `Config.ShopDistance` | `3.0` | Server-side maximum distance (meters) between the player and a shop for paying. |
| `Config.DrawDistance` | `10.0` | Distance at which markers are drawn. |
| `Config.MarkerSize` | `vector3(1.5, 1.5, 1.0)` | Marker size. The interaction radius is fixed to `1.5` in the client. |
| `Config.MarkerColor` | `{r = 102, g = 102, b = 204}` | Marker color (alpha fixed to 100). |
| `Config.MarkerType` | `1` | Marker type. |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale for help text and notifications. |
| `Config.Shops` | 7 `vector3` positions | Shop locations. Each gets a marker and a blip (sprite `71`, color `51`). |

Default shop positions:

```lua title="config.lua"
Config.Shops = {
	vector3(-814.3, -183.8, 36.6),
	vector3(136.8, -1708.4, 28.3),
	vector3(-1282.6, -1116.8, 6.0),
	vector3(1931.5, 3729.7, 31.8),
	vector3(1212.8, -472.9, 65.2),
	vector3(-32.9, -152.3, 56.1),
	vector3(-278.1, 6228.5, 30.7)
}
```

## Callbacks [#callbacks]

Both callbacks are registered with `xLib.callback.registerCompat`, so they work with `xLib.callback` and `ESX.TriggerServerCallback`.

### esx_barbershop:pay [#esx_barbershoppay]

```lua
xLib.callback('esx_barbershop:pay', false, function(paid, reason) end, skin)
```

| Name | Type | Description |
|---|---|---|
| `skin` | `table` | Full skin table from `skinchanger:getSkin`. |

Returns `paid` (`boolean`) and, on failure, a `reason` string. Reasons set by this resource: `player_unavailable`, `rate_limited`, `not_near_shop`, `invalid_appearance:payload:expected_table`, `payment_pending`, `insufficient_funds`, `payment_failed`, `appearance_save_failed`, `appearance_save_error`, `appearance_save_rejected`, `resource_stopped`. esx_skin can pass its own reasons through as well (for example `appearance_scope_mismatch:sex`).

The server:

- rate limits each player (2 requests, refilled 1 per 2 seconds),
- requires the player within `Config.ShopDistance` of a shop,
- locks the account with `xPlayer.beginAccountOperation()` and allows only one pending payment per identifier,
- calls `exports.esx_skin:SaveSkin(source, skin, finish, 'barber')` and refunds the price with reason `Haircut Refund` if the save does not succeed.

### esx_barbershop:checkMoney [#esx_barbershopcheckmoney]

```lua
xLib.callback('esx_barbershop:checkMoney', false, function(canPay) end)
```

Returns `true` when the player is near a shop and has at least `Config.Price` cash.

## Server events [#server-events]

### esx_barbershop:pay [#esx_barbershoppay-1]

```lua
TriggerServerEvent('esx_barbershop:pay', skin)
```

Legacy net event that runs the same logic as the callback without returning a result. The bundled client uses the callback.

<Callout type="info" title="Client-triggerable">
Any client can send this event with an arbitrary skin table. The server still enforces the distance check, rate limit and full price, and esx_skin's `SaveSkin` with scope `'barber'` only copies the fields listed in its `Config.SkinEditFields.barber` onto the stored skin and rejects a changed `sex`. A cheater cannot get a free haircut or swap clothes this way, as long as esx_skin is current.
</Callout>

## Client events [#client-events]

Local events fired by the marker zones:

| Event | Argument | Fired when |
|---|---|---|
| `esx_barbershop:hasEnteredMarker` | `zone` (shop index) | Player enters a shop marker. Shows the help text with `ESX.TextUI`. |
| `esx_barbershop:hasExitedMarker` | `zone` | Player leaves. Closes the menu and restores the old skin if nothing was paid. |

## Common issues [#common-issues]

- **Purchase failed notification, money returned.** esx_skin rejected the save. Check the server console for `[esx_barbershop] Appearance save failed` and the reason; usually esx_skin is outdated and has no `SaveSkin` export.
- **"Not enough money" although the bank is full.** Only cash (`money` account) is used.
- **Look resets after relog.** The save failed or another skin script overwrote the skin. Make sure only one appearance resource writes to `users.skin`.

## Related [#related]

- [esx_skin](/docs/core/esx_skin)
- [skinchanger](/docs/core/skinchanger)
- [esx_clotheshop](/docs/addons/esx_clotheshop)
- [esx_accessories](/docs/addons/esx_accessories)
