Ir al contenido

Hooks del servidor

Los hooks de ciclo de vida están en server/handlers.lua y se llaman Handlers.On…. Se ejecutan después de que algo haya pasado y no pueden deshacerlo; lo que devuelvan se ignora. Toda tabla data trae además hook, timestamp, schemaVersion y resource (detalles).

Área Hooks
Ventas y subastas OnSaleCompleted, OnAuctionEnded, OnReverseOfferAccepted
Anuncios OnListingCreated, OnListingExpired, OnListingCancelled, OnListingBumped
Pujas OnBidPlaced, OnProxyBidSet
Repartidor NPC OnDeliveryCompleted, OnDeliveryFailed, OnDeliveryRobbed
Intercambios en persona y trueque OnInPersonTradeCompleted, OnInPersonTradeCancelled, OnMeetupStatusChanged, OnBarterProposalCreated, OnBarterProposalStatusChanged
Reembolsos OnRefundRequested, OnRefundApproved, OnRefundRejected
Social OnPlayerRated, OnMessageSent, OnFavoriteChanged, OnFollowChanged, OnWishlistChanged, OnReportCreated
Perfil y buzón OnProfileCreated, OnProfileUpdated, OnMailboxClaimed
Administración OnAdminAction
Callbacks de config.lua Config.Notify, Config.OnDeliveryRobbed

Cuándo: un comprador paga un anuncio de precio fijo, un lote o una subasta holandesa, o usa Buy Now en una subasta. Se dispara en cuanto el pago queda registrado.

Lo que pasa después depende del modo de entrega, que te indican data.deliveryMode y data.stage:

deliveryMode stage settlementPending Qué pasa después
'mailbox' 'settled' false El objeto ya está en el inventario del comprador (o en su buzón del marketplace si no le cabía). No se dispara nada más
'meetup' 'settled' false El objeto espera en el buzón del comprador para recogerlo en el punto de recogida. No se dispara nada más
'npc' 'committed' true Sale una furgoneta de reparto. Más tarde se dispara OnDeliveryCompleted, OnDeliveryFailed u OnDeliveryRobbed
'person' 'committed' true Se crea un intercambio en persona (el vendedor tiene que estar conectado o la compra se rechaza). Más tarde se dispara OnInPersonTradeCompleted u OnInPersonTradeCancelled
Campo Significado
buyerSource, buyerId, buyerName El comprador
sellerSource, sellerId, sellerName El vendedor (sellerSource es nil si está desconectado)
item, itemLabel, itemMetadata, quantity Lo vendido
category, rarity Categoría y rareza
basePrice Precio del anuncio pagado (con descuentos), sin extras
grossPaid Total pagado por el comprador
platformFee, deliveryFee, modeSurcharge, npcDeliveryFee, insuranceFee Comisiones y extras (campos de dinero)
sellerPayout Lo que recibe el vendedor
price Alias antiguo de basePrice
deliveryMode 'mailbox', 'meetup', 'npc' o 'person'
listingType 'fixed', 'auction' (Buy Now), 'dutch' o 'bundle'
stage, settlementPending Ver la tabla de arriba
listingId ID del anuncio

Ejemplo: XP para el vendedor cuando la venta es definitiva

Handlers.OnSaleCompleted = function(data)
if data.settlementPending then return end -- repartidor / en persona: espera al hook final
TriggerEvent('yourxp:addXP', data.sellerId, math.floor(data.basePrice / 100))
end

Cómo probarlo

  1. Con el registrador activo y Config.Debug = true, ejecuta /markettest listing mailbox fixed bread 50.
  2. Date dinero si hace falta: /markettest grant 1000.
  3. Abre el marketplace y compra el anuncio Test item: bread.
  4. La consola muestra OnSaleCompleted con deliveryMode = mailbox y stage = settled.
  5. Repite con /markettest listing npc fixed bread 50 para ver stage = committed y settlementPending = true.

