# Mqpay — 示例支付插件(真实微信 / 支付宝) 通过监听 `app/member/controller/Payment.php` 暴露的 **Payment\* 事件** 接入真实微信/支付宝支付, 核心控制器零改动,所有支付能力由本插件注入(基于 [Yansongda Pay v3](https://pay.yansongda.cn))。 ## 前置依赖 ```bash composer require yansongda/pay # ^3.0 ``` 在 `.env` 中配置真实密钥(对应 `config/pay.php`): ```env # 微信支付 PAY_WECHAT_MCH_ID=你的商户号 PAY_WECHAT_APP_ID=你的AppID PAY_WECHAT_SECRET_KEY=APIv3密钥 # PAY_WECHAT_CERT_CLIENT=/path/apiclient_cert.pem # PAY_WECHAT_CERT_KEY=/path/apiclient_key.pem # 支付宝 PAY_ALIPAY_APP_ID=你的AppID PAY_ALIPAY_PUBLIC_KEY=支付宝公钥 PAY_ALIPAY_PRIVATE_KEY=应用私钥 ``` > 未安装 SDK 或未配置密钥时,插件会优雅降级:`methods` 仍返回通道,`create` 返回友好错误提示,不会致命报错。 ## 监听的事件契约 | 事件 | 订阅方法 | 入参 | 返回值 | |------|----------|------|--------| | `PaymentMethods` | `onPaymentMethods` | `[]` | `[['code'=>'wechat'\|'alipay','name','desc','icon','sort']]` | | `PaymentOrderCreate` | `onPaymentOrderCreate` | `['order'=>[...]]` | 无(落库 Mqpay_order) | | `PaymentCreate` | `onPaymentCreate` | `['order'=>[...],'params'=>[...]]` | `['type'=>'qrcode','qrcode_url','order_sn']` | | `PaymentNotify` | `onPaymentNotify` | `['method','post','get']` | `'success'` / `'fail'`(网关要求) | | `PaymentFreeActive` | `onPaymentFreeActive` | `['user','plan']` | 无 | ## 前端流程 1. 用户选择套餐 → 弹窗列出 `wechat` / `alipay` 两个通道; 2. 确认后 `POST /user/payment/create` → 触发 `PaymentOrderCreate` + `PaymentCreate`; 3. 插件调用 Yansongda 统一下单(扫码),前端用返回的 `qrcode_url` 渲染二维码; 4. 前端每 2 秒轮询 `POST /Mqpay/pay/query`(带 `order_sn`)检测支付结果; 5. 微信/支付宝异步通知 `POST /Mqpay/pay/notify?method=xxx` → 插件验签、标记订单、触发 `UserMemberActivate` 事件,返回 `success`。 ## 会员开通:UserMemberActivate 监听器 支付成功后本插件仅完成**订单激活**(把 `wxapp_mqpay_order.status` 置 1), 真正的「会员权益写入」(更新用户组 / 会员有效期 / 资金流水 / 操作日志)通过触发 `UserMemberActivate` 事件交给业务层处理,保证插件与业务解耦。 ### 监听器位置 - 实现:`app/member/listener/UserMemberActivate.php` - `handle($event)` 读取 `$event['uid']`、`$event['plan_id']`、`$event['order']`、`$event['method']`; - 依据 `config('member.plans')` 取套餐的 `duration`/`level`/`gid`; - 在原未过期时间基础上**顺延**会员期限(避免重复购买覆盖); - 写入 4 处:用户主表 `vip_expire`/`vip_level`/`gid`、用户组关联 `user_group_access`、 资金流水 `user_bill`、操作日志 `user_log`。 - 注册:`app/event.php` 的 `listen` 数组(`app/event.php:27-29`): ```php 'UserMemberActivate' => [ app\member\listener\UserMemberActivate::class, ], ``` > 注:该监听器注册在全局 `app/event.php`(非插件 `info.php`),因此与具体支付插件解耦—— > 任何触发 `UserMemberActivate` 事件的插件都能复用同一套会员开通逻辑。 > (`app/event.php` 注释里残留的 `paydemo` 字样是重命名前身,可忽略。) ### 触发点 本插件在两处触发该事件: | 触发路径 | 调用链 | |---|---| | 安卓免签自动确认 | `POST /mqpay/pay/notify-app` → `notifyApp()` 验签 → 按 `real_amount` 精确匹配 → `confirmOrder()` | | 后台人工确认 | 后台 `Confirm` 接口 → `Pay.php` → `confirmOrder()` | `confirmOrder()` 触发代码示例(`subscribe/Payment.php:326`): ```php Event::trigger('UserMemberActivate', [ 'uid' => $order['uid'], 'plan_id' => $order['plan_id'], 'order' => $order, 'method' => 'personal', ]); ``` ## 支付钩子完整调用链路(时序图) ``` 用户浏览器 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`。 ## 钩子 / 事件速查表 | 事件名 | 订阅方法 | 订阅位置 | 触发位置(Payment.php) | 返回值 | |---|---|---|---|---| | `PaymentMethods` | `onPaymentMethods` | `subscribe/Payment.php` | `methods()` | `array` 方式列表 | | `PaymentOrderCreate` | `onPaymentOrderCreate` | `subscribe/Payment.php` | `create()` | 无(写库) | | `PaymentCreate` | `onPaymentCreate` | `subscribe/Payment.php` | `create()` | `array`/`null` 支付参数 | | `PaymentNotify` | `onPaymentNotify` | `subscribe/Payment.php` | `notify()` | `'success'`/`'fail'` | | `PaymentFreeActive` | `onPaymentFreeActive` | `subscribe/Payment.php` | `create()` | 无 | | `UserMemberActivate` | `handle()` | `app/member/listener/UserMemberActivate.php` | `confirmOrder()`(`subscribe/Payment.php`) | 无(写库/日志) | ### 注册方式对照 - 支付钩子(前 5 个):在 `addon/mqpay/info.php` 的 `events.subscribe` 声明订阅器类, 框架 `AppService::loadAddonRelevant()` 启动时自动 `loadEvent` 注册。 - 会员开通(最后 1 个):在全局 `app/event.php` 的 `listen` 中注册,与支付插件解耦。 ## 相关文档 - [支付钩子调用机制与时序图](docs/payment_hooks.md) — 事件系统、订阅器注册、会员监听器链路详解 - [安卓到账监听 App 接入指南](docs/listen_app_guide.md) — 免签监听 App 对接 `notify-app` 接口说明