[插件] 赞助中心(payment_center)插件发布

👑Lv.11 元老 🌏 正式会员
2026-09-30 20:33:57

版本: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,替代防护为:

  1. 验签(各通道口径不同,见 Channel/*.php)
  2. 三重比对:订单号存在 + 通道匹配 + 金额一致 + 状态为待支付
  3. 幂等:UPDATE ... WHERE order_no = :no AND status = 0 + rowCount()
  4. 限流:RateLimiter 60 次/分钟
  5. 取证:原始报文(脱敏后)落 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)。


六、注意事项(踩过的坑)

  1. 路由占位符名必须与方法参数名逐字一致 —— Router::dispatch() 用 call_user_func_array($handler, $params) 传关联数组,PHP 8 下按参数名匹配: {order_no} 配 $orderNo 会抛 Error: Unknown named parameter。故本插件统一用 {orderNo}。
  2. 目录名含下划线 → Plugin.php 末尾必须 class_alias(核心按目录名硬拼类名, 否则生命周期三方法静默跳过 → 卸载留孤儿表)。
  3. 后台控制器必须继承 app\Controllers\Admin\BaseController(构造函数内含管理员门禁 + 二次验证); 继承 app\Core\Controller 会绕过后台门禁。
  4. 建表 marker 带版本号(data/.schema.v1.ok)—— 表结构变更时必须递增, 否则老安装不会重跑 activate()。每个后台入口另有 ensureTables() 兜底。
  5. nav_plugin_links 双触发 —— 该钩子文件禁声明顶层函数/类,否则降级为 include_once, 第二处入口静默消失。
  6. route_after_dispatch 必须节流 —— 该钩子每请求触发,未加节流会把超时关闭变成每请求全表扫。
  7. 微信 V3 回调应答必须是 HTTP 200 + 空体,否则微信会持续重试。
  8. ★ RateLimiter::check() 的契约是 true=允许 / false=超限(app/Helpers/RateLimiter.php:223)—— 写成 if (RateLimiter::check(...)) { 拒绝 } 会把每一次请求都拒掉。本插件下单与回调两处 都曾写反:回调恒得 429 busy → 资金链路 100% 不到账。它不报错、error.log 无痕、 静态检查看不出,只有发真实 HTTP 才能发现。
  9. ★ 文本列比较禁用 (int) 强转 —— (int)'points' === 0,于是 if ((int)($order['biz_type'] ?? 'points') !== 'points') return; 会恒真提前返回,积分一列不发。 biz_type 是 TEXT 列,必须用 (string) 比较。
  10. 发布前必跑:
  • 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)
  • 前台文案已按「赞助 / 打赏」定位(不出现「充值」「余额」「提现」)
  • 部署后执行一次后台「维护 → 清理缓存」
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 11 次 | 回覆 4 次

全部回覆 (3)

👑Lv.11 元老 🌏 正式会员
2026-09-30 20:34:15
有问题及时反馈在本帖即可。
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
#1 樓
🌟Lv.6 资深 🌛 见习会员
2026-09-30 20:35:41
新鲜的,抢沙发
#2 樓
💡Lv.10 顾问 🌏 正式会员
2026-09-30 20:54:35
还有板凳,不算晚
知识,奉行,知行合一
#3 樓

請 登入