QB-Shops - General Shopping System

The qb-shops resource provides a comprehensive shopping system for QBCore servers, featuring configurable shops, dynamic pricing, stock management, and support for various shop types including 24/7 stores, hardware stores, and specialty retailers.

Overview

QB-Shops creates an immersive shopping experience with realistic store mechanics, inventory management, and economic systems. Players can purchase items from various shops throughout the map, each with their own product catalogs and pricing structures.

Key Features

  • Multiple Shop Types: 24/7 stores, hardware shops, weapon stores, and custom retailers
  • Dynamic Inventory: Stock management with restocking timers
  • Economic Integration: Price fluctuations and regional pricing
  • Job Restrictions: Shop access based on player jobs or licenses
  • Blip Management: Automatic map markers for shop locations
  • Receipt System: Transaction logging and purchase receipts
  • Admin Tools: Shop management and inventory control
  • Integration Ready: Compatible with qb-inventory and banking systems

Installation

Prerequisites

  • QBCore Framework
  • qb-inventory (for item management)
  • qb-target (for shop interactions)
  • qb-menu (for shop interfaces)

Installation Steps

  1. Download the Resource
cd resources/[qb]
git clone https://github.com/qbcore-framework/qb-shops.git
  1. Database Setup
-- Create shop tables
CREATE TABLE IF NOT EXISTS `shop_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `shop` varchar(255) NOT NULL,
  `item` varchar(50) NOT NULL,
  `price` int(11) NOT NULL,
  `amount` int(11) NOT NULL,
  `info` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '{}' CHECK (json_valid(`info`)),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `shop_receipts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `citizenid` varchar(50) NOT NULL,
  `shop` varchar(255) NOT NULL,
  `item` varchar(50) NOT NULL,
  `amount` int(11) NOT NULL,
  `price` int(11) NOT NULL,
  `date` timestamp NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
  1. Add to server.cfg
ensure qb-shops
  1. Restart Server
restart qb-shops

Ensure qb-shops loads after qb-core, qb-inventory, and qb-target for proper functionality

Configuration

Basic Configuration

The main configuration file is located at config.lua:

Config = {}
 
-- General settings
Config.UseTarget = true
Config.MinimumShopDistance = 2.0
Config.ShowBlips = true
Config.RestockInterval = 60 -- minutes
 
-- Shop types and locations
Config.Shops = {
    ["24/7"] = {
        label = "24/7 Store",
        coords = vector4(372.66, 325.95, 103.57, 255.73),
        ped = 'mp_m_shopkeep_01',
        scenario = 'WORLD_HUMAN_STAND_MOBILE',
        radius = 1.5,
        targetIcon = 'fas fa-shopping-basket',
        targetLabel = 'Open Shop',
        products = {
            [1] = {name = 'sandwich', price = 4, amount = 50, info = {}, type = 'item', slot = 1},
            [2] = {name = 'water_bottle', price = 2, amount = 50, info = {}, type = 'item', slot = 2},
            [3] = {name = 'coffee', price = 3, amount = 25, info = {}, type = 'item', slot = 3},
            [4] = {name = 'cigarette', price = 8, amount = 20, info = {}, type = 'item', slot = 4},
            [5] = {name = 'phone', price = 850, amount = 5, info = {}, type = 'item', slot = 5}
        },
        showblip = true,
        blipsprite = 52,
        blipcolor = 0
    },
    
    ["hardware"] = {
        label = "Hardware Store",
        coords = vector4(45.67, -1749.89, 29.61, 53.5),
        ped = 'ig_terry_01',
        scenario = 'WORLD_HUMAN_STAND_MOBILE',
        radius = 1.5,
        targetIcon = 'fas fa-hammer',
        targetLabel = 'Browse Tools',
        products = {
            [1] = {name = 'lockpick', price = 20, amount = 25, info = {}, type = 'item', slot = 1},
            [2] = {name = 'repairkit', price = 35, amount = 15, info = {}, type = 'item', slot = 2},
            [3] = {name = 'screwdriverset', price = 85, amount = 10, info = {}, type = 'item', slot = 3},
            [4] = {name = 'phone', price = 850, amount = 5, info = {}, type = 'item', slot = 4},
            [5] = {name = 'radio', price = 250, amount = 10, info = {}, type = 'item', slot = 5}
        },
        showblip = true,
        blipsprite = 402,
        blipcolor = 47
    }
}
 
