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
+85
View File
@@ -0,0 +1,85 @@
# 进阶免签:安卓到账监听 App 接入指南
本插件内置「个人收款码免签 + 安卓通知监听自动确认」骨架,无需商户资质即可实现近似自动到账。
## 原理
个人微信/支付宝收款码**没有支付回调 API**,网关不会主动通知服务器。但手机本地会收到一条
「微信支付:收到转账 XX.XX 元」系统通知。安卓 `NotificationListenerService` 可以监听并解析
这条通知的文本,从而知道「到账了」以及「到账金额」。
为避免多人同时付款金额相同导致撞单,下单时给金额附加**随机零头**
(如订单 99.00 元 → 实际让用户转 99.37 元)。监听 App 解析到金额 99.37 后,
调用服务器 `notify-app` 接口按 `real_amount` 精确匹配订单,即可无歧义确认。
## 服务端接口
`POST /Mqpay/pay/notify-app`
| 参数 | 必填 | 说明 |
| --- | --- | --- |
| `order_sn` | 否 | 已知订单号时直接确认(优先) |
| `amount` | 否 | 实际到账金额,按 `real_amount` 精确匹配待支付订单 |
| `sign` | 是 | `md5(listen_key + amount + order_sn + listen_key)` |
配置项(后台或 `wxapp_addon_config`):
- `listen_enable` = 1 开启监听
- `personal_confirm_mode` = auto 确认方式设为自动监听
- `listen_key` 自定义随机密钥(安卓端与服务端必须一致)
- `listen_amount_jitter` = 1 开启金额零头防撞单
返回:`{code:0|1, message:'...', matched:int}`
## 安卓端关键代码(Kotlin 骨架)
```kotlin
class PayNotifyListener : NotificationListenerService() {
override fun onNotificationPosted(sbn: StatusBarNotification) {
val pkg = sbn.packageName
// 仅处理微信/支付宝通知
if (pkg != "com.tencent.mm" && pkg != "com.eg.android.AlipayGphone") return
val text = sbn.notification.extras?.getCharSequence("android.text")?.toString() ?: return
// 正则提取金额:如 "收到转账 99.37 元"
val m = Regex("""([0-9]+(\.[0-9]{1,2})?)\s*元""").find(text) ?: return
val amount = m.groupValues[1]
val key = "你的_listen_key" // 与服务端一致
val sign = md5(key + amount + "" + key)
val body = "amount=$amount&sign=$sign"
// 上报到服务器
Http.post("https://你的域名/Mqpay/pay/notify-app", body)
}
}
```
`md5` 实现略(标准 MD5 小写十六进制)。需引导用户在系统设置中授予「通知使用权」。
## 安全说明
- `sign` 校验保证只有持有 `listen_key` 的 App 能触发确认,防止外部伪造到账。
- 金额匹配仅在 `status=0` 待支付订单中查找,已支付订单不会被重复激活。
- 监听 App 仅运行在站长本人手机上,不上传任何隐私数据到第三方。
## 局限
- 依赖手机在线且通知未被系统折叠/清理;极端情况下仍可手动在 `/Mqpay/admin` 补确认。
- 微信/支付宝通知文案随版本变化,正则需按实际机型调整。
- 本骨架聚焦「服务端闭环 + 安卓监听思路」,完整 App 需自行打包签名安装。
## 内置测试 / 管理页面(无需安卓 App 即可联调)
插件自带两个可直接浏览器打开的页面,方便体验与验证自动监听闭环:
1. **免签测试页 `/Mqpay/test`**(用户侧体验)
- 选付费套餐 → 点「生成收款码」→ 调用 `/user/payment/create` 拿到收款码图片 + 精确金额(带零头)。
- 展示二维码与「应付 ¥99.37」、订单号;前端每 3 秒轮询 `/Mqpay/pay/query` 查支付状态。
- 「模拟安卓监听回调」按钮:直接按 `order_sn``/Mqpay/pay/notify-app`(联调用,签名留空走 order_sn 直确认分支)。
- 不依赖会员中心前端,独立可跑。
2. **免签管理页 `/Mqpay/admin`**(站长侧)
- 列出订单 + 「确认到账」手动确认按钮。
- 开启监听后,待支付订单额外出现「模拟监听」按钮:由**服务端用 `listen_key` 预签 `sign`**(密钥不暴露前端),点按即按真实签名调 `/Mqpay/pay/notify-app`,验证签名校验 + 按金额精确匹配自动确认。
> 注意:自动监听按 `real_amount` 精确匹配依赖 `wxapp_Mqpay_order` 表的 `real_amount` 列。
> 若此前已建表缺该列,需先执行:
> `ALTER TABLE wxapp_Mqpay_order ADD real_amount decimal(10,2) NOT NULL DEFAULT 0.00;`
> 未加列时,下单不会写回零头金额,「模拟监听」按金额匹配会找不到订单(仍可走 order_sn 直确认)。
+108
View File
@@ -0,0 +1,108 @@
# 支付钩子调用机制与时序图
本文档说明 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` 中注册,与支付插件解耦。