# Documentation Home

This repository contains installation, configuration, operations, and developer references for Source Development FiveM resources.

## Resources

* [src-phone](/src-phone/overview): modular phone resource with framework bridges, persistent applications, calls, messaging, media, connectivity, and a custom application SDK.
* [src-bank](/src-bank/overview): banking, credit, loans, ATMs, and society account integration.
* [src-billing](/src-billing/overview): invoice creation, payment workflows, administration, and framework integration.

## How to use these docs

For a new installation, follow the resource's installation page in order. Do not begin with configuration or API integration before the database and dependency checks pass.

For an existing installation:

1. Read the configuration reference before changing framework or database mappings.
2. Use documented exports as the stable integration surface.
3. Treat undocumented network events and NUI callbacks as internal implementation details.
4. Back up the database before importing a newer schema.

Framework compatibility is summarized in [Framework Compatibility](/general/frameworks).


# Framework Compatibility

Source Development resources isolate framework operations behind their `bridge/` directory. Support still varies by resource and feature, so configure the framework explicitly and verify feature-specific integrations.

## Framework matrix

| Framework  | src-phone             | Notes                                                                                                                     |
| ---------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| QBox       | Supported             | Set `Config.Core = 'qbox'`. Uses the QBox/QB bridge mappings where applicable.                                            |
| QBCore     | Supported             | Set `Config.Core = 'qb'`.                                                                                                 |
| ESX        | Supported             | Set `Config.Core = 'esx'` and verify vehicle and service SQL mappings.                                                    |
| vRP        | Supported             | Set `Config.Core = 'vrp'` and verify the server's identity and vehicle schema.                                            |
| Standalone | Supported with limits | Set `Config.Core = 'standalone'`. Framework-owned money, job, inventory, and vehicle features require custom integration. |

See the banking and billing sections for their resource-specific framework support.

## Bridge responsibilities

A bridge translates resource operations into the APIs and schema of the running framework. Depending on the resource, this includes:

* Player identifiers and character data.
* Jobs and service membership.
* Cash and bank accounts.
* Inventory items.
* Owned vehicles, garage state, keys, and fuel.
* Notifications and callbacks.
* Voice channels.

Selecting a framework name does not automatically make an unsupported third-party garage, inventory, fuel, or voice resource compatible. Review the relevant bridge configuration and implementation before deployment.

## Custom framework work

When adding a custom framework:

1. Keep the public resource API unchanged.
2. Implement identifier, player lookup, money, job, inventory, and callback operations in the bridge.
3. Map database columns in configuration rather than hard-coding them in application files.
4. Validate every client-supplied identifier and amount on the server.
5. Test player load, unload, reconnect, and resource restart behavior.


# Overview

`src-phone` is a modular FiveM phone resource. The phone shell owns the physical frame, lock screen, status bar, Dynamic Island, common navigation, application switcher, notifications, control center, and home gesture. Built-in and third-party applications run inside that shell.

Current resource version: `1.0.3`

## Supported frameworks

* QBCore
* QBox
* ESX
* vRP
* Standalone

The selected framework is defined once in `shared/config.lua` and passed to the bridge layer. Garage, vehicle key, fuel, voice, character, job, and money operations are isolated behind bridge files so framework-specific changes do not need to be made in the web application.

## Runtime architecture

| Layer      | Responsibility                                                                                                            |
| ---------- | ------------------------------------------------------------------------------------------------------------------------- |
| `client/`  | Phone visibility, NUI callbacks, camera, calls, application bridges, router placement, and external application registry. |
| `server/`  | Persistence, validation, messaging, mail, social applications, cloud accounts, wallet, cellular data, and router state.   |
| `bridge/`  | Framework, garage, keys, fuel, voice, contacts, mail, messages, and service-directory integrations.                       |
| `shared/`  | Public configuration and shared Lua modules.                                                                              |
| `web/`     | React phone shell and built-in applications. Production uses `web/dist`.                                                  |
| `locales/` | Lua and JSON locale catalogs.                                                                                             |

## Included systems

The default package includes:

* Phone calls, optional video calls, call history, speaker routing, and voice integration.
* Messages, contacts, reactions, replies, read receipts, pins, media, and transfers.
* Mail, camera, gallery, notes, clock, voice memos, maps, music, news, and Yellow Pages.
* Z, MyFans, and Vibe social applications.
* Bank view, services directory, garage controls, vehicle tracking, and valet.
* MyCloud account-scoped settings and data.
* Crypto wallet balances, swaps, transfers, and transaction history.
* Placeable Wi-Fi routers, quota accounting, and personal cellular data plans.
* Light and dark themes, localized UI, configurable home layout, widgets, lock screen, and notifications.
* Versioned third-party application registration through a separate FiveM resource.

## Stable integration boundary

Third-party resources should use the exports documented in [API and Exports](/src-phone/api-exports). The `src-phone:server:*` and `src-phone:client:*` events used by built-in applications are internal transport. Their payloads may change and they should not be called directly by integrations.

Custom phone applications must use the external application contract described in [Custom Applications](/src-phone/custom-apps). This keeps application CSS, navigation, and overlays from modifying the phone header or Dynamic Island.

## Recommended reading order

1. [Installation](/src-phone/installation)
2. [Configuration](/src-phone/configuration)
3. [Database](/src-phone/database)
4. [API and Exports](/src-phone/api-exports)
5. [Custom Applications](/src-phone/custom-apps)
6. [Troubleshooting](/src-phone/troubleshooting)


# Installation

Complete the steps in this order. Starting the resource before importing the schema can leave individual applications partially functional and makes the original error harder to identify.

## 1. Requirements

Required:

* A supported FiveM server artifact.
* `oxmysql`.
* One supported framework, unless `Config.Core` is set to `standalone`.
* A MySQL or MariaDB database supported by your `oxmysql` version.

Feature-dependent:

* `pma-voice` for the default call bridge.
* A supported garage, vehicle key, and fuel resource if those phone features are enabled.
* A Fivemanage API token for camera media uploads.
* A Giphy API key for GIF search.

## 2. Place the resource

Keep the resource folder name exactly `src-phone` unless every external dependency and export call is updated to use a different name.

```
resources/
  [phone]/
    src-phone/
      fxmanifest.lua
      install.sql
      client/
      server/
      bridge/
      shared/
      locales/
      web/dist/
```

The distributed resource uses `web/dist/index.html`. A source checkout can rebuild the interface from `web/`, but Node.js is not required on a production server when a valid `web/dist` is already included.

## 3. Import the database

Back up the database, then run the complete `install.sql` file once. The schema uses `CREATE TABLE IF NOT EXISTS` and additive migrations for supported upgrades.

Do not import only the first few tables. Applications such as MyCloud, wallet, MyFans, Vibe, Wi-Fi, and cellular data depend on tables defined later in the file.

See [Database](/src-phone/database) for the table groups and verification queries.

## 4. Select the framework

Edit `shared/config.lua`:

```lua
Config.Core = 'qbox'
```

Accepted values:

```
qb
qbox
esx
vrp
standalone
```

`bridge/config.lua` reads this value through `BridgeConfig.Framework`. Do not configure the two files with different frameworks.

## 5. Configure integrations

Review `bridge/config.lua` before the first start:

* `BridgeConfig.Garage.GarageSystem`
* `BridgeConfig.Garage.KeySystem`
* `BridgeConfig.Garage.FuelSystem`
* `BridgeConfig.Garage.SQL`
* `BridgeConfig.Call.VoiceSystem`
* `BridgeConfig.Services`

If your vehicle table does not use the default QB, ESX, or vRP columns, update the SQL mapping before testing Garage or Valet.

## 6. Configure server-only keys

Set secrets in `shared/server_config.lua`:

```lua
Config.FivemanageApiKey = 'YOUR_FIVEMANAGE_TOKEN'
Config.GiphyApiKey = 'YOUR_GIPHY_API_KEY'
```

This file is loaded only by the server scripts. Do not move these keys into `shared/config.lua`, client Lua, the React application, or a public documentation repository. Rotate a token immediately if it has been committed or printed publicly.

## 7. Start order

Framework and database resources must start before the phone. Voice should also start first when the default call bridge is used.

QBox example:

```cfg
ensure oxmysql
ensure qbx_core
ensure pma-voice
ensure src-phone
```

QBCore example:

```cfg
ensure oxmysql
ensure qb-core
ensure pma-voice
ensure src-phone
```

ESX example:

```cfg
ensure oxmysql
ensure es_extended
ensure pma-voice
ensure src-phone
```

External applications start after `src-phone`:

```cfg
ensure src-phone
ensure src-phone-app
```

An external application should also declare `dependency 'src-phone'` in its `fxmanifest.lua`.

## 8. First-start verification

Verify each item before adding custom integrations:

1. `ensure src-phone` produces no missing-table or manifest errors.
2. `/tel` opens and closes the phone.
3. The configured key mapping opens the phone.
4. A character receives a persistent phone number.
5. Contacts and messages survive reconnecting.
6. A second online player can receive a call and message.
7. Camera upload succeeds after a Fivemanage key is configured.
8. Garage records match the configured vehicle table mapping.
9. Wi-Fi or cellular state loads without database errors.
10. Any custom application appears only inside the phone.

## Rebuilding the phone UI

Only needed for a source checkout or web modification:

```powershell
cd src-phone/web
npm install
npm run typecheck
npm run build
```

Restart `src-phone` after replacing `web/dist`.


# Configuration

Public runtime configuration is split between three files:

| File                       | Scope                                                                                                                     |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `shared/config.lua`        | Framework selection, phone behavior, applications, database mappings, connectivity, camera, services, and feature limits. |
| `shared/server_config.lua` | Server-only API credentials. Never expose this file to the client.                                                        |
| `bridge/config.lua`        | Framework and external-resource mappings.                                                                                 |

Restart `src-phone` after changing Lua configuration.

## Core phone settings

| Setting                    | Default         | Purpose                                                                 |
| -------------------------- | --------------- | ----------------------------------------------------------------------- |
| `Config.Core`              | `qb`            | Framework bridge: `qb`, `qbox`, `esx`, `vrp`, or `standalone`.          |
| `Config.PhoneOpenKey`      | `F1`            | Default FiveM key mapping. Players may have an existing local override. |
| `Config.PhonePrefix`       | `555-`          | Prefix used when assigning phone numbers.                               |
| `Config.PhoneNumberLength` | `4`             | Random digits appended after the prefix.                                |
| `Config.MailDomain`        | `@sourcedevpro` | Domain appended to generated mail addresses.                            |
| `Config.DefaultWallpaper`  | URL             | Default wallpaper for a new profile.                                    |
| `Config.DefaultBrightness` | `80`            | Initial brightness percentage.                                          |
| `Config.WeatherUnit`       | `C`             | `C` or `F`.                                                             |
| `Config.WeatherLocation`   | `Los Santos`    | Home weather widget label.                                              |

Changing phone-number rules affects newly generated numbers. Existing values in `src_phone_data` are not rewritten automatically.

## Application availability

`Config.Apps` is the server-wide allowlist for built-in applications:

```lua
Config.Apps = {
    phone = true,
    messages = true,
    bank = true,
    garage = true,
    appstore = true,
}
```

Setting an application to `false` removes it from the home screen and App Store. This does not delete its database data. Re-enabling the application restores access to existing records.

Do not add a custom application ID to this table. External applications register through the `registerApp` export.

## Database mappings

`Config.Database` maps phone queries to your schema. The bundled `install.sql` already matches the defaults.

Only change these values when integrating an existing custom schema:

* `Config.Database.PhoneData`
* `Config.Database.Contacts`
* `Config.Database.Mail`
* `Config.Database.Messages`

Column mappings must be changed consistently. For example, changing only the contacts table name while leaving incompatible column names will allow the query to run but return invalid records.

## Garage and valet

`Config.Garage.Valet` controls whether the phone can deliver a stored vehicle:

| Setting             | Purpose                                   |
| ------------------- | ----------------------------------------- |
| `Enabled`           | Enables the valet action.                 |
| `Price`             | Amount charged before spawn.              |
| `Account`           | Framework money account, normally `bank`. |
| `SpawnRadius`       | Search radius for a valid spawn position. |
| `DriveSpeed`        | Valet driving speed.                      |
| `DriverPedModel`    | Ped model used by the valet.              |
| `Timeout`           | Delivery timeout in milliseconds.         |
| `BlacklistedModels` | Vehicles that cannot be delivered.        |
| `BlacklistedZones`  | Zone flags used to prevent delivery.      |

The SQL table and state mappings are configured separately under `BridgeConfig.Garage.SQL`.

## Phone prop

`Config.PhoneProp` controls the model attached to the player:

