docsresourcesQb Vehicleshop

QB-VehicleShop - Vehicle Dealership System

The qb-vehicleshop resource provides a comprehensive vehicle dealership system for QBCore servers, featuring multiple dealership locations, financing options, test drives, and dealership management.

Overview

QB-VehicleShop creates realistic vehicle purchasing experiences with multiple dealership types, financing systems, trade-ins, and complete dealership operations. Players can buy, sell, and manage vehicle inventory.

Key Features

  • Multiple Dealerships: Various vehicle categories and locations
  • Financing System: Loans and payment plans
  • Test Drive System: Try before you buy
  • Trade-In System: Vehicle exchange and valuation
  • Dealership Management: Inventory and sales tracking
  • Custom Orders: Special vehicle requests
  • Insurance Integration: Vehicle protection plans
  • Sales Analytics: Performance tracking

Installation

Prerequisites

  • QBCore Framework
  • qb-target (for interaction system)
  • qb-menu (for dealership menus)
  • qb-vehiclekeys (for vehicle access)
  • qb-garage (for vehicle storage)

Installation Steps

  1. Download the Resource
cd resources/[qb]
git clone https://github.com/qbcore-framework/qb-vehicleshop.git
  1. Database Setup
-- Vehicle shop tables
CREATE TABLE IF NOT EXISTS `vehicle_sales` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `citizenid` varchar(50) DEFAULT NULL,
  `vehicle` varchar(100) DEFAULT NULL,
  `plate` varchar(50) DEFAULT NULL,
  `price` int(11) DEFAULT 0,
  `dealership` varchar(100) DEFAULT NULL,
  `salesperson` varchar(50) DEFAULT NULL,
  `financing` tinyint(1) DEFAULT 0,
  `sale_date` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `vehicle_financing` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `citizenid` varchar(50) DEFAULT NULL,
  `vehicle_plate` varchar(50) DEFAULT NULL,
  `total_amount` int(11) DEFAULT 0,
  `monthly_payment` int(11) DEFAULT 0,
  `payments_remaining` int(11) DEFAULT 0,
  `next_payment_date` timestamp DEFAULT NULL,
  `status` varchar(50) DEFAULT 'active',
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `dealership_inventory` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dealership` varchar(100) DEFAULT NULL,
  `vehicle` varchar(100) DEFAULT NULL,
  `stock` int(11) DEFAULT 0,
  `price` int(11) DEFAULT 0,
  `cost` int(11) DEFAULT 0,
  `last_updated` timestamp DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`)
);
  1. Add Job Configuration to qb-core/shared/jobs.lua
['cardealer'] = {
    label = 'Vehicle Dealer',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Sales Associate',
            payment = 50
        },
        ['1'] = {
            name = 'Sales Representative',
            payment = 75
        },
        ['2'] = {
            name = 'Senior Salesperson',
            payment = 100
        },
        ['3'] = {
            name = 'Sales Manager',
            payment = 125,
            isboss = true
        },
        ['4'] = {
            name = 'General Manager',
            payment = 150,
            isboss = true
        }
    }
}
  1. Add to server.cfg
ensure qb-vehicleshop

Configuration

Basic Configuration

Config = {}
 
-- General Settings
Config.UseTarget = true
Config.TestDriveTime = 300 -- 5 minutes
Config.FinancingEnabled = true
Config.TradeInEnabled = true
 
