# 支付钩子调用机制与时序图 本文档说明 mqpay 支付插件内部基于 ThinkPHP **事件系统(Event)+ 订阅器(Subscriber)** 的钩子调用方式, 以及与会员开通(`UserMemberActivate`)的解耦关系。完整插件说明见 `../README.md`。 ## 一、整体机制 支付钩子**不是**传统 `Hook::add`/`Hook::listen`,而是 ThinkPHP 的事件系统: - 业务核心 `app/member/controller/Payment.php` 只负责「触发事件」(`Event::trigger`); - 支付插件 `addon/mqpay/subscribe/Payment.php` 通过「事件订阅器」监听并注入实现; - 两者解耦,框架在启动阶段按 `info.php` 声明自动注册,无需手动绑定。 ## 二、调用链路(5 步) 1. **声明订阅器** — `addon/mqpay/info.php` 的 `events.subscribe`: ```php 'events' => [ 'subscribe' => [ 'addon\\mqpay\\subscribe\\Payment', ], ], ``` 2. **自动注册** — `ywxapp/service/AppService.php::loadAddonRelevant()` 遍历启用插件,调用 `app()->loadEvent($info['events'])`,把订阅器注册进事件系统。 3. **订阅方法即钩子** — `subscribe/Payment.php` 中方法名约定 `on + 事件名`: - `onPaymentMethods` ↔ `PaymentMethods` - `onPaymentOrderCreate` ↔ `PaymentOrderCreate` - `onPaymentCreate` ↔ `PaymentCreate` - `onPaymentNotify` ↔ `PaymentNotify` - `onPaymentFreeActive` ↔ `PaymentFreeActive` 4. **业务点火** — `app/member/controller/Payment.php`: ```php $results = Event::trigger('PaymentMethods', []); // Payment.php:77 Event::trigger('PaymentOrderCreate', ['order' => $order]); // Payment.php:153 $results = Event::trigger('PaymentCreate', [...]); // Payment.php:156 $results = Event::trigger('PaymentNotify', [...]); // Payment.php:180 Event::trigger('PaymentFreeActive', [...]); // Payment.php:134 ``` 5. **返回值消费**: - `PaymentMethods` → 返回 `array`,合并去重后返回前端(按 `sort` 排序)。 - `PaymentCreate` → 返回 `array`(如 `['type'=>'qrcode','qrcode_url'=>...]`)或 `null`(不处理);`create()` 取第一个非空作为支付参数。 - `PaymentNotify` → 返回 `'success'`/`'fail'`,`notify()` 直接作为网关响应体输出。 - `PaymentOrderCreate` / `PaymentFreeActive` → 副作用型(写库/日志),返回值忽略。 ## 三、会员开通:UserMemberActivate 监听器 支付成功后本插件仅完成**订单激活**(`wxapp_mqpay_order.status=1`),真正的「会员权益写入」 (用户组 / 会员有效期 / 资金流水 / 操作日志)通过触发 `UserMemberActivate` 事件交给业务层处理。 - **实现**:`app/member/listener/UserMemberActivate.php` - `handle($event)` 读取 `uid` / `plan_id` / `order` / `method`; - 按 `config('member.plans')` 取套餐 `duration`/`level`/`gid`; - 在原未过期时间上**顺延**期限(防重复购买覆盖); - 写 4 处:用户主表 `vip_expire`/`vip_level`/`gid`、`user_group_access`、`user_bill`、`user_log`。 - **注册**:全局 `app/event.php` 的 `listen` 数组(**非**插件 `info.php`)—— 与具体支付插件解耦。 - **触发**:本插件 `confirmOrder()`(`subscribe/Payment.php:326`),两条路径殊途同归: - 安卓免签:`POST /mqpay/pay/notify-app` → `notifyApp()` 验签 → 按 `real_amount` 匹配 → `confirmOrder()` - 后台人工:后台 `Confirm` 接口 → `Pay.php` → `confirmOrder()` ## 四、完整时序图(以"个人免签"为例) ``` 用户浏览器 app/member/controller/Payment addon/mqpay/subscribe/Payment 安卓监听App 会员监听器 │ │ │ │ │ │─ GET methods ───────▶│ │ │ │ │ │ Event::trigger('PaymentMethods') │ │ │ │────────────────────────────▶│ onPaymentMethods() │ │ │◀─ 方式列表 ──────────│◀─────────────────────────────│ │ │ │ │ │ │ │ │─ POST create(personal)▶│ │ │ │ │ │ 生成 order │ │ │ │ │ Event::trigger('PaymentOrderCreate') │ │ │ │────────────────────────────▶│ onPaymentOrderCreate() │ │ │ │ Event::trigger('PaymentCreate') │ │ │ │────────────────────────────▶│ onPaymentCreate() │ │ │ │ │ personalQrcode()+jitter │ │ │◀─ 收款码+精确金额 ───│◀─────────────────────────────│ │ │ │ │ │ ║ 用户扫码转账到账 ║ │ │ │ │ │ │ 监听通知栏 │ │ │ │ │─ POST /mqpay/pay/notify-app(amount)▶│ │ │ │ notifyApp() 验签 md5(key+amount+sn+key) │ │ │ │ 按 real_amount 精确匹配 │ │ │ │ │ confirmOrder(order_sn) │ │ │ │ │ status=1 │ │ │ │ │ Event::trigger('UserMemberActivate') │ │ │ │────────────────────────── 开通会员 ──────▶│ │◀─ 轮询/刷新看到已开通─│ │ │ │ ``` > 网关支付(微信/支付宝)走 `onPaymentNotify` 异步回调;**免签**(personal)无网关回调, > 由 `notify-app`(安卓)或后台 `Confirm` 触发 `confirmOrder()`,殊途同归到 `UserMemberActivate`。 ## 五、钩子 / 事件速查表 | 事件名 | 订阅方法 | 订阅位置 | 触发位置 | 返回值 | |---|---|---|---|---| | `PaymentMethods` | `onPaymentMethods` | `subscribe/Payment.php` | `Payment.php::methods()` | `array` 方式列表 | | `PaymentOrderCreate` | `onPaymentOrderCreate` | `subscribe/Payment.php` | `Payment.php::create()` | 无(写库) | | `PaymentCreate` | `onPaymentCreate` | `subscribe/Payment.php` | `Payment.php::create()` | `array`/`null` 支付参数 | | `PaymentNotify` | `onPaymentNotify` | `subscribe/Payment.php` | `Payment.php::notify()` | `'success'`/`'fail'` | | `PaymentFreeActive` | `onPaymentFreeActive` | `subscribe/Payment.php` | `Payment.php::create()` | 无 | | `UserMemberActivate` | `handle()` | `app/member/listener/UserMemberActivate.php` | `confirmOrder()` | 无(写库/日志) | ### 注册方式对照 - 支付钩子(前 5 个):`addon/mqpay/info.php` 的 `events.subscribe` 声明,框架启动时自动 `loadEvent` 注册。 - 会员开通(最后 1 个):全局 `app/event.php` 的 `listen` 中注册,与支付插件解耦。