```lua
Config.PhoneProp = {
    Model = 'src_phone',
    Bone = 28422,
    Offset = vector3(0.0, 0.0, 0.0),
    Rotation = vector3(0.0, 0.0, 0.0),
    CameraOffset = vector3(0.0, -0.005, 0.0),
    CameraRotation = vector3(0.0, 0.0, 180.0),
}
```

The model must be streamed and available before testing offsets. Adjust one axis at a time and restart the resource after changing the model.

## Wi-Fi routers

`Config.Router` controls placeable routers and metered Wi-Fi:

* `Enabled`: enables placement and router UI.
* `InternetUsageEnabled`: enables quota enforcement.
* `Command`: placement command, default `setrouter`.
* `RequiredItem`, `ItemAmount`, `ConsumeItem`: inventory cost.
* `PropModel`, `FallbackPropModels`: placement object selection.
* `Range`: usable Wi-Fi radius.
* Network-name, password, and admin-password length rules.
* Login attempt and lockout limits.
* Initial data quota and purchasable packages.
* Per-action and active-stream usage costs.

`UsageCostsMb.music` and active-stream accounting can consume quota quickly. Test the configured values with the server's expected session length before release.

## Cellular data

`Config.Cellular` provides a player-owned fallback connection when Wi-Fi is unavailable:

* Initial plan name, quota, and validity.
* Money account used for purchases.
* Available packages.
* Per-action and streaming usage costs.
* Usage tick, flush, and rate-limit values.

When both systems are enabled, the web layer attempts authorized Wi-Fi first and cellular data second.

## Feature-specific sections

| Section                | Controls                                                           |
| ---------------------- | ------------------------------------------------------------------ |
| `Config.CryptoWallet`  | Tokens, fallback prices, bank account, refresh interval, and fees. |
| `Config.Camera.Selfie` | Selfie camera offset, rotation, and FOV limits.                    |
| `Config.News`          | Jobs allowed to publish and valid categories.                      |
| `Config.YellowPages`   | Listing categories.                                                |
| `Config.Services`      | Service IDs, jobs, labels, icons, and colors.                      |
| `Config.VoiceMemos`    | Duration, item count, and payload-size limits.                     |

## Bridge configuration

### Garage

Select the external garage, key, and fuel resources and verify the SQL mapping. `FuelSystem = 'auto'` uses bridge detection; use an explicit integration when detection is ambiguous.

### Calls

The default is:

```lua
BridgeConfig.Call.VoiceSystem = 'pma-voice'
```

Other listed bridge targets require a compatible implementation in the call bridge. A name in the config is not sufficient if the target resource API differs from the bridge code.

### Services

`BridgeConfig.Services` maps character and job columns for ESX, QB/QBox, and vRP service-directory lookups.

## Configuration change checklist

1. Confirm the value type and accepted range.
2. Check whether the setting belongs in shared config, server-only config, or bridge config.
3. Back up the changed file.
4. Restart `src-phone`.
5. Check server and client consoles.
6. Test with a new character and an existing character.
7. Test the affected feature with two players when it involves calls, messages, or sharing.


# Database

The complete schema is distributed as `install.sql`. Import that file instead of creating tables manually from this page.

## Upgrade procedure

1. Stop `src-phone`.
2. Back up all `src_phone_%` tables.
3. Import the new `install.sql`.
4. Review SQL errors before starting the resource.
5. Start `src-phone` and test an existing character.

The schema contains additive `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` statements. Do not remove migration statements because a new database appears to work without them; existing installations may still require them.

## Table groups

| Feature                       | Tables                                                                                                                                                                                                                                                                                                                                     |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Core identity and preferences | `src_phone_data`, `src_phone_kv`                                                                                                                                                                                                                                                                                                           |
| Contacts and messages         | `src_phone_contacts`, `src_phone_messages`, `src_phone_pins`, `src_phone_message_settings`                                                                                                                                                                                                                                                 |
| Mail and media                | `src_phone_mails`, `src_phone_photos`, `src_phone_voice_memos`                                                                                                                                                                                                                                                                             |
| Music                         | `src_phone_playlists`, `src_phone_playlist_tracks`                                                                                                                                                                                                                                                                                         |
| Z                             | `src_phone_z_users`, `src_phone_z_posts`, `src_phone_z_likes`, `src_phone_z_reposts`, `src_phone_z_comments`, `src_phone_z_comment_likes`, `src_phone_z_follows`                                                                                                                                                                           |
| News and listings             | `src_phone_news`, `src_phone_yellowpages`                                                                                                                                                                                                                                                                                                  |
| Bank history                  | `src_phone_bank_transactions`                                                                                                                                                                                                                                                                                                              |
| MyFans                        | `src_phone_myfans_accounts`, `src_phone_myfans_posts`, `src_phone_myfans_likes`, `src_phone_myfans_comments`, `src_phone_myfans_follows`, `src_phone_myfans_subscriptions`, `src_phone_myfans_purchases`, `src_phone_myfans_wallet_transactions`, `src_phone_myfans_blocks`, `src_phone_myfans_messages`, `src_phone_myfans_notifications` |
| Vibe                          | `src_phone_vibe_profiles`, `src_phone_vibe_swipes`, `src_phone_vibe_matches`, `src_phone_vibe_messages`                                                                                                                                                                                                                                    |
| MyCloud                       | `src_phone_cloud_accounts`, `src_phone_cloud_links`, `src_phone_cloud_kv`, `src_phone_cloud_migrations`                                                                                                                                                                                                                                    |
| Crypto wallet                 | `src_phone_crypto_wallets`, `src_phone_crypto_balances`, `src_phone_crypto_transactions`                                                                                                                                                                                                                                                   |
| Wi-Fi                         | `src_phone_routers`, `src_phone_router_connections`                                                                                                                                                                                                                                                                                        |
| Cellular                      | `src_phone_cellular_accounts`, `src_phone_cellular_purchases`, `src_phone_cellular_usage`                                                                                                                                                                                                                                                  |

## Data ownership

Phone data uses two scopes:

* Character scope uses the framework identifier, normally `citizenid`.
* MyCloud scope uses `account_id` and follows the authenticated cloud account.

Do not replace one scope with the other in custom queries. Character contacts, phone numbers, and some feature records must not leak between characters; cloud preferences and wallet state must remain account-scoped.

## Collation and identifier length

The bundled schema uses `utf8mb4` and Unicode-compatible collation for user-facing text. Preserve this when migrating tables or indexes. Framework identifiers are generally stored in `VARCHAR(50)` or `VARCHAR(64)` columns depending on the feature.

Before changing identifier width, inspect every table and related index. Truncating an identifier can merge data from different characters.

## Verification queries

Confirm the core tables exist:

```sql
SHOW TABLES LIKE 'src_phone_%';
```

Check whether phone identities are being created:

```sql
SELECT citizenid, phone_number, mail_address
FROM src_phone_data
ORDER BY citizenid
LIMIT 20;
```

Check the latest messages:

```sql
SELECT id, sender, receiver, time, is_read
FROM src_phone_messages
ORDER BY id DESC
LIMIT 20;
```

Check account links without exposing password hashes:

```sql
SELECT citizenid, account_id, auto_login, last_used_at
FROM src_phone_cloud_links
ORDER BY last_used_at DESC
LIMIT 20;
```

## Backup scope

Back up every table matching `src_phone_%`. Backing up only `src_phone_data` does not preserve messages, social content, MyCloud links, wallet state, routers, or cellular plans.

Never include `shared/server_config.lua` or API credentials in a database dump or support attachment.


# API and Exports

This is the supported integration surface for resources that need to control or read `src-phone`. Client exports are called from a client script; server exports are called from a server script. Internal `src-phone:client:*` and `src-phone:server:*` transport events are not public API.

## Result conventions

Commands normally return `true` on success or `false, errorCode` when validation fails. Read-only exports return the requested value or `nil` when it is unavailable. Database-backed client reads use a callback because FiveM client exports cannot synchronously wait for a server callback.

```lua
local ok, reason = exports['src-phone']:openApp('messages', { number = '555-1234' })
if not ok then
    print(('phone request failed: %s'):format(reason))
end
```

## Client exports

### Phone and application control

| Export             | Parameters       | Return            | Purpose                                                                  |
| ------------------ | ---------------- | ----------------- | ------------------------------------------------------------------------ |
| `openPhone`        | `options?`       | `boolean, error?` | Opens the phone. `options.appId` and `options.params` can launch an app. |
| `closePhone`       | none             | `boolean, error?` | Closes the phone and releases its normal focus flow.                     |
| `togglePhone`      | none             | `boolean, error?` | Toggles the phone; the boolean is the resulting open state.              |
| `isPhoneOpen`      | none             | `boolean`         | Returns current visibility.                                              |
| `getPhoneState`    | none             | `table`           | Returns open, locked, active app, call, airplane and online state.       |
| `openApp`          | `appId, params?` | `boolean, error?` | Opens an installed built-in or registered external app.                  |
| `closeApp`         | none             | `boolean`         | Returns to the phone home screen.                                        |
| `getActiveApp`     | none             | `string?`         | Returns the active app ID.                                               |
| `refreshApp`       | `appId`          | `boolean, error?` | Increments that app's refresh key.                                       |
| `isAppInstalled`   | `appId`          | `boolean`         | Checks the player's installed-app list.                                  |
| `getInstalledApps` | none             | `string[]`        | Returns installed app IDs.                                               |

```lua
exports['src-phone']:openPhone({
    appId = 'dispatch-board',
    params = { incidentId = 2481 }
})
```

### External application registry

| Export              | Parameters                | Return            | Purpose                                                  |
| ------------------- | ------------------------- | ----------------- | -------------------------------------------------------- |
| `registerApp`       | `manifest`                | `boolean, error?` | Registers an external app owned by the calling resource. |
| `unregisterApp`     | `appId`                   | `boolean`         | Removes an app owned by the calling resource.            |
| `isAppRegistered`   | `appId`                   | `boolean`         | Checks the runtime registry.                             |
| `getRegisteredApps` | none                      | `manifest[]`      | Returns accepted, sanitized runtime manifests.           |
| `sendAppEvent`      | `appId, action, payload?` | `boolean, error?` | Delivers a typed event to an external app iframe.        |

Registration takes metadata from the external resource's root `config.lua`. See [Custom applications](/src-phone/custom-apps) for the complete manifest and resource lifecycle.

The owner is always `GetInvokingResource()`. Re-registering the same ID from the same resource updates it atomically; another resource cannot claim that ID. Registrations are removed automatically when their owner stops.

Registration errors:

| Error                                            | Meaning                                                                 |
| ------------------------------------------------ | ----------------------------------------------------------------------- |
| `manifest_must_be_a_table`                       | The manifest is not a Lua table.                                        |
| `unsupported_api_version`                        | `apiVersion` is missing or unsupported.                                 |
| `invalid_id`                                     | The ID fails the length or kebab-case rules.                            |
| `reserved_id`                                    | The ID belongs to a built-in application.                               |
| `id_already_registered`                          | A different resource already owns the ID.                               |
| `invalid_name`                                   | The display name is empty or longer than 18 Unicode characters.         |
| `invalid_ui_page` / `invalid_icon`               | An asset is not a safe relative resource path.                          |
| `invalid_accent_color`                           | The accent is not a six-digit hex color.                                |
| `invalid_permissions` / `unsupported_permission` | The permissions table is invalid or requests an unsupported capability. |
| `invalid_icon_class`                             | The fallback class contains unsupported characters or is too long.      |
| `external_resource_required`                     | The call did not originate from another resource.                       |
| `resource_not_started`                           | The owner resource is not starting or started.                          |

```lua
local ok, reason = exports['src-phone']:registerApp(PhoneAppConfig)
exports['src-phone']:sendAppEvent('dispatch-board', 'incident:updated', {
    id = 2481,
    status = 'assigned'
})
```

The external TypeScript app receives the event through the vendored SDK:

```ts
phone.onEvent((action, payload) => {
  if (action === 'incident:updated') {
    renderIncident(payload);
  }
});
```

### Notifications and badges

| Export                       | Parameters     | Return            |
| ---------------------------- | -------------- | ----------------- |
| `sendNotification`           | `notification` | `id?, error?`     |
| `removeNotification`         | `id`           | `boolean, error?` |
| `clearNotifications`         | `appId?`       | `boolean, error?` |
| `getUnreadNotificationCount` | `appId?`       | `number`          |
| `setAppBadge`                | `appId, count` | `boolean, error?` |
| `clearAppBadge`              | `appId`        | `boolean, error?` |

```lua
local id = exports['src-phone']:sendNotification({
    appId = 'dispatch-board',
    appName = 'Dispatch',
    title = 'New incident',
    body = 'Unit requested at Alta Street.',
    duration = 6000,
    params = { incidentId = 2481 }
})

exports['src-phone']:setAppBadge('dispatch-board', 3)
```