-- Job restricted shops
Config.JobShops = {
    ["police_armory"] = {
        label = "Police Armory",
        coords = vector4(461.45, -979.69, 30.69, 0.0),
        job = "police",
        mingrade = 0,
        products = {
            [1] = {name = 'weapon_flashlight', price = 0, amount = 10, info = {}, type = 'weapon', slot = 1},
            [2] = {name = 'weapon_nightstick', price = 0, amount = 10, info = {}, type = 'weapon', slot = 2},
            [3] = {name = 'weapon_pistol', price = 0, amount = 5, info = {}, type = 'weapon', slot = 3}
        }
    }
}
 
-- Shop settings
Config.ShopSettings = {
    dynamicPricing = true,
    priceFluctuation = 0.1, -- 10% price variation
    restockEnabled = true,
    restockAmount = {min = 10, max = 50},
    receiptEnabled = true
}

Product Categories

Config.ProductCategories = {
    ['food'] = {
        label = 'Food & Beverages',
        icon = 'fas fa-hamburger',
        items = {'sandwich', 'water_bottle', 'coffee', 'taco'}
    },
    ['tools'] = {
        label = 'Tools & Equipment',
        icon = 'fas fa-wrench',
        items = {'lockpick', 'repairkit', 'screwdriverset'}
    },
    ['electronics'] = {
        label = 'Electronics',
        icon = 'fas fa-mobile-alt',
        items = {'phone', 'radio', 'laptop'}
    },
    ['clothing'] = {
        label = 'Clothing',
        icon = 'fas fa-tshirt',
        items = {'mask', 'hat', 'shoes'}
    }
}

Economic Settings

Config.Economy = {
    taxRate = 0.08, -- 8% sales tax
    shopkeeperCut = 0.02, -- 2% goes to shop owner
    baseMarkup = 1.25, -- 25% markup from wholesale
    demandMultiplier = {
        high = 1.5,    -- High demand items cost 50% more
        normal = 1.0,  -- Normal pricing
        low = 0.8      -- Low demand items cost 20% less
    }
}

API Reference

Client Exports

OpenShop

Opens a specific shop interface.

-- Open shop by name
exports['qb-shops']:OpenShop('24/7')
 
-- Open custom shop
exports['qb-shops']:OpenShop('custom_shop', customProducts)

GetShopData

Gets information about a shop.

-- Get shop data
local shopData = exports['qb-shops']:GetShopData('24/7')
print("Shop label: " .. shopData.label)

IsShopOpen

Checks if a shop is currently open.

-- Check if shop is open (for shops with hours)
local isOpen = exports['qb-shops']:IsShopOpen('24/7')

Server Exports

AddShop

Adds a new shop dynamically.

-- Add custom shop
exports['qb-shops']:AddShop('new_shop', {
    label = 'New Shop',
    coords = vector4(100.0, 200.0, 30.0, 0.0),
    products = {
        [1] = {name = 'water_bottle', price = 2, amount = 50, type = 'item', slot = 1}
    }
})

UpdateShopStock

Updates the stock of shop items.

-- Update stock for specific item
exports['qb-shops']:UpdateShopStock('24/7', 'water_bottle', 25)
 
-- Restock entire shop
exports['qb-shops']:RestockShop('24/7')

GetShopStock

Gets current stock levels.

-- Get stock for specific item
local stock = exports['qb-shops']:GetShopStock('24/7', 'water_bottle')
 
-- Get all shop stock
local allStock = exports['qb-shops']:GetAllShopStock('24/7')

SetItemPrice

Sets the price of shop items.

-- Set item price
exports['qb-shops']:SetItemPrice('24/7', 'water_bottle', 3)
 
-- Set price with dynamic calculation
exports['qb-shops']:SetDynamicPrice('24/7', 'water_bottle', 'high') -- high demand

Events

Client Events

-- Shop interaction events
RegisterNetEvent('qb-shops:client:openShop', function(shopName)
    -- Handle shop opening
end)
 
RegisterNetEvent('qb-shops:client:purchaseComplete', function(receipt)
    -- Handle successful purchase
end)
 
RegisterNetEvent('qb-shops:client:restockAlert', function(shopName, item)
    -- Handle restock notifications
end)

Server Events

-- Purchase handling
RegisterNetEvent('qb-shops:server:purchaseItem', function(shopName, item, amount))
RegisterNetEvent('qb-shops:server:sellItem', function(shopName, item, amount))
 
-- Shop management
RegisterNetEvent('qb-shops:server:updateStock', function(shopName, item, amount))
RegisterNetEvent('qb-shops:server:setPrice', function(shopName, item, price))
 
