> For the complete documentation index, see [llms.txt](https://safky.gitbook.io/product-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://safky.gitbook.io/product-docs/scripts/s_premium.md).

# PREMIUM

VIP packages, coins, penthouses, vehicle stands, shops, monthly subscription and daily rewards, claimed from one dashboard.

VIP packages, coins, penthouses, vehicle stands, shops, a monthly subscription and daily rewards, claimed and spent from one dashboard.

***

## ✅ Requirements

|                     |                                                                                                       |
| ------------------- | ----------------------------------------------------------------------------------------------------- |
| **Frameworks**      | ESX, QBCore, QBX (detected automatically)                                                             |
| **Dependencies**    | [ox\_lib](https://github.com/overextended/ox_lib), [oxmysql](https://github.com/overextended/oxmysql) |
| **Inventories**     | ox\_inventory, qb, qs, ps, lj, codem, or the framework's own (detected automatically)                 |
| **Penthouse stash** | ox\_inventory by default. Other inventories: `OpenStash` in `config/cl-functions.lua`                 |

***

## 📥 Installation

```
ensure ox_lib
ensure oxmysql
ensure s_premium
```

There is no SQL file. The script creates its tables on first start.

Each package sold on Tebex needs a server command that runs on purchase. `packagename` is the key of the package in `Packages`:

```
purchase_package_tebex {"identifier":"{id}", "packagename":"diamond", "transid":"{transaction}"}
```

The purchase shows up in the player's dashboard, under Pending Purchases, and they claim it with one click. The player's Cfx.re account must be the one that bought.

For a subscription package, add the command that runs on expiry or cancellation:

```
remove_package_tebex {"identifier":"{id}", "packagename":"gold_sub"}
```

It removes any unclaimed purchase of that package and ends the subscription if the active tier is the one of that package. A package name that is not in the config ends the subscription anyway, with a console warning.

***

## ⚙️ Configuration

Everything in `config/` is open. Every option in `config/config.lua` is commented in the file; this page covers the ones that need more than a comment.

### Packages

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-8d48418f0f04efb484f1ccb7b61b98dceef1ef0d%2F01-overview.jpg?alt=media" alt="The Overview tab: a pending Tebex purchase with its Claim button, and the gift code field."><figcaption><p>The Overview tab: a pending Tebex purchase with its Claim button, and the gift code field.</p></figcaption></figure>

A package is a bundle of rewards. Any combination of fields works:

```lua
Packages = {
    ['diamond'] = {
        label = 'Diamond VIP',
        vip_coins     = { amount = 100 },
        account_money = { account = 'bank', amount = 100000 },
        items = { items = {
            { item_name = 'water_bottle', amount = 2 },
        } },
        penthouse = { penthouse_name = 'penthouse1', replace_penthouse = 50 },
        vehicle   = 'adder',
        custom    = { func = 'GiveSecondCharacter', params = { maxChars = 2 } },
    },
    ['gold_sub'] = {
        label   = 'Gold Monthly',
        monthly = 'gold',
    },
},
```

| Field              | What it gives                                                                             |
| ------------------ | ----------------------------------------------------------------------------------------- |
| `vip_coins.amount` | VIP coins                                                                                 |
| `account_money`    | Money in `bank`, `cash` or `black`, mapped to the framework's account names               |
| `items.items`      | Inventory items                                                                           |
| `penthouse`        | A penthouse. If the player already owns it, `replace_penthouse` coins instead             |
| `vehicle`          | A vehicle model, written to the garage table by `SaveVehicle` with a generated plate      |
| `monthly`          | A subscription. The value is a key of `Monthly.tiers`                                     |
| `custom`           | A function of `CustomPackageFunctions` in `config/sv-functions.lua`, called with `params` |

Packages with `monthly` are not offered in the Admin tab: subscriptions start and end through the Tebex commands.

{% hint style="warning" %}
`monthly` must match a tier key exactly, `'gold'` and not `'Gold'`. A value that matches nothing activates the lowest-priority tier and prints a console error.
{% endhint %}

### Monthly subscription

```lua
Monthly = {
    enabled = true,
    defaultInventoryWeight = 75000, -- restored when a subscription ends
    paycheckInterval = 30,          -- minutes, every subscriber is paid at the same time
    tiers = {
        ['gold'] = {
            label    = 'Gold',
            priority = 2,
            color    = '#FFD700',
            perks = {
                inventoryWeight = 200000,
                paycheck = { amount = 15000 },
                dailyPremium = true,
            },
        },
    },
},
```

* `inventoryWeight` is applied by `OnMonthlyActivate` in `config/sv-functions.lua`, which calls ox\_inventory. On another inventory, replace that call.
* `paycheck.amount` goes to the bank account.
* `dailyPremium` unlocks the premium track of the daily rewards.
* A subscription belongs to the Cfx.re account. Activating it on a second character moves it there.

Add perks of your own in `OnMonthlyActivate` and `OnMonthlyDeactivate`.

### Daily rewards

One item per day of the month, on two tracks: `basic` for everyone, `premium` for subscribers whose tier has `dailyPremium`. The calendar resets on the first of each month.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-f294d8fce3477c9b6a4f8e62187bc25ff1b4475f%2F02-daily.jpg?alt=media" alt="The daily rewards calendar, with the Basic and Premium tracks."><figcaption><p>The daily rewards calendar, with the Basic and Premium tracks.</p></figcaption></figure>

```lua
DailyRewards = {
    enabled     = true,
    cooldown    = 60,        -- seconds between claims
    premiumTier = 'monthly', -- 'monthly' or 'none'
    joinReminder = { enabled = true, delay = 10000 }, -- ms after spawn
    rewards = {
        basic   = { [1] = { item = 'bandage', label = 'Bandage', amount = 3 } },
        premium = { [1] = { item = 'armor',   label = 'Armor',   amount = 1 } },
    },
},
```

A reward that does not fit in the inventory is refused and the day stays open. The space check runs on ox\_inventory and qb-inventory; the other inventories try to add the item.

### Shops

Shops live in the Shops tab. Players pay in VIP coins.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-36245fac5d74c3a3de1db6edf2187f9a96a954c5%2F03-shops.jpg?alt=media" alt="A shop in the Shops tab, priced in VIP coins."><figcaption><p>A shop in the Shops tab, priced in VIP coins.</p></figcaption></figure>

```lua
Shops = {
    ['vip_shop'] = {
        label = 'VIP Shop',
        items = {
            { item = 'armor', label = 'Armor', price = 5, amount = 1, dailyLimit = 3, description = 'Standard body armor.' },
        },
    },
},
```

`dailyLimit` is per player and resets at midnight, server time. `amount` and `description` are optional. The icon comes from `Branding.ItemImageUrl` with the item name, or from an `image` field on the item.

### Penthouses

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-4ae7f16964d8c6832520af265d53d984400f6fc5%2F04-penthouses.jpg?alt=media" alt="Owned penthouses, each with stash, closet, doorbell and GPS."><figcaption><p>Owned penthouses, each with stash, closet, doorbell and GPS.</p></figcaption></figure>

```lua
Penthouses = {
    ['penthouse1'] = {
        label     = 'Penthouse',
        image     = 'nui://s_premium/web/assets/penthouse.webp',
        sellable  = false,   -- true shows a Buy button in the dashboard, for coinPrice
        coinPrice = 500,
        blip      = { sprite = 40, col = 48, scale = 0.7 },
        entryOnFoot = {
            enter = vec4(-305.07, -721.07, 28.03, 159.58),  -- door outside
            out   = vec4(-287.81, -722.86, 125.47, 247.9),  -- exit inside
        },
        airspace = {
            altura = 20,
            points = {
                vec3(-269.58, -750.54, 125.9),
                vec3(-303.74, -738.16, 125.9),
                vec3(-294.48, -712.35, 125.9),
                vec3(-259.62, -724.86, 125.9),
            },
        },
        stash  = { coords = vec3(-286.41, -735.21, 125.49), weight = 500000, slots = 50 },
        closet = vec3(-270.1, -731.41, 125.47),
    },
},
```

* An owner enters at the door with `E` and gets a private instance (routing bucket). Visitors ring the bell with `G` and the owner accepts or refuses from a menu.
* `airspace` is a polygon at roof level, `altura` metres high. An owner who flies into it as driver takes the passengers and the vehicle along into the instance. Omit it for a door-only penthouse.
* `stash` is registered on ox\_inventory on start, one stash per owner. On other inventories it is created by `OpenStash` in `config/cl-functions.lua`, which receives the `stash` table.
* `closet` calls `OpenCloset` in `config/cl-functions.lua`. The default opens illenium-appearance; blocks for qb-clothing, fivem-appearance and esx\_skin are commented there.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-a5bd4369cf51a4f3cc9b8956e822150d07f6368d%2F05-heli.jpg?alt=media" alt="A penthouse with a helicopter entry on the roof."><figcaption><p>A penthouse with a helicopter entry on the roof.</p></figcaption></figure>

### Vehicle stands

Two modes. `showcase` is a card in the Vehicles tab with an image, a description and a GPS button. `full` is a catalogue with categories, a price in coins and a test drive.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-4acff5c6be962dee77c9c0c9703081474be6d5fa%2F06-stand.jpg?alt=media" alt="The Vehicles tab: a showcase stand and a full catalogue."><figcaption><p>The Vehicles tab: a showcase stand and a full catalogue.</p></figcaption></figure>

```lua
Stands = {
    ['vip_cars'] = {
        label = 'VIP Cars',
        mode  = 'full',
        vehicleType = 'car',  -- 'car', 'air' or 'boat'
        testDrive = {
            coords      = vec3(-34.6, -1687.4, 28.3),
            radius      = 80.0,
            spawnCoords = vector4(-34.6, -1687.4, 28.3, 210.0),
            duration    = 30,   -- seconds
            blip        = { sprite = 326, col = 46, scale = 0.7, label = 'VIP Test Drive' },
        },
        categories = {
            { label = 'Super', vehicles = {
                { label = 'Nero 2', spawn = 'nero2', price = 10 },
            } },
        },
    },
    ['vip_helis'] = {
        label       = 'VIP Helicopters',
        mode        = 'showcase',
        vehicleType = 'air',
        description = 'Premium helicopters for VIP members.',
        coords      = vec3(-995.5, -2986.6, 13.9),
    },
},
```

* The test drive starts from the dashboard when the player stands inside `radius` of `testDrive.coords`. The vehicle is deleted after `duration`. Keys come from `GiveCarKeys` in `config/cl-functions.lua`: the default is the s\_garages event, with other key scripts commented.
* A bought vehicle is written by `SaveVehicle` in `config/sv-functions.lua`, with a plate generated on the server. The default insert fits s\_garages; blocks for qb-garages, qbx\_garages and esx\_advancedgarage are commented there.
* Thumbnails: a PNG in `web/assets/cars/` named after `spawn` is picked up through `Branding.VehicleImageUrl`. A vehicle can set its own `image` instead.

### Coins

`CoinsSource = 'builtin'` keeps the balances in the `s_premium_coins` table. `'framework'` delegates to `GetCoins`, `AddCoins` and `RemoveCoins` in `config/sv-functions.lua`, which have commented examples for an ESX account, a QB money type and a custom export.

### Branding

`Branding.ItemImageUrl` is the icon template for shops and rewards, `%s` being the item name:

| Inventory       | Template                                       |
| --------------- | ---------------------------------------------- |
| ox\_inventory   | `nui://ox_inventory/web/images/%s.png`         |
| qb-inventory    | `nui://qb-inventory/html/images/%s.png`        |
| qs-inventory    | `nui://qs-inventory/html/images/%s.png`        |
| codem-inventory | `nui://codem-inventory/html/itemimages/%s.png` |

`Branding.BannerUrl` is the image of the Overview tab. It is `nil` by default. To use one, put a PNG in `web/assets/cars/` and set `'nui://s_premium/web/assets/cars/banner.png'`.

### Other files

| File                      | What it holds                                                                                                              |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `config/cl-functions.lua` | `Notify`, `OpenStash`, `OpenCloset`, `GiveCarKeys`                                                                         |
| `config/sv-functions.lua` | `GetCoins`, `AddCoins`, `RemoveCoins`, `OnMonthlyActivate`, `OnMonthlyDeactivate`, `SaveVehicle`, `CustomPackageFunctions` |
| `config/sv-tebex.lua`     | Headless API token and store URL for the Store tab                                                                         |
| `config/sv-webhooks.lua`  | One Discord webhook per log type, plus the bot name. Empty string disables a log.                                          |
| `config/locales/`         | `en` and `pt-pt`. Copy one to add a language; missing keys fall back to English.                                           |
| `config/framework/`       | Framework and inventory detection, item and money functions.                                                               |

***

## 💬 Commands

| Command    | Who      | Description                                                                       |
| ---------- | -------- | --------------------------------------------------------------------------------- |
| `/premium` | Everyone | Opens the dashboard. Members of `AdminPermission.requiredGroup` get the Admin tab |

The name is set in `config/config.lua`. In the Admin tab, staff generate gift codes, send a package to a player (online by server id, offline by Cfx.re id) and delete codes and pending purchases.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-756abd8826acad5a34d03843b5b93d6a93c5739b%2F07-admin.jpg?alt=media" alt="The Admin tab: gift codes, pending purchases and the packages to send."><figcaption><p>The Admin tab: gift codes, pending purchases and the packages to send.</p></figcaption></figure>

***

## 🔌 Integration

```lua
-- server
local tier = exports.s_premium:GetPlayerTier(playerId)        -- 'gold' or nil
local sub  = exports.s_premium:IsMonthlySubscribed(playerId)  -- true or false
local ok   = exports.s_premium:CreateZone(identifier, 'penthouse1')
-- gives the penthouse to that character identifier. false if already owned or the key does not exist

-- client
local sub  = exports.s_premium:IsMonthlySubscribed()
local tier = exports.s_premium:GetMonthlyTier()
```

### Store tab (Tebex Headless API)

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-363d7f8f2d1949c86d372cc7eeca0051075c3668%2F08-store.jpg?alt=media" alt="The Store tab lists the packages of your Tebex store."><figcaption><p>The Store tab lists the packages of your Tebex store.</p></figcaption></figure>

The Store tab lists your Tebex packages in game, with prices and images, and gives the player a checkout link to open in the browser. In `config/sv-tebex.lua`:

```lua
TebexHeadless = {
    enabled  = true,
    token    = 'your public token',
    storeUrl = 'https://yourstore.tebex.io',
}
```

The token is in creator.tebex.io, Developer, API Keys, Headless API. It is read-only. All three values are needed, or the tab stays hidden. The package list is cached for 5 minutes.

***

## 🗂️ Database

| Table                       | Holds                                      | Created                       |
| --------------------------- | ------------------------------------------ | ----------------------------- |
| `s_premium_codes`           | Gift codes waiting to be redeemed          | Always                        |
| `s_premium_tebex_purchases` | Purchases waiting to be claimed            | Always                        |
| `s_premium_penthouses`      | Owner and penthouse key                    | Always                        |
| `s_premium_coins`           | Coin balance per character                 | `CoinsSource = 'builtin'`     |
| `s_premium_monthly`         | Subscription per Cfx.re account, with tier | `Monthly.enabled = true`      |
| `s_premium_dailyrewards`    | Claimed days per character                 | `DailyRewards.enabled = true` |
| `s_premium_shop_purchases`  | Daily purchase counts                      | At least one shop             |