Notification limits are 80 characters for `title`, 500 for `body`, and 1-15 seconds for `duration`. Pressing a public notification opens `appId` with `params` and emits `src-phone:client:notificationPressed`.

### Identity and connectivity

| Export              | Parameters | Return                     |
| ------------------- | ---------- | -------------------------- |
| `getPhoneProfile`   | `callback` | `boolean, error?`          |
| `getPhoneNumber`    | `callback` | `boolean, error?`          |
| `getMailAddress`    | `callback` | `boolean, error?`          |
| `isPhoneAvailable`  | none       | `boolean`                  |
| `getConnectivity`   | none       | `{ online, airplaneMode }` |
| `isOnline`          | none       | `boolean`                  |
| `hasInternetAccess` | none       | `boolean`                  |
| `getAirplaneMode`   | none       | `boolean`                  |

```lua
exports['src-phone']:getPhoneProfile(function(profile)
    if not profile then return end
    print(profile.phoneNumber, profile.mailAddress, profile.firstName)
end)
```

### Calls

| Export              | Parameters         | Return            |
| ------------------- | ------------------ | ----------------- |
| `startCall`         | `number, options?` | `boolean, error?` |
| `answerCall`        | `number?`          | `boolean, error?` |
| `declineCall`       | `number?`          | `boolean, error?` |
| `endCall`           | `number?`          | `boolean, error?` |
| `getCallState`      | none               | `table?`          |
| `isInCall`          | none               | `boolean`         |
| `setSpeakerEnabled` | `enabled`          | `boolean, error?` |

`options.video = true` requests a video call. Call state is server-authoritative; a successful request does not guarantee that the receiver is online or accepts the call.

### Messages and mail

| Export                  | Parameters                    | Return            |
| ----------------------- | ----------------------------- | ----------------- |
| `sendMessage`           | `number, message, options?`   | `boolean, error?` |
| `openConversation`      | `number`                      | `boolean, error?` |
| `markConversationRead`  | `number`                      | `boolean, error?` |
| `getUnreadMessageCount` | none                          | `number`          |
| `sendMail`              | `address, { subject?, body }` | `boolean, error?` |
| `openMail`              | `mailId?`                     | `boolean, error?` |
| `getUnreadMailCount`    | none                          | `number`          |

```lua
exports['src-phone']:sendMessage('555-1234', 'Meet at the station.')
exports['src-phone']:sendMail('alex.doe@sourcedevpro', {
    subject = 'Shift report',
    body = 'The report is ready.'
})
```

### Contacts, pickers, media and location

| Export               | Parameters                      | Return                |
| -------------------- | ------------------------------- | --------------------- |
| `getContacts`        | `callback`                      | `boolean, error?`     |
| `getContactByNumber` | `number, callback`              | `boolean, error?`     |
| `addContact`         | `{ name, number, avatar? }`     | `boolean, error?`     |
| `editContact`        | `id, { name, number }`          | `boolean, error?`     |
| `deleteContact`      | `id`                            | `boolean, error?`     |
| `openContactPicker`  | `options, callback`             | `boolean, requestId?` |
| `openPhotoPicker`    | `options, callback`             | `boolean, requestId?` |
| `openLocationPicker` | `options, callback`             | `boolean, requestId?` |
| `openShareSheet`     | `options, callback`             | `boolean, requestId?` |
| `openConfirmDialog`  | `options, callback`             | `boolean, requestId?` |
| `openCamera`         | `options?`                      | `boolean, error?`     |
| `openGallery`        | `params?`                       | `boolean, error?`     |
| `saveSharedMedia`    | `{ url, mediaType? }, callback` | `boolean, error?`     |
| `getCurrentLocation` | none                            | `{ street, x, y, z }` |
| `setPhoneWaypoint`   | `{ x, y }`                      | `boolean, error?`     |
| `openMap`            | `params?`                       | `boolean, error?`     |
| `shareLocation`      | `number, options?`              | `boolean, error?`     |
| `stopLocationShare`  | none                            | `boolean`             |

Pickers are asynchronous. Cancellation and the 60-second timeout both return `nil` to the callback.

```lua
exports['src-phone']:openContactPicker({ title = 'Send to' }, function(contact)
    if not contact then return end
    exports['src-phone']:sendMessage(contact.number, 'Selected from the picker')
end)

exports['src-phone']:openConfirmDialog({
    title = 'Delete report',
    message = 'This cannot be undone.'
}, function(confirmed)
    if confirmed then deleteReport() end
end)
```

## Client lifecycle events

These are local client events. Listen with `AddEventHandler`; do not send them over the network.

| Event                                  | Arguments               |
| -------------------------------------- | ----------------------- |
| `src-phone:client:phoneOpened`         | none                    |
| `src-phone:client:phoneClosed`         | none                    |
| `src-phone:client:appOpened`           | `appId`                 |
| `src-phone:client:appClosed`           | `appId`                 |
| `src-phone:client:connectivityChanged` | `online`                |
| `src-phone:client:callStateChanged`    | `callState?`            |
| `src-phone:client:notificationPressed` | `{ id, appId, params }` |

```lua
AddEventHandler('src-phone:client:appOpened', function(appId)
    if appId == 'dispatch-board' then startLocalUpdates() end
end)
```

## Server exports

Server exports always take an explicit player `source` when the operation acts on a player. This makes ownership visible at the call site and prevents accidental access through the event sender global.

### Identity and state

| Export                       | Parameters | Return           |
| ---------------------------- | ---------- | ---------------- |
| `getPhoneNumber`             | `source`   | `string?`        |
| `getMailAddress`             | `source`   | `string?`        |
| `getPhoneIdentity`           | `source`   | `table?, error?` |
| `getSourceByPhoneNumber`     | `number`   | `number?`        |
| `getIdentifierByPhoneNumber` | `number`   | `string?`        |
| `isPhoneNumberOnline`        | `number`   | `boolean`        |
| `isPhoneAvailable`           | `source`   | `boolean`        |
| `getConnectivity`            | `source`   | `table?, error?` |
| `getPlayerPhoneState`        | `source`   | `table?`         |

### Player UI, notifications and app events

| Export               | Parameters                        | Return            |
| -------------------- | --------------------------------- | ----------------- |
| `openApp`            | `source, appId, params?`          | `boolean, error?` |
| `sendNotification`   | `source, notification`            | `boolean, error?` |
| `removeNotification` | `source, id`                      | `boolean, error?` |
| `clearNotifications` | `source, appId?`                  | `boolean, error?` |
| `setAppBadge`        | `source, appId, count`            | `boolean, error?` |
| `clearAppBadge`      | `source, appId`                   | `boolean, error?` |
| `sendAppEvent`       | `source, appId, action, payload?` | `boolean, error?` |

```lua
exports['src-phone']:sendNotification(source, {
    appId = 'dispatch-board',
    title = 'Assignment updated',
    body = 'You are now assigned to incident 2481.',
    params = { incidentId = 2481 }
})
```

### Calls, messages and mail

| Export                  | Parameters                          | Return               |
| ----------------------- | ----------------------------------- | -------------------- |
| `requestCall`           | `source, number, options?`          | `boolean, error?`    |
| `endPlayerCall`         | `source`                            | `boolean, error?`    |
| `getPlayerCallState`    | `source`                            | `table?`             |
| `isNumberBusy`          | `number`                            | `boolean`            |
| `sendMessage`           | `source, number, message, options?` | `boolean, error?`    |
| `sendSystemMessage`     | `number, message, options?`         | `boolean, id/error?` |
| `getUnreadMessageCount` | `source`                            | `number`             |
| `sendMail`              | `source, address, data`             | `boolean, error?`    |
| `sendSystemMail`        | `address, data`                     | `boolean, id/error?` |
| `sendMailToPlayer`      | `source, data`                      | `boolean, id/error?` |
| `getUnreadMailCount`    | `source`                            | `number`             |

`sendMessage` and `sendMail` act as the player and follow the normal player-owned validation path. `sendSystemMessage` and `sendSystemMail` create server-authored records and should only be called after the integrating resource has completed its own authorization checks.

```lua
exports['src-phone']:sendSystemMessage('555-1234', 'Your vehicle is ready.', {
    sender = '5550000'
})

exports['src-phone']:sendMailToPlayer(source, {
    sender = 'dispatch@sourcedevpro',
    subject = 'Assignment',
    body = 'Report to Mission Row.'
})
```

### Server contact access

| Export          | Parameters            | Return               |
| --------------- | --------------------- | -------------------- |
| `getContacts`   | `source`              | `contacts, error?`   |
| `addContact`    | `source, contact`     | `boolean, id/error?` |
| `editContact`   | `source, id, contact` | `boolean, error?`    |
| `deleteContact` | `source, id`          | `boolean, error?`    |

Contacts are scoped to the player's active MyCloud account. The exports do not accept an arbitrary account or owner key.

## Validation and security boundary

The public API validates IDs, phone numbers, text lengths, URLs, badge ranges, player sources and external manifest ownership. It deliberately does not expose:

* Bank balance mutations or transfers.
* Another player's message history, mailbox contents or contacts without an explicit player source.
* Raw SQL, KV storage or MyCloud owner keys.
* RTC channels, call routing tables or raw internal network events.
* Shell DOM access or unrestricted overlay creation.

An export confirms that a request was accepted; it does not replace authorization in the calling resource. Job checks, permissions, prices, inventory changes and rate limits remain the caller's server-side responsibility.

## Start order

```cfg
ensure src-phone
ensure your-integration
```

For an external application, also declare `dependency 'src-phone'` in its `fxmanifest.lua`. Registration is client-side and the boilerplate automatically re-registers when `src-phone` restarts.


# Custom Applications

External applications run from a separate FiveM resource and register with `src-phone` at runtime. The phone does not compile third-party code into its own React bundle.

## Design boundary

The phone shell remains responsible for:

* Status bar and Dynamic Island.
* Shared application header and back button.
* Home gesture and application switcher.
* Lock state and phone visibility.
* Notifications and network availability checks.
* Application viewport clipping and pointer-event control.

The external resource owns only the content rendered below the shared header. It runs in a sandboxed iframe on its own `cfx-nui` origin. This prevents application CSS and DOM code from modifying the phone shell.

## Starting from the boilerplate

Copy `src-phone/boilerplate/src-phone-app` to a top-level resource directory and rename the folder:

```
resources/
  src-phone-app/
    fxmanifest.lua
    config.lua
    client.lua
    web/
      blank.html
      package.json
      tsconfig.json
      vite.config.ts
      index.html
      public/
      src/
      dist/
```

Keep `fxmanifest.lua`, `config.lua`, and `client.lua` at the resource root. TypeScript, npm, Vite, source assets, and the production bundle belong under `web/`.

Install and build from the web directory:

```powershell
cd resources/src-phone-app/web
npm install
npm run typecheck
npm run build
```

Start order:

```cfg
ensure src-phone
ensure src-phone-app
```

## Why `blank.html` is the resource UI page

FiveM mounts every resource `ui_page` as a game-wide NUI surface. If the application uses `web/dist/index.html` directly as `ui_page`, its interface appears across the game screen as soon as the resource starts.

Use a transparent page for the automatic surface:

```lua
ui_page 'web/blank.html'

files {
    'web/blank.html',
    'web/dist/index.html',
    'web/dist/**/*'
}

client_scripts {
    'config.lua',
    'client.lua'
}
```

`src-phone` loads the real `uiPage` inside the phone and appends `srcPhoneEmbedded=1`. Keep the entry-page guard supplied by the boilerplate as a second protection:

```html
<style>
  html:not(.src-phone-embedded) {
    display: none !important;
    background: transparent !important;
  }
</style>
<script>
  if (new URLSearchParams(window.location.search).get('srcPhoneEmbedded') === '1') {
    document.documentElement.classList.add('src-phone-embedded');
  }
</script>
```

Do not replace this with `window.self !== window.top`. FiveM's normal NUI surface already runs inside an iframe, so that check cannot distinguish the phone viewport.

## Application config

The root `config.lua` file is the single source for application metadata. The shell validates this table during registration and sends the accepted values to the TypeScript SDK when the application opens:

```lua
PhoneAppConfig = {
    apiVersion = 1,
    id = 'my-first-app',
    name = 'My App',
    uiPage = 'web/dist/index.html',
    icon = 'web/dist/app-icon.svg',
    iconClass = 'fas fa-cube',
    accentColor = '#5B8CFF',
    permissions = { 'storage', 'notify' },
    requiresInternet = false,
    defaultInstalled = true,
}
```