-- Dealership Locations
Config.Dealerships = {
    ["pdm"] = {
        label = "Premium Deluxe Motorsport",
        coords = vector3(-56.79, -1098.75, 26.42),
        blip = {
            sprite = 326,
            color = 3,
            scale = 0.8
        },
        categories = {"compacts", "sedans", "suvs", "coupes", "sports", "super"},
        commission_rate = 0.10, -- 10% commission for salespeople
        zones = {
            management = vector3(-27.47, -1107.13, 27.27),
            testdrive = vector4(-56.79, -1109.85, 26.43, 71.5),
            delivery = vector4(-11.36, -1097.71, 26.67, 339.85)
        }
    },
    ["luxury"] = {
        label = "Luxury Autos",
        coords = vector3(-1255.6, -361.16, 36.91),
        categories = {"super", "sports", "sportsclassics"},
        commission_rate = 0.15,
        zones = {
            management = vector3(-1267.0, -349.86, 36.91),
            testdrive = vector4(-1244.27, -349.97, 36.91, 202.5),
            delivery = vector4(-1231.46, -349.97, 36.91, 294.5)
        }
    },
    ["boats"] = {
        label = "Boat Shop",
        coords = vector3(-729.89, -1315.83, 1.6),
        categories = {"boats"},
        commission_rate = 0.12,
        zones = {
            testdrive = vector4(-722.89, -1327.83, 1.6, 137.5),
            delivery = vector4(-705.89, -1327.83, 1.6, 137.5)
        }
    },
    ["aircraft"] = {
        label = "Aircraft Dealer",
        coords = vector3(-1652.06, -3143.34, 13.99),
        categories = {"helicopters", "planes"},
        commission_rate = 0.20,
        zones = {
            testdrive = vector4(-1617.49, -3155.35, 13.99, 329.5),
            delivery = vector4(-1652.06, -3120.34, 13.99, 60.5)
        }
    }
}
 
-- Vehicle Categories and Pricing
Config.VehicleCategories = {
    ["compacts"] = {
        label = "Compact Cars",
        vehicles = {
            ["blista"] = {price = 15000, stock = 10},
            ["brioso"] = {price = 18000, stock = 8},
            ["dilettante"] = {price = 12000, stock = 12},
            ["issi2"] = {price = 16000, stock = 6}
        }
    },
    ["sedans"] = {
        label = "Sedans",
        vehicles = {
            ["asea"] = {price = 25000, stock = 8},
            ["asterope"] = {price = 28000, stock = 6},
            ["cognoscenti"] = {price = 45000, stock = 4},
            ["emperor"] = {price = 22000, stock = 10}
        }
    },
    ["suvs"] = {
        label = "SUVs",
        vehicles = {
            ["baller"] = {price = 65000, stock = 5},
            ["cavalcade"] = {price = 55000, stock = 6},
            ["dubsta"] = {price = 70000, stock = 4},
            ["fq2"] = {price = 50000, stock = 7}
        }
    },
    ["sports"] = {
        label = "Sports Cars",
        vehicles = {
            ["alpha"] = {price = 150000, stock = 3},
            ["banshee"] = {price = 120000, stock = 4},
            ["comet2"] = {price = 135000, stock = 3},
            ["feltzer2"] = {price = 145000, stock = 2}
        }
    },
    ["super"] = {
        label = "Supercars",
        vehicles = {
            ["adder"] = {price = 1000000, stock = 1},
            ["entityxf"] = {price = 800000, stock = 1},
            ["t20"] = {price = 2200000, stock = 1},
            ["zentorno"] = {price = 725000, stock = 2}
        }
    }
}
 
-- Financing Options
Config.FinancingPlans = {
    [12] = {months = 12, interest_rate = 0.05}, -- 5% APR
    [24] = {months = 24, interest_rate = 0.07}, -- 7% APR
    [36] = {months = 36, interest_rate = 0.09}, -- 9% APR
    [48] = {months = 48, interest_rate = 0.11}  -- 11% APR
}

Trade-In System

-- Vehicle Depreciation Calculation
Config.TradeInSystem = {
    enabled = true,
    base_depreciation = 0.20, -- 20% immediate depreciation
    age_depreciation = 0.05,  -- 5% per month
    condition_multipliers = {
        perfect = 1.0,
        excellent = 0.95,
        good = 0.85,
        fair = 0.70,
        poor = 0.50,
        damaged = 0.30
    },
    modification_bonus = 0.10 -- 10% bonus for modifications
}
 