Cuándo: una subasta ascendente o a ciegas llega a su hora de fin con al menos una puja. El script revisa las subastas terminadas cada 30 segundos, así que puede dispararse hasta 30 s después de la hora de fin. El objeto ya está en el buzón del ganador (o en el punto de recogida) y el dinero en el buzón del vendedor.

Una subasta que termina sin pujas dispara OnListingExpired.

Campo Significado
winnerSource, winnerId, winnerName La puja más alta
sellerSource, sellerId, sellerName El vendedor
item, itemLabel, itemMetadata, category, rarity, quantity El objeto
winningBid / grossPrice Puja ganadora
platformFee, sellerPayout Comisión y lo que recibe el vendedor
finalPrice Alias antiguo de sellerPayout
listingType 'auction' o 'blind'
deliveryMode, meetupPoint Cómo lo recoge el ganador
stage 'settled'
listingId ID del anuncio

Ejemplo: anunciar las subastas grandes en el chat

Handlers.OnAuctionEnded = function(data)
if data.winningBid >= 100000 then
TriggerClientEvent('chat:addMessage', -1, {
args = { 'Casa de subastas', ('%s ganó %s por $%s'):format(data.winnerName, data.itemLabel, data.winningBid) }
})
end
end

Cómo probarlo

  1. /markettest listing mailbox auction bread 100 crea una subasta de prueba que termina en 5 minutos.
  2. Puja desde tu cuenta.
  3. Espera 5 minutos (más hasta 30 s). La consola muestra OnAuctionEnded y tu cliente recibe sl-marketplace:onAuctionWon.

Cuándo: en una subasta inversa (un comprador publica “busco X, hasta $Y”), el comprador acepta la oferta de uno de los vendedores. El objeto del vendedor pasa al comprador, se paga al vendedor y se devuelve el resto del depósito del comprador.

Campo Significado
buyerSource, buyerId, buyerName Autor de la solicitud (el que paga)
sellerSource, sellerId, sellerName El vendedor cuya oferta se aceptó
item, itemLabel, itemMetadata, quantity El objeto
grossPrice Oferta aceptada
platformFee, sellerPayout Comisión y lo que recibe el vendedor
depositRefund Parte del depósito que se devuelve al comprador
messageId, listingId La oferta y la solicitud
stage 'settled'

Cómo probarlo: dos jugadores. A crea un anuncio inverso; B le envía una oferta desde el anuncio; A la acepta. Antes se ejecuta CanBuyListing con action = 'accept_reverse_offer'.

Cuándo: se ha publicado un anuncio de cualquier tipo (el objeto ya ha salido del inventario del vendedor). El cliente del vendedor recibe también sl-marketplace:onListingCreated.

Campo Significado
sellerSource, sellerId, sellerName El vendedor
item, itemLabel, itemKind, itemMetadata, category, rarity, quantity El objeto
price / basePrice Precio o precio de salida
buyNowPrice Precio de Buy Now, o nil
listingType 'fixed', 'auction', 'blind', 'dutch', 'reverse', 'bundle' o 'barter'
deliveryMode, deliveryModes, meetupPoint Opciones de entrega
featured, featuredCost Si se destacó y el coste
expiresAt Hora de fin (segundos Unix), o nil en anuncios que no caducan
images, bundleItems, wantedItems, wantsAny Contenido extra
listingId ID del nuevo anuncio

Ejemplo: registrar los anuncios destacados

Handlers.OnListingCreated = function(data)
if data.featured then
print(('[market] %s pagó $%d por destacar %s'):format(data.sellerName, data.featuredCost, data.itemLabel))
end
end

Cómo probarlo: abre el marketplace, publica cualquier objeto y mira la consola.

Cuándo: un anuncio con hora de fin llega a ella sin venderse. La comprobación se hace cada 30 segundos.