Do not duplicate these values in a TypeScript manifest. In particular, permission checks and storage namespacing use the validated runtime config sent by `src-phone`.

### Config fields

| Field              | Required | Rules                                                                                           |
| ------------------ | -------- | ----------------------------------------------------------------------------------------------- |
| `apiVersion`       | Yes      | Must be `1`. Unknown major versions are rejected.                                               |
| `id`               | Yes      | Unique kebab-case ID, 2-48 characters, beginning with a lowercase letter.                       |
| `name`             | Yes      | Display name, 1-18 Unicode characters.                                                          |
| `uiPage`           | No       | Relative resource path. Defaults to `web/dist/index.html`. External URLs and `..` are rejected. |
| `icon`             | No       | Relative SVG or PNG path. At least 128 by 128 is recommended.                                   |
| `iconClass`        | No       | Font Awesome fallback class. Defaults to `fas fa-cube`.                                         |
| `accentColor`      | No       | Six-digit hex color. Defaults to `#5B8CFF`.                                                     |
| `permissions`      | No       | v1 allowlist: `storage`, `notify`.                                                              |
| `requiresInternet` | No       | When `true`, the phone blocks launch while offline. Defaults to `false`.                        |
| `defaultInstalled` | No       | Defaults to `true`. See installation modes below.                                               |

Built-in application IDs are reserved. Do not use IDs such as `phone`, `messages`, `settings`, `camera`, `appstore`, or `garage`.

## Installation modes

### Resource-provided application

```lua
defaultInstalled = true,
```

The application is installed automatically while its resource is registered. It behaves like a resource-provided system application and cannot be uninstalled from edit mode.

### App Store application

```lua
defaultInstalled = false,
```

The application appears in the phone App Store. Each player installs or removes it through the existing persistent installed-app and home-layout flow.

Stopping the owner resource removes its registration. Starting it again makes it available under the same ID.

## Registration

Load `config.lua` before `client.lua` in `fxmanifest.lua`, then pass `PhoneAppConfig` to the public export:

```lua
local function registerPhoneApp()
    if type(PhoneAppConfig) ~= 'table' then
        print('PhoneAppConfig is missing or invalid')
        return
    end

    local called, registered, reason = pcall(function()
        return exports['src-phone']:registerApp(PhoneAppConfig)
    end)

    if not called or not registered then
        print(('src-phone registration failed: %s'):format(reason or registered or 'unknown'))
    end
end

CreateThread(function()
    Wait(0)
    registerPhoneApp()
end)

AddEventHandler('onResourceStart', function(resource)
    if resource == 'src-phone' then
        registerPhoneApp()
    end
end)
```

The second registration path is required because restarting `src-phone` clears its in-memory registry while the external resource remains running.

## TypeScript contract

The boilerplate SDK exposes the application context:

```ts
export interface PhoneAppContext {
  navigate(route: string, params?: Record<string, unknown>): void;
  goBack(): void;
  close(): void;
  notify(message: string, tone?: 'neutral' | 'success' | 'error'): void;
  storage: {
    get<T>(key: string): T | null;
    set<T>(key: string, value: T): void;
    remove(key: string): void;
  };
  setTitle(title: string): void;
}
```

Initialize one SDK instance:

```ts
import { createPhoneApp } from './sdk';

const phone = createPhoneApp();

phone.onReady((app) => {
  console.log(`${app.name} initialized for ${app.resource}`);
});
```

`onReady` receives the validated `id`, `name`, owner `resource`, `accentColor`, and `permissions` from the shell. Do not create one SDK instance per screen. A single instance owns the runtime application metadata, route stack, shell messaging, launch parameters, and lifecycle subscriptions.

## Navigation and header

Use SDK navigation instead of creating a second top-level header:

```ts
phone.context.navigate('/details', { recordId: 42 });

phone.onRoute(({ route, params }) => {
  renderRoute(route, params);
});
```

The shell back button sends a back request to the SDK. `goBack()` pops the application route stack. At the root route it closes the application and returns to the phone home screen.

Set the shared header title when a route changes:

```ts
phone.context.setTitle('Details');
```

The shell truncates title input to 32 characters. Do not render a duplicate fixed header inside the content viewport.

## Launch parameters

Read parameters passed when the shell opens the application:

```ts
const params = phone.getLaunchParams();
```

Treat all launch parameters as untrusted input. Validate types and identifiers before using them in a resource callback.

## Lifecycle

Applications remain mounted for switching and recent-app behavior. Pause work when inactive:

```ts
phone.onLifecycle((active) => {
  if (active) {
    startPolling();
  } else {
    stopPolling();
  }
});
```

Stop polling, timers, expensive animation, camera access, and continuous audio when `active` is `false`. Background applications do not receive pointer events.

## Runtime events from Lua

Client or server integrations can deliver data to a mounted external app with the public `sendAppEvent` export. The shell checks the target app ID and forwards the event into that app's iframe; the external app does not listen to raw FiveM window messages.

Client:

```lua
exports['src-phone']:sendAppEvent('my-first-app', 'order:updated', {
    id = 42,
    status = 'ready'
})
```

Server:

```lua
exports['src-phone']:sendAppEvent(source, 'my-first-app', 'order:updated', {
    id = 42,
    status = 'ready'
})
```

TypeScript:

```ts
phone.onEvent((action, payload) => {
  if (action !== 'order:updated') return;
  const update = payload as { id?: number; status?: string };
  if (typeof update.id !== 'number') return;
  updateOrder(update);
});
```

Validate every payload in TypeScript. Server-authored events still become browser input once they enter the iframe.

## Calling the application's resource

The SDK targets the external application's own NUI callbacks, not `src-phone`:

```ts
const response = await phone.nui<{ ok: boolean; name?: string }>(
  'example:getPlayerName',
  {},
);
```

Register the callback in the external resource:

```lua
RegisterNUICallback('example:getPlayerName', function(_, cb)
    cb({
        ok = true,
        name = GetPlayerName(PlayerId()),
    })
end)
```

Every callback path must call `cb(...)` exactly once. Validate server-owned actions on the server. A NUI payload must never be trusted for money, inventory, permissions, jobs, or ownership.

## Storage permission

Declare `storage` before using the SDK storage wrapper:

```ts
type Draft = { text: string; updatedAt: number };

phone.context.storage.set<Draft>('draft', {
  text: 'Example',
  updatedAt: Date.now(),
});

const draft = phone.context.storage.get<Draft>('draft');
phone.context.storage.remove('draft');
```

Keys are namespaced by application ID and limited to safe characters. Storage is suitable for UI preferences and drafts. Do not store authoritative gameplay state, credentials, or secrets in browser storage.

## Notification permission

Declare `notify` before sending a shell notification:

```ts
phone.context.notify('Record saved', 'success');
```

The phone limits the message length and routes it through the shared notification banner and notification center. External applications do not create global fixed-position toasts.

## Theme and layout tokens

The SDK writes shell theme values to CSS custom properties:

```css
var(--phone-bg)
var(--phone-card)
var(--phone-card-active)
var(--phone-separator)
var(--phone-text)
var(--phone-text-secondary)
var(--phone-accent)
var(--phone-destructive)
var(--phone-safe-left)
var(--phone-safe-right)
var(--phone-safe-bottom)
```

Recommended baseline:

* 16 px horizontal content padding.
* 8, 12, 16, 24, and 32 px spacing scale.
* 12 px card radius.
* At least 52 px list-row height.
* At least 44 by 44 px interaction targets.
* 20/24 px page headings, 14/20 px body text, and 12/16 px metadata.

Use theme tokens rather than fixed light or dark colors.

## UI restrictions

External applications must not:

* Access or modify the parent document.
* Render a replacement Dynamic Island, status bar, home indicator, or phone header.
* Use `position: fixed` for application-wide overlays.
* Depend on `100vw` or `100vh` for phone sizing.
* Use arbitrary global z-index values to escape the application viewport.
* Navigate the top frame, open popup windows, or target external form windows.
* Call another resource's private NUI endpoint.
* Request permissions that the application does not use.

The iframe sandbox is a boundary, not a replacement for server-side authorization.

## Custom application release checklist

1. Use a unique ID and keep it unchanged after release.
2. Confirm `web/blank.html` remains the `ui_page`.
3. Run `npm run typecheck` and `npm run build` from `web/`.
4. Confirm `web/dist/index.html` and the icon are included in `files`.
5. Start the app before and after restarting `src-phone`.
6. Test install and uninstall behavior for the selected `defaultInstalled` mode.
7. Test root and nested-route back behavior.
8. Test light and dark themes.
9. Test offline launch when `requiresInternet` is enabled.
10. Test inactive lifecycle behavior in the application switcher.
11. Test empty, loading, error, and long-content states.
12. Confirm no application UI appears outside the phone when the resource starts.

See [API and Exports](/src-phone/api-exports) for registration errors and return values.


# Troubleshooting

Start with the first failing layer. Do not change UI code to work around a database, framework, resource-order, or manifest problem.

## Diagnostic order

1. Confirm resource start order.
2. Check the server console for Lua, JavaScript, and SQL errors.
3. Check the client F8 console.
4. Verify the selected framework and bridge mappings.
5. Verify the database schema.
6. Reproduce with built-in applications before testing a custom application.
7. Rebuild web assets only when source files changed.

## Phone does not open

Check:

* `src-phone` is started.
* `web/dist/index.html` exists and is included by `fxmanifest.lua`.
* `/tel` works even if the configured key does not.
* `Config.PhoneOpenKey` contains a valid FiveM mapping name.
* The player does not have a conflicting saved key binding.
* The client console does not report a missing phone prop or NUI file.

If `/tel` works but the key does not, reset or change the local FiveM key binding. Restarting the resource does not overwrite a player's existing local mapping.

## Missing-table or unknown-column errors

Cause: `install.sql` was not imported completely, or an older schema was not migrated.

Resolution:

1. Stop `src-phone`.
2. Back up all `src_phone_%` tables.
3. Import the current complete `install.sql`.
4. Review the first SQL error rather than continuing through the remaining failures.
5. Restart the resource.

See [Database](/src-phone/database).

## Phone number or mail address is missing

Check that:

* Framework player-loaded events are reaching the bridge.
* The configured framework identifier is non-empty.
* `src_phone_data` exists and is writable.
* `Config.PhonePrefix`, `Config.PhoneNumberLength`, and `Config.MailDomain` are valid.
* No unique-value conflict exists from a manually edited number.

## Calls connect without voice

Check:

* The configured voice resource starts before `src-phone`.
* `BridgeConfig.Call.VoiceSystem` matches the running voice integration.
* Both players have valid phone numbers.
* The voice resource supports the channel operations expected by the bridge.
* Speaker mode is tested separately from the normal private call channel.

A working call UI does not prove that the voice bridge joined the players to the same channel.

## Camera opens but upload fails

Check:

* `Config.FivemanageApiKey` is set in `shared/server_config.lua`.
* The token is active and permitted to upload the requested media type.
* The server can reach the upload provider.
* Payload limits are not exceeded.
* The key has not been placed in client-visible configuration.

Do not post the token in a support message. Rotate it if it has been exposed.

## Garage is empty or shows incorrect state

Check `BridgeConfig.Garage.SQL` against the actual vehicle table:

* Table name.
* Owner identifier column.
* Plate and model columns.
* Stored/out/impound state column and values.
* Fuel, engine, body, and mods columns when used.

Also verify that the selected garage, key, and fuel resources match their bridge configuration.

## App reports no internet

Check:

* Airplane mode is disabled.
* Wi-Fi is enabled and the character is connected to an authorized router within range.
* The router has remaining quota.
* Cellular data is enabled, valid, and has remaining quota.
* `Config.Router.InternetUsageEnabled` and `Config.Cellular.Enabled` match the intended server rules.
* Streaming usage values are not consuming the quota faster than expected.

An external application with `requiresInternet: true` uses the same launch gate as supported built-in network applications.

## Custom application does not appear

Check:

1. `src-phone` starts before the application resource.
2. The external resource declares `dependency 'src-phone'`.
3. `config.lua` defines `PhoneAppConfig` and loads before `client.lua`.
4. `registerApp` returns `true`.
5. The ID is valid, unique, and not reserved.
6. `Config.Apps` is not being used for the custom ID.
7. `defaultInstalled` matches the expected installation mode.
8. With `defaultInstalled: false`, open the phone App Store and install the app.

Print the returned registration error code. Do not discard the second return value from `registerApp`.

## Custom application appears outside the phone

Cause: the external resource uses the real application entry as its FiveM `ui_page`.

The correct `fxmanifest.lua` uses the transparent page:

```lua
ui_page 'web/blank.html'
```

The real application path remains in `config.lua`:

```lua
uiPage = 'web/dist/index.html',
```

