YuPay
🎮 YuPay —— 专为 Minecraft 服务器打造的现代化赞助支付插件
> 让赞助管理不再是腐竹的噩梦。
> 微信支付、支付宝、卡密、退款、排行榜、PAPI、多语言、订单审计、跨服发奖、开发者 API —— 从玩家扫码到奖励到账,从异常排查到退款复核,YuPay 帮你把服务器赞助流程做成一套完整闭环。
---
📌 YuPay 是什么?
YuPay 是一款面向 Minecraft 服务器腐竹的现代化赞助支付插件。
它的目标很简单
让玩家赞助更方便,让腐竹收款更安全,让奖励发放更自动,让每一笔订单都有记录可查。
YuPay 支持 微信支付 与 支付宝 官方接口对接,玩家在游戏内发起赞助后即可扫码支付。支付成功后,插件会自动完成订单确认、验签、奖励发放、排行榜刷新、PAPI 数据更新和日志记录。
资金不经过第三方代收平台,直接进入你自己的微信 / 支付宝商户账户。
> ⚠️ YuPay 面向“玩家自愿赞助服务器运营”场景。请确保你的使用方式符合当地法律法规、服务器运营规范以及微信支付 / 支付宝平台规则。
---
✨ 核心亮点速览
| 模块 | 你能获得什么 |
|---|---|
| 💳 官方支付 | 微信支付 + 支付宝,支持扫码支付,资金直达商户账户 |
| 🧾 订单系统 | 订单创建、取消、过期、成功、异常、退款、补发全生命周期记录 |
| 🎁 自动奖励 | 经济奖励 + 自定义命令,支持单笔、累计、近期累计等多层触发 |
| 🔁 补发机制 | 支付状态与发奖状态分离,玩家可自助补发,管理员可重试奖励 |
| 🔑 卡密系统 | 免费码、付费码、礼包码、激活码、次数限制、过期时间、核销日志 |
| 💵 退款系统 | 退款申请、管理员审核、直接退款、部分退款、退款状态同步 |
| 📊 数据统计 | 个人总额、全服总额、排行榜、历史订单、订单详情、回调流水 |
| 🌐 多语言 | 默认中文 / 英文,可扩展任意语言,支持玩家个人语言偏好 |
| 🔌 PAPI 支持 | 个人、全服、排行榜、卡密多维度占位符 |
| 🛡️ 安全审计 | 签名验证、IP 白名单、请求体限制、敏感日志脱敏、API 授权审计 |
| 🧩 开发者 API | Bukkit ServicesManager API + 完整事件监听,方便商城 / 菜单 / 跨服系统集成 |
| 🌍 跨服发奖 | 支持共享数据库队列,多子服领取发奖任务,适合群组服网络 |
| ⚡ 性能友好 | 异步订单处理、HikariCP 连接池、Undertow 回调服务、启动健康检查 |
| 📚 插件 Wiki / 使用文档 | 插件文档 |
---
🧭 一次完整赞助流程
玩家只需要输入一条命令
/donate 50 wechat map然后 YuPay 会自动完成
- 校验玩家权限、金额上下限、每日限额、支付渠道可用性
- 创建本地订单并写入数据库
- 调用微信 / 支付宝接口生成支付二维码
- 按配置展示支付方式:
- 可点击链接
- 聊天栏文本二维码
- 背包地图二维码
- 玩家扫码完成支付
- 支付平台向服务器回调
- YuPay 验证签名、校验金额、确认订单状态
- 写入回调流水与订单快照
- 自动发放经济奖励和命令奖励
- 更新排行榜、PAPI 数据和玩家历史记录
- 若发奖失败,可由玩家或管理员重试补发
腐竹不需要手动确认收款,不需要手动发奖励,也不需要翻聊天记录对账。
---
💳 官方双通道支付
YuPay 支持两种主流支付方式
| 支付渠道 | 支持能力 |
|---|---|
| 微信支付 | Native 扫码支付、支付回调、退款、退款回调、退款查询 |
| 支付宝 | 扫码支付、公钥模式、证书模式、支付回调、退款、退款查询 |
支持配置默认支付方式
pay:
default-method: "wechat"玩家也可以在命令中指定支付方式
/donate 10 wechat
/donate 10 alipay如果玩家不指定支付方式,则使用服主配置的默认渠道。
---
🧾 不只是收款:完整订单生命周期
YuPay 将订单状态和发奖状态分离管理,让排障更加清晰。
支付订单状态
| 状态 | 含义 |
|---|---|
| PENDING | 待支付 |
| SUCCESS | 支付成功 |
| CANCELLED_LOCAL | 玩家本地取消 |
| CLOSED | 平台订单关闭 |
| FAILED | 支付失败 |
| EXPIRED | 待支付订单过期 |
| AMOUNT_MISMATCH | 回调金额与订单金额不一致 |
| HELD / PAID_AFTER_CANCELLED | 取消后到账等历史异常状态 |
| RESOLVED_REWARDED | 异常订单已人工判定并补发 |
| RESOLVED_REFUNDED | 异常订单已人工判定并退款 |
| RESOLVED_IGNORED | 异常订单已人工判定忽略 |
发奖状态
| 状态 | 含义 |
|---|---|
| PENDING | 等待发奖 |
| PROCESSING | 正在处理 |
| DONE | 已完成 |
| FAILED | 发奖失败 |
| SKIPPED | 无奖励赞助或无需发奖 |
这意味着
即使玩家已经付款成功,但命令奖励因为权限插件、经济插件、服务器瞬时异常导致失败,YuPay 也能保留订单成功状态,并允许后续重试补发。
---
🔁 自助补发与管理员补发
玩家可使用
/yp retry
/yp retry <订单号>当订单已支付成功但奖励未完成时,玩家可以自行提交补发请求。
管理员也可以使用
/yp order retry-reward <订单号>
/yp order steps <订单号>查看发奖步骤流水并手动重试。
YuPay 会记录每一步奖励执行状态,避免重复发放。
---
🎁 四层奖励系统,运营玩法随便搭
YuPay 的奖励系统支持多层触发,既能满足普通赞助奖励,也能做充值档位、累计成就、限时活动。
| 奖励类型 | 触发时机 | 适合场景 |
|---|---|---|
| 基础奖励 | 每次支付成功 | 每笔赞助都送基础礼包 |
| 单笔达标 | 单次金额达到门槛 | 50 元档、100 元档、300 元档 |
| 累计达标 | 历史累计金额达到门槛 | VIP、SVIP、永久称号 |
| 近期累计 | 指定时间范围内累计达标 | 周末冲榜、节日活动、限时礼包 |
| 退款命令 | 退款完成后 | 通知管理员、撤销标记、记录日志 |
基础奖励
commands:
on-success:
- "give {player} diamond 1"
- "effect give {player} minecraft:speed 30 1"单笔达标奖励
commands:
on-single-achieved:
- amount: 50
commands:
- "say {player} 单笔赞助达到 50 元!"
- "give {player} minecraft:gold_block 5"累计达标奖励
commands:
on-total-achieved:
- total: 500
commands:
- "lp user {player} parent set svip"
- "give {player} minecraft:netherite_block 5"限时累计奖励
commands:
on-recent-achieved:
- time: 86400000
total: 200
commands:
- "give {player} minecraft:enchanted_golden_apple 5"> `time` 单位为毫秒
> 24 小时 = 86400000
> 7 天 = 604800000
可用变量
| 变量 | 含义 |
|---|---|
| {player} | 玩家名 |
| {amount} | 支付金额 |
| {points} | 经济奖励数量 |
| {order} | 订单号,主要用于退款命令 |
---
💛 无奖励赞助
有些玩家只是想支持服务器,不想领取任何道具或权限。
YuPay 支持无奖励赞助
/donate 10 -n玩家添加 -n 参数后,该订单会正常收款、记录、统计,但不会触发经济奖励和命令奖励。
适合
- 纯支持服务器运营
- 不影响生存平衡
- 避免 P2W 争议
- 公益服 / 原版服赞助场景
---
💰 Vault + PlayerPoints 双经济支持
YuPay 支持两类经济奖励
| 类型 | 说明 |
|---|---|
| vault | 对接 Vault,兼容 EssentialsX、CMI 等主流经济插件 |
| playerpoints | 对接 PlayerPoints 点券系统 |
配置示例
economy:
enabled: true
type: "vault"
rate: 100.0表示
1 元 = 100 游戏币 / 点数还支持管理员进行两种经济之间的换算
/yp convert <玩家> <vault|points> <vault|points> <数量>---
🔑 卡密系统:活动码、礼包码、付费激活码都能做
YuPay 的卡密系统不是简单的“输入兑换码领奖励”,而是一套可支付、可核销、可追踪的兑换系统。
卡密可以配置
| 能力 | 说明 |
|---|---|
| 免费核销 | 玩家输入卡密后直接领取奖励 |
| 付费核销 | 玩家输入卡密后需扫码支付指定金额 |
| 经济奖励 | 核销后发放 Vault 货币或 PlayerPoints 点数 |
| 命令奖励 | 核销后执行任意控制台命令 |
| 使用次数 | 每张卡密可限制最大核销次数 |
| 过期时间 | 支持永久、相对时间、指定日期 |
| 状态管理 | 启用、禁用、用尽、过期 |
| 日志追踪 | 每次核销都有记录可查 |
| 支付锁 | 付费卡密支付中会加锁,防止多人同时抢占 |
免费礼包码
/yp code create --code FREEGIFT --max-uses -1 --economy vault --eco-amount 1000 --remark "新手福利"付费激活码
/yp code create --code PREMIUM --amount 50 --economy vault --eco-amount 5000 --commands "lp user {player} parent set vip" --max-uses 100 --expire 30d纯命令卡密
/yp code create --code WELCOME --commands "give {player} diamond 5; say {player} 欢迎回来!"卡密管理命令
| 命令 | 功能 |
|---|---|
| /yp code create ... | 创建卡密 |
| /yp code remove <卡密> | 彻底移除卡密 |
| /yp code disable <卡密> | 禁用卡密 |
| /yp code enable <卡密> | 启用卡密 |
| /yp code modify <卡密> <字段> <值> | 修改卡密属性 |
| /yp code info <卡密> | 查看卡密详情 |
| /yp code list [页数] | 查看卡密列表 |
| /yp code logs [卡密] [页数] | 查看核销日志 |
| /yp code redeem <卡密> | 玩家核销卡密 |
---
💵 退款系统:从申请到平台退款全流程打通
退款不再靠手动转账、不再靠聊天记录确认。
YuPay 支持完整退款流程
| 功能 | 说明 |
|---|---|
| 玩家申请退款 | 玩家提交退款请求 |
| 管理员审核 | 管理员通过或拒绝 |
| 直接退款 | 管理员直接对订单发起退款 |
| 部分退款 | 可指定退款金额 |
| 退款流水 | 记录退款单号、状态、金额、渠道 |
| 退款查询 | 查询平台退款结果 |
| 退款重试 | 对失败 / 未知退款进行重试 |
| 手动确认 | 必要时手动确认退款结果 |
| 状态同步 | 定时查询平台退款状态 |
| 退款命令 | 退款完成后执行自定义命令 |
玩家申请退款
/yp refund request <订单号> [金额]管理员查看待审核
/yp refund pending审核通过 / 拒绝
/yp refund approve <请求ID>
/yp refund reject <请求ID>管理员直接退款
/yp refund <订单号> [金额]退款流水查询
/yp refund list
/yp refund info <退款单号>
/yp refund query <退款单号>
/yp refund retry <退款单号>
/yp refund confirm <退款单号>---
📊 数据统计:对账、排行、历史一目了然
YuPay 内置常用运营统计命令。
排行榜
/ytop显示全服赞助排行榜 Top10。
查询个人 / 全服总额
/ytotal
/ytotal <玩家名>
/ytotal all玩家订单列表
/yp orders
/yp history单个订单详情
/yp order <订单号>管理员订单中心
/yp order info <订单号>
/yp order abnormal [页数]
/yp order risk [页数]
/yp order list <abnormal|pending|cancelled|risk|reward_failed|refund_failed|success> [页数]
/yp order player <玩家名|UUID> [页数]
/yp order steps <订单号>
/yp order callbacks <订单号|recent|failed|duplicate> [页数]
/yp order refunds <订单号> [页数]
/yp order mark-reviewed <订单号> [备注]
/yp order resolve <订单号> <reward|refund|ignore|hold>
/yp order expire这套订单中心非常适合处理
- 玩家说“我付了但没到账”
- 取消后平台仍然到账
- 金额异常
- 发奖失败
- 回调重复
- 平台退款状态不明确
- 多服网络中发奖节点异常
---
🌐 多语言系统:同一服务器,不同玩家不同语言
YuPay 默认提供
- 简体中文
- English
并支持服主自定义任意语言。
配置示例
language:
mappings:
zh_cn:
file: "messages.yml"
aliases: [zh, cn, chinese]
client-locales: ["zh_cn", "zh_*"]
en:
file: "messages_en.yml"
aliases: [english, eng]
client-locales: ["en_*"]玩家可自行切换语言
/yp lang en
/yp lang zh_cn语言偏好会写入数据库,下次进服自动生效。
首次加入服务器时,YuPay 会尝试根据玩家客户端语言匹配合适语言文件。
---
🔌 PlaceholderAPI 占位符
YuPay 内置多维度 PAPI,占位符标识为
%yupay_xxx%个人维度
%yupay_is_banned%
%yupay_total%
%yupay_total_orders%
%yupay_success_orders%
%yupay_rank%
%yupay_has_paid_in_<time>%
%yupay_recent_amount_gt_<time>_<amount>%
%yupay_orders_in_<time>%
%yupay_success_orders_in_<time>%
%yupay_amount_in_<time>%
%yupay_is_limited%
%yupay_daily_remaining%
%yupay_lang%
%yupay_pay_methods_count%
%yupay_pay_methods_list%全服维度
%yupay_server_total%
%yupay_server_total_orders%
%yupay_server_success_orders%
%yupay_server_orders_in_<time>%
%yupay_server_success_orders_in_<time>%
%yupay_server_amount_in_<time>%排行榜维度
%yupay_top_<rank>_name%
%yupay_top_<rank>_amount%
%yupay_top<rank>_name%
%yupay_top<rank>_amount%<rank> 支持 1 到 10。
卡密维度
%yupay_code_total%
%yupay_code_active%
%yupay_code_<code>_status%
%yupay_code_<code>_amount%
%yupay_code_<code>_economy_type%
%yupay_code_<code>_economy_amount%
%yupay_code_<code>_used%
%yupay_code_<code>_max_uses%
%yupay_code_<code>_expire%
%yupay_code_<code>_expire_formatted%
%yupay_code_<code>_remark%
%yupay_code_<code>_locked%
%yupay_code_<code>_redeemed%
%yupay_code_<code>_can_redeem%参数说明
| 参数 | 说明 |
|---|---|
| <time> | 毫秒,例如 24 小时 = 86400000 |
| <amount> | 金额阈值,单位元 |
| <rank> | 排行榜名次,1-10 |
| <code> | 卡密字符串,会自动大写匹配 |
金额类占位符统一保留两位小数,例如
99.50
100.00可用于
- 计分板
- BossBar
- 菜单 GUI
- 聊天前缀
- 称号系统
- 物品 Lore
- 排行榜展示
- 活动进度展示
---
🌍 跨服发奖:适合群组服网络
如果你运营的是多子服网络,YuPay 支持跨服发奖队列。
设计目标是
> 支付回调可以由任意服务器接收,但奖励由目标服务器通过共享数据库队列领取执行。
支持路由模式
| 模式 | 说明 |
|---|---|
| LOCAL | 当前服务器直接发奖 |
| SERVER | 固定交给指定服务器发奖 |
| PLAYER_ONLINE | 玩家在哪个子服在线,就由哪个子服发奖 |
| ANY | 任意在线子服都可领取发奖任务 |
推荐生产环境使用
database:
type: "mysql"
cross-server:
enabled: true
server-id: "lobby-1"
security:
require-shared-secret: true
shared-secret: "请改成足够长的随机字符串"查看跨服状态
/yp crossserver status
/yp crossserver queue---
🛡️ 安全能力
YuPay 对支付类插件最重要的安全问题做了完整处理。
| 安全能力 | 说明 |
|---|---|
| 支付回调验签 | 微信 / 支付宝回调都会进行签名验证 |
| 金额校验 | 回调金额必须与订单金额匹配 |
| 幂等处理 | 避免重复回调导致重复发奖 |
| 回调 IP 白名单 | 支持 CIDR 白名单限制来源 |
| 请求体限制 | 防止异常大请求压垮回调服务 |
| 敏感日志脱敏 | 密钥、签名、平台交易号、IP 等可脱敏 |
| 回调流水 | 可记录平台回调处理结果、哈希、状态 |
| 安全审计日志 | API 拒绝、回调异常、退款等敏感行为单独记录 |
| API 权限控制 | 外部插件调用 YuPay API 需要按插件名授权 |
| 事件监听审计 | 可检查哪些插件正在监听 YuPay 资金相关事件 |
| 地图支付锁 | 地图二维码支付期间限制玩家乱丢 / 切换关键物品 |
安全审计命令
/yp audit
/yp audit listeners
/yp audit security健康检查命令
/yp health
/yp health summary
/yp health full---
🧩 给开发者的 API 与 Bukkit 事件
YuPay 提供两种开发者接入方式
- YuPayApi:适合商城插件、菜单插件、网页充值系统、跨服系统主动创建订单、查询订单、退款、补发奖励。
- Bukkit 事件:适合监听支付流程,在订单创建前拦截、支付完成后联动、退款完成后同步数据。
---
1. 在其他插件中依赖 YuPay
如果你的插件需要调用 YuPay API,建议在 plugin.yml 中声明依赖:
depend: [YuPay]如果只是可选支持 YuPay,可以写
softdepend: [YuPay]Gradle 本地依赖示例
dependencies {
compileOnly files("libs/YuPay.jar")
}将 YuPay.jar 放入你插件项目的 libs/ 目录即可编译。
---
2. 获取 YuPayApi 实例
YuPay 会通过 Bukkit ServicesManager 注册 YuPayApi。
推荐写法
import org.bukkit.plugin.java.JavaPlugin;
import org.yutay.yupay.api.YuPayApi;
import org.yutay.yupay.api.YuPayProvider;
public final class MyShopPlugin extends JavaPlugin {
private YuPayApi yuPayApi;
@Override
public void onEnable() {
yuPayApi = YuPayProvider.get();
if (yuPayApi == null) {
getLogger().warning("未检测到 YuPay,支付功能将不可用。");
return;
}
getLogger().info("已成功接入 YuPay API。");
}
public YuPayApi getYuPayApi() {
return yuPayApi;
}
}也可以直接通过 Bukkit ServicesManager 获取:
import org.bukkit.Bukkit;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.yutay.yupay.api.YuPayApi;
RegisteredServiceProvider<YuPayApi> provider =
Bukkit.getServicesManager().getRegistration(YuPayApi.class);
YuPayApi api = provider == null ? null : provider.getProvider();---
3. YuPay API 授权配置
YuPay 默认会检查外部插件是否有权调用 API。
第三方插件创建订单时必须声明 sourcePlugin,通常传入自己插件的名字:
.sourcePlugin(getName())然后服主需要在 config.yml 中授权:
api:
enabled: true
require-plugin-declaration: true
audit-log: true
allowed-plugins:
MyShop:
create-order: true
cancel-order: true
query-order: true
refund-order: false
retry-reward: false
no-reward-order: false
expire-orders: false权限说明
| 权限项 | 说明 |
|---|---|
| create-order | 允许创建支付订单 |
| cancel-order | 允许取消待支付订单 |
| query-order | 允许查询订单、订单列表、发奖步骤 |
| refund-order | 允许通过 API 发起退款 |
| retry-reward | 允许通过 API 重试发奖 |
| no-reward-order | 允许创建无奖励订单 |
| expire-orders | 允许通过 API 主动过期待支付订单 |
如果未授权,API 调用会返回失败结果,并在 YuPay 安全审计中记录拒绝原因。
---
4. 创建支付订单
示例:商城插件为玩家创建一笔 30 元的微信订单。
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.yutay.yupay.api.YuPayOrderRequest;
import org.yutay.yupay.api.YuPayOrderResult;
import java.util.LinkedHashMap;
import java.util.Map;
public void createShopOrder(Player player, String shopOrderId) {
if (yuPayApi == null) {
player.sendMessage("§c支付系统暂不可用。");
return;
}
Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("shopOrderId", shopOrderId);
metadata.put("itemId", "vip_30d");
YuPayOrderRequest request = YuPayOrderRequest
.builder(player.getUniqueId(), player.getName(), 30.00)
.payMethod("wechat")
.sourcePlugin(getName())
.externalOrderId(shopOrderId)
.businessType("SHOP")
.metadata(metadata)
.expireMinutes(15)
.noReward(false)
.build();
yuPayApi.createOrder(request).thenAccept(result -> {
if (!result.isSuccess()) {
Bukkit.getScheduler().runTask(this, () -> {
player.sendMessage("§c订单创建失败:" + result.getMessage());
});
return;
}
Bukkit.getScheduler().runTask(this, () -> {
player.sendMessage("§a订单创建成功!");
player.sendMessage("§7订单号:§f" + result.getOrderId());
player.sendMessage("§7支付方式:§f" + result.getPayMethod());
player.sendMessage("§7金额:§f" + result.getAmount());
player.sendMessage("§e请使用以下二维码链接完成支付:");
player.sendMessage("§b" + result.getQrUrl());
});
}).exceptionally(error -> {
Bukkit.getScheduler().runTask(this, () -> {
player.sendMessage("§c创建订单时发生异常:" + error.getMessage());
});
return null;
});
}> 注意:createOrder 返回的是 CompletableFuture,回调不一定在 Bukkit 主线程。
> 如果要给玩家发消息、操作背包、执行 Bukkit API,建议使用 Bukkit.getScheduler().runTask(...) 切回主线程。
---
5. YuPayOrderRequest 参数说明
| 方法 | 说明 |
|---|---|
| builder(UUID, String, double) | 创建请求,传入玩家 UUID、玩家名、金额 |
| payMethod(String) | 支付方式,例如 wechat 或 alipay,不填则使用 YuPay 默认支付方式 |
| orderId(String) | 自定义 YuPay 订单号,不填则自动生成 |
| noReward(boolean) | 是否为无奖励订单 |
| sourcePlugin(String) | 调用方插件名,用于 API 授权和安全审计 |
| externalOrderId(String) | 外部系统订单号,例如商城订单号 |
| businessType(String) | 业务类型,例如 SHOP、VIP、REDEEM、CUSTOM |
| metadataJson(String) | 自定义 JSON 元数据 |
| metadata(Map<String, ?>) | 以 Map 方式传入元数据,YuPay 会转为 JSON |
| expireMinutes(int) | 当前订单过期分钟数,0 表示使用 YuPay 全局配置 |
---
6. YuPayOrderResult 返回值说明
| 方法 | 说明 |
|---|---|
| isSuccess() | 是否创建成功 |
| getOrderId() | YuPay 订单号 |
| getPayMethod() | 实际使用的支付方式 |
| getAmount() | 订单金额 |
| getQrUrl() | 支付二维码链接 |
| getMessage() | 失败原因 |
---
7. 查询订单
yuPayApi.getOrder(getName(), orderId).thenAccept(record -> {
if (record == null) {
getLogger().info("订单不存在:" + orderId);
return;
}
getLogger().info("订单号:" + record.getOrderId());
getLogger().info("玩家:" + record.getPlayerName());
getLogger().info("金额:" + record.getAmount());
getLogger().info("支付状态:" + record.getStatus());
getLogger().info("发奖状态:" + record.getRewardStatus());
getLogger().info("支付方式:" + record.getPayMethod());
getLogger().info("外部订单号:" + record.getExternalOrderId());
getLogger().info("业务类型:" + record.getBusinessType());
});常用字段
| 方法 | 说明 |
|---|---|
| getOrderId() | YuPay 订单号 |
| getPlayerUuid() | 玩家 UUID |
| getPlayerName() | 玩家名 |
| getAmount() | 金额 |
| getStatus() | 支付状态 |
| getRewardStatus() | 发奖状态 |
| getPayMethod() | 支付方式 |
| isNoReward() | 是否无奖励 |
| getQrUrl() | 二维码链接 |
| getFailReason() | 失败原因 |
| getRefundStatus() | 退款状态 |
| getRefundAmount() | 已退款金额 |
| getSourcePlugin() | 来源插件 |
| getExternalOrderId() | 外部订单号 |
| getMetadataJson() | 元数据 JSON |
| isCreatedByApi() | 是否由 API 创建 |
| getBusinessType() | 业务类型 |
| getExpireTime() | 过期时间戳 |
---
8. 查询订单列表
yuPayApi.listOrders(getName(), "success", 20, 0).thenAccept(records -> {
for (var record : records) {
getLogger().info(record.getOrderId() + " - " + record.getStatus());
}
});| type | 说明 |
|---|---|
| pending | 待支付订单 |
| cancelled | 已取消订单 |
| success | 支付成功订单 |
| reward_failed | 发奖失败订单 |
| risk | 风险订单 |
| abnormal | 异常订单 |
---
9. 取消订单
yuPayApi.cancelOrderDetailed(getName(), orderId, "玩家在商城界面取消支付")
.thenAccept(result -> {
if (result.isSuccess()) {
getLogger().info("订单取消成功:" + orderId);
} else {
getLogger().warning("订单取消失败:" + result.getCode() + " - " + result.getMessage());
}
});也可以取消玩家最近一笔待支付订单
yuPayApi.cancelLatestPendingOrder(getName(), player.getUniqueId(), "玩家取消最近订单")
.thenAccept(result -> {
player.sendMessage(result.isSuccess() ? "§a已取消最近订单。" : "§c取消失败:" + result.getMessage());
});---
10. 发起退款
yuPayApi.refundOrderDetailed(getName(), orderId, 10.00, "商城售后退款")
.thenAccept(result -> {
if (result.isSuccess()) {
getLogger().info("退款已受理:订单=" + result.getOrderId()
+ ", 本次退款=" + result.getRefundAmount()
+ ", 累计退款=" + result.getTotalRefunded()
+ ", 退款状态=" + result.getRefundStatus());
} else {
getLogger().warning("退款失败:" + result.getCode() + " - " + result.getMessage());
}
});退款需要在 YuPay 配置中给调用插件开启
api:
allowed-plugins:
MyShop:
refund-order: true---
11. 重试发奖
当订单已经支付成功,但发奖状态为失败时,可以通过 API 重试:
yuPayApi.retryReward(getName(), orderId).thenAccept(result -> {
if (result.isSuccess()) {
getLogger().info("已提交发奖重试:" + orderId);
} else {
getLogger().warning("发奖重试失败:" + result.getCode() + " - " + result.getMessage());
}
});需要授权
api:
allowed-plugins:
MyShop:
retry-reward: true---
12. 查询发奖步骤
yuPayApi.listRewardSteps(getName(), orderId).thenAccept(steps -> {
for (var step : steps) {
getLogger().info("步骤:" + step.getStepKey()
+ ", 类型:" + step.getStepType()
+ ", 状态:" + step.getStatus()
+ ", 尝试次数:" + step.getAttempts()
+ ", 错误:" + step.getLastError());
}
});发奖步骤字段
| 方法 | 说明 |
|---|---|
| getOrderId() | 订单号 |
| getStepKey() | 步骤键 |
| getStepType() | 步骤类型 |
| getStatus() | 步骤状态 |
| getAttempts() | 尝试次数 |
| getDetail() | 步骤详情 |
| getLastError() | 最近错误 |
| getCreatedTime() | 创建时间 |
| getUpdatedTime() | 更新时间 |
---
13. 主动过期待支付订单
yuPayApi.expirePendingOrders(getName()).thenAccept(result -> {
getLogger().info("已过期待支付订单数量:" + result.getAffected());
});需要授权
api:
allowed-plugins:
MyShop:
expire-orders: true---
14. 支付方式工具方法
String defaultMethod = yuPayApi.getDefaultPayMethod();
boolean wechatAvailable = yuPayApi.isPayMethodAvailable("wechat");
boolean alipayAvailable = yuPayApi.isPayMethodAvailable("alipay");
String orderId = yuPayApi.generateOrderId(player.getUniqueId(), false);如果第三方插件想自己展示二维码图片,也可以使用
BufferedImage image = yuPayApi.createPaymentQrImage(qrUrl, 256);免费版可能会自动在二维码图中加入 YuPay 品牌标识,可通过:
boolean required = yuPayApi.isFreeEditionBrandingRequired();
String adLink = yuPayApi.getFreeEditionAdLink();判断是否需要展示品牌信息。
---
Bukkit 事件监听
YuPay 的事件位于
org.yutay.yupay.event使用方式与普通 Bukkit 事件完全一致。
---
1. 注册监听器
import org.bukkit.plugin.java.JavaPlugin;
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(new YuPayListener(this), this);
}
}监听器示例
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.yutay.yupay.event.*;
public final class YuPayListener implements Listener {
private final MyPlugin plugin;
public YuPayListener(MyPlugin plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPrePayment(PrePaymentEvent event) {
Player player = event.getPlayer();
// 示例:低于 1 元的订单禁止创建
if (event.getAmount() < 1.0) {
event.setCancelled(true);
event.setCancelReason("§c最低赞助金额为 1 元。");
return;
}
// 示例:强制某个世界只能使用支付宝
if ("activity".equalsIgnoreCase(player.getWorld().getName())) {
event.setPayMethod("alipay");
}
// 示例:超过 100 元自动改为链接模式
if (event.getAmount() >= 100.0) {
event.setOutputMode("link");
}
}
@EventHandler
public void onOrderCreated(PaymentOrderCreatedEvent event) {
plugin.getLogger().info("YuPay 订单已创建:"
+ event.getOrderId()
+ ", 玩家=" + event.getPlayerName()
+ ", 金额=" + event.getAmount()
+ ", 支付方式=" + event.getPayMethod()
+ ", 二维码=" + event.getQrCodeUrl());
}
@EventHandler
public void onPaymentCompleted(PaymentCompletedEvent event) {
plugin.getLogger().info("YuPay 支付完成:"
+ event.getPlayerName()
+ " 支付 " + event.getAmountRmb()
+ " 元,订单号:" + event.getOrderId());
Player player = event.getPlayer();
if (player != null) {
player.sendMessage("§a感谢赞助,订单已完成!");
}
// 示例:支付完成后联动其他系统
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"say " + event.getPlayerName() + " 刚刚完成了一笔赞助!"
);
}
@EventHandler
public void onPaymentFailed(PaymentFailedEvent event) {
plugin.getLogger().warning("YuPay 支付失败或取消:"
+ event.getOrderId()
+ ", 玩家=" + event.getPlayerName()
+ ", 原因=" + event.getReason());
}
@EventHandler
public void onRefundCompleted(RefundCompletedEvent event) {
plugin.getLogger().info("YuPay 退款完成:"
+ event.getOrderId()
+ ", 玩家=" + event.getPlayerName()
+ ", 退款金额=" + event.getRefundAmount());
}
@EventHandler
public void onRedeemCodeRedeemed(RedeemCodeRedeemedEvent event) {
plugin.getLogger().info("YuPay 卡密已核销:"
+ event.getCode()
+ ", 玩家=" + event.getPlayerName()
+ ", 支付金额=" + event.getAmountPaid()
+ ", 订单=" + event.getOrderId());
}
@EventHandler
public void onRedeemCodeLifetime(RedeemCodeLifetimeEvent event) {
plugin.getLogger().info("YuPay 卡密状态变化:"
+ event.getCode()
+ ", " + event.getOldStatus()
+ " -> " + event.getNewStatus());
}
@EventHandler
public void onPlayerPointsChanged(PlayerPointsChangedEvent event) {
plugin.getLogger().info("YuPay 经济变化:"
+ event.getPlayerUUID()
+ ", 类型=" + event.getEconomyType()
+ ", 变化=" + event.getDelta()
+ ", 新余额=" + event.getNewBalance());
}
}---
2. PrePaymentEvent:支付创建前拦截
PrePaymentEvent 在玩家通过 /donate 等游戏内命令创建订单前触发,可取消,也可修改部分参数。
> 注意:第三方插件直接调用 YuPayApi#createOrder 创建订单时,不会经过玩家命令流程,因此不依赖 PrePaymentEvent。API 调用方应自行在创建订单前完成自己的业务校验。
可读取 / 修改
| 方法 | 说明 |
|---|---|
| getPlayer() | 发起支付的玩家 |
| getAmount() / setAmount(double) | 金额 |
| getPayMethod() / setPayMethod(String) | 支付方式 |
| getOrderId() / setOrderId(String) | 订单号 |
| isNoReward() / setNoReward(boolean) | 是否无奖励 |
| getOutputMode() / setOutputMode(String) | 输出模式,link / text / map |
| setCancelled(boolean) | 是否取消 |
| setCancelReason(String) | 取消后提示给玩家的原因 |
示例:禁止某玩家创建赞助订单。
@EventHandler(priority = EventPriority.HIGHEST)
public void onPrePayment(PrePaymentEvent event) {
if (event.getPlayer().getName().equalsIgnoreCase("BadPlayer")) {
event.setCancelled(true);
event.setCancelReason("§c你暂时无法使用赞助功能。");
}
}---
3. PaymentOrderCreatedEvent:订单创建成功
当 YuPay 成功向支付平台创建订单,并获得二维码链接后触发。
可读取
| 方法 | 说明 |
|---|---|
| getPlayerUUID() | 玩家 UUID |
| getPlayerName() | 玩家名 |
| getAmount() | 金额 |
| getPayMethod() | 支付方式 |
| getOrderId() | 订单号 |
| getQrCodeUrl() | 支付二维码链接 |
| isNoReward() | 是否无奖励订单 |
示例
@EventHandler
public void onOrderCreated(PaymentOrderCreatedEvent event) {
plugin.getLogger().info("订单创建成功:" + event.getOrderId());
}---
4. PaymentCompletedEvent:支付成功并完成发奖
当支付成功,并且 YuPay 内部奖励逻辑处理完成后触发。
可读取
| 方法 | 说明 |
|---|---|
| getPlayerUUID() | 玩家 UUID |
| getPlayerName() | 玩家名 |
| getAmountRmb() | 支付金额 |
| getPointsEarned() | 获得的经济奖励数量 |
| getPayMethod() | 支付方式 |
| getOrderId() | 订单号 |
| isNoReward() | 是否无奖励 |
| getTimestamp() | 事件时间戳 |
| isPlayerOnline() | 玩家是否在线 |
| getPlayer() | 在线时返回 Player,否则返回 null |
示例:支付完成后发全服广播。
@EventHandler
public void onPaymentCompleted(PaymentCompletedEvent event) {
Bukkit.broadcastMessage("§6感谢 §e" + event.getPlayerName()
+ " §6赞助服务器 §e" + event.getAmountRmb() + " 元!");
}---
5. PaymentFailedEvent:支付失败或取消
可读取
| 方法 | 说明 |
|---|---|
| getPlayerUUID() | 玩家 UUID |
| getPlayerName() | 玩家名 |
| getOrderId() | 订单号 |
| getReason() | 失败或取消原因 |
| getPayMethod() | 支付方式 |
示例
@EventHandler
public void onPaymentFailed(PaymentFailedEvent event) {
plugin.getLogger().warning("订单失败:" + event.getOrderId()
+ ", 原因:" + event.getReason());
}---
6. RefundCompletedEvent:退款完成
当外部支付平台退款成功,并且 YuPay 内部状态已经更新后触发。
可读取
| 方法 | 说明 |
|---|---|
| getOperatorUUID() | 操作者 UUID |
| getOperatorName() | 操作者名称 |
| getOrderId() | 原支付订单号 |
| getRefundAmount() | 本次退款金额 |
| getOriginalAmount() | 原订单金额 |
| getPlayerUUID() | 被退款玩家 UUID |
| getPlayerName() | 被退款玩家名 |
| isRedeemRefund() | 是否为卡密相关退款 |
示例
@EventHandler
public void onRefundCompleted(RefundCompletedEvent event) {
Bukkit.getLogger().info("[YuPay] 订单 " + event.getOrderId()
+ " 已退款 " + event.getRefundAmount() + " 元");
}---
7. RedeemCodeRedeemedEvent:卡密核销完成
可读取
| 方法 | 说明 |
|---|---|
| getCode() | 卡密 |
| getPlayerUUID() | 玩家 UUID |
| getPlayerName() | 玩家名 |
| getAmountPaid() | 核销时支付金额,免费卡密为 0 |
| getEconomyType() | 经济类型 |
| getEconomyAmount() | 经济奖励数量 |
| hasCommands() | 是否绑定命令奖励 |
| getOrderId() | 关联订单号,免费卡密可能为空 |
示例
@EventHandler
public void onRedeem(RedeemCodeRedeemedEvent event) {
plugin.getLogger().info(event.getPlayerName()
+ " 核销了卡密 " + event.getCode());
}---
8. RedeemCodeLifetimeEvent:卡密状态变化
当卡密进入 DEPLETED、EXPIRED、DISABLED 等状态时触发。
可读取
| 方法 | 说明 |
|---|---|
| getCode() | 卡密 |
| getOldStatus() | 旧状态 |
| getNewStatus() | 新状态 |
示例
@EventHandler
public void onCodeStatusChange(RedeemCodeLifetimeEvent event) {
plugin.getLogger().info("卡密状态变化:"
+ event.getCode()
+ " " + event.getOldStatus()
+ " -> " + event.getNewStatus());
}---
9. PlayerPointsChangedEvent:YuPay 经济变动
当 YuPay 内部发放 Vault 或 PlayerPoints 经济奖励后触发。
可读取
| 方法 | 说明 |
|---|---|
| getPlayerUUID() | 玩家 UUID |
| getEconomyType() | 经济类型,通常为 vault 或 playerpoints |
| getDelta() | 本次变化数量 |
| getNewBalance() | 新余额,未知时为 -1 |
示例
@EventHandler
public void onEconomyChanged(PlayerPointsChangedEvent event) {
plugin.getLogger().info("玩家 " + event.getPlayerUUID()
+ " 的 " + event.getEconomyType()
+ " 变化:" + event.getDelta());
}---
API 与事件使用建议
- 如果你要“主动创建支付订单”,使用
YuPayApi#createOrder。 - 如果你要“监听玩家通过 YuPay 命令发起支付”,使用
PrePaymentEvent。 - 如果你要“支付完成后同步商城订单状态”,监听
PaymentCompletedEvent。 - 如果你要“玩家取消或订单失败后回滚业务状态”,监听
PaymentFailedEvent。 - 如果你要“退款后撤销外部权益”,监听
RefundCompletedEvent。 - 如果你要“卡密核销后同步外部系统”,监听
RedeemCodeRedeemedEvent。 CompletableFuture回调中如需操作 Bukkit API,请切回主线程。- 不建议在事件监听器里执行耗时数据库或网络请求;如有耗时逻辑,请自行异步处理。
[/md]
[md]
---
⚙️ 环境要求
| 项目 | 要求 |
|---|---|
| 服务端 | Spigot / Paper / 兼容 Bukkit API 的分支 |
| Minecraft 版本 | 1.8+ 主流版本,具体以实际服务端测试为准 |
| Java | Java 8 或更高 |
| 可选前置 | Vault、PlayerPoints、PlaceholderAPI、ProtocolLib |
可选前置说明
| 插件 | 用途 |
|---|---|
| Vault | 发放 Vault 经济奖励 |
| PlayerPoints | 发放点券奖励 |
| PlaceholderAPI | 启用 PAPI 占位符 |
| ProtocolLib | 地图二维码支付锁与更安全的地图交互控制 |
---
🚀 两分钟上手
- 将
YuPay.jar放入服务器plugins/目录 - 启动服务器,等待自动生成配置文件
- 打开
plugins/YuPay/config.yml - 配置微信支付或支付宝参数
- 将支付平台后台的异步通知地址配置为你的公网回调地址
- 放行回调端口,默认
8080 - 执行重载:
/yp reload- 运行健康检查:
/yp health full- 使用小额订单测试:
/donate 0.01确认支付成功、回调正常、奖励到账后,即可正式使用。
---
🔧 常用配置速查
支付核心
pay:
default-method: "wechat"
order-subject: "赞助-{player}-{amount}元"
min-amount: 0.01
max-amount: -1
max-daily-amount: -1待支付订单自动过期
pay:
pending-expire:
enabled: true
minutes: 15
check-interval-seconds: 60
batch-size: 100回调服务器
callback:
port: 8080
host: "0.0.0.0"
io-threads: 2
worker-threads: 8
max-body-size: 65536
allowed-ips: []
access-log: false数据库
database:
type: "sqlite"或使用 MySQL
database:
type: "mysql"
mysql:
host: "localhost"
port: 3306
database: "minecraft"
username: "root"
password: "password"二维码展示
qrcode:
default-output-mode: "map"
text-qr-size: 12
text-black-char: "██"可选输出模式
| 模式 | 说明 |
|---|---|
| link | 发送可点击支付链接 |
| text | 发送聊天栏字符二维码 |
| map | 发送背包地图二维码 |
---
🎮 常用命令
> 以下命令按推荐别名展示。实际别名可在 config.yml 的 commands 节点中自定义。
玩家命令
| 命令 | 功能 |
|---|---|
| /donate <金额> [支付方式] [输出模式] [-n] | 发起赞助 |
| /ytop | 查看赞助排行榜 |
| /ytotal [玩家|all] | 查询赞助总额 |
| /yp history [页数] | 查看自己的赞助历史 |
| /yp orders [页数] | 查看自己的订单列表 |
| /yp order <订单号> | 查看自己的订单详情 |
| /yp cancel [订单号] | 取消待支付订单 |
| /yp retry [订单号] | 自助重试补发奖励 |
| /yp lang <语言> | 切换语言 |
| /yp code redeem <卡密> | 核销卡密 |
| /yp refund request <订单号> [金额] | 申请退款 |
管理员命令
| 命令 | 功能 |
|---|---|
| /yp reload | 重载配置 |
| /yp config | 查看核心配置 |
| /yp set <选项> <值> | 在线修改配置项 |
| /yp ban <玩家|all> [原因] | 禁止赞助 |
| /yp unban <玩家|all> | 解除禁止 |
| /yp convert <玩家> <来源> <目标> <数量> | Vault / PlayerPoints 互转 |
| /yp health [summary|full] | 健康检查 |
| /yp audit [listeners|security] | 事件监听 / 安全日志审计 |
| /yp crossserver [status|queue] | 查看跨服发奖状态 |
| /yp order ... | 订单中心 |
| /yp code ... | 卡密管理 |
| /yp refund ... | 退款管理 |
---
🔐 权限节点
玩家权限
| 权限 | 说明 |
|---|---|
| yupay.command.pay | 发起赞助 |
| yupay.command.top | 查看排行榜 |
| yupay.command.total | 查询赞助总额 |
| yupay.command.history | 查看历史订单 |
| yupay.command.orders | 查看订单列表 |
| yupay.command.cancel | 取消待支付订单 |
| yupay.command.retry | 自助补发 |
| yupay.command.code | 使用卡密入口 |
| yupay.code.redeem | 核销卡密 |
| yupay.command.refund | 使用退款入口 |
| yupay.refund.request | 申请退款 |
管理权限
| 权限 | 说明 |
|---|---|
| yupay.reload | 重载配置 |
| yupay.ban | 黑名单管理 |
| yupay.convert | 货币转换 |
| yupay.audit | 审计 |
| yupay.order | 订单管理 |
| yupay.health | 健康检查 |
| yupay.crossserver | 跨服发奖状态 |
| yupay.code.admin | 卡密管理 |
| yupay.refund | 退款管理 |
| yupay.admin | 管理员总权限 |
---
🗄️ 数据表
YuPay 会自动创建并维护所需数据表。
| 表 | 用途 |
|---|---|
| yu_payments | 支付订单 |
| yu_total_levels | 累计奖励档位记录 |
| yu_banned_players | 赞助黑名单 |
| yu_player_lang | 玩家语言偏好 |
| yu_redeem_codes | 卡密主表 |
| yu_redeem_logs | 卡密核销日志 |
| yu_refund_logs | 退款操作日志 |
| yu_refund_requests | 退款审核请求 |
| yu_refunds | 平台退款流水 |
| yu_reward_steps | 发奖步骤流水 |
| yu_payment_callbacks | 支付 / 退款回调流水 |
| yu_cross_server_rewards | 跨服发奖队列 |
| yu_schema_migrations | 数据库迁移记录 |
支持自定义表名前缀,适合多插件共用数据库场景。
---
🧯 常见问题
支付成功但奖励没到账
先让玩家执行
/yp retry管理员可继续排查
/yp order info <订单号>
/yp order steps <订单号>
/yp order callbacks <订单号>重点检查
- 订单是否为
SUCCESS - 发奖状态是否为
FAILED - Vault / PlayerPoints 是否正常加载
- 命令奖励中是否有错误命令
- LuckPerms、CMI、EssentialsX 等插件是否可用
---
玩家取消订单后又付款了怎么办?
YuPay 会以支付平台成功回调为准进行幂等处理。
管理员可以查看异常订单
/yp order abnormal
/yp order risk并根据实际情况处理
/yp order resolve <订单号> reward
/yp order resolve <订单号> refund
/yp order resolve <订单号> ignore
/yp order resolve <订单号> hold---
回调服务器启动失败
检查
- 端口是否被占用
- 防火墙是否放行
- 云服务器安全组是否放行
- 支付平台后台 notify-url 是否填写正确
- 是否使用了内网 IP 或 localhost
可执行
/yp health full快速查看问题。
---
地图二维码无法使用
检查
- 是否安装 ProtocolLib
- ProtocolLib 版本是否兼容当前服务端
qrcode.default-output-mode是否为map- 玩家背包是否有空位
如果 ProtocolLib 不可用,建议临时切换:
qrcode:
default-output-mode: "link"---
微信 / 支付宝配置失败
建议先执行
/yp health full重点检查
- 微信商户号、AppID、证书序列号
- 微信 API v3 密钥
- 微信平台公钥 / 商户私钥路径
- 支付宝 app-id
- 支付宝商户私钥
- 支付宝公钥或证书模式配置
- notify-url 是否仍为示例地址
---
🏆 为什么腐竹会需要 YuPay?
因为它解决的是服务器运营最麻烦的几件事
- 玩家付款后,奖励能不能自动到账?
- 付款成功但没到账,能不能查清楚?
- 玩家取消订单后又付款,怎么处理?
- 退款有没有审核流程?
- 多个子服如何统一收款和发奖?
- 排行榜和赞助数据怎么展示?
- 商城插件能不能接入支付?
- 出问题时,能不能快速定位?
YuPay 不只是“收钱插件”,而是一套围绕 Minecraft 服务器赞助场景设计的完整支付运营系统。
---
💬 交流与支持
插件交流群:1080918424
遇到问题时,建议带上
- 控制台日志
config.yml关键配置截图/yp health full输出- 订单号
- 支付渠道
- 是否使用 MySQL / 跨服模式
这样更容易快速定位问题。
---
💛 支持作者
爱发电:https://afdian.com/a/vicuna
如果 YuPay 帮你节省了对账、发奖、退款和排查问题的时间,欢迎支持作者继续维护。
---
一句话总结
YuPay 把 Minecraft 服务器赞助从“手动收款 + 手动发奖 + 手动对账”,升级成“官方支付 + 自动发奖 + 完整审计 + 可扩展 API”的现代化运营系统。
把繁琐的赞助流程交给 YuPay,腐竹就能把时间留给真正重要的事:
做好服务器,服务好玩家。