-- Receipt system
RegisterNetEvent('qb-shops:server:generateReceipt', function(shopName, items, total))

Usage Examples

Basic Shop Implementation

-- Create a simple food shop
local foodShop = {
    label = "Joe's Diner",
    coords = vector4(123.45, -234.56, 30.0, 180.0),
    ped = 'a_m_m_farmer_01',
    products = {
        [1] = {name = 'burger', price = 12, amount = 25, type = 'item', slot = 1},
        [2] = {name = 'cola', price = 5, amount = 30, type = 'item', slot = 2},
        [3] = {name = 'fries', price = 8, amount = 20, type = 'item', slot = 3}
    },
    showblip = true,
    blipsprite = 93,
    blipcolor = 2
}
 
exports['qb-shops']:AddShop('joes_diner', foodShop)

Dynamic Pricing System

-- Implement demand-based pricing
RegisterNetEvent('qb-shops:server:calculateDynamicPrice', function(shopName, itemName)
    local basePrice = GetItemBasePrice(itemName)
    local demand = CalculateItemDemand(itemName)
    local supply = exports['qb-shops']:GetShopStock(shopName, itemName)
    
    local priceMultiplier = 1.0
    
    -- High demand, low supply = higher prices
    if demand > 80 and supply < 10 then
        priceMultiplier = 1.5
    elseif demand < 20 and supply > 50 then
        priceMultiplier = 0.8
    end
    
    local newPrice = math.floor(basePrice * priceMultiplier)
    exports['qb-shops']:SetItemPrice(shopName, itemName, newPrice)
end)

Job-Restricted Shop Access

-- Police equipment shop
RegisterNetEvent('qb-shops:client:checkPoliceAccess', function()
    local PlayerData = QBCore.Functions.GetPlayerData()
    
    if PlayerData.job.name == 'police' and PlayerData.job.grade.level >= 1 then
        exports['qb-shops']:OpenShop('police_armory')
    else
        QBCore.Functions.Notify('Access denied - Police personnel only', 'error')
    end
end)

Custom Shop Categories

-- Electronics shop with categories
local electronicsShop = {
    label = "Tech World",
    coords = vector4(400.0, -500.0, 35.0, 90.0),
    categories = {
        ['phones'] = {
            [1] = {name = 'phone', price = 850, amount = 10, type = 'item', slot = 1},
            [2] = {name = 'phone_case', price = 25, amount = 20, type = 'item', slot = 2}
        },
        ['computers'] = {
            [1] = {name = 'laptop', price = 2500, amount = 5, type = 'item', slot = 1},
            [2] = {name = 'tablet', price = 1200, amount = 8, type = 'item', slot = 2}
        }
    }
}

Receipt and Transaction Logging

-- Advanced purchase handling with receipts
RegisterNetEvent('qb-shops:server:advancedPurchase', function(shopName, items)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    local totalCost = 0
    local receipt = {
        shop = shopName,
        items = {},
        timestamp = os.time(),
        customer = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname
    }
    
    -- Calculate total and validate stock
    for _, item in pairs(items) do
        local stock = exports['qb-shops']:GetShopStock(shopName, item.name)
        if stock >= item.amount then
            local itemCost = item.price * item.amount
            totalCost = totalCost + itemCost
            
            table.insert(receipt.items, {
                name = item.name,
                amount = item.amount,
                price = item.price,
                total = itemCost
            })
        end
    end
    
    -- Process payment
    if Player.Functions.RemoveMoney('cash', totalCost) then
        -- Give items and update stock
        for _, item in pairs(items) do
            exports['qb-inventory']:AddItem(src, item.name, item.amount)
            exports['qb-shops']:UpdateShopStock(shopName, item.name, -item.amount)
        end
        
        -- Generate receipt
        receipt.total = totalCost
        TriggerClientEvent('qb-shops:client:showReceipt', src, receipt)
        
        -- Log transaction
        exports['qb-shops']:LogTransaction(Player.PlayerData.citizenid, receipt)
    end
end)

Automatic Restocking System

-- Automated restocking with configurable intervals
CreateThread(function()
    while true do
        Wait(Config.RestockInterval * 60000) -- Convert minutes to milliseconds
        
        for shopName, shopData in pairs(Config.Shops) do
            for _, product in pairs(shopData.products) do
                local currentStock = exports['qb-shops']:GetShopStock(shopName, product.name)
                local maxStock = product.amount
                
                -- Restock if below 25% capacity
                if currentStock < (maxStock * 0.25) then
                    local restockAmount = maxStock - currentStock
                    exports['qb-shops']:UpdateShopStock(shopName, product.name, restockAmount)
                    
                    print("Restocked " .. shopName .. " - " .. product.name .. " +" .. restockAmount)
                end
            end
        end
    end
end)