-- Vehicle Condition Assessment
Config.ConditionFactors = {
    engine_health = 0.3,   -- 30% weight
    body_health = 0.25,    -- 25% weight
    mileage = 0.2,         -- 20% weight
    modifications = 0.15,  -- 15% weight
    age = 0.1              -- 10% weight
}

API Reference

Client Exports

OpenDealership

Open dealership interface.

exports['qb-vehicleshop']:OpenDealership(dealershipId)
 
-- Parameters:
-- dealershipId: ID of dealership to open

StartTestDrive

Begin test drive for a vehicle.

local success = exports['qb-vehicleshop']:StartTestDrive(vehicleModel, dealershipId)
 
-- Parameters:
-- vehicleModel: Vehicle model to test drive
-- dealershipId: Dealership ID
-- Returns: boolean success status

CalculateTradeValue

Calculate trade-in value for a vehicle.

local tradeValue = exports['qb-vehicleshop']:CalculateTradeValue(vehicleData)
 
-- Parameters:
-- vehicleData: Vehicle information including condition
-- Returns: trade-in value

Server Exports

ProcessVehicleSale

Complete a vehicle purchase.

local success = exports['qb-vehicleshop']:ProcessVehicleSale(buyerId, vehicleData, paymentMethod)
 
-- Parameters:
-- buyerId: Buyer's server ID
-- vehicleData: Vehicle information
-- paymentMethod: "cash" or "finance"

CreateFinancingPlan

Set up vehicle financing.

local planId = exports['qb-vehicleshop']:CreateFinancingPlan(citizenId, vehicleData, planDetails)
 
-- Parameters:
-- citizenId: Customer citizen ID
-- vehicleData: Vehicle information
-- planDetails: Financing plan details

UpdateInventory

Update dealership inventory.

exports['qb-vehicleshop']:UpdateInventory(dealership, vehicle, newStock, newPrice)
 
-- Parameters:
-- dealership: Dealership ID
-- vehicle: Vehicle model
-- newStock: New stock amount
-- newPrice: New price

Events

Client Events

-- Vehicle purchased
RegisterNetEvent('qb-vehicleshop:client:vehiclePurchased', function(vehicleData)
    -- Handle vehicle purchase
end)
 
-- Test drive started
RegisterNetEvent('qb-vehicleshop:client:testDriveStarted', function(vehicleModel, timeLimit)
    -- Handle test drive start
end)
 
-- Financing approved
RegisterNetEvent('qb-vehicleshop:client:financingApproved', function(planDetails)
    -- Handle financing approval
end)

Server Events

-- Sale completed
RegisterNetEvent('qb-vehicleshop:server:saleCompleted', function(saleData)
    -- Handle sale completion
end)
 
-- Inventory updated
RegisterNetEvent('qb-vehicleshop:server:inventoryUpdated', function(dealership, vehicle, change)
    -- Handle inventory changes
end)
 
-- Payment due
RegisterNetEvent('qb-vehicleshop:server:paymentDue', function(citizenId, amount)
    -- Handle financing payment
end)

Usage Examples

Vehicle Purchase System

-- Client-side vehicle purchasing
RegisterNetEvent('qb-vehicleshop:client:purchaseVehicle', function(vehicleModel, dealership)
    local vehicleConfig = GetVehicleConfig(vehicleModel)
    if not vehicleConfig then
        QBCore.Functions.Notify("Vehicle not available", "error")
        return
    end
    
    -- Create purchase menu
    local purchaseMenu = {
        {
            header = "Purchase " .. vehicleConfig.label,
            isMenuHeader = true
        },
        {
            header = "Cash Purchase",
            txt = "Pay full amount: $" .. vehicleConfig.price,
            params = {
                event = "qb-vehicleshop:client:processPurchase",
                args = {
                    vehicle = vehicleModel,
                    dealership = dealership,
                    method = "cash"
                }
            }
        },
        {
            header = "Financing Options",
            txt = "View available financing plans",
            params = {
                event = "qb-vehicleshop:client:viewFinancing",
                args = {
                    vehicle = vehicleModel,
                    dealership = dealership
                }
            }
        },
        {
            header = "Trade-In + Purchase",
            txt = "Trade current vehicle",
            params = {
                event = "qb-vehicleshop:client:tradeInPurchase",
                args = {
                    vehicle = vehicleModel,
                    dealership = dealership
                }
            }
        }
    }
    
    exports['qb-menu']:openMenu(purchaseMenu)
end)
 