Solo caducan estos tipos de anuncio: subasta y subasta a ciegas sin pujas, subasta holandesa, subasta inversa y trueque. Los anuncios de precio fijo y los lotes no caducan nunca.

listingType Qué hace el script returnMode
'auction', 'blind' (sin pujas) Devuelve el objeto al buzón del vendedor 'mailbox'
'dutch' Devuelve el objeto al buzón del vendedor 'mailbox'
'barter' Devuelve el objeto al vendedor; rechaza todas las propuestas abiertas y devuelve sus objetos y dinero 'mailbox'
'reverse' Devuelve el depósito del comprador (si Config.Auctions.Reverse.DepositRequired) 'deposit_refund' o 'none'
Campo Significado
sellerSource, sellerId, sellerName Dueño del anuncio
item, itemLabel, itemMetadata, category, rarity, quantity El objeto
price / basePrice Precio del anuncio
listingType Tipo (ver arriba)
deliveryMode, meetupPoint Opciones de entrega
reason Por ahora siempre 'timeout'
returnMode Ver arriba
expiredAt Cuándo caducó (segundos Unix)
listingId ID del anuncio

Cómo probarlo: /markettest listing mailbox auction bread 100 y no pujes. A los 5 minutos (más hasta 30 s) la consola muestra OnListingExpired con returnMode = mailbox.

Cuándo: se cancela un anuncio, por su dueño o por un administrador desde el panel. El objeto se devuelve al vendedor.

Campo Significado
actorSource, actorId, actorName Quién lo canceló
sellerSource, sellerId, sellerName Dueño del anuncio
item, itemLabel, itemMetadata, quantity, price El anuncio
listingType Tipo
cancellationType 'owner' o 'admin'
listingId ID del anuncio

La cancelación del dueño pasa antes por CanCancelListing; la de un administrador dispara además OnAdminAction con action = 'force_cancel'.

Cómo probarlo: publica algo y cancélalo (cancellationType = 'owner'). Después publica otra vez y cancélalo desde /marketadmin ('admin').

Cuándo: un vendedor paga Config.BumpCost para volver a subir uno de sus anuncios a lo más alto de los resultados. Limitado a una vez cada 5 minutos (Config.RateLimits.bump).

Campo Significado
sellerSource, sellerId, sellerName El vendedor
item, itemLabel, quantity El anuncio
cost Lo que pagó
bumpedAt Cuándo (segundos Unix)
listingId ID del anuncio

Cómo probarlo: sube (bump) uno de tus anuncios desde el marketplace.

Cuándo: se acepta una puja en una subasta. Se dispara con las pujas hechas a mano (bidType = 'manual') y con las pujas automáticas que hace la puja proxy en nombre de alguien (bidType = 'proxy_auto').

Campo Significado
bidderSource, bidderId, bidderName Quién pujó
sellerId, sellerName El vendedor
item, itemLabel El objeto
amount Nueva puja
previousBid Puja más alta anterior
bidType 'manual' o 'proxy_auto'
listingType 'auction' o 'blind'
listingId ID del anuncio

Ejemplo: avisar al staff de subidas sospechosas

Handlers.OnBidPlaced = function(data)
if data.previousBid and data.previousBid > 0 and data.amount > data.previousBid * 10 then
print(('[market] salto grande en #%d: %d -> %d por %s'):format(data.listingId, data.previousBid, data.amount, data.bidderName))
end
end

Cómo probarlo: puja en una subasta de prueba (/markettest listing mailbox auction bread 100). Para ver proxy_auto, un segundo jugador fija un máximo automático y después tú le superas.

Cuándo: un jugador fija o cambia su máximo de puja automática (Config.Auctions.ProxyBidding.Enabled). El script pujará por él automáticamente hasta esa cantidad.

Campo Significado
bidderSource, bidderId, bidderName El jugador
sellerId, sellerName El vendedor
maxAmount Su máximo
minimumBid Puja mínima válida cuando lo fijó
listingId ID del anuncio

