chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
# 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` 接口说明