Also keep the `srcPhoneEmbedded=1` guard in `web/index.html`. Rebuild from `web/`, then restart the external resource:

```powershell
cd resources/src-phone-app/web
npm run build
```

```
restart src-phone-app
```

## Custom application is blank inside the phone

Check:

* `web/dist/index.html` exists.
* Vite uses `base: './'` so built asset URLs are relative.
* `uiPage` is relative to the resource root, normally `web/dist/index.html`.
* `fxmanifest.lua` includes `web/dist/index.html` and `web/dist/**/*`.
* The entry-page guard receives `srcPhoneEmbedded=1`.
* Browser code does not require an unrestricted top frame or popup capability.

Open the client console and look for failed `cfx-nui` asset requests.

## Custom application callback never returns

Check every path in the Lua handler calls `cb(...)` exactly once:

```lua
RegisterNUICallback('records:get', function(data, cb)
    if type(data) ~= 'table' then
        cb({ ok = false, error = 'invalid_request' })
        return
    end

    cb({ ok = true, records = {} })
end)
```

Also confirm the TypeScript SDK has received its resource name from the shell before calling `phone.nui`.

## Changes are not visible after a web edit

The runtime loads `web/dist`, not `web/src`.

```powershell
cd src-phone/web
npm run build
```

For an external application:

```powershell
cd src-phone-app/web
npm run build
```

Restart the resource that owns the changed bundle. Restarting only `src-phone` does not rebuild or reload an external resource's top-level NUI.

## Information to collect for support

Provide:

* `src-phone` version.
* Framework and version.
* Voice, garage, keys, fuel, and inventory resources in use.
* Exact start order.
* The first relevant server error.
* The first relevant client F8 error.
* Registration error code for a custom app.
* Whether the issue reproduces with a new character.

Do not provide API keys, database credentials, password hashes, authentication tokens, or full player records.


# Overview

## System Overview