Administrative Commands

Player Commands

  • /shops - List nearby shops
  • /receipt - View last purchase receipt
  • /shopinfo [shop] - Get information about a shop

Admin Commands

  • /addshop [name] [x] [y] [z] - Create new shop at location
  • /removeshop [name] - Remove existing shop
  • /restockshop [name] - Manually restock shop
  • /setshopprice [shop] [item] [price] - Set item price
  • /shopstock [shop] [item] [amount] - Set item stock

Console Commands

# Restock all shops
restockall
 
# Set shop hours
setshophours 24/7 0 24
 
# Generate shop report
shopreport

Integration with Other Resources

qb-inventory Integration

Advanced item handling and metadata:

-- Purchase with item metadata
RegisterNetEvent('qb-shops:server:purchaseWeapon', function(shopName, weaponName)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    local weaponData = {
        durability = 100,
        ammo = 0,
        serial = GenerateSerial(),
        registered = Player.PlayerData.citizenid
    }
    
    exports['qb-inventory']:AddItem(src, weaponName, 1, false, weaponData)
end)

qb-banking Integration

Shop payments through banking:

-- Credit card payments
RegisterNetEvent('qb-shops:server:cardPayment', function(shopName, amount)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    
    if exports['qb-banking']:RemoveMoney(Player.PlayerData.citizenid, 'bank', amount, 'Shop purchase - ' .. shopName) then
        TriggerClientEvent('qb-shops:client:paymentSuccess', src)
    else
        TriggerClientEvent('qb-shops:client:paymentFailed', src)
    end
end)

qb-phone Integration

Shop delivery and ordering:

-- Phone app for shop orders
RegisterNetEvent('qb-phone:server:shopOrder', function(shopName, items, deliveryAddress)
    local src = source
    local deliveryFee = CalculateDeliveryFee(deliveryAddress)
    local totalCost = CalculateOrderTotal(items) + deliveryFee
    
    -- Process order and schedule delivery
    if ProcessPayment(src, totalCost) then
        ScheduleDelivery(src, shopName, items, deliveryAddress, 15) -- 15 minute delivery
    end
end)

Troubleshooting

Common Issues

Shop Not Appearing

Problem: Shop doesn’t show up or can’t be interacted with.

Solutions:

  1. Check coordinates and radius settings
  2. Verify qb-target configuration
  3. Check for conflicting resources
-- Debug shop detection
RegisterCommand('debugshop', function()
    local coords = GetEntityCoords(PlayerPedId())
    local nearestShop = GetNearestShop(coords)
    
    if nearestShop then
        print("Nearest shop: " .. nearestShop.label)
        print("Distance: " .. #(coords - nearestShop.coords))
    else
        print("No shops found nearby")
    end
end, false)

Purchase Failures

Problem: Items can’t be purchased or transactions fail.

Solutions:

  1. Check player funds
  2. Verify item existence in qb-inventory
  3. Check shop stock levels
-- Debug purchase process
RegisterNetEvent('qb-shops:debug:purchase', function(shopName, itemName, amount)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    local stock = exports['qb-shops']:GetShopStock(shopName, itemName)
    local playerMoney = Player.PlayerData.money.cash
    
    print("Player: " .. Player.PlayerData.charinfo.firstname)
    print("Item: " .. itemName .. " Amount: " .. amount)
    print("Stock available: " .. stock)
    print("Player cash: $" .. playerMoney)
end)

Stock Issues

Problem: Shop stock doesn’t update or restocking fails.

Solution:

-- Manual stock reset
RegisterCommand('resetstock', function(source, args)
    local shopName = args[1]
    if shopName and Config.Shops[shopName] then
        for _, product in pairs(Config.Shops[shopName].products) do
            exports['qb-shops']:UpdateShopStock(shopName, product.name, product.amount, true) -- true = set absolute amount
        end
        print("Stock reset for " .. shopName)
    end
end, true)

Performance Optimization

-- Optimize shop updates
Config.UpdateFrequency = 30000 -- Update every 30 seconds
Config.MaxShopDistance = 50.0 -- Only process shops within 50 units
 
-- Disable features if not needed
Config.DynamicPricing = false
Config.RestockSystem = false
Config.ReceiptSystem = false

Support

For issues and feature requests: