本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/payment_center/说明.md。
赞助中心(payment_center)插件说明
版本:1.0.0 | 作者:FlintHub | 状态:✅ 已启用
功能:通过聚合支付 / 官方直连通道接收用户赞助,赞助成功后自动兑换为站点积分。
支持易支付协议(EPAY)、虎皮椒、支付宝当面付、微信支付 V3 与手动到账五种通道。
定位:赞助 / 打赏 —— 不产生站内余额,不可提现,从产品层面规避资金池与二清风险。
一、功能简介
1.1 前台
| 页面 | 路径 | 说明 |
|---|---|---|
| 赞助中心 | /sponsor | 档位列表 + 我的积分 + 最近赞助 |
| 我的赞助记录 | /sponsor/orders | 分页列表,可继续支付 / 查看详情 |
| 收银台 | /sponsor/cashier/{订单号} | 选择支付方式 → 扫码 / 跳转 → 轮询到账 |
| 支付结果 | /sponsor/result/{订单号} | 展示状态;待支付时自动轮询刷新 |
1.2 后台(/admin/sponsor)
| 页面 | 路径 | 说明 |
|---|---|---|
| 概览 + 订单 | /admin/sponsor | 四项统计、订单筛选、手动确认到账 / 关闭 / 补发积分、最近回调日志 |
| 通道配置 | /admin/sponsor/channels | 五通道密钥配置(加密存储)、启用停用、回调地址展示、订单超时设置 |
| 档位管理 | /admin/sponsor/products | 档位增删改、启停、排序 |
三页之间靠页内导航(.pc-tabs)互跳 —— 核心插件卡片的「设置」按钮进的是
admin_url = /admin/sponsor(订单页),配置页不在卡片上,必须靠页内导航进入。
导航样式在插件自己的 assets/admin.css(核心无全局 Tab 组件,不改核心资源)。
1.3 支付通道
| 通道标识 | 名称 | 资质要求 | 说明 |
|---|---|---|---|
epay | 易支付(聚合) | 无(选平台) | 兼容 EPAY 协议的聚合平台;换平台只改后台三个字段,代码零改动 |
xunhupay | 虎皮椒 | 个人可申请 | 免签通道,官方清算 |
alipay_face | 支付宝当面付 | 个人可申请 | 官方直连,RSA2 签名 + 支付宝公钥验签 |
wechat_v3 | 微信支付 V3 | 需商户资质 | Native 扫码,平台公钥验签 + AES-256-GCM 解回调 |
manual | 手动到账 | 无 | 线下赞助,管理员后台确认;未配置在线通道时的兜底 |
二、目录结构
plugins/payment_center/
├── plugin.json # 插件清单(6 个钩子 + admin_url + permissions)
├── Plugin.php # 主类:建表/迁移、订单状态机、通道配置加解密、积分发放(幂等)
├── FrontController.php # 前台:赞助中心 / 下单 / 收银台 / 轮询 / 回跳 / 异步回调
├── AdminController.php # 后台:订单 / 通道 / 档位 / 设置(继承 Admin\BaseController)
├── Channel/ # 通道适配器
│ ├── ChannelInterface.php # 契约:create / query / verifyNotify / notifyAck / refund
│ ├── ChannelFactory.php # 通道标识 → 实现类映射
│ ├── ChannelUtil.php # 签名 / 验签 / 密钥规范化 / HTTP / 金额换算
│ ├── ManualChannel.php # 手动到账
│ ├── EpayChannel.php # 易支付协议 V1(MD5)
│ ├── XunhuChannel.php # 虎皮椒
│ ├── AlipayFaceChannel.php # 支付宝当面付(RSA2)
│ └── WechatV3Channel.php # 微信支付 V3 Native
├── hook/
│ ├── init_after.php # 幂等建表兜底(版本化 marker 门控)
│ ├── route_register.php # 前台路由
│ ├── admin_route_register.php # 后台路由(不写 /admin 前缀)
│ ├── nav_plugin_links.php # 「应用」入口(双触发)
│ ├── nav_user_menu_items.php # 用户菜单入口(PC + 移动,双触发)
│ └── route_after_dispatch.php # 订单超时关闭(60s 节流 + 文件锁)
├── views/ # 9 个视图(前台 4 + 后台 3 + 共用)
├── assets/ # style.css / admin.css / script.js(零内联)
├── lang/ # zh / en / zh_tw(各 168 键,键集合与键序零差异)
└── data/ # 独立库 + 建表 marker + 节流/锁文件(自动防 Web 访问)三、数据表(插件独立库)
库文件:plugins/payment_center/data/payment_center.sqlite
| 表 | 用途 | 关键字段 |
|---|---|---|
pc_orders | 订单主表(也是资金台账) | order_no(UNIQUE) / amount_fen / paid_fen / channel / status / biz_value / points_awarded |
pc_notify_log | 回调原始报文(排障 + 取证) | order_no / channel / raw(脱敏) / verified / result / ip |
pc_channels | 通道配置 | channel(PK) / enabled / config_enc(AES-256-GCM 密文) |
pc_products | 赞助档位 | name / amount_fen / biz_type / biz_value / enabled |
pc_settings | 插件设置 | skey / sval(订单超时时长等) |
订单状态:0 待支付 | 1 已支付(已到账) | 2 已关闭 | 3 已退款(预留) | 4 支付失败
⚠️ 金额一律以「分」存整数,杜绝浮点误差。
⚠️ 本插件不建余额表 —— 定位是「赞助换积分」,不产生站内余额。
四、路由清单
4.1 前台(hook/route_register.php)
| 方法 | 路径 | 鉴权 |
|---|---|---|
| GET | /sponsor | 登录 |
| GET | /sponsor/orders | 登录 |
| GET | /sponsor/cashier/{orderNo} | 登录 + 归属校验 |
| GET | /sponsor/result/{orderNo} | 登录 + 归属校验 |
| POST | /sponsor/create | 登录 → CSRF |
| POST | /sponsor/pay/{orderNo} | 登录(JSON 401) → CSRF |
| POST | /sponsor/cancel/{orderNo} | 登录 → CSRF |
| GET | /sponsor/status/{orderNo} | 登录(只读) |
| GET | /sponsor/return/{channel} | 登录(只跳转,不到账) |
| POST | /sponsor/notify/{channel} | 无登录、无 CSRF,靠验签 ★ |
4.2 后台(hook/admin_route_register.php,不写 /admin 前缀)
/sponsor(概览+订单)、/sponsor/order/close|confirm|retry-award、
/sponsor/channels + /save|toggle、/sponsor/settings/save、
/sponsor/products + /save|delete|toggle
五、核心规则(改这个插件前必读)
5.1 ★ 到账只信异步回调
- 唯一可信入口 =
POST /sponsor/notify/{channel},必须通过验签 + 三重比对。 /sponsor/return/{channel}与/sponsor/result/{orderNo}只做展示,绝不到账。
> 同步回跳 URL 可被用户直接访问伪造,据它发货就是白嫖。这是支付系统第一大坑。
5.2 回调的替代防护(规范红线 8 的唯一合法例外)
外部服务器无法持有 CSRF token,故回调不校验 CSRF,替代防护为:
- 验签(各通道口径不同,见
Channel/*.php) - 三重比对:订单号存在 + 通道匹配 + 金额一致 + 状态为待支付
- 幂等:
UPDATE ... WHERE order_no = :no AND status = 0+rowCount() - 限流:
RateLimiter60 次/分钟 - 取证:原始报文(脱敏后)落
pc_notify_log
⚠️ 只验签不验金额 = 「1 分钱买 100 元档位」。
5.3 幂等与跨库事务
订单在插件库、积分在核心库 —— 两个独立 SQLite 文件,无法跨库事务。因此顺序固定为:
① 条件 UPDATE 订单 status 0→1(原子闸门,重复回调只有第一次能过)
② 发积分(失败则 points_awarded 保持 0,后台「补发积分」可重试)发积分前会查 points_log(related_id + related_type='payment_center') 去重,
防「发放成功但标记失败」的窗口重复发。
5.4 金额只信服务端
前端只传 product_id + channel,金额由服务端反查 pc_products 得出。绝不接受前端传金额。
5.5 密钥加密
- 通道密钥经 AES-256-GCM 加密后存
pc_channels.config_enc。 - 加密密钥解析顺序:
FLINTHUB_PAYMENT_KEY环境变量 →protected/payment_key.php→ 回退MAIL_PASS_KEY(后台会提示加固)。 - 后台永不回显明文,只显示「已配置 / 未配置」;留空提交 = 保持原值。
5.6 订单号不可枚举
SP + yyyyMMddHHmmss + 10 位随机十六进制。所有按订单号的操作必须校验 user_id 归属(防 IDOR)。
六、注意事项(踩过的坑)
- 路由占位符名必须与方法参数名逐字一致 ——
Router::dispatch()用
call_user_func_array($handler, $params) 传关联数组,PHP 8 下按参数名匹配:
{order_no} 配 $orderNo 会抛 Error: Unknown named parameter。故本插件统一用 {orderNo}。
- 目录名含下划线 →
Plugin.php末尾必须class_alias(核心按目录名硬拼类名,
否则生命周期三方法静默跳过 → 卸载留孤儿表)。
- 后台控制器必须继承
app\Controllers\Admin\BaseController(构造函数内含管理员门禁 + 二次验证);
继承 app\Core\Controller 会绕过后台门禁。
- 建表 marker 带版本号(
data/.schema.v1.ok)—— 表结构变更时必须递增,
否则老安装不会重跑 activate()。每个后台入口另有 ensureTables() 兜底。
nav_plugin_links双触发 —— 该钩子文件禁声明顶层函数/类,否则降级为include_once,
第二处入口静默消失。
route_after_dispatch必须节流 —— 该钩子每请求触发,未加节流会把超时关闭变成每请求全表扫。- 微信 V3 回调应答必须是 HTTP 200 + 空体,否则微信会持续重试。
- ★
RateLimiter::check()的契约是true=允许 / false=超限(app/Helpers/RateLimiter.php:223)——
写成 if (RateLimiter::check(...)) { 拒绝 } 会把每一次请求都拒掉。本插件下单与回调两处
都曾写反:回调恒得 429 busy → 资金链路 100% 不到账。它不报错、error.log 无痕、
静态检查看不出,只有发真实 HTTP 才能发现。
- ★ 文本列比较禁用
(int)强转 ——(int)'points' === 0,于是
if ((int)($order['biz_type'] ?? 'points') !== 'points') return; 会恒真提前返回,积分一列不发。
biz_type 是 TEXT 列,必须用 (string) 比较。
- 发布前必跑:
python .workbuddy-ai/tools/plugin_case_lint.py plugins/payment_center.workbuddy-ai/tools/php_var_cjk_check.php plugins/payment_center- 零内联扫描 + 三语键集合比对
七、上线前检查清单
- [ ] 聚合支付平台按四条判据核验:企业备案 / 对公结算 / 运营满 1 年 / 小额实测通道
- [ ] 先配
manual通道跑通一笔,再启用在线通道 - [ ] 每个通道小额实付一笔,确认从下单到积分到账全链路
- [ ] 后台 → 通道配置页复制「回调地址」填到支付平台后台
- [ ] 配置独立加密密钥(
protected/payment_key.php或FLINTHUB_PAYMENT_KEY) - [ ] 前台文案已按「赞助 / 打赏」定位(不出现「充值」「余额」「提现」)
- [ ] 部署后执行一次后台「维护 → 清理缓存」
八、当前状态
- ✅ 插件骨架 / 数据层 / 5 个通道适配器 / 前台 / 后台 / 6 个钩子 / 三语语言包
- ✅ 静态校验:
php -l27 文件零错误 / 命名空间 Linux 精确匹配 / 零内联 /
三语各 168 键集合零差异 / i18n 引用键零缺失 / 全角标点零命中 / script.js 语法通过
- ✅ CLI 资金链路探针 46 项全绿 —— 下单 → 到账 → 发积分 → 重复回调幂等 →
金额/通道/订单号三类拒绝 → 关闭状态机 → 越权 404 → 加解密往返 → 清理后库表归零
- ✅ HTTP 端到端冒烟 52 项全绿 —— 静态资源 / 后台三重门禁 / 插件列表可见 / 导航钩子生效 /
前台 302 与 401 双口径 / 回调拒绝路径 / 通道启用后回调真正进入验签 / 库表增量守卫 / error.log 无新增
- ✅ 后台入口可达性探针全绿 —— 订单页含页内导航 + 两个子页链接、当前页高亮唯一、
通道配置页渲染出完整表单(保存 action / 通道字段 / 密钥框 / 回调地址展示)
- ⏳ 通道密钥需站长在后台自行配置(本插件不含任何预设密钥)
- ⏳ 微信支付 V3 需商户资质,未取得资质时该通道保持停用即可
- ⏳ 真实实付需站长用真实商户号小额验证(本机无法模拟上游,故未覆盖)