![billing](https://github.com/user-attachments/assets/24c0386e-fabd-449d-a3bd-ba6e9a81a20f)

`src-bank` is a comprehensive and modular banking/economy management system designed for FiveM servers. It introduces a deep economy structure with features such as credit scores, ATM ownership, and advanced interest configurations.

### 💳 Advanced Credit and Interest System

* **Credit Score:** A dynamically updated score based on the player's loan repayment history. Different credit limits can be set depending on specific professions or score tiers.
* **Installment Options:** Automatically adjusting interest rates based on the chosen maturity (e.g., 4, 6, 8, 10, or 12 installments).
* **Overdue & Blacklist:** Late payment penalties are applied to unpaid loans. Consecutive missed installments trigger the Blacklist, blocking access to bank withdrawals.

### 🏧 ATM Ownership and Taxation

* Players can purchase and sell available or admin-placed ATMs (`Config.AllowAtmSelling`).
* ATM owners can set custom percentage fees for every transaction made on their ATM.
* The system can automatically deduct fees for unowned ATMs depending on the configuration.

### ⚙️ Technical Infrastructure

* **Framework Support:** QBox (`qbx_core`), QBCore (`qb-core`), and ESX (`es_extended`).
* **Interaction Standards:** Supports `target` (ox\_target, qb-target), `text` (drawtext), or both simultaneously (`Config.Interaction`).
* **Dynamic Model Swapper:** An integrated swap architecture that can instantly replace old or problematic ATM prop models around the map with new, standardized models.


# Installation

To deploy the system, you must import the database schema and complete the baseline configurations.

## 1. Database Setup (SQL)

The integrated loan and ATM structures in `src-bank` require specific database tables to function. Run the `install.sql` file using an interface like HeidiSQL or phpMyAdmin.

### Table Structure:

* `src_bank_accounts`: Stores the credit score, active PIN code, and IBAN data.
* `src_bank_transactions`: Contains transaction logs for all deposits, withdrawals, and transfers.
* `src_bank_atms`: Keeps track of purchased or placed ATM owner IDs, 3D coordinates, and profit fees.
* `src_bank_loans` & `src_bank_loan_blacklist`: Holds loan limits, maturity cycles, current overdues, debt amounts, and blacklist records.

## 2. Resource Integration

1. Place the downloaded `src-bank` package into your server's `resources` directory (or a relevant subfolder).
2. Add the following to your `server.cfg`:

```bash
ensure qbx_core # or qb-core / es_extended (Your core framework)
ensure ox_lib   # Dependency
ensure src-bank # Banking system
```

## 3. Additional Dependencies (Optional / Required)

* **UI Notifications:** Most of the UI components and notifications rely on `ox_lib`.
* **Inventory Receipts:** If you wish to give players a physical receipt item after their transactions, ensure you add the `receipt` item possessing metadata support to your inventory system. You can disable this feature in the config if not needed.
* **Targeting System:** Integration is supported via `qbx_core`, `qb-core`, `ox_target`, or `qb-target`. If you prefer not to use targeting, you can switch to `text` mode in the Config.


# Configuration

The system's entire flexibility is controlled through the `config.lua` file. You can adjust the parameters according to your server's economic design or player interaction dynamics.

Below are the main configuration blocks and their descriptions:

## 1. Core Framework and Interaction

|                  Setting |                  Default                 | Description                                                                                     |
| -----------------------: | :--------------------------------------: | ----------------------------------------------------------------------------------------------- |
|            `Config.Core` |               `"qbx_core"`               | The core data handler: supports `qbx_core`, `qb-core`, `es_extended`.                           |
|     `Config.Interaction` |                `"target"`                | Method for environment interactions (ATMs and NPCs): `target`, `text`, or `both`.               |
|  `Config.TxHistoryLimit` |                   `30`                   | The maximum number of historical transactions displayed in the list UI (Database optimization). |
| `Config.ReceiptPrinting` | `{Enabled = true, ItemName = 'receipt'}` | Receipt printing system. Connects to the inventory system (Optional).                           |

## 2. Loans & Credit (Blacklist)

This section has the highest impact on the economy; managing loan conditions, repayment rates, and tracking limits:

* **`Config.Loans.InstallmentRates`**: Calculates percentage profit taken from the principal based on the chosen installment period (`[6] = 0.15` -> 15% interest added for a 6-month term).
* **`Config.Loans.JobLimits`**: Sets the maximum loan cap based on the player's profession (e.g., `police`, `taxi`, `default`).
* **`Config.Loans.CreditScoreTiers`**: The multiplier applied based heavily on the player's repayment track record. While a "Poor" account has a 0.5 (50%) multiplier, an "Excellent" account can pull a loan up to 2.0x the base limit.
* **`Config.Loans.BlockWithdrawOnOverdue`**: If set to `true` and the player has a missed or overdue loan installment, they will be blocked from withdrawing cash.

## 3. Dynamic ATM Fees

* **`Config.AllowAtmSelling`**: Allows players to exchange or sell the ATM assets they own.
* **`Config.ATM_DefaultFeePercentage`**: The base fee amount for an unowned or placed ATM; when a player withdraws money, the bank (or the ATM owner) takes this commission (`0.05` => `5%`).
* **`Config.ApplyFeeToUnownedAtms`**: If set to `true`, the central system applies taxation on all unowned ATMs. This is an excellent alternative for creating a persistent money sink within the city economy.

## 4. Developer / Debug Modes

* **`Config.ModelSwaps`**: A feature to force invalid, conflicting, or unwanted ATM models caused by custom MLO/Map loadings into a standardized model (e.g., `prop_atm_03`).
* **`Config.BankPeds`**: Coordinates where NPC tellers will spawn across the city.


# Admin Guide

The system includes built-in administrative tools to monitor the economy and dynamically alter ATM locations across the map.

## 1. ATM Placement Mode (Raycast)

`src-bank` contains a Raycast tool allowing you to place ATM objects like `prop_atm_03` directly within the game, avoiding the need for external map editing software.

1. Type `/atmadmin` (`Config.AdminCommands.AtmAdmin`) in the in-game chat.
2. An exclusive administration panel notification will appear at the top-right corner.
3. Using the configured keyboard inputs (e.g., **L** as defined by `Config.AdminKeys.ToggleRaycast`), point at a surface with your mouse and place the ghosted ATM prop gracefully onto the environment.
4. Through the pop-up menu, select "Delete (`DeleteAtm`)" to entirely remove an already placed ATM from your server.

## 2. Credit & Record Mode (Loan Debug)

This command allows you to view the loan status, debt history, and registry of players who have defaulted, missed installments, or entered the blacklist without accessing the SQL database directly.

* Command Usage: `/loandebug [player_id]`
* (If `Config.AdminCommands.LoanDebug` has been modified, use your updated command).
* You can supervise the player's total loan balance, unpaid sum history, and record status dynamically from the Debug interface.

## 3. Authorization Standards

To prevent data manipulation, these panels rely on your framework's permission structure (Group System):

|                                       Framework                                      | Authorization Levels (Group) |
| :----------------------------------------------------------------------------------: | ---------------------------- |
|                                      `qbx_core`                                      | `'god', 'admin'`             |
|                                       `qb-core`                                      | `'god', 'admin'`             |
|                                     `es_extended`                                    | `'superadmin', 'admin'`      |
| *(Configurations can be modified to match custom groups using `Config.AdminGroups`)* |                              |

***

*Developers can efficiently interlock using the (Open/Shared) exports. Don't forget to examine the Export/API sections for customized integration specifics.*


# API and Exports

The SRC Advanced Banking system provides a robust set of exports and events, allowing developers to integrate banking, loans, and credit scoring into their own resources.

## Client-Side Exports

### Open Bank UI

Opens the main banking tablet or teller interface.

```lua
-- mode: 'tablet' or 'atm'
-- atmId: 'teller' or a specific ATM ID (optional)
exports['src-bank']:OpenBank(mode, atmId)
```

### Open Teller

Quickly opens the bank teller interface.

```lua
exports['src-bank']:OpenTeller()
```

### Open ATM

Quickly opens the interface for a specific ATM.

```lua
exports['src-bank']:OpenATM(atmId)
```

### Close Bank UI

Forcefully closes the banking interface.

```lua
exports['src-bank']:CloseBank()
```

### Check if Bank is Open

Returns `true` if the banking UI is currently active.

```lua
local isOpen = exports['src-bank']:IsBankOpen()
```

### Get Closest ATM

Finds the ID of the ATM nearest to the player based on the configuration.

```lua
local atmId = exports['src-bank']:GetClosestAtmId()
```

***

## Server-Side Exports

### Get IBAN

Retrieves the IBAN associated with a player's citizen ID.

```lua
local iban = exports['src-bank']:GetIBAN("CITIZEN_ID")
```

### Get Credit Score

Retrieves the current credit score for a player.

```lua
local score = exports['src-bank']:GetCreditScore("CITIZEN_ID")
```

### Update Credit Score

Adjusts a player's credit score by adding or removing points.

```lua
-- points: can be positive or negative
exports['src-bank']:UpdateCreditScore("CITIZEN_ID", 10)
```

### Add Transaction

Manually inserts a custom transaction into a player's bank history.

```lua
-- citizenid: Target player
-- type: 'deposit', 'withdraw', 'transfer_in', 'transfer_out'
-- amount: Number
-- description: Text to display in the UI
exports['src-bank']:AddTransaction("CITIZEN_ID", "deposit", 500, "Quest Reward")
```

### Society Banking Exports

Manage organization and society bank accounts.

#### Get Society Balance

Retrieves the current balance of a society account.

```lua
-- job: Society job name (e.g., 'police')
local balance = exports['src-bank']:GetSocietyBalance("police")
```

#### Add Society Money

Adds money to a society bank account.

```lua
-- job: Society job name
-- amount: Number
-- description: Log entry for the society history
local success = exports['src-bank']:AddSocietyMoney("police", 5000, "Fine Revenue")
```

#### Remove Society Money

Removes money from a society bank account.

```lua
-- job: Society job name
-- amount: Number
-- description: Log entry for the society history
local success = exports['src-bank']:RemoveSocietyMoney("police", 1500, "Equipment Purchase")
```

#### Get or Generate Society Account

Retrieves IBAN and balance, or creates the account if it doesn't exist.

```lua
local iban, balance = exports['src-bank']:GetOrGenerateSocietyAccount("police")
```

***

### Programmatic Transfer

Performs a bank transfer between two players entirely via script.

```lua
-- senderCid: Citizen ID of the sender
-- receiverIbanOrCid: Target IBAN or Citizen ID
-- amount: Number
-- description: Custom log entry (optional)
local success, errorOrBalance = exports['src-bank']:ProgrammaticTransfer("SENDER_CID", "TARGET_CID", 1000, "Contract Payment")
```

### Check Blacklist

Checks if a player is blacklisted from taking out new loans.

```lua
local isBlacklisted = exports['src-bank']:IsBlacklisted("CITIZEN_ID")
```

***

## Events

### Client Events

#### `src-bank:client:bankRefresh`

Triggered whenever the bank data is updated (balance changes, loans paid, etc.). Use this to sync your UI if you have custom banking widgets.

```lua
RegisterNetEvent('src-bank:client:bankRefresh', function(data)
    -- data = { success, newBalance, transactions, chartData, message, ... }
end)
```

#### `src-bank:client:syncBalance`

Triggered for minor balance updates.

```lua
RegisterNetEvent('src-bank:client:syncBalance', function(balance, cash)
    -- handle balance update
end)
```

***

{% hint style="info" %}
**Developer Tip:** Use `ProgrammaticTransfer` when you want to ensure both players get a transaction log entry and the corresponding money is moved safely across frameworks (QBCore/ESX).
{% endhint %}


# Overview

## Introduction

The Advanced Billing System is a modern, feature-rich invoice management solution for FiveM servers. Built with a beautiful tablet interface and framework-agnostic architecture, it provides a seamless billing experience for both players and administrators.

## Key Features

### 💰 Invoice Management

* Create and send invoices to players
* Support for cash and bank payments
* Recurring/installment payment system
* Multi-recipient invoices
* Rich text descriptions
* Photo attachments

### 📊 Admin Dashboard

* Real-time analytics and statistics
* Cashbox management
* Customizable billing menus
* Revenue tracking
* Invoice history

### 🎨 Modern UI

* Beautiful tablet interface
* Dark theme with glassmorphism
* Smooth animations
* Responsive design
* In-tablet notifications
* Camera overlay for photos

### 🔌 Framework Support

* **QBox** (qbx\_core)
* **QBCore** (qb-core)
* **ESX** (es\_extended)
* **Custom** frameworks (easy to add)

### 🌍 Multi-Language

* English (EN)
* Turkish (TR)
* Easy to add more languages

## System Requirements

* **Framework:** QBox, QBCore, ESX, or custom
* **Database:** MySQL/MariaDB
* **Optional:** ox\_lib (for enhanced notifications)
* **Optional:** ox\_inventory (for receipt items)

## Screenshots

> Add your screenshots here in GitBook

## Support

Need help? Check out:

* [Troubleshooting Guide](https://github.com/dollar-src/docs/blob/main/billing/gitbook-troubleshooting.md)
* [Framework Bridge Documentation](https://github.com/dollar-src/docs/blob/main/billing/gitbook-bridge-overview.md)
* [API Reference](https://github.com/dollar-src/docs/blob/main/billing/gitbook-api-exports.md)

## License

This project is open source under the MIT License.


# Installation

## Prerequisites

Before installing the Billing System, ensure you have:

* ✅ A working FiveM server
* ✅ One of the supported frameworks (QBox, QBCore, or ESX)
* ✅ MySQL/MariaDB database
* ✅ Basic knowledge of FiveM resource installation

## Step 1: Download

Download the latest version of `src-billing` from your source.

## Step 2: Extract Files

Extract the `src-billing` folder to your server's `resources` directory:

```
server/
└── resources/
    └── [SRC]/
        └── src-billing/
```

## Step 3: Database Setup

### Import SQL File

Execute the SQL file to create the required database tables:

1. Open your database management tool (HeidiSQL, phpMyAdmin, etc.)
2. Select your FiveM database
3. Import the file: `src-billing/sql/billing.sql`

### Verify Tables

After importing, verify these tables exist:

* `billing_invoices` - Stores all invoices
* `billing_cashbox` - Stores cashbox balances per job
* `billing_menus` - Stores customizable billing menus

## Step 4: Configuration

### Basic Configuration

Edit `config.lua` to match your server:

```lua
Config.Framework = 'auto'  -- 'qbox', 'qb', 'esx', or 'auto'
Config.AllowedJobs = {
    'police',
    'ambulance',
    'mechanic',
    -- Add your jobs here
}
Config.Keybind = 'F7'  -- Tablet open key
```

### Framework Configuration

If auto-detection doesn't work, manually set your framework:

```lua
-- For QBox
Config.Framework = 'qbox'

-- For QBCore
Config.Framework = 'qb'

-- For ESX
Config.Framework = 'esx'

-- For Custom Framework
Config.Framework = 'custom'
-- Then configure bridge functions (see Framework Bridge section)
```

## Step 5: Add to server.cfg

Add the resource to your `server.cfg`:

```cfg
ensure src-billing
```

{% hint style="info" %}
Make sure to start `src-billing` **after** your framework resource.
{% endhint %}

## Step 6: Restart Server

Restart your FiveM server or start the resource:

```
restart src-billing
```

## Step 7: Verify Installation

### Check Console

Look for this message in your server console:

```
[Billing Bridge] Framework auto-detected: qbox
[src-billing] Resource started successfully
```

### Test In-Game

1. Join your server
2. Get a job from `Config.AllowedJobs`
3. Press `F7` (or your configured key)
4. Tablet should open

## Troubleshooting Installation

### Resource Won't Start

**Check:**

* Resource is in correct folder
* `fxmanifest.lua` exists
* No syntax errors in config

**Solution:**

```
restart src-billing
```

### Database Errors

**Check:**

* SQL file was imported correctly
* Database connection is working
* Tables exist in database

**Solution:** Re-import `sql/billing.sql`

### Framework Not Detected

**Check:**

* Framework resource is started
* Framework name is correct in config

**Solution:**

```lua
Config.Framework = 'qbox'  -- Set manually
```

### Tablet Won't Open

**Check:**

* You have an allowed job
* Keybind is correct
* No console errors

**Solution:** Check [Troubleshooting Guide](https://github.com/dollar-src/docs/blob/main/billing/gitbook-troubleshooting.md)

## Next Steps

{% content-ref url="<https://github.com/dollar-src/docs/blob/main/billing/gitbook-configuration.md>" %}
<https://github.com/dollar-src/docs/blob/main/billing/gitbook-configuration.md>
{% endcontent-ref %}

{% content-ref url="<https://github.com/dollar-src/docs/blob/main/billing/gitbook-user-guide.md>" %}
<https://github.com/dollar-src/docs/blob/main/billing/gitbook-user-guide.md>
{% endcontent-ref %}

## Optional Dependencies

### ox\_lib (Recommended)

For enhanced notifications:

```cfg
ensure ox_lib
ensure src-billing
```

### ox\_inventory

For receipt printing feature:

```cfg
ensure ox_inventory
ensure src-billing
```

{% hint style="success" %}
Installation complete! You're ready to start using the Billing System.
{% endhint %}


# Configuration

## config.lua Overview

The `config.lua` file contains all configuration options for the Billing System.

## Framework Settings

### Framework Selection

```lua
Config.Framework = 'auto'
```

**Options:**

* `'auto'` - Automatically detect framework (recommended)
* `'qbox'` - Force QBox framework
* `'qb'` - Force QBCore framework
* `'esx'` - Force ESX framework
* `'custom'` - Use custom framework (requires bridge setup)

{% hint style="info" %}
Auto-detection works in most cases. Only set manually if detection fails.
{% endhint %}

## Job Configuration

### Allowed Jobs

Define which jobs can create invoices:

```lua
Config.AllowedJobs = {
    'police',
    'ambulance',
    'mechanic',
    'taxi',
    'realestate',
}
```

{% hint style="warning" %}
Job names must match exactly with your framework's job names (case-insensitive).
{% endhint %}

## Keybind Settings

### Tablet Open Key

```lua
Config.Keybind = 'F7'
```

**Common Options:**

* `'F7'` - F7 key (default)
* `'F6'` - F6 key
* `'K'` - K key
* Any valid FiveM key code

## Distance Settings

### Nearby Player Detection

```lua
Config.NearbyPlayerDistance = 10.0
```

Maximum distance (in meters) to detect nearby players for invoicing.

## Invoice Settings

### Amount Limits

```lua
Config.InvoiceSettings = {
    MinAmount = 1,
    MaxAmount = 999999,
}
```

* `MinAmount` - Minimum invoice amount
* `MaxAmount` - Maximum invoice amount

### Tax Configuration

```lua
Config.InvoiceSettings = {
    TaxRate = 0,  -- 0 = no tax
}
```

**Examples:**

* `0` - No tax
* `0.18` - 18% tax
* `0.25` - 25% tax

{% hint style="info" %}
Tax is automatically calculated and added to invoice total.
{% endhint %}

### Payment Methods

```lua
Config.InvoiceSettings = {
    AllowBankPayment = true,
    AllowCashPayment = true,
}
```

Enable or disable payment methods:

* `AllowBankPayment` - Allow bank transfers
* `AllowCashPayment` - Allow cash payments

### Feature Toggles

```lua
Config.InvoiceSettings = {
    AllowMultiRecipient = true,
    AllowPhotoAttachment = true,
    AllowRecurringPayments = true,
}
```

* `AllowMultiRecipient` - Allow invoices to multiple players
* `AllowPhotoAttachment` - Allow photo attachments
* `AllowRecurringPayments` - Allow installment payments

## Notification Settings

### Duration and Position

```lua
Config.Notifications = {
    Duration = 5000,  -- milliseconds
    Position = 'top-right',
}
```

**Position Options:**

* `'top-right'`
* `'top-left'`
* `'bottom-right'`
* `'bottom-left'`

## Receipt Settings

### Print Receipt Feature

```lua
Config.ReceiptSettings = {
    Enabled = true,
    RequireOxInventory = true,
    ItemName = 'receipt',
}
```

* `Enabled` - Enable receipt printing
* `RequireOxInventory` - Require ox\_inventory
* `ItemName` - Item name to give player

{% hint style="warning" %}
Receipt printing requires ox\_inventory to be installed.
{% endhint %}

## Cashbox Settings

### Per-Job Cashbox

```lua
Config.CashboxSettings = {
    Enabled = true,
    SharedBetweenJobs = false,
}
```

* `Enabled` - Enable cashbox feature
* `SharedBetweenJobs` - Share cashbox between all jobs

## Camera Settings

### Photo Attachment Camera

```lua
Config.CameraSettings = {
    FOV = 70.0,
    MaxPhotos = 3,
    PhotoQuality = 'high',
}
```

* `FOV` - Camera field of view
* `MaxPhotos` - Maximum photos per invoice
* `PhotoQuality` - `'low'`, `'medium'`, `'high'`

## Language Settings

### Default Language

```lua
Config.Language = 'en'
```

**Available Languages:**

* `'en'` - English
* `'tr'` - Turkish

See [Translations](https://github.com/dollar-src/docs/blob/main/billing/gitbook-translations.md) for adding more languages.

## Example Configurations

### Police Department

```lua
Config.AllowedJobs = {'police'}
Config.InvoiceSettings = {
    MinAmount = 50,
    MaxAmount = 50000,
    TaxRate = 0,
    AllowBankPayment = true,
    AllowCashPayment = false,  -- Police only accepts bank
    AllowMultiRecipient = false,
    AllowPhotoAttachment = true,
}
```

### Mechanic Shop

```lua
Config.AllowedJobs = {'mechanic'}
Config.InvoiceSettings = {
    MinAmount = 100,
    MaxAmount = 100000,
    TaxRate = 0.18,  -- 18% tax
    AllowBankPayment = true,
    AllowCashPayment = true,
    AllowMultiRecipient = true,
    AllowPhotoAttachment = true,
}
```

### Hospital

```lua
Config.AllowedJobs = {'ambulance', 'doctor'}
Config.InvoiceSettings = {
    MinAmount = 500,
    MaxAmount = 25000,
    TaxRate = 0,
    AllowBankPayment = true,
    AllowCashPayment = false,
    AllowMultiRecipient = false,
    AllowPhotoAttachment = false,
}
```

## Advanced Configuration

### Billing Menus

Billing menus are configured per-job in the admin panel or database.

See [Admin Guide](https://github.com/dollar-src/docs/blob/main/billing/gitbook-admin-guide.md) for details.

## Validation

After configuring, restart the resource:

```
restart src-billing
```

Check console for any configuration errors.

{% hint style="success" %}
Configuration complete! Test your settings in-game.
{% endhint %}


# User Guide

This guide will help you understand how to use the Billing System tablet and its features as a player and organization member.

## Getting Started

### Opening the Tablet

To open the Billing Tablet, press the configured key (default is **F7**).

{% hint style="info" %}
You must have one of the **allowed jobs** (e.g., Police, Ambulance, Mechanic) to open the tablet for creating invoices. However, all players can open the tablet to view and pay their own invoices.
{% endhint %}

### Home Screen

The home screen displays the main dashboard with statistics and shortcuts to the various "apps" available in the system:

* **My Invoices:** View and pay your personal invoices.
* **Create Invoice:** Issue new invoices to players.
* **Invoice History:** (Job only) View invoices sent by your organization.
* **Admin:** (Boss/Admin only) Manage organization settings.

***

## 💰 Managing Your Invoices

### Viewing Unpaid Invoices

1. Open the tablet and navigate to the **My Invoices** app.
2. Here you will see a list of all your unpaid invoices.
3. Each entry shows the issuer, amount, date, and description.

### Paying an Invoice

1. Locate the invoice you want to pay in the **My Invoices** list.
2. Click the **View** button to see the full details, including any photo attachments.
3. Click the **Pay** button.
4. Select your payment method:
   * **Cash:** Pay using the cash in your wallet.
   * **Bank:** Pay directly from your bank account.
5. Confirm the payment.

### Installment Payments (if enabled)

If the invoice allows installments:

1. You can choose to pay the minimum required amount or any amount above it.
2. The remaining balance will stay as a pending invoice.
3. Recurring payments may be automatically deducted if configured.

***

## 📝 Creating Invoices

### Creating a New Invoice

1. Open the tablet and navigate to the **Create Invoice** app.
2. **Select Player:** Choose a player from the list of nearby citizens.
3. **Add Items:**
   * Click **Add Item** to manually enter an item name and price.
   * Alternatively, use the **Billing Menu** (left side) to select predefined templates (e.g., "Speeding Fine").
4. **Description:** Add a detailed description of why the invoice is being issued.
5. **Attach Photo (Optional):** Click the camera icon to take a photo of the scene (e.g., a traffic violation or repair job).
6. **Send:** Review the total amount and click **Send Invoice**.

### Multi-Recipient Invoices

If enabled, you can select multiple players from the nearby list to send the same invoice to all of them at once. Ideal for group fines or shared service charges.

***

## 📸 Camera Features

The built-in camera allows you to attach visual evidence to invoices.

1. Click the **Camera Icon** in the Create Invoice screen.
2. An overlay will appear. Use **Mouse** to aim and **Click** to take a photo.
3. You can attach up to 3 photos per invoice (configurable).
4. Photos are stored in the database and can be viewed by anyone who has access to the invoice.

***

## 🧾 Receipts

After a successful payment, a digital receipt is generated.

### Viewing Receipts

You can view your past receipts in the **History** section of the **My Invoices** app.

### QR Code Verification

Each receipt features a unique QR code. Admins or other players can scan this (if a scanner is provided) to verify the authenticity of the payment.

### Physical Receipts (ox\_inventory)

If your server uses `ox_inventory`, you may receive a physical "Receipt" item in your inventory upon payment. This item contains the invoice details in its metadata.

***

{% hint style="success" %}
**Tip:** You can use the search bar in the Invoice History to find specific invoices by name, ID, or description.
{% endhint %}


# Admin Guide

This guide is intended for server administrators and organization bosses who manage the Billing System.

## Dashboard Overview

The **Admin App** provides advanced management tools for your organization. To access it, you must have the required boss rank (or be a server admin).

### Key Metrics

The dashboard displays real-time statistics for your job:

* **Total Revenue:** Total money collected through invoices.
* **Pending Invoices:** Total amount of money currently owed to your job.
* **Paid vs Unpaid:** A visual breakdown of payment success.
* **Top Employees:** Who is issuing the most invoices.

***

## 🏦 Cashbox Management

Each organization has its own **Cashbox** (if enabled in `config.lua`). This is where the money from paid invoices is deposited.

### Accessing the Cashbox

1. Open the **Admin App**.
2. Navigate to the **Cashbox** tab.

### Deposit & Withdraw

* **Deposit:** Add money from your personal account to the organization's cashbox.
* **Withdraw:** Take money from the cashbox to your personal account (requires permissions).
* **History:** View all transactions related to the cashbox.

{% hint style="info" %}
In `config.lua`, you can choose whether the cashbox is unique to each job or shared between certain jobs.
{% endhint %}

***

## 📋 Billing Menus

One of the most powerful features is the customizable **Billing Menu**. This allows you to create predefined templates for common invoices.

### Managing Categories

1. In the Admin App, go to **Menu Settings**.
2. Click **Add Category** (e.g., "Traffic Violations", "Criminal Offenses").
3. You can enable or disable categories instantly.

### Adding Item Templates

1. Select a category.
2. Click **Add Item**.
3. Set a **Template Name** (e.g., "Speeding 10+ over").
4. Set a **Predefined Price**.
5. Set a **Default Quantity**.
6. Now, when members of your job create an invoice, they can simply select this item from the list.

***

## 📊 Analytics & Reports

The analytics section provides deep insights into your organization's financial health.

### Exporting Data

You can filter invoices by:

* Date Range
* Employee
* Status (Paid / Unpaid / Cancelled)
* Minimum/Maximum Amount

Use this data to monitor performance or conduct audits.

### Invoice Management

Admins can use the **History** tab to:

* Review any invoice issued by a member of their organization.
* **Cancel Invoice:** If an invoice was issued in error, an admin can cancel it, removing the debt from the player.
* **Mark as Paid:** Manually mark an invoice as paid (e.g., if the player paid via an external method).

***

## ⚙️ Global Admin Settings

The following settings are managed by server administrators via the database (`billing_menus` table) or through the in-game admin menu:

* **Manual Entry:** Allow/Disallow players to type in custom prices.
* **Vehicle Selection:** Enable/Disable the ability to pick a nearby vehicle for an invoice.
* **Recipient Limits:** Set how many people can be billed at once.

***

{% hint style="warning" %}
**Permission Note:** Access to the Admin App is restricted to those with appropriate ranks in the framework (e.g., "boss" in ESX or "god" in QBox). Ensure your framework ranks are correctly configured.
{% endhint %}


# Translations

The Billing System supports multiple languages. By default, it includes English (EN) and Turkish (TR) translations.

## Changing the Language

To change the system language, edit `Config.Language` in `config.lua`:

```lua
Config.Language = 'en' -- Options: 'en', 'tr'
```

***

## Adding a New Language

Adding a new language involves two steps: translating the Lua strings (Server/Client) and the React UI strings.

### Step 1: Lua Translations

1. Navigate to the `languages/` folder.
2. Copy `en.lua` and rename it to your language code (e.g., `fr.lua`).
3. Open the file and translate all the values in the table.
4. Open `fxmanifest.lua` and add your new file to the `files` section:

   ```lua
   files {
       'languages/*.lua', -- This already covers all .lua files in the folder
       -- ...
   }
   ```

### Step 2: UI Translations

The UI translations are managed in the `web/src/locales/translations.ts` file (if you are building from source) or automatically loaded from the Lua files if the bridge supports it.

In this version, the UI automatically fetches translations from the Lua files using the `src-billing:client:openTablet` message.

**To ensure your new language works in the UI:**

1. Ensure your `languages/XX.lua` file is correctly formatted as a Lua table.
2. The `client/main.lua` function `getAllLocales()` should ideally be updated to include your new language if it's not dynamically scanning:

```lua
-- In client/main.lua
local function getAllLocales()
    return {
        en = loadLocaleTable('en'),
        tr = loadLocaleTable('tr'),
        fr = loadLocaleTable('fr'), -- Add your language here
    }
end
```

***

## Translation Contribution

If you translate the system into a new language, please consider sharing it with the community or submitting a Pull Request!

### Current Supported Languages:

* 🇬🇧 **English (en)** - Default
* 🇹🇷 **Turkish (tr)** - Full support

***

{% hint style="success" %}
**Tip:** You can use HTML tags in some descriptions and reason fields if they are supported by the framework's notification system.
{% endhint %}


# Troubleshooting

This guide provides solutions to common issues you might encounter while installing or using the Billing System.

## General Issues

### The Tablet Won't Open (F7 Key)

**Possibilities:**

1. **Wrong Job:** Check if your job is listed in `Config.AllowedJobs` in `config.lua`.
2. **Keybind Conflict:** Another resource might be using the same key. Try changing `Config.Keybind`.
3. **Resource Not Started:** Ensure `src-billing` is started in your `server.cfg`.
4. **Console Errors:** Open the F8 console to check for any client-side JavaScript or Lua errors.

**Solution:**

* Type `/ensure src-billing` in the console to restart the resource.
* Check the F8 console for error messages.
* Ensure you have a job that is allowed to open the tablet.

***

## Technical Issues

### Framework Not Detected

By default, the bridge uses `Config.Framework = 'auto'`. Occasionally, detection might fail if your framework resource is named differently.

**Check:**

* Is your framework (QBox, QBCore, or ESX) started before `src-billing`?
* Are you using a highly modified or renamed version of a framework?

**Solution:**

* Manually set the framework in `config.lua`:

  ```lua
  Config.Framework = 'qbox' -- or 'qb', 'esx'
  ```

### Database Errors

If you see errors related to `MySQL` or missing tables in your server console.

**Check:**

* Did you import `sql/billing.sql`?
* Are the table names exactly as expected?

**Solution:**

* Re-import the SQL file.
* Ensure your database connection string in `server.cfg` is correct.

***

## Payment Issues

### Money Not Being Removed

If a player pays an invoice but no money is taken from their account.

**Check:**

* Check the server console for errors when the payment is made.
* If using a **Custom Framework**, verify your `Bridge.RemoveMoney` implementation in `bridge/server.lua`.

**Solution:**

* Ensure the `Bridge.RemoveMoney` function returns `true` on success and `false` on failure.

### Physical Receipt Not Given

**Check:**

* Is `ox_inventory` started?
* Is `Config.ReceiptSettings.Enabled = true`?
* Do you have the `receipt` item added to your inventory's items list?

***

## UI Issues

### Black Screen or Layout Glitches

If the tablet UI appears broken or shows a black screen.

**Possibilities:**

1. **NUI Focus:** Something might be blocking the NUI focus.
2. **Build Error:** If you modified the web code, the build might have failed.

**Solution:**

* Restart the resource: `ensure src-billing`.
* If you made changes to the `web` folder, run `npm run build` again.
* Clear your local browser cache (FiveM cache folder).

***

## Getting More Help

If you're still having issues:

1. **Check the Logs:** Both server console and F8 client console contain vital clues.
2. **Verify Dependencies:** Ensure `ox_lib` and/or `ox_inventory` are up to date if you're using them.
3. **Open an Issue:** If you believe you've found a bug, please report it on the GitHub repository with detailed logs.


# Bridge Overview

## What is the Bridge System?

The Bridge System is a framework-agnostic layer that allows the Billing System to work with any FiveM framework. It provides a unified API for all core functions, regardless of which framework you're using.

## Supported Frameworks

Out of the box, the bridge supports:

| Framework              | Status            | Auto-Detect |
| ---------------------- | ----------------- | ----------- |
| **QBox** (qbx\_core)   | ✅ Fully Supported | ✅ Yes       |
| **QBCore** (qb-core)   | ✅ Fully Supported | ✅ Yes       |
| **ESX** (es\_extended) | ✅ Fully Supported | ✅ Yes       |
| **Custom**             | 🔧 Easy to Add    | ⚙️ Manual   |

## How It Works

```mermaid
graph LR
    A[Billing System] --> B[Bridge Layer]
    B --> C[QBox]
    B --> D[QBCore]
    B --> E[ESX]
    B --> F[Custom Framework]
```

The bridge automatically detects your framework and routes all function calls to the correct implementation.

## File Structure

```
bridge/
├── shared.lua    # Framework detection & initialization
├── client.lua    # Client-side bridge functions
├── server.lua    # Server-side bridge functions
└── README.md     # Detailed documentation
```

## Key Features

### ✅ Automatic Detection

The bridge automatically detects your framework:

```lua
-- In config.lua
Config.Framework = 'auto'  -- Automatically detects QBox, QBCore, or ESX
```

### ✅ Manual Override

Force a specific framework if needed:

```lua
Config.Framework = 'qbox'  -- or 'qb', 'esx', 'custom'
```

### ✅ Easy to Extend

Adding support for a custom framework is straightforward:

```lua
-- Just implement the required functions
function Bridge.GetPlayer(source)
    if GetFW() == 'custom' then
        return exports['your-core']:GetPlayer(source)
    end
end
```

## Bridge Functions

### Client-Side Functions

| Function                | Purpose              | Returns   |
| ----------------------- | -------------------- | --------- |
| `GetPlayerData()`       | Get player data      | `table`   |
| `GetPlayerJob()`        | Get player's job     | `table`   |
| `GetPlayerName()`       | Get character name   | `string`  |
| `GetPlayerIdentifier()` | Get unique ID        | `string`  |
| `HasAllowedJob()`       | Check if job allowed | `boolean` |
| `GetNearbyPlayers()`    | Get nearby players   | `table`   |
| `Notify(msg, type)`     | Show notification    | `void`    |

### Server-Side Functions

| Function                            | Purpose           | Returns   |
| ----------------------------------- | ----------------- | --------- |
| `GetPlayer(source)`                 | Get player object | `table`   |
| `GetIdentifier(source)`             | Get player ID     | `string`  |
| `GetPlayerNameServer(source)`       | Get name          | `string`  |
| `GetPlayerJobServer(source)`        | Get job           | `table`   |
| `GetPlayerBank(source)`             | Get bank balance  | `number`  |
| `GetPlayerCash(source)`             | Get cash balance  | `number`  |
| `RemoveMoney(source, amount, type)` | Remove money      | `boolean` |
| `AddMoney(source, amount, type)`    | Add money         | `boolean` |
| `GetAllPlayers()`                   | Get all players   | `table`   |
| `IsJobBossServer(source, job)`      | Check if boss     | `boolean` |

## Quick Start

### Using the Bridge

```lua
-- Client-side example
local playerData = Bridge.GetPlayerData()
local job = Bridge.GetPlayerJob()
Bridge.Notify('Invoice sent!', 'success')

-- Server-side example
local Player = Bridge.GetPlayer(source)
local bank = Bridge.GetPlayerBank(source)
local success = Bridge.RemoveMoney(source, 100, 'bank')
```

### Adding Custom Framework

See the detailed guides:

{% content-ref url="<https://github.com/dollar-src/docs/tree/main/billing/gitbook-custom-framework.md>" %}
<https://github.com/dollar-src/docs/tree/main/billing/gitbook-custom-framework.md>
{% endcontent-ref %}

## Critical Functions

### ⚠️ Money Handling

The money functions are **CRITICAL** and must be implemented correctly:

```lua
-- Remove money (MUST validate and return boolean)
Bridge.RemoveMoney(source, amount, 'bank')

-- Add money (MUST be transaction-safe)
Bridge.AddMoney(source, amount, 'cash')
```

{% hint style="danger" %}
**IMPORTANT:** Incorrect money handling can lead to:

* Money duplication exploits
* Money loss bugs
* Transaction failures

Always test money functions thoroughly!
{% endhint %}

## Testing Your Bridge

### Basic Test

```lua
-- In-game console (F8)
/invoice  -- Should open if you have allowed job
```

### Money Test

1. Create invoice for $100
2. Pay with cash
3. Verify money was removed
4. Pay with bank
5. Verify money was removed

### Full Test Checklist

* [ ] Framework detected correctly
* [ ] Player data retrieved
* [ ] Job checks working
* [ ] Nearby players detected
* [ ] Notifications showing
* [ ] Money removal working
* [ ] Money addition working
* [ ] No console errors

## Troubleshooting

### Framework Not Detected

```lua
-- Check current framework
print(Bridge.GetFramework())

-- Manually set
Config.Framework = 'qbox'
```

### Money Functions Not Working

1. Verify player object is valid
2. Check money type ('cash' or 'bank')
3. Add debug prints
4. Check framework documentation

## Next Steps

{% content-ref url="<https://github.com/dollar-src/docs/tree/main/billing/gitbook-client-functions.md>" %}
<https://github.com/dollar-src/docs/tree/main/billing/gitbook-client-functions.md>
{% endcontent-ref %}

{% content-ref url="<https://github.com/dollar-src/docs/tree/main/billing/gitbook-server-functions.md>" %}
<https://github.com/dollar-src/docs/tree/main/billing/gitbook-server-functions.md>
{% endcontent-ref %}

{% content-ref url="<https://github.com/dollar-src/docs/tree/main/billing/gitbook-custom-framework.md>" %}
<https://github.com/dollar-src/docs/tree/main/billing/gitbook-custom-framework.md>
{% endcontent-ref %}


# Client Functions

This page provides detailed information about the functions available in the Client-side Bridge (`bridge/client.lua`).

## Core Functions

### `Bridge.GetPlayerData()`

Returns the full player data table from the framework.

* **Returns:** `table`

### `Bridge.GetPlayerJob()`

Returns the player's current job information.

* **Returns:** `table { name, label, grade, isBoss }`

### `Bridge.GetPlayerName()`

Returns the character's full name.

* **Returns:** `string`

### `Bridge.GetPlayerIdentifier()`

Returns the player's unique character identifier (e.g., Citizen ID, License).

* **Returns:** `string`

***

## Utility Functions

### `Bridge.Notify(message, type, duration)`

Shows a framework-specific notification.

* **Arguments:**
  * `message` (string): The text to display.
  * `type` (string): 'success', 'error', 'info', or 'warning'.
  * `duration` (number): Duration in milliseconds.

### `Bridge.HasAllowedJob()`

Checks if the player's current job is in the allowed jobs list.

* **Returns:** `boolean`

### `Bridge.GetNearbyPlayers()`

Returns a list of players within the detection radius.

* **Returns:** `table`

***

## Example Usage

### Checking Permissions

```lua
if Bridge.HasAllowedJob() then
    print("Player is authorized to issue invoices")
end
```

### Showing a Custom Notification

```lua
Bridge.Notify("Operation successful", "success", 3000)
```

### Getting Character Name

```lua
local name = Bridge.GetPlayerName()
print("Hello, " .. name)
```


# Server Functions

This page provides detailed information about the functions available in the Server-side Bridge (`bridge/server.lua`).

## Player Management

### `Bridge.GetPlayer(source)`

Retrieves the framework's player object for the given server ID.

* **Returns:** `table` (Player Object)

### `Bridge.GetIdentifier(source)`

Retrieves the unique identifier for the player.

* **Returns:** `string`

### `Bridge.GetPlayerNameServer(source)`

Retrieves the character name on the server side.

* **Returns:** `string`

***

## Money & Transactions

### `Bridge.GetPlayerBank(source)`

Gets the bank balance of a player.

* **Returns:** `number`

### `Bridge.GetPlayerCash(source)`

Gets the cash balance of a player.

* **Returns:** `number`

### `Bridge.RemoveMoney(source, amount, type)`

**CRITICAL:** Deducts money from a player.

* **Arguments:**
  * `source` (number): Player server ID.
  * `amount` (number): Amount to remove.
  * `type` (string): 'bank' or 'cash'.
* **Returns:** `boolean` (True if successful, False if insufficient funds or error)

### `Bridge.AddMoney(source, amount, type)`

Adds money to a player's account.

* **Arguments:**
  * `source` (number): Player server ID.
  * `amount` (number): Amount to add.
  * `type` (string): 'bank' or 'cash'.
* **Returns:** `boolean`

***

## Permission Checks

### `Bridge.IsJobBossServer(source, jobName)`

Checks if a player is the boss of a specific job.

* **Returns:** `boolean`

### `Bridge.GetAllPlayers()`

Returns a list of all currently online players.

* **Returns:** `table`

***

## Internal Utilities

### `Bridge.RegisterServerCallback(name, callback)`

Registers a server-side callback that can be triggered from the client.

### `Bridge.TriggerClientEvent(name, target, ...)`

Wrapper for TriggerClientEvent to maintain consistency.

***

## Example Usage

### Safe Money Removal

```lua
local success = Bridge.RemoveMoney(source, 500, 'bank')
if success then
    -- Proceed with the transaction
else
    -- Notify player they can't afford it
end
```

### Checking for Online Player

```lua
local identifier = "CITIZEN_ID"
local target = Bridge.GetPlayerByIdentifier(identifier)
if target then
    print("Player is online with source: " .. target.source)
end
```


# Custom Framework

If you are using a framework other than QBox, QBCore, or ESX, you can still use the Billing System by implementing the Bridge API.

## Implementation Steps

### 1. Set Config

In your `config.lua`, set the framework to `'custom'`:

```lua
Config.Framework = 'custom'
```

### 2. Client-Side Bridge (`bridge/client.lua`)

Open `bridge/client.lua` and locate the `elseif fw == 'custom' then` blocks. You need to implement the following functions:

* `GetPlayerData()`: Should return a table with player information.
* `Notify(msg, type)`: Your framework's notification function.
* `GetNearbyPlayers()`: (Optional) If your framework has a specific way to get nearby players.

```lua
-- Example Client implementation
elseif fw == 'custom' then
    function Bridge.GetPlayerData()
        return exports['my-core']:GetPlayerData()
    end

    function Bridge.Notify(msg, type)
        exports['my-notifications']:Show(msg, type)
    end
end
```

### 3. Server-Side Bridge (`bridge/server.lua`)

Open `bridge/server.lua` and implement the core logic for money and player handling:

* `GetPlayer(source)`: Return your framework's player object.
* `GetPlayerBank(source)`: Return player's bank balance.
* `GetPlayerCash(source)`: Return player's cash balance.
* `RemoveMoney(source, amount, type)`: **CRITICAL** - Deduct money and return boolean.
* `AddMoney(source, amount, type)`: Add money to a player.

```lua
-- Example Server implementation
elseif fw == 'custom' then
    function Bridge.GetPlayer(source)
        return exports['my-core']:GetPlayer(source)
    end

    function Bridge.RemoveMoney(source, amount, type)
        local player = Bridge.GetPlayer(source)
        if player.money >= amount then
            player.removeMoney(amount, type)
            return true
        end
        return false
    end
end
```

### 4. Shared Bridge (`bridge/shared.lua`)

Ensure your framework detection logic is added to `bridge/shared.lua` if you want it to be auto-detected, or just rely on the manual `'custom'` setting.

***

## Required Return Formats

### Player Data

```lua
{
    identifier = "string",
    name = "string",
    job = {
        name = "string",
        label = "string",
        grade = number,
        isBoss = boolean
    }
}
```

### Money Functions

* `RemoveMoney` **MUST** return `true` if deduction was successful, and `false` otherwise. This is vital for preventing payment exploits.

***

## Testing Your Implementation

1. **Verify Startup:** Check the console for `[Billing Bridge] Framework set to: custom`.
2. **Verify Job Check:** Ensure you can open the tablet with an allowed job.
3. **Verify Money:**
   * Try to pay with not enough money (should fail).
   * Try to pay with enough money (should succeed and deduct).
4. **Verify Notifications:** Success/Error messages should appear using your framework's UI.

***

{% hint style="danger" %}
**Security Warning:** Never trust client-side data for money transactions. Always perform the final check and deduction on the server side within the bridge.
{% endhint %}


# API and Exports

The Billing System provides several exports that allow other resources to interact with the system programmatically.

## Client-Side Exports

### Open Tablet

Opens the billing tablet for the player.

```lua
exports['src-billing']:OpenBilling()
```

### Close Tablet

Closes the billing tablet if it is currently open.

```lua
exports['src-billing']:CloseBilling()
```

### Check if Open

Returns `true` if the tablet is currently open, `false` otherwise.

```lua
local isOpen = exports['src-billing']:IsBillingOpen()
```

***

## Server-Side Exports

### Create Invoice

Programmatically create an invoice without using the UI.

```lua
local data = {
    creatorId = "CITIZEN_ID_HERE",
    creatorName = "John Doe",
    creatorJob = "police",
    targetId = "TARGET_CITIZEN_ID",
    targetServerId = 1, -- Optional: trigger notification for online player
    customerName = "Jane Doe",
    amount = 500,
    reason = "Speeding Fine", -- Can be simple string or JSON metadata
    recurring = {
        enabled = true,
        intervalDays = 7,
        totalPayments = 4
    }
}

exports['src-billing']:CreateInvoice(data)
```

### Get Invoice Details

Retrieve data for a specific invoice ID.

```lua
local invoice = exports['src-billing']:GetInvoice("INV-123456")
```

### Get Player Invoices

Retrieve all invoices where the player is either the creator or the target.

```lua
local identifier = "CITIZEN_ID_HERE"
local invoices = exports['src-billing']:GetPlayerInvoices(identifier)
```

### Get Job Settings

Retrieve configuration settings for a specific job.

```lua
local settings = exports['src-billing']:GetJobSettings("police")
```

***

## Events

### Client Events

#### `src-billing:client:receiveInvoice`

Triggered when a player receives a new invoice.

```lua
AddEventHandler('src-billing:client:receiveInvoice', function(invoice)
    -- invoice = { id, amount, creatorName, ... }
end)
```

#### `src-billing:client:invoicePaid`

Triggered when a player successfully pays an invoice.

```lua
AddEventHandler('src-billing:client:invoicePaid', function(invoiceId)
    -- handle payment completion
end)
```

### Server Events

#### `src-billing:server:createInvoice`

Triggered to create a new invoice (Internal use recommended).

***

{% hint style="info" %}
**Note:** When using `CreateInvoice` via export, the system automatically handles database insertion and cache updates.
{% endhint %}


# Coming Soon