Cómo probarlo: abre una subasta de prueba y fija un máximo automático en lugar de una puja normal.

Cuándo: una furgoneta de reparto llega al comprador y le entrega el objeto; en ese momento se paga al vendedor. Es el momento final de una venta por repartidor. El cliente del comprador recibe también sl-marketplace:onDeliveryArrived y sl-marketplace:onDeliveryResult (result = 'delivered').

Campo Significado
deliveryId ID del reparto
buyerSource, buyerId, buyerName El comprador
sellerSource, sellerId, sellerName El vendedor
item, itemLabel, itemMetadata, quantity El objeto
basePrice, grossPaid, platformFee, deliveryFee, modeSurcharge, npcDeliveryFee, insuranceFee, sellerPayout Campos de dinero
price Alias antiguo de sellerPayout
deliveryMode, stage 'npc', etapa final
listingId ID del anuncio

Cómo probarlo: /markettest listing npc fixed bread 50, cómpralo y espera a la furgoneta (entre 1 y 2 minutos con los MinDistance / MaxDistance por defecto).

Cuándo: un reparto termina sin entregar el objeto (y sin que lo atraquen). Al comprador se le reembolsa automáticamente.

reason Causa
'timeout' La furgoneta no llegó dentro de Config.Gameplay.NPCDelivery.DeliveryTimeout (600 s por defecto)
'buyer_offline' El comprador salió del servidor durante el reparto
'destroyed' La furgoneta fue destruida
'unknown' Cualquier otro fallo
Campo Significado
deliveryId ID del reparto
buyerSource, buyerId, buyerName, sellerSource, sellerId, sellerName Los jugadores
item, itemLabel, itemMetadata, quantity El objeto
price / basePrice, grossPaid Lo que se cobró
refundAmount Lo reembolsado al comprador
deliveryFee, modeSurcharge, npcDeliveryFee, insuranceFee Extras
deliveryMode, stage, reason, listingId Detalles

Cómo probarlo: compra un anuncio de prueba npc y, mientras viene la furgoneta, destrúyela (destroyed) o desconéctate (buyer_offline). Para timeout, pon temporalmente DeliveryTimeout = 30 y bloquéale el camino.

Cuándo: otro jugador atraca una furgoneta de reparto: fuerza el maletero (el minijuego de puntería) o, si Config.Gameplay.NPCDelivery.PoliceAlertOnKill es true, mata al conductor. Se dispara una sola vez por reparto, con lo que ocurra primero. Justo antes se ejecuta Config.OnDeliveryRobbed. El cliente del atracador recibe sl-marketplace:onDeliveryRobbed.

Campo Significado
deliveryId ID del reparto
thiefSource, thiefId, thiefName El atracador
buyerId, sellerId Comprador y vendedor
item, itemLabel, itemMetadata, quantity La carga
value Valor de la carga
trigger 'cargo_stolen' o 'driver_killed'
stage 'robbed'
listingId ID del anuncio

Ejemplo: aviso a la policía

Handlers.OnDeliveryRobbed = function(data)
local coords = GetEntityCoords(GetPlayerPed(data.thiefSource))
TriggerEvent('your_dispatch:alert', {
code = '10-90', message = 'Atraco a una furgoneta de reparto', coords = coords, job = 'police'
})
end

Cómo probarlo: dos jugadores. El jugador B compra un anuncio por repartidor (/markettest listing npc fixed bread 50); el jugador A intercepta la furgoneta, amenaza al conductor y supera el minijuego del maletero. El pedido tiene que valer al menos MinValueForIntercept.

Cuándo: los dos jugadores han confirmado el intercambio cara a cara, en una venta con modo de entrega en persona o en un trueque. Es cuando los objetos y el dinero cambian de manos de verdad. Los dos clientes reciben sl-marketplace:onInPersonTradeCompleted.