-- Process cash purchase
RegisterNetEvent('qb-vehicleshop:client:processPurchase', function(data)
    QBCore.Functions.TriggerCallback('qb-vehicleshop:server:canAfford', function(canAfford)
        if canAfford then
            TriggerServerEvent('qb-vehicleshop:server:purchaseVehicle', data)
        else
            QBCore.Functions.Notify("Insufficient funds", "error")
        end
    end, data.vehicle, data.method)
end)
 
-- Server-side purchase processing
RegisterNetEvent('qb-vehicleshop:server:purchaseVehicle', function(purchaseData)
    local src = source
    local player = QBCore.Functions.GetPlayer(src)
    local vehicleConfig = Config.VehicleCategories[GetVehicleCategory(purchaseData.vehicle)].vehicles[purchaseData.vehicle]
    
    if not player or not vehicleConfig then return end
    
    -- Check payment method
    if purchaseData.method == "cash" then
        if player.Functions.RemoveMoney('bank', vehicleConfig.price, 'vehicle-purchase') then
            -- Generate vehicle
            local plate = GenerateRandomPlate()
            local vehicleData = {
                model = purchaseData.vehicle,
                plate = plate,
                mods = {},
                fuel = 100
            }
            
            -- Add vehicle to player garage
            MySQL.insert('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, garage, fuel, engine, body) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', {
                player.PlayerData.license,
                player.PlayerData.citizenid,
                purchaseData.vehicle,
                GetHashKey(purchaseData.vehicle),
                json.encode(vehicleData.mods),
                plate,
                'pillboxgarage',
                100,
                1000,
                1000
            })
            
            -- Record sale
            MySQL.insert('INSERT INTO vehicle_sales (citizenid, vehicle, plate, price, dealership, sale_date) VALUES (?, ?, ?, ?, ?, NOW())', {
                player.PlayerData.citizenid,
                purchaseData.vehicle,
                plate,
                vehicleConfig.price,
                purchaseData.dealership
            })
            
            -- Update inventory
            UpdateDealershipInventory(purchaseData.dealership, purchaseData.vehicle, -1)
            
            TriggerClientEvent('QBCore:Notify', src, 'Vehicle purchased successfully! Plate: ' .. plate, 'success')
            TriggerClientEvent('qb-vehicleshop:client:vehicleDelivered', src, vehicleData)
        else
            TriggerClientEvent('QBCore:Notify', src, 'Payment failed', 'error')
        end
    end
end)

Test Drive System

-- Test drive implementation
local testDriveVehicles = {}
 
