148 lines
8.8 KiB
Markdown
148 lines
8.8 KiB
Markdown
# 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` 接口说明
|