Campo Significado
tradeId ID del intercambio
buyerSource, buyerId, buyerName, sellerSource, sellerId, sellerName Los jugadores
item, itemLabel, quantity El objeto (nil en un trueque)
price / basePrice Precio (0 en un trueque)
grossPaid, platformFee, sellerPayout Dinero
isBarter, tradeKind true / 'barter' o false / 'person'
barter Lo que dio cada parte, en un trueque
stage 'settled'
listingId ID del anuncio

Cómo probarlo: dos jugadores, los dos conectados. B compra el anuncio de A eligiendo en persona, acuerdan un punto de encuentro en el chat, se reúnen allí y confirman los dos (se ejecuta CanConfirmInPersonTrade para cada uno).

Cuándo: se cancela un intercambio en persona o un trueque: por cualquiera de los dos jugadores, porque uno salió de la zona de encuentro durante más de AbandonTimeoutSeconds o porque pasó TimeoutMinutes. A los dos se les reembolsa automáticamente.

Campo Significado
tradeId ID del intercambio
buyerSource, buyerId, buyerName, sellerSource, sellerId, sellerName Los jugadores
item, itemLabel, quantity, price / basePrice El trato
isBarter, tradeKind, stage Detalles
reason Por qué, como texto (por ejemplo un tiempo agotado o quién lo canceló)
listingId ID del anuncio

Cómo probarlo: empieza un intercambio en persona como en el anterior y cancélalo desde el chat.

Cuándo: el punto de encuentro de un intercambio en persona cambia de estado en el chat.

status Significado
'proposed' Un jugador propuso un lugar
'accepted' El otro jugador lo aceptó (aparecen el blip y los avisos)
'declined' El otro jugador lo rechazó
'cancelled' El autor lo retiró
'expired' Nadie respondió en MeetRequestTimeoutSeconds (300 s)
Campo Significado
actorSource, actorId, actorName Quién lo cambió
partnerSource, partnerId El otro jugador
status Ver arriba
tradeIds Intercambios que cubre este encuentro
coords Lugar propuesto
windowEndsAt Cuándo se cierra la ventana del encuentro

Cómo probarlo: en un intercambio en persona, propón un lugar desde el chat y acéptalo o recházalo con el otro jugador.

Cuándo: se ha enviado una propuesta de trueque o una contraoferta del vendedor (después de CanCreateBarterProposal). Los objetos y el dinero ofrecidos quedan retenidos en depósito.

Campo Significado
actorSource, actorId Quién la envió
recipientSource, recipientId Quién la recibe
proposalType 'proposal' o 'counter'
offeredItems, wantedItems, offerAmount Contenido
deliveryMode Modo de entrega elegido
messageId, listingId IDs

Cómo probarlo: dos jugadores; B envía una propuesta al anuncio de trueque de A y A le hace una contraoferta.

Cuándo: una propuesta de trueque cambia de estado.

status Significado
'accepted' Aceptada; se crea el intercambio
'countered' Respondida con una contraoferta
'rejected' Rechazada; se devuelven los objetos y el dinero ofrecidos
'cancelled' Retirada por su autor
'completed' El intercambio terminó
Campo Significado
actorSource, actorId, actorName Quién la cambió
proposalFromId, proposalToId Autor y destinatario
status Ver arriba
messageId, tradeId, listingId IDs

Cómo probarlo: sigue un trueque desde la propuesta hasta la aceptación, y otro hasta el rechazo.

Los reembolsos necesitan Config.Refunds.Enabled = true. El comprador puede pedirlo dentro de los WindowDays (7) siguientes a la compra.

Cuándo: un comprador abre una solicitud de reembolso (después de CanRequestRefund).

Campo Significado
refundId, historyId Solicitud y compra
buyerSource, buyerId, buyerName, sellerSource, sellerId, sellerName Los jugadores
item, itemLabel, quantity Lo que se compró
amount Cantidad pedida
reason Explicación del comprador
listingId ID del anuncio