RegisterNetEvent('qb-vehicleshop:client:startTestDrive', function(vehicleModel, dealership)
    local dealershipConfig = Config.Dealerships[dealership]
    local spawnCoords = dealershipConfig.zones.testdrive
    
    -- Spawn test drive vehicle
    QBCore.Functions.SpawnVehicle(vehicleModel, function(vehicle)
        SetEntityHeading(vehicle, spawnCoords.w)
        SetVehicleNumberPlateText(vehicle, "TEST")
        SetVehicleFuelLevel(vehicle, 100.0)
        
        -- Set vehicle properties
        SetVehicleModKit(vehicle, 0)
        SetVehicleColours(vehicle, 0, 0)
        
        -- Give keys to player
        TriggerEvent("vehiclekeys:client:SetOwner", GetVehicleNumberPlateText(vehicle))
        
        -- Store test drive data
        testDriveVehicles[GetVehicleNumberPlateText(vehicle)] = {
            vehicle = vehicle,
            startTime = GetGameTimer(),
            dealership = dealership,
            model = vehicleModel
        }
        
        QBCore.Functions.Notify("Test drive started! Return within " .. (Config.TestDriveTime / 60) .. " minutes", "info")
        
        -- Start test drive timer
        CreateThread(function()
            Wait(Config.TestDriveTime * 1000)
            
            local plate = GetVehicleNumberPlateText(vehicle)
            if testDriveVehicles[plate] then
                QBCore.Functions.Notify("Test drive time expired! Please return the vehicle", "warning")
                
                -- Create return blip
                local returnBlip = AddBlipForCoord(dealershipConfig.coords.x, dealershipConfig.coords.y, dealershipConfig.coords.z)
                SetBlipSprite(returnBlip, 326)
                SetBlipColour(returnBlip, 1)
                SetBlipRoute(returnBlip, true)
                BeginTextCommandSetBlipName("STRING")
                AddTextComponentString("Return Test Drive Vehicle")
                EndTextCommandSetBlipName(returnBlip)
                
                -- Auto-return after additional time
                Wait(300000) -- 5 minutes grace period
                if DoesEntityExist(vehicle) then
                    DeleteEntity(vehicle)
                    testDriveVehicles[plate] = nil
                    RemoveBlip(returnBlip)
                    QBCore.Functions.Notify("Test drive vehicle automatically returned", "info")
                end
            end
        end)
        
    end, spawnCoords, true)
end)
 
-- Return test drive vehicle
RegisterNetEvent('qb-vehicleshop:client:returnTestDrive', function()
    local ped = PlayerPedId()
    local vehicle = GetVehiclePedIsIn(ped, false)
    
    if vehicle and vehicle ~= 0 then
        local plate = GetVehicleNumberPlateText(vehicle)
        local testDriveData = testDriveVehicles[plate]
        
        if testDriveData then
            -- Remove vehicle
            DeleteEntity(vehicle)
            testDriveVehicles[plate] = nil
            
            QBCore.Functions.Notify("Test drive completed! Would you like to purchase this vehicle?", "success")
            
            -- Open purchase menu
            Wait(1000)
            TriggerEvent('qb-vehicleshop:client:purchaseVehicle', testDriveData.model, testDriveData.dealership)
        else
            QBCore.Functions.Notify("This is not a test drive vehicle", "error")
        end
    else
        QBCore.Functions.Notify("You must be in a vehicle to return it", "error")
    end
end)

Financing System

-- Vehicle financing implementation
RegisterNetEvent('qb-vehicleshop:client:viewFinancing', function(data)
    local vehicleConfig = GetVehicleConfig(data.vehicle)
    local financingMenu = {
        {
            header = "Financing Options for " .. vehicleConfig.label,
            txt = "Total Price: $" .. vehicleConfig.price,
            isMenuHeader = true
        }
    }
    
    -- Add financing plans
    for months, planData in pairs(Config.FinancingPlans) do
        local monthlyPayment = CalculateMonthlyPayment(vehicleConfig.price, planData.interest_rate, months)
        local totalCost = monthlyPayment * months
        
        table.insert(financingMenu, {
            header = months .. " Month Plan",
            txt = "Monthly: $" .. monthlyPayment .. " | Total: $" .. totalCost .. " | APR: " .. (planData.interest_rate * 100) .. "%",
            params = {
                event = "qb-vehicleshop:client:selectFinancing",
                args = {
                    vehicle = data.vehicle,
                    dealership = data.dealership,
                    months = months,
                    monthly_payment = monthlyPayment,
                    total_cost = totalCost
                }
            }
        })
    end
    
    exports['qb-menu']:openMenu(financingMenu)
end)
 
