ProVouchers

Developer API

Depend on ProVouchers, query vouchers, and react to redemptions.

ProVouchers ships a small, interface-only API (so.alaz.provouchers.api) for other plugins: a service to query and give vouchers, and events to veto or observe redemptions. It has been available since 0.4.0, and from the 1.0.0 release the surface is stable and follows semantic versioning.

Depend on the API

Add the alazso repository and the provouchers-api artifact, compile-time only. The runtime classes ship inside the installed ProVouchers plugin, so you bundle nothing.

repositories {
    maven("https://repo.alaz.so/releases") { name = "alazso" }
}

dependencies {
    compileOnly("so.alaz.provouchers:provouchers-api:1.0.0")
}

Declare ProVouchers as a dependency in your paper-plugin.yml so it loads first:

dependencies:
  server:
    ProVouchers:
      load: BEFORE
      required: true   # or false for a soft integration

The VoucherService

Obtain it from Bukkit's services manager once ProVouchers has enabled:

import so.alaz.provouchers.api.VoucherService;

VoucherService vouchers = getServer().getServicesManager().load(VoucherService.class);
if (vouchers != null) {
    vouchers.getVoucher("crate_key").ifPresent(v ->
        getLogger().info("Cooldown: " + v.cooldownSeconds() + "s"));

    vouchers.give(player, "crate_key", 1);   // async, Folia-safe
}
MethodReturns
getVoucher(id)Optional<Voucher>
voucherIds()List<String>
getCode(input)Optional<VoucherCode>
voucherCount() / codeCount()int
give(player, voucherId, amount)boolean (false if the id is unknown)
stash(playerUuid, voucherId, amount)boolean (queue a virtual voucher in the player's Stash; works offline)

Voucher and VoucherCode are read-only views (id, display name, lore, flags, cooldown, expiry, use limits). They are implemented by ProVouchers and must not be implemented by consumers.

Events

All events live in so.alaz.provouchers.api.event and are standard Bukkit events, so you listen for them the usual way.

Veto a redemption

VoucherPreRedeemEvent and VoucherCodePreRedeemEvent are cancellable and fire before the redemption commits, after ProVouchers' own checks. Cancel one to veto:

@EventHandler
public void onPreRedeem(VoucherPreRedeemEvent event) {
    if (isFrozen(event.getPlayer())) {
        event.setCancelled(true);   // anti-cheat, spending limits, etc.
    }
}

Observe a redemption

VoucherRedeemEvent and VoucherCodeRedeemEvent fire after a successful redeem (item consumed, anti-dupe passed, rewards granted). They are not cancellable:

@EventHandler
public void onRedeem(VoucherRedeemEvent event) {
    getLogger().info(event.getPlayer().getName()
        + " redeemed " + event.getVoucher().id()
        + " (uid " + event.getUid() + ")");
}

getUid() is the item's anti-dupe unique id. It is null for a stackable voucher, which is not dupe-tracked.

EventCancellableWhen
VoucherPreRedeemEventyesbefore an item voucher is redeemed
VoucherRedeemEventnoafter an item voucher is redeemed
VoucherCodePreRedeemEventyesbefore a code is redeemed
VoucherCodeRedeemEventnoafter a code is redeemed

These events fire on the redeeming player's region thread, not necessarily the global main thread. Keep handlers Folia-safe: schedule any cross-region work through your own scheduler rather than assuming the main thread.

A complete consumer plugin

A small but complete example that ties the pieces together: it resolves the service on enable, gives a welcome voucher when a player first joins, and rewards a token of its own whenever any voucher is redeemed.

VoucherRewardsPlugin.java
package com.example.voucherrewards;

import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import so.alaz.provouchers.api.VoucherService;
import so.alaz.provouchers.api.event.VoucherRedeemEvent;

public final class VoucherRewardsPlugin extends JavaPlugin implements Listener {

    private VoucherService vouchers;

    @Override
    public void onEnable() {
        // ProVouchers registers the service on enable; load BEFORE in paper-plugin.yml
        // guarantees it is present by the time we run.
        vouchers = getServer().getServicesManager().load(VoucherService.class);
        if (vouchers == null) {
            getLogger().warning("ProVouchers not found; voucher features disabled.");
            return;
        }
        getLogger().info("Hooked ProVouchers: " + vouchers.voucherCount() + " vouchers loaded.");
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        if (vouchers != null && !player.hasPlayedBefore()) {
            // give(...) is async and Folia-safe; it returns false if the id is not loaded.
            vouchers.give(player, "welcome_crate", 1);
        }
    }

    @EventHandler
    public void onRedeem(VoucherRedeemEvent event) {
        // Fires on the player's region thread, after the redeem has committed.
        getLogger().info(event.getPlayer().getName()
            + " redeemed " + event.getVoucher().id());
        // ... award loyalty points, update a scoreboard, etc.
    }
}
paper-plugin.yml
name: VoucherRewards
version: 1.0.0
main: com.example.voucherrewards.VoucherRewardsPlugin
api-version: "1.21"
dependencies:
  server:
    ProVouchers:
      load: BEFORE
      required: false   # soft integration: we degrade gracefully when it is absent

With required: false, guard every use with the vouchers != null check shown above so your plugin still loads when ProVouchers is not installed. Use required: true if ProVouchers is mandatory and you would rather fail fast.

Stability

The API is annotated with @ApiStatus.AvailableSince("0.4.0"), and the read-only types are @ApiStatus.NonExtendable. As of the 1.0.0 release the API surface is stable; changes follow semantic versioning.

On this page