Cuándo: se aprueba un reembolso y se devuelve el dinero al comprador. De tres formas: lo aprueba el vendedor, lo fuerza un administrador (forced = true, y se dispara también OnAdminAction) o nadie responde en AutoApproveAfterHours (72 h; se comprueba cada hora).

Campo Significado
refundId, historyId Solicitud y compra
buyerSource, buyerId, buyerName, sellerSource, sellerId, sellerName Los jugadores
item, itemLabel, quantity Lo que se compró
originalPrice Precio pagado
amount / refundAmount Cantidad reembolsada
forced true si lo forzó un administrador
resolvedBy, resolutionType, status, stage Cómo se resolvió
listingId ID del anuncio

Cuándo: el vendedor rechaza una solicitud de reembolso.

Campo Significado
refundId, historyId Solicitud y compra
buyerSource, buyerId, buyerName, sellerSource, sellerId, sellerName Los jugadores
amount Cantidad pedida
note La nota del vendedor
resolvedBy Quién la rechazó
listingId ID del anuncio

Cómo probar los tres: dos jugadores. B compra a A y pide un reembolso (OnRefundRequested). A lo aprueba (OnRefundApproved) o lo rechaza (OnRefundRejected). Fuerza otro desde /marketadmin para ver forced = true.

Cuándo: un comprador valora a un vendedor después de una compra.

Campo Significado
raterSource, raterId, raterName Quién valora
sellerSource, sellerId, sellerName A quién se valora
stars De 1 a 5
comment Comentario, puede ser un texto vacío
historyId, listingId Compra y anuncio
stage 'submitted'

Ejemplo: premiar las reseñas

Handlers.OnPlayerRated = function(data)
if #data.comment >= 20 and data.raterSource then
exports.ox_inventory:AddItem(data.raterSource, 'money', 50)
end
end

Cómo probarlo: compra algo a otro jugador y valóralo.

Cuándo: se envía un mensaje en el chat de un anuncio: texto normal, ofertas de precio, propuestas de trueque… El cliente del destinatario recibe sl-marketplace:onMessageReceived.

Campo Significado
senderSource, senderId, senderName Remitente
recipientSource, recipientId, recipientName Destinatario
messageId, listingId IDs
messageKind Tipo de mensaje (texto, oferta, trueque…)
body Texto
offerAmount, offeredItems, deliveryMode Para ofertas y propuestas

Cómo probarlo: abre el anuncio de otro jugador y envíale un mensaje.

Cuándo: un jugador añade o quita un anuncio de sus favoritos.

Campo Significado
playerSource, playerId, playerName El jugador
listingId Anuncio
favorited true añadido, false quitado

Cuándo: un jugador sigue o deja de seguir a un vendedor.

Campo Significado
followerSource, followerId, followerName El jugador
sellerSource, sellerId El vendedor
following true sigue, false dejó de seguir

Cuándo: un jugador añade o quita una entrada de su lista de deseos (Config.Wishlist.Enabled).

Campo Significado
playerSource, playerId, playerName El jugador
action 'added' o 'removed'
wishlistId ID de la entrada
item, maxPrice, notifyOnce Lo que busca

Cuándo: un jugador reporta un anuncio (Config.Reports.Enabled). Con AutoHideThreshold (5) reportes abiertos, el anuncio se oculta hasta que lo revise un administrador.

Campo Significado
reportId ID del reporte
reporterSource, reporterId, reporterName Quién reportó
sellerId, sellerName Vendedor reportado
item, itemLabel, listingId El anuncio
reason Uno de Config.Reports.Reasons
description Texto del jugador

Cómo probar los hooks sociales: desde el marketplace, marca un anuncio como favorito, sigue a un vendedor, añade un objeto a tu lista de deseos y reporta un anuncio; cada acción muestra su hook.

