OnShopCompleteTrade
OnShopCompleteTrade is called on a ShopFront vending machine just before a completed purchase is finalized, after the buyer's payment has been taken and the reserved flags are set.
object OnShopCompleteTrade(ShopFront shopFront)Parameters
| Parameter | Type | Description |
|---|---|---|
shopFront | ShopFront | The ShopFront vending machine whose sale is being completed. Use it to read ownerPlayer, customerPlayer, vendorInventory, sellOrders, IsVending() and the machine's position. |
Return value
Returning a non-null value (for example true) aborts CompleteTrade immediately: the items are not transferred and the vendor is not paid, while the caller may have already deducted payment from the buyer. Returning null lets the trade proceed normally.
How it works
The hook fires inside ShopFront.CompleteTrade, which runs only when the machine has both a vendor player and a customer player and carries the Reserved1 and Reserved4 flags that mark a trade in progress. Because the call sits at the very top of the method, it fires before the sale items are moved and before any currency is credited to the vendor, so it is the last point where you can still cancel the transaction.
shopFront gives you everything about the deal: customerPlayer and vendorPlayer as BasePlayer (either can be null if one side disconnected), sellOrders describing the listed offers, and the inventories involved. Declaring the parameter as ShopFront is already the exact type used by the game, so filtering by type is not needed here; use per-machine or per-player checks inside the hook instead.
This hook covers sales that complete through the normal buy path. It does not fire for opening the UI, adding stock, or cancelling, which have their own hooks. Keep the body short: it runs for every finished purchase on the server and players are waiting on the result.
Example plugin: Block sales from vending machines whose owner is offline
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Shop Owner Online Only", "RustPluginGenerator", "1.0.0")]
[Description("Cancels vending machine sales when the shop owner is not connected")]
public class ShopOwnerOnlineOnly : RustPlugin
{
private const string PermAdmin = "shopowneronlineonly.admin";
private PluginConfig config;
private int blockedCount;
private class PluginConfig
{
[JsonProperty("Notify customer on blocked sale")]
public bool NotifyCustomer = true;
[JsonProperty("Allow NPC vendors")]
public bool AllowNpcVendors = false;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["SaleBlocked"] = "This vending machine is not taking purchases right now.",
["Stats"] = "ShopOwnerOnlineOnly has blocked {0} sale(s) this session."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) =>
string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermAdmin, this);
}
private object OnShopCompleteTrade(ShopFront shopFront)
{
if (shopFront == null) return null;
BasePlayer vendor = shopFront.vendorPlayer;
if (vendor == null) return null;
if (config.AllowNpcVendors && vendor.IsNpc) return null;
if (vendor.IsConnected && !vendor.IsSleeping()) return null;
blockedCount++;
BasePlayer customer = shopFront.customerPlayer;
if (config.NotifyCustomer && customer != null && customer.IsConnected)
{
customer.ChatMessage(Msg("SaleBlocked", customer.UserIDString));
}
return true;
}
[ChatCommand("shopblockstats")]
private void CmdStats(BasePlayer player, string command, string[] args)
{
if (player == null || !permission.UserHasPermission(player.UserIDString, PermAdmin)) return;
player.ChatMessage(Msg("Stats", player.UserIDString, blockedCount));
}
}
}The plugin refuses to complete any vending machine sale whose vendor player is not currently connected, which stops shops belonging to banned, dormant or offline accounts from selling stock. OnShopCompleteTrade checks shopFront.vendorPlayer, validates it is connected and not an NPC, and returns true to abort when the check fails. Because returning a value cancels the transaction, the plugin sends a short language message to the customer as feedback and records a counter for admins. A chat command, permission-gated, prints how many sales were blocked this session so admins can judge whether the rule is too aggressive before keeping it.
Common use cases
Restrict shops to authorised sellers
Check
shopFront.vendorPlayeragainst a permission or stored list of approved shop owners and cancel the trade when the vendor is not on it, keeping a curated vending machine economy.Block sales while the vendor is offline
Refuse completed trades when
vendorPlayeris null or not connected, preventing stock from disappearing at machines owned by inactive or banned accounts.Log high-value trades
Read
sellOrdersand the item amounts before the transfer happens to record who bought what and for how much, feeding a data file for economy review.
Pitfalls
Cancelling after payment was taken
Returning a value aborts the rest of CompleteTrade, but the buyer's currency may already have been removed by the calling flow. Refund manually or restrict the hook to cases where you can accept the loss.
Null player references
vendorPlayerorcustomerPlayercan be null when a player disconnects mid-transaction. Always null-check before using either one.Multiple shop instances per machine type
The hook only fires for ShopFront vending machines that complete a real sale. Adding stock, changing prices or opening the UI never triggers it, so do not use it as a general shop activity logger.
Build a plugin with OnShopCompleteTrade
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnShopCompleteTrade
Generate