Skip to content
RustPluginGenerator

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.

Signature
object OnVendingTransaction(VendingMachine vendingMachine, BasePlayer buyer, int sellOrderId, int numberOfTransactions, ItemContainer targetContainer)

Parameters

Hook parameters
ParameterTypeDescription
vendingMachineVendingMachineThe VendingMachine entity running the transaction. Use it to read sellOrders, the machine owner, or its world position.
buyerBasePlayerThe BasePlayer purchasing from the machine. May be null for NPC-driven or scripted calls, so null-check it before using it.
sellOrderIdintIndex into vendingMachine.sellOrders.sellOrders identifying which offer is being bought. Valid only while the transaction has not yet been resolved.
numberOfTransactionsintHow 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.
targetContainerItemContainerThe 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

VendingOwnerGuard.cs
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 false for 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 reject numberOfTransactions above 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 sellOrderId is not a valid index into vendingMachine.sellOrders.sellOrders, indexing directly throws. Validate the list and index before reading the offer.

  • Returning a bool overrides the game result

    Returning true marks the transaction as done without delivering items or taking currency, which can be duplicated or confusing. Return false to block and null to allow.

  • Target container may be null

    When targetContainer is 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.

Prompt: Restrict shops to a permission group using the OnVendingTransaction hook, with a config file and a permission
Generate this plugin

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