Cuándo: un jugador crea su tienda del marketplace (la primera vez que lo abre).

Campo Significado
playerSource, playerId, playerName El jugador
shopName, bio, avatarColor Su perfil
lang Idioma elegido

Cómo probarlo: con un personaje que nunca haya abierto el marketplace, pulsa F6 y crea la tienda. Para repetirlo, borra la fila de ese jugador de marketplace_users (solo en un servidor de pruebas).

Cuándo: un jugador cambia su perfil o su escaparate.

Campo Significado
playerSource, playerId, playerName El jugador
shopName Nombre de la tienda
changedFields Qué campos cambiaron
profile El perfil después del cambio

Cómo probarlo: edita el nombre de tu tienda o tu biografía desde tu perfil.

Cuándo: un jugador recoge algo de su buzón del marketplace: un objeto (de una subasta, una devolución, una recogida…) o dinero (de una venta, un reembolso…).

Campo Significado
playerSource, playerId, playerName El jugador
mailboxId Entrada del buzón
claimType 'item' o 'money'
amount Dinero recogido
item, itemLabel, itemMetadata, quantity Objeto recogido
meetupPoint Punto de recogida, si había que recogerlo allí
reason Por qué estaba en el buzón

Cómo probarlo: gana una subasta de prueba (/markettest listing mailbox auction bread 100, puja y espera 5 minutos): el objeto siempre va a tu buzón. Abre el buzón y recógelo (claimType = 'item'). Si vendes algo a otro jugador, el dinero queda en tu buzón, lo que prueba claimType = 'money'.

Cuándo: un administrador hace algo desde /marketadmin.

action Significado Campos extra
'force_cancel' Canceló un anuncio listingId
'ban_user' Bloqueó a un jugador en el marketplace targetId
'unban_user' Levantó un bloqueo targetId
'resolve_report' Resolvió un reporte reportId
'force_refund' Forzó un reembolso refundId, listingId
Campo Significado
adminSource, adminId, adminName El administrador
action Ver arriba
targetId Jugador afectado
details Datos extra (en un reembolso forzado: amount y sellerId)

Ejemplo: registro de auditoría en Discord

Handlers.OnAdminAction = function(data)
PerformHttpRequest(GetConvar('my_admin_webhook', ''), function() end, 'POST',
json.encode({ content = ('**%s** hizo `%s` sobre %s'):format(data.adminName, data.action, tostring(data.targetId or data.listingId)) }),
{ ['Content-Type'] = 'application/json' })
end

Cómo probarlo: date permisos de administrador (add_ace group.admin marketplace.admin allow), abre /marketadmin y cancela un anuncio, bloquea y desbloquea a un jugador de prueba, resuelve un reporte y fuerza un reembolso.

Dos callbacks más antiguos están en config.lua en lugar de en handlers.lua. Siguen funcionando y se llaman junto a los hooks de arriba.

Cuándo: cada vez que el marketplace muestra una notificación a un jugador desde el servidor. Cambia su contenido para usar tu propio sistema de notificaciones.

Config.Notify = function(src, msg, type)
TriggerClientEvent('ox_lib:notify', src, { description = msg, type = type or 'inform' })
end

Cómo probarlo: haz cualquier cosa que muestre una notificación (comprar algo, intentar una compra sin dinero) y comprueba que sale con tu sistema.

Cuándo: exactamente cuando se dispara OnDeliveryRobbed, justo antes, una vez por reparto.

Config.OnDeliveryRobbed = function(thiefSrc, deliveryId, value, trigger)
-- trigger: 'driver_killed' | 'cargo_stolen'
TriggerEvent('dispatch:robbery', thiefSrc, GetEntityCoords(GetPlayerPed(thiefSrc)))
end

Usa uno de los dos, no ambos, o mandarás dos avisos por atraco. Se recomienda Handlers.OnDeliveryRobbed: recibe más datos y aparece en /markethandlers.