Explorer
KNOW-PAT-092

FiveM - Inventory Transaction avec Validation : possession, taxe, ticket, durée

Domaine
fivem
Type
pattern
Priorité
P1

Parent : [[INDEX-FIVEM-GAMEPLAY]]

FiveM - Inventory Transaction avec Validation : possession, taxe, ticket, durée

Problème

Les transactions d'inventaire (blanchiment, craft, échange) doivent vérifier que le joueur possède bien les items requis, appliquer des taxes, consommer des tickets, et respecter une durée de traitement.

Solution

Transaction server-side avec 4 validations :

  1. Possession : ox_inventory:Search ou getInventoryItem
  2. Taxe : calcul du montant final après déduction
  3. Ticket : consommation d'un item requis (optionnel)
  4. Durée : Citizen.Wait ou progress bar côté client avec vérification serveur
RegisterServerEvent('stevo_moneywash:cleanmoney')
AddEventHandler('stevo_moneywash:cleanmoney', function(Amount)
    local Player = source
    
    -- 1. Validation possession argent sale
    local black_money = exports.ox_inventory:Search(Player, 'count', 'black_money')
    if black_money < Amount then
        TriggerClientEvent('ox_lib:notify', Player, { type = 'error', description = 'Pas assez d\'argent sale' })
        return
    end
    
    -- 2. Validation ticket (optionnel)
    if Config.UseTickets then
        local ticket = exports.ox_inventory:Search(Player, 'count', 'moneywash_ticket')
        if ticket < 1 then
            TriggerClientEvent('ox_lib:notify', Player, { type = 'error', description = 'Ticket de blanchiment requis' })
            return
        end
        exports.ox_inventory:RemoveItem(Player, 'moneywash_ticket', 1)
    end
    
    -- 3. Retrait argent sale
    exports.ox_inventory:RemoveItem(Player, 'black_money', Amount)
    
    -- 4. Durée de traitement (progressive)
    local duration = math.min(Amount * Config.TimePerDollar, Config.MaxDuration)
    TriggerClientEvent('stevo_moneywash:startProgress', Player, duration)
    
    -- 5. Après durée → ajout argent propre (avec taxe)
    Citizen.SetTimeout(duration, function()
        local finalAmount = math.floor(Amount * (1 - Config.TaxRate))
        exports.ox_inventory:AddItem(Player, 'money', finalAmount)
        TriggerClientEvent('ox_lib:notify', Player, {
            type = 'success',
            description = ('Blanchiment terminé : $%s (taxe %d%%)'):format(finalAmount, Config.TaxRate * 100)
        })
    end)
end)

Pourquoi

  • ox_inventory:Search est server-side et non spoofable
  • La taxe et le ticket créent une économie de friction réaliste
  • La durée empêche le spam instantané de blanchiment

Quand l'utiliser

  • Blanchiment d'argent, craft d'items illégaux
  • Échanges avec intermédiaire (pawn shop, revente)
  • Toute transaction où le joueur doit attendre

Quand NE PAS l'utiliser

  • Transferts directs P2P (pas besoin de durée)
  • Achat instantané en shop (sauf si volontairement frictionné)

Exemples

Correct

-- Transaction avec rollback si le joueur déconnecte pendant le wait
local pendingTransactions = {}

RegisterNetEvent('craft:start')
AddEventHandler('craft:start', function(item, amount)
    local src = source
    local cost = Config.CraftRecipes[item].materials
    
    -- Vérification possession
    for mat, qty in pairs(cost) do
        if exports.ox_inventory:Search(src, 'count', mat) < qty * amount then
            return TriggerClientEvent('notify', src, 'Matériaux manquants')
        end
    end
    
    -- Retrait
    for mat, qty in pairs(cost) do
        exports.ox_inventory:RemoveItem(src, mat, qty * amount)
    end
    
    pendingTransactions[src] = { item = item, amount = amount, startTime = os.time() }
end)

AddEventHandler('playerDropped', function(reason)
    local src = source
    if pendingTransactions[src] then
        -- Remboursement si déconnexion pendant craft
        local tx = pendingTransactions[src]
        for mat, qty in pairs(Config.CraftRecipes[tx.item].materials) do
            exports.ox_inventory:AddItem(src, mat, qty * tx.amount)
        end
        pendingTransactions[src] = nil
    end
end)

Incorrect

-- Le client envoie le montant → spoof possible
RegisterNetEvent('wash:finish')
AddEventHandler('wash:finish', function(amount)
    -- Le client dit "j'ai blanchi 100000$" → le serveur fait confiance
    exports.ox_inventory:AddItem(source, 'money', amount)
end)

Références

  • moneywash-fivemroleplay/server/server.lua L1-31
  • rm_trainheist/server.lua L58-72

Liens connexes

  • [[KNOW-ANT-FIVEM-002-client-side-validation-only|KNOW-ANT-FIVEM-002]]