OnVendingTransaction
OnVendingTransaction is called inside VendingMachine.DoTransaction just before the sell order is resolved and currency is taken, letting a plugin allow, block, or override the purchase.
object OnVendingTransaction(VendingMachine vendingMachine, BasePlayer buyer, int sellOrderId, int numberOfTransactions, ItemContainer targetContainer)Parameters
| Parameter | Type | Description |
|---|---|---|
vendingMachine | VendingMachine | The VendingMachine entity running the transaction. Use it to read sellOrders, the machine owner, or its world position. |
buyer | BasePlayer | The BasePlayer purchasing from the machine. May be null for NPC-driven or scripted calls, so null-check it before using it. |
sellOrderId | int | Index into vendingMachine.sellOrders.sellOrders identifying which offer is being bought. Valid only while the transaction has not yet been resolved. |
numberOfTransactions | int | How many times the buyer is purchasing the selected offer in this call. Multiply the offer's currency and item amounts by this value for totals. |
targetContainer | ItemContainer | The ItemContainer the sold items are routed into, or null when the buyer is not at an external container and must be within 4m of the machine. |
Return value
Returning null lets the transaction continue normally. Returning a bool short-circuits VendingMachine.DoTransaction and that value becomes its return value: true reports the purchase as completed without the game taking currency or handing out items, and false reports failure so the purchase is cancelled.
How it works
The hook fires from VendingMachine.DoTransaction after the distance check (when no target container is supplied, the buyer must be within 4m of the machine) and before the sell order is looked up in sellOrders.sellOrders[sellOrderId]. Because it runs first, you can inspect exactly which offer (sellOrderId), how many (numberOfTransactions), and who (buyer) is involved before any currency leaves the buyer's inventory. The targetContainer argument is the container the output items will flow into; when it is null the sale goes to the buyer normally.
Declaring a more specific parameter type is not useful here because the game passes a VendingMachine and BasePlayer already; changing to a base type like BaseEntity would still match but adds nothing. Note the order id is passed as a raw int, not a ProtoBuf.VendingMachine.SellOrder, so you must index sellOrders.sellOrders yourself to read currency and item amounts. This hook fires for every purchase, including bulk buys, so keep it fast and never call Puts/PrintWarning inside it.
Example plugin: Block purchases from machines whose owner is offline or banned
using System.Collections.Generic;
using System;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Vending Owner Guard", "RustPluginGenerator", "1.0.0")]
[Description("Blocks vending machine purchases when the shop owner is offline or lacks a permission")]
public class VendingOwnerGuard : RustPlugin
{
private const string PermUse = "vendingownerguard.use";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Only permission holders can buy")]
public bool RequirePermission { get; set; } = false;
[JsonProperty("Notify buyer when sale allowed")]
public bool NotifyBuyer { get; set; } = 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>
{
["NoPermission"] = "You are not permitted to use this vending machine.",
["OwnerOffline"] = "The shop owner is offline, this machine is disabled.",
["Allowed"] = "Purchase confirmed."
}, 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(PermUse, this);
}
private object OnVendingTransaction(VendingMachine vendingMachine, BasePlayer buyer, int sellOrderId, int numberOfTransactions, ItemContainer targetContainer)
{
if (vendingMachine == null || buyer == null) return null;
if (vendingMachine.sellOrders?.sellOrders == null) return null;
if (sellOrderId < 0 || sellOrderId >= vendingMachine.sellOrders.sellOrders.Count) return null;
if (config.RequirePermission && !permission.UserHasPermission(buyer.UserIDString, PermUse))
{
buyer.ChatMessage(Msg("NoPermission", buyer.UserIDString));
return false;
}
var owner = BasePlayer.FindByID(vendingMachine.OwnerID);
if (owner == null || !owner.IsConnected)
{
buyer.ChatMessage(Msg("OwnerOffline", buyer.UserIDString));
return false;
}
if (config.NotifyBuyer)
{
buyer.ChatMessage(Msg("Allowed", buyer.UserIDString));
}
return null;
}
}
}The plugin blocks any vending transaction whose owning player cannot be located, is not connected, or does not have the vendingownerguard.use permission. It resolves the owner from vendingMachine.OwnerID and checks BasePlayer.FindByID. When allowed, it returns null so the vanilla sale proceeds untouched; when blocked it returns false so the sale fails and the buyer keeps their currency. On a successful allowed purchase where the buyer still has the permission, a chat line confirms the buy is going through only if the NotifyBuyer config option is enabled. Config toggles the notice and warnings are kept out of the hot path so it scales across busy shops.
Common use cases
Restrict shops to a permission group
Only allow purchases when the buyer has a permission, returning
falsefor everyone else so the machine effectively becomes group-locked.Apply a per-offer purchase cap
Read the sell order from
vendingMachine.sellOrders.sellOrders[sellOrderId]and rejectnumberOfTransactionsabove a configured limit before currency is charged.Log shop revenue
Record the buyer, machine owner, and total currency for each sale to a data file for server economy bookkeeping.
Pitfalls
Sell order index can be out of range
If
sellOrderIdis not a valid index intovendingMachine.sellOrders.sellOrders, indexing directly throws. Validate the list and index before reading the offer.Returning a bool overrides the game result
Returning
truemarks the transaction as done without delivering items or taking currency, which can be duplicated or confusing. Returnfalseto block andnullto allow.Target container may be null
When
targetContaineris null the buyer must stand within 4m of the machine; do not assume the output always lands in a given container.
Build a plugin with OnVendingTransaction
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 OnVendingTransaction
Generate