function CalculateMonthlyPayment(principal, annualRate, months)
    local monthlyRate = annualRate / 12
    local payment = principal * (monthlyRate * math.pow(1 + monthlyRate, months)) / (math.pow(1 + monthlyRate, months) - 1)
    return math.ceil(payment)
end
 
-- Process financing application
RegisterNetEvent('qb-vehicleshop:client:selectFinancing', function(data)
    -- Credit check simulation
    QBCore.Functions.Progressbar("credit_check", "Processing credit application...", 10000, false, true, {
        disableMovement = true,
        disableCarMovement = true,
        disableMouse = false,
        disableCombat = true,
    }, {
        animDict = "amb@world_human_clipboard@male@base",
        anim = "base",
    }, {}, {}, function()
        -- Credit check complete
        TriggerServerEvent('qb-vehicleshop:server:processFinancing', data)
    end)
end)
 
-- Server-side financing processing
RegisterNetEvent('qb-vehicleshop:server:processFinancing', function(financingData)
    local src = source
    local player = QBCore.Functions.GetPlayer(src)
    
    if not player then return end
    
    -- Credit score simulation (simplified)
    local creditScore = CalculateCreditScore(player)
    
    if creditScore >= 600 then -- Minimum credit score
        -- Approve financing
        local downPayment = math.floor(GetVehiclePrice(financingData.vehicle) * 0.1) -- 10% down
        
        if player.Functions.RemoveMoney('bank', downPayment, 'vehicle-downpayment') then
            -- Create financing record
            local nextPaymentDate = os.date("%Y-%m-%d", os.time() + (30 * 24 * 60 * 60)) -- 30 days
            
            MySQL.insert('INSERT INTO vehicle_financing (citizenid, vehicle_plate, total_amount, monthly_payment, payments_remaining, next_payment_date) VALUES (?, ?, ?, ?, ?, ?)', {
                player.PlayerData.citizenid,
                GenerateRandomPlate(),
                financingData.total_cost - downPayment,
                financingData.monthly_payment,
                financingData.months,
                nextPaymentDate
            })
            
            TriggerClientEvent('QBCore:Notify', src, 'Financing approved! Down payment: $' .. downPayment, 'success')
            TriggerClientEvent('qb-vehicleshop:client:financingApproved', src, financingData)
        else
            TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds for down payment', 'error')
        end
    else
        TriggerClientEvent('QBCore:Notify', src, 'Financing application denied - insufficient credit', 'error')
    end
end)

Troubleshooting

Common Issues

Vehicle Spawning Problems

-- Check spawn coordinates
function ValidateSpawnLocation(coords)
    local groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false)
    if math.abs(coords.z - groundZ) > 5.0 then
        print("Invalid spawn height at:", coords)
        return false
    end
    return true
end

Inventory Sync Issues

  • Verify database updates for stock changes
  • Check for concurrent purchase handling
  • Ensure proper transaction rollback on failures

Financing Payment Problems

-- Debug financing system
RegisterCommand('checkfinancing', function()
    QBCore.Functions.TriggerCallback('qb-vehicleshop:server:getFinancing', function(financing)
        if financing then
            print("Active financing plans:", #financing)
            for i = 1, #financing do
                print("Plan", i, "- Remaining:", financing[i].payments_remaining)
            end
        else
            print("No active financing found")
        end
    end)
end)

Debug Commands

-- Give vehicle to player
/givevehicle [player_id] [vehicle_model]
 
-- Update dealership stock
/updatestock [dealership] [vehicle] [amount]
 
-- Check vehicle value
/vehiclevalue [vehicle_model]
 
-- Process financing payment
/payfinancing [player_id] [amount]

Regular monitoring of dealership economics and player purchasing patterns helps maintain server balance.