mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-05-10 02:44:27 +08:00
1. 维护代码健壮
2. 更新项目结构文档
This commit is contained in:
72
doc/backend/README.md
Normal file
72
doc/backend/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# 后端总说明
|
||||
|
||||
`mpay` 是支付中台后端服务,基于 Webman。命令默认在 `mpay/` 目录执行。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- PHP `>=8.1`
|
||||
- `workerman/webman-framework ^2.1`
|
||||
- MySQL、Redis
|
||||
- JWT、Webman validation/cache/event/redis/database
|
||||
- OSS/COS SDK 用于对象存储
|
||||
|
||||
## 快速启动
|
||||
|
||||
```bash
|
||||
composer install
|
||||
Copy-Item .env.example .env
|
||||
php webman start
|
||||
```
|
||||
|
||||
Windows 开发环境如需启动自定义进程,可使用:
|
||||
|
||||
```bash
|
||||
php windows.php
|
||||
```
|
||||
|
||||
## 主要目录
|
||||
|
||||
```text
|
||||
app/
|
||||
command/ 命令与烟雾测试
|
||||
common/ 基类、常量、工具、中间件、支付插件
|
||||
http/ admin、mer、api 三类 HTTP 入口
|
||||
model/ 数据模型
|
||||
repository/ 数据访问
|
||||
route/ 显式路由
|
||||
service/ 业务服务
|
||||
config/ Webman 与业务配置
|
||||
public/ 静态资源与前端构建产物
|
||||
support/ Webman 支撑代码
|
||||
```
|
||||
|
||||
## 关键入口
|
||||
|
||||
- 路由:`config/route.php`、`app/route/admin.php`、`app/route/mer.php`、`app/route/api.php`
|
||||
- 支付:`app/service/payment/order/PayOrderService.php`
|
||||
- 退款:`app/service/payment/order/RefundService.php`
|
||||
- 清算:`app/service/payment/settlement/SettlementService.php`
|
||||
- 路由:`app/service/payment/runtime/PaymentRouteService.php`
|
||||
- 插件:`app/service/payment/runtime/PaymentPluginManager.php`
|
||||
- 商户:`app/service/merchant/MerchantService.php`
|
||||
- 商户后台:`app/service/merchant/portal/MerchantPortalService.php`
|
||||
- 文件:`app/service/file/FileRecordService.php`
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
php webman start
|
||||
php webman restart
|
||||
php webman mpay:test --all
|
||||
php webman epay:mapi
|
||||
php webman system:config-sync
|
||||
```
|
||||
|
||||
## 关联文档
|
||||
|
||||
- [后端路由](./routing.md)
|
||||
- [后端服务层](./services.md)
|
||||
- [后端命令](./commands.md)
|
||||
- [文件资产](./files.md)
|
||||
- [接口总说明](../api/README.md)
|
||||
- [部署说明](../deployment/backend.md)
|
||||
41
doc/backend/commands.md
Normal file
41
doc/backend/commands.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 后端命令
|
||||
|
||||
命令默认在 `mpay/` 目录执行。
|
||||
|
||||
## Webman 启动
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `php webman start` | 启动后端 HTTP 服务 |
|
||||
| `php webman restart` | 重启后端服务 |
|
||||
| `php windows.php` | Windows 开发环境启动 Webman 与自定义进程 |
|
||||
| `php start.php start` | 生产/Linux 常见启动入口 |
|
||||
|
||||
`payment-runtime` 是自定义进程,不是 Console 命令;它随 Webman 进程启动,负责通知重试、支付超时扫描和支付中订单主动查单。
|
||||
|
||||
## 业务命令
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `php webman mpay:test --all` | 支付、退款、清结算、余额、追踪链路烟雾测试 |
|
||||
| `php webman epay:mapi` | ePay V1 `mapi.php` 兼容接口烟雾测试 |
|
||||
| `php webman epay:v2-api` | ePay V2 创建、查询、关闭、退款、商户信息等核心 API 烟雾测试 |
|
||||
| `php webman epay:mock-chain` | 自动写入 mock 配置并跑 ePay V1/V2 全链路 |
|
||||
| `php webman epay:v2-bootstrap` | 生成开发联调用平台和商户 RSA 密钥 |
|
||||
| `php webman payment:notify-retry` | 手动重试到期商户通知任务 |
|
||||
| `php webman system:config-sync` | 将 `config/system_config.php` 默认配置同步到数据库 |
|
||||
| `php webman test` | 基础命令注册检查 |
|
||||
|
||||
## 常用参数
|
||||
|
||||
- `mpay:test`:`--payment`、`--refund`、`--settlement`、`--balance`、`--trace`、`--all`、`--live`
|
||||
- `epay:mapi`:`--live`、`--merchant-id`、`--merchant-no`、`--type`、`--money`、`--refund-trade-no`
|
||||
- `epay:v2-api`:`--live`、`--merchant-id`、`--merchant-no`、`--merchant-private-key-file`、`--type`、`--method`、`--money`
|
||||
- `epay:mock-chain`:`--only=v1|v2|all`
|
||||
- `payment:notify-retry`:`--limit`
|
||||
|
||||
## 关联文档
|
||||
|
||||
- [后端总说明](./README.md)
|
||||
- [支付运行时数据契约](./payment-runtime-contract.md)
|
||||
- [ePay 兼容协议](../api/legacy/epay.md)
|
||||
39
doc/backend/compat.md
Normal file
39
doc/backend/compat.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# ePay 兼容层
|
||||
|
||||
后端同时保留 ePay V1 旧入口和 ePay V2 新接口。兼容层只负责协议适配,不作为后台管理或商户后台的新能力入口。
|
||||
|
||||
## 当前实现
|
||||
|
||||
| 协议 | 控制器 | 服务 |
|
||||
| --- | --- | --- |
|
||||
| ePay V1 | `app/http/api/controller/epay/EpayV1Controller.php` | `app/service/payment/epay/EpayV1ProtocolService.php` |
|
||||
| ePay V2 | `app/http/api/controller/epay/EpayV2Controller.php` | `app/service/payment/epay/EpayV2ProtocolService.php` |
|
||||
|
||||
签名实现:
|
||||
|
||||
- `Md5Signer`
|
||||
- `RsaSigner`
|
||||
- `EpaySignerManager`
|
||||
|
||||
## V1 路由
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `ANY` | `/submit.php` | 页面跳转支付 |
|
||||
| `POST` | `/mapi.php` | 接口支付 |
|
||||
| `ANY` | `/api.php` | 旧版标准 API |
|
||||
|
||||
## V2 路由
|
||||
|
||||
| 分组 | 路径 |
|
||||
| --- | --- |
|
||||
| 支付 | `/api/pay/submit`、`/api/pay/create`、`/api/pay/query`、`/api/pay/refund`、`/api/pay/refundquery`、`/api/pay/close`、`/api/pay/{payNo}/callback` |
|
||||
| 商户 | `/api/merchant/info`、`/api/merchant/orders` |
|
||||
| 转账 | `/api/transfer/submit`、`/api/transfer/query`、`/api/transfer/balance` |
|
||||
|
||||
## 关联文档
|
||||
|
||||
- [接口总说明](../api/README.md)
|
||||
- [收银台与开放接口](../api/cashier.md)
|
||||
- [ePay 兼容协议](../api/legacy/epay.md)
|
||||
- [后端路由](./routing.md)
|
||||
46
doc/backend/files.md
Normal file
46
doc/backend/files.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 文件资产
|
||||
|
||||
文件资产由后端记录元数据,实际文件交给本地、远程 URL、OSS 或 COS 驱动处理。
|
||||
|
||||
## 当前入口
|
||||
|
||||
- API 前缀:`/adminapi/file-asset`
|
||||
- 控制器:`FileRecordController`
|
||||
- 模型:`FileRecord`
|
||||
- 数据表:`ma_file_asset`
|
||||
|
||||
## 接口
|
||||
|
||||
| 方法 | 路径 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/file-asset/options` | 文件选项 |
|
||||
| `GET` | `/file-asset` | 文件列表 |
|
||||
| `POST` | `/file-asset/upload` | 上传文件 |
|
||||
| `POST` | `/file-asset/import-remote` | 导入远程文件 |
|
||||
| `GET` | `/file-asset/{id}/preview` | 文件预览 |
|
||||
| `GET` | `/file-asset/{id}/download` | 文件下载 |
|
||||
| `GET` | `/file-asset/{id}` | 文件详情 |
|
||||
| `DELETE` | `/file-asset/{id}` | 删除文件 |
|
||||
|
||||
## 服务与驱动
|
||||
|
||||
- `FileRecordService`
|
||||
- `FileRecordQueryService`
|
||||
- `FileRecordCommandService`
|
||||
- `StorageConfigService`
|
||||
- `StorageManager`
|
||||
- `LocalStorageDriver`
|
||||
- `RemoteUrlStorageDriver`
|
||||
- `OssStorageDriver`
|
||||
- `CosStorageDriver`
|
||||
|
||||
## 字段口径
|
||||
|
||||
- `object_key`:站点相对路径或对象存储 key。
|
||||
- `url`:公开文件的完整访问地址,私有文件可以为空。
|
||||
- `preview_url`:后台预览使用,不作为长期业务访问地址。
|
||||
- `previewable`:列表返回给前端判断是否允许在线预览。
|
||||
|
||||
## 表单上传
|
||||
|
||||
系统配置和插件配置中的上传字段仍使用 `type: "upload"`。需要走项目定制选择器时,在 `props.fileUpload` 中声明 `selectorType`、`scene`、`isLocal`、`isPublic`、`getKey` 等配置。前端说明见 [管理后台前端](../frontend/admin.md)。
|
||||
261
doc/backend/payment-plugin-template.md
Normal file
261
doc/backend/payment-plugin-template.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# 支付插件模板说明
|
||||
|
||||
这份说明对应 `mpay/app/common/payment/TemplatePayment.php`。
|
||||
|
||||
它的作用不是提供一个可直接上线的插件,而是提供一份可以复制、改名、接第三方网关的起点模板。
|
||||
|
||||
## 1. 这个模板解决什么问题
|
||||
|
||||
支付插件开发最容易踩坑的地方,不是第三方接口调用本身,而是这些基础约定容易不统一:
|
||||
|
||||
- 插件元信息怎么写
|
||||
- 配置表单怎么定义
|
||||
- `class_name` 怎么配置
|
||||
- `pay()` 返回什么结构
|
||||
- 回调怎么收口
|
||||
- 哪些字段该放在插件配置里,哪些字段该放在订单入参里
|
||||
|
||||
`TemplatePayment` 把这些骨架先搭好,后面新增插件时可以直接复制,再替换为真实渠道逻辑。
|
||||
|
||||
## 2. 模板在系统里怎么被加载
|
||||
|
||||
插件并不是直接被业务代码 `new` 出来的,而是先经过插件注册表,再由工厂服务实例化。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[后台维护插件定义] --> B[ma_payment_plugin]
|
||||
B --> C[ma_payment_plugin_conf]
|
||||
C --> D[ma_payment_channel]
|
||||
D --> E[PaymentPluginFactoryService]
|
||||
E --> F[实例化插件类]
|
||||
F --> G[init(配置)]
|
||||
G --> H[pay / query / close / refund / notify]
|
||||
```
|
||||
|
||||
关键点有两个:
|
||||
|
||||
- `ma_payment_plugin.class_name` 可以填短类名,也可以填完整类名
|
||||
- 如果是短类名,工厂会自动补成 `app\\common\\payment\\{class_name}`
|
||||
|
||||
也就是说,`TemplatePayment` 这种类既可以直接写成 `TemplatePayment`,也可以写成完整命名空间。
|
||||
|
||||
## 3. 复制时要改哪些地方
|
||||
|
||||
复制这个模板后,优先改下面几块:
|
||||
|
||||
1. `paymentInfo.code`
|
||||
2. `paymentInfo.name`
|
||||
3. `paymentInfo.pay_types`
|
||||
4. `paymentInfo.transfer_types`
|
||||
5. `paymentInfo.config_schema`
|
||||
6. `init()`
|
||||
7. `pay()`
|
||||
8. `query()`
|
||||
9. `close()`
|
||||
10. `refund()`
|
||||
11. `notify()`
|
||||
|
||||
其中最重要的是:
|
||||
|
||||
- `paymentInfo` 决定后台怎么展示这个插件
|
||||
- `config_schema` 决定后台怎么维护插件配置
|
||||
- `init()` 决定插件怎么吃到 `ma_payment_plugin_conf.config` 里的运行时参数
|
||||
- `pay()` 决定第三方下单时返回给系统什么支付参数
|
||||
- `notify()` 决定第三方回调回来后怎么验签和归一化结果
|
||||
|
||||
## 4. 这个模板里的返回结构
|
||||
|
||||
### 4.1 `pay()` 的返回值
|
||||
|
||||
模板已经按项目当前口径返回了这几个字段:
|
||||
|
||||
- `pay_product`
|
||||
- `pay_action`
|
||||
- `pay_params`
|
||||
- `chan_order_no`
|
||||
- `chan_trade_no`
|
||||
- `ext_json`
|
||||
|
||||
其中 `pay_params` 是给收银台前端或业务调用方用的,常见类型包括:
|
||||
|
||||
- `html`
|
||||
- `qrcode`
|
||||
- `jump`
|
||||
- `h5`
|
||||
- `jsapi`
|
||||
- `urlscheme`
|
||||
- `mini`
|
||||
- `json`
|
||||
- `error`
|
||||
|
||||
后端会在支付单拉起后立即校验这份返回值。校验失败会把支付单收口为失败态并抛出 `PaymentException`,所以新插件不要返回旧字段名或半结构化内容,必须直接返回标准结构。
|
||||
|
||||
不同 `pay_params.type` 的必要字段:
|
||||
|
||||
- `jump` / `web` / `h5`:`redirect_url`、`payurl`、`pay_url`、`mweb_url` 或 `url`
|
||||
- `qrcode`:`qrcode_text`、`qrcode_data`、`qrcode_url` 或 `qrcode`
|
||||
- `html`:`html` 或 `action`
|
||||
- `jsapi`:`jsapi_params`,或 `order_str` / `order_string` 等拉起参数
|
||||
- `urlscheme`:`urlscheme`、`redirect_url`、`order_str` 或 `order_string`
|
||||
- `mini`:`path`、`scheme`、`urlscheme`、`trade_no` 或 `mini_params`
|
||||
|
||||
实际接第三方时,你只需要把 `pay_params` 换成真实可渲染的数据结构即可。`ext_json` 只能放插件私有的轻量补充信息,完整请求、响应和通知记录不要放在这里。
|
||||
|
||||
标准示例:
|
||||
|
||||
```php
|
||||
[
|
||||
'pay_product' => 'alipay',
|
||||
'pay_action' => 'jump',
|
||||
'pay_params' => [
|
||||
'type' => 'jump',
|
||||
'redirect_url' => 'https://...',
|
||||
],
|
||||
'chan_order_no' => '渠道订单号',
|
||||
'chan_trade_no' => '渠道交易号,可选;未生成时返回空字符串',
|
||||
'ext_json' => [],
|
||||
]
|
||||
```
|
||||
|
||||
### 4.2 `query()` 的返回值
|
||||
|
||||
主动查单依赖插件 `query()`。新插件建议直接返回下面的标准结构,便于定时维护进程统一推进订单状态:
|
||||
|
||||
```php
|
||||
[
|
||||
'success' => true,
|
||||
'status' => 'success|failed|closed|pending',
|
||||
'channel_order_no' => '渠道订单号',
|
||||
'channel_trade_no' => '渠道交易号',
|
||||
'channel_status' => '渠道原始状态',
|
||||
'message' => '查询说明,可选',
|
||||
'paid_at' => '2026-04-25 12:00:00',
|
||||
'failed_at' => null,
|
||||
'ext_json' => [],
|
||||
]
|
||||
```
|
||||
|
||||
`status=success` 会推进支付成功,`failed` 会推进失败,`closed` 会推进关闭。`pending`、`unknown` 或查询异常只记录轻量查单快照,不改变支付单终态。
|
||||
|
||||
### 4.3 `notify()` 的返回值
|
||||
|
||||
回调处理建议返回这些语义字段:
|
||||
|
||||
- `status`
|
||||
- `message`
|
||||
- `channel_order_no`
|
||||
- `channel_trade_no`
|
||||
- `channel_status`
|
||||
- `paid_at`
|
||||
- `failed_at`
|
||||
- `fee_actual_amount`
|
||||
- `ext_json`
|
||||
|
||||
如果是失败回调,也可以补充:
|
||||
|
||||
- `channel_error_code`
|
||||
- `channel_error_msg`
|
||||
|
||||
后端回调链路会根据 `status` 统一推进支付单状态。完整回调原文和插件解析结果会保存到 `ma_pay_callback_log`,不要再塞进支付单 `ext_json`。
|
||||
|
||||
标准示例:
|
||||
|
||||
```php
|
||||
[
|
||||
'status' => 'success',
|
||||
'message' => 'TRADE_SUCCESS',
|
||||
'channel_order_no' => '渠道订单号',
|
||||
'channel_trade_no' => '渠道交易号',
|
||||
'channel_status' => 'TRADE_SUCCESS',
|
||||
'paid_at' => '2026-04-25 12:00:00',
|
||||
'fee_actual_amount' => null,
|
||||
'ext_json' => [],
|
||||
]
|
||||
```
|
||||
|
||||
### 4.4 订单扩展字段
|
||||
|
||||
业务单和支付单的 `ext_json` 使用分区结构:
|
||||
|
||||
- 顶层 `_protocol_version`:协议版本,方便后台查询。
|
||||
- `merchant`:商户透传字段,如 `param`、`buyer`。
|
||||
- `payment`:本次支付载体字段,如 `method`、`auth_code`、`sub_openid`。
|
||||
- `presentation`:插件返回给收银台承接的支付参数快照。
|
||||
- `plugin`:插件私有轻量信息,以及主动查单的 `active_query` 快照。
|
||||
- `lifecycle`:关单、超时等生命周期原因。
|
||||
|
||||
详细契约见 [支付运行时数据契约](./payment-runtime-contract.md)。
|
||||
|
||||
## 5. 模板里的占位逻辑
|
||||
|
||||
`TemplatePayment` 里有意保留了一些占位实现:
|
||||
|
||||
- `query()`
|
||||
- `close()`
|
||||
- `refund()`
|
||||
- `notify()`
|
||||
|
||||
这些方法现在会直接抛出 `PaymentException`,避免被误当成真实插件投入使用。
|
||||
|
||||
`pay()` 里也保留了示例结构:
|
||||
|
||||
- 默认支付形态可选 `html`、`qrcode`、`jump`、`jsapi`
|
||||
- `buildAutoSubmitForm()` 只是表单跳转的通用模板
|
||||
- `sign` 目前是 `TODO`
|
||||
|
||||
所以它更像“开发脚手架”,不是上线成品。
|
||||
|
||||
## 6. 推荐的开发步骤
|
||||
|
||||
建议按这个顺序做新插件:
|
||||
|
||||
1. 复制 `TemplatePayment.php`,改成新的类名
|
||||
2. 修改 `paymentInfo`,让插件代码和后台展示名称唯一
|
||||
3. 根据第三方网关要求补齐 `config_schema`
|
||||
4. 在 `init()` 里读取配置、初始化 SDK
|
||||
5. 在 `pay()` 里实现真实下单
|
||||
6. 在 `notify()` 里实现真实回调验签
|
||||
7. 把 `query()`、`close()`、`refund()` 按第三方能力补齐
|
||||
8. 在后台创建 `ma_payment_plugin` 记录
|
||||
9. 再创建对应的 `ma_payment_plugin_conf` 记录
|
||||
10. 把通道的 `plugin_code` 和 `api_config_id` 绑定起来
|
||||
|
||||
## 7. 后台配置关系
|
||||
|
||||
从数据库角度看,通常会涉及三张表:
|
||||
|
||||
- `ma_payment_plugin`:插件注册表,保存 `code`、`name`、`class_name`、`config_schema`、`pay_types`
|
||||
- `ma_payment_plugin_conf`:插件运行配置表,保存 `config`
|
||||
- `ma_payment_channel`:支付通道表,保存 `plugin_code` 和 `api_config_id`
|
||||
|
||||
也就是说:
|
||||
|
||||
- `ma_payment_plugin` 负责“这个插件是什么”
|
||||
- `ma_payment_plugin_conf` 负责“这个插件怎么运行”
|
||||
- `ma_payment_channel` 负责“这个插件被哪个通道使用”
|
||||
|
||||
## 8. 适合复制的场景
|
||||
|
||||
这个模板特别适合下面几类插件:
|
||||
|
||||
- 表单跳转类支付
|
||||
- 二维码类支付
|
||||
- 链接跳转类支付
|
||||
- 需要自定义回调验签的第三方网关
|
||||
- 先接通主链路、后逐步补齐查单退款的渠道
|
||||
|
||||
如果是像支付宝这种已经有成熟 SDK 的渠道,也可以直接参考现有 `AlipayPayment` 的实现,再用模板做新的落地版本。
|
||||
|
||||
## 9. 使用建议
|
||||
|
||||
- 新插件先保证 `pay()` 和 `notify()` 跑通,再补 `query()`、`refund()`、`close()`
|
||||
- 插件配置只放运行时必需信息,不要把订单级入参混进去
|
||||
- `pay_types` 存的是支付方式编码,不是支付方式 ID
|
||||
- 新插件类名和 `code` 一定要唯一,避免和已有插件冲突
|
||||
- 真正上线前,必须把占位异常和 `TODO` 字段全部替换掉
|
||||
|
||||
## 10. 相关代码
|
||||
|
||||
- `mpay/app/common/payment/TemplatePayment.php`
|
||||
- `mpay/app/service/payment/runtime/PaymentPluginFactoryService.php`
|
||||
- `docs/db/payment-middle-ddl.sql`
|
||||
243
doc/backend/payment-runtime-contract.md
Normal file
243
doc/backend/payment-runtime-contract.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# 支付运行时数据契约
|
||||
|
||||
本文档约定支付链路里订单扩展字段、插件入参和插件返回值的结构。当前项目仍处于开发阶段,不保留旧平铺结构兼容。
|
||||
|
||||
## 1. `ext_json` 职责
|
||||
|
||||
`ext_json` 只保存单据恢复、页面承接、插件运行所需的轻量扩展信息。通知、回调、重试、原始报文不进入 `ext_json`。
|
||||
|
||||
专门表职责如下:
|
||||
|
||||
- `ma_pay_callback_log`:保存每一次渠道回调原始参数、请求摘要、验签状态、处理状态、插件解析结果。
|
||||
- `ma_notify_task`:按 `event_type + ref_no` 保存商户通知内容、通知状态、重试次数、最后响应。
|
||||
- `ma_channel_notify_log`:保存渠道通知或查单类日志。
|
||||
- `ma_pay_order`:保存支付尝试当前状态、渠道单号、回调状态、错误码和页面承接快照。
|
||||
|
||||
## 2. `ma_biz_order.ext_json`
|
||||
|
||||
业务单只保存稳定业务上下文。同一商户订单号复用时会校验这些字段,支付载体参数不得放在业务单里。
|
||||
|
||||
```php
|
||||
[
|
||||
'_protocol_version' => 'v1', // 顶层强语义字段,方便后台查询和排障
|
||||
'merchant' => [
|
||||
'param' => '商户透传参数',
|
||||
'buyer' => '商户侧买家标识,可选',
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
## 3. `ma_pay_order.ext_json`
|
||||
|
||||
支付单是一笔支付尝试的快照,可以保存本次支付载体和页面承接信息。
|
||||
|
||||
```php
|
||||
[
|
||||
'_protocol_version' => 'v2',
|
||||
'merchant' => [
|
||||
'param' => '商户透传参数',
|
||||
'buyer' => '商户侧买家标识,可选',
|
||||
],
|
||||
'payment' => [
|
||||
'method' => 'web|jump|jsapi|app|scan|applet',
|
||||
'auth_code' => '条码/付款码支付时的付款码',
|
||||
'sub_openid' => 'JSAPI 支付所需 openid',
|
||||
'sub_appid' => '服务商模式子应用 ID',
|
||||
],
|
||||
'presentation' => [
|
||||
'params_type' => 'jump|qrcode|html|jsapi|urlscheme|json|error',
|
||||
'product' => 'alipay|wxpay|unionpay|...',
|
||||
'action' => '插件动作名',
|
||||
'params_snapshot' => [
|
||||
'type' => 'qrcode',
|
||||
'qrcode_url' => 'https://...',
|
||||
],
|
||||
],
|
||||
'plugin' => [
|
||||
'pay_result' => [],
|
||||
'close_result' => [],
|
||||
'active_query' => [
|
||||
'queried_at' => '2026-04-25 12:00:00',
|
||||
'status' => 'success|failed|closed|pending|unknown|error',
|
||||
'raw_status' => '渠道原始状态',
|
||||
'channel_status' => '渠道状态码',
|
||||
'message' => '查单说明或异常信息',
|
||||
'success' => true,
|
||||
'channel_order_no' => '渠道订单号',
|
||||
'channel_trade_no' => '渠道交易号',
|
||||
'query_count' => 1,
|
||||
],
|
||||
],
|
||||
'lifecycle' => [
|
||||
'close_reason' => '关闭原因',
|
||||
'timeout_reason' => '超时原因',
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
## 4. 插件 `pay()` 入参
|
||||
|
||||
系统调用插件下单时会传入结构化 `extra`。插件读取商户透传、支付载体或协议字段时,从对应分区取值。
|
||||
|
||||
```php
|
||||
[
|
||||
'pay_no' => 'PAY...',
|
||||
'biz_no' => 'BIZ...',
|
||||
'channel_request_no' => 'REQ...',
|
||||
'merchant_id' => 1,
|
||||
'merchant_no' => 'M...',
|
||||
'pay_type_code' => 'alipay',
|
||||
'amount' => 100,
|
||||
'subject' => '订单标题',
|
||||
'callback_url' => 'https://platform/api/pay/PAY.../callback',
|
||||
'notify_url' => 'https://merchant/notify',
|
||||
'return_url' => 'https://merchant/return',
|
||||
'client_ip' => '127.0.0.1',
|
||||
'_env' => 'pc',
|
||||
'extra' => [
|
||||
'_protocol_version' => 'v2',
|
||||
'merchant' => [],
|
||||
'payment' => [],
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
## 5. 插件 `pay()` 返回值
|
||||
|
||||
插件下单必须返回系统可承接的标准结构。后端会在 `PayOrderChannelDispatchService` 中严格校验字段和 `pay_params.type` 所需载荷,校验通过后才会写入 `ma_pay_order.ext_json.presentation`。
|
||||
|
||||
```php
|
||||
[
|
||||
'pay_product' => 'alipay',
|
||||
'pay_action' => 'jump',
|
||||
'pay_params' => [
|
||||
'type' => 'jump',
|
||||
'redirect_url' => 'https://...',
|
||||
],
|
||||
'chan_order_no' => '渠道订单号',
|
||||
'chan_trade_no' => '渠道交易号,可选;未生成时返回空字符串',
|
||||
'ext_json' => [
|
||||
// 插件私有轻量信息,可选
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
`pay_params.type` 决定收银台如何承接:
|
||||
|
||||
- `jump` / `web` / `h5`:必须提供 `redirect_url`、`payurl`、`pay_url`、`mweb_url` 或 `url`。
|
||||
- `qrcode`:必须提供 `qrcode_text`、`qrcode_data`、`qrcode_url` 或 `qrcode`。
|
||||
- `html` / `form`:必须提供 `html` 或 `action`;`form` 会归一为 `html`。
|
||||
- `jsapi` / `urlscheme` / `mini`:必须提供对应拉起参数、跳转参数或小程序参数。
|
||||
- `pos` / `transfer`:展示结构化参数,适合收银设备或转账类场景。
|
||||
- `json`:直接展示结构化参数,由业务端继续处理。
|
||||
- `error`:展示插件返回的错误信息。
|
||||
|
||||
`pay_params.type` 的兼容别名只在平台内部归一化使用,插件文档和新插件代码应直接返回标准值。常见别名包括:`scan|qr|code -> qrcode`、`redirect|url -> jump`、`wap -> h5`、`form -> html`、`app -> urlscheme`、`applet|wxplugin -> mini`。
|
||||
|
||||
## 6. 插件 `query()` 返回值
|
||||
|
||||
主动查单由 `PaymentRuntimeProcess` 定时触发,只处理 `status=支付中` 且已超过最小等待时间的支付单。插件 `query()` 应尽量返回标准字段:
|
||||
|
||||
```php
|
||||
[
|
||||
'success' => true,
|
||||
'status' => 'success|failed|closed|pending',
|
||||
'channel_order_no' => '渠道订单号',
|
||||
'channel_trade_no' => '渠道交易号',
|
||||
'channel_status' => 'TRADE_SUCCESS',
|
||||
'message' => '渠道说明,可选',
|
||||
'paid_at' => '2026-04-25 12:00:00',
|
||||
'failed_at' => null,
|
||||
'ext_json' => [
|
||||
// 插件私有轻量补充信息,可选
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
- `success`:推进支付单成功,并发出 `payment.pay_order.succeeded` 事件。
|
||||
- `failed`:推进支付单失败。
|
||||
- `closed`:推进支付单关闭。
|
||||
- `pending` / `unknown` / 查单异常:不推进终态,只把轻量快照写入 `ma_pay_order.ext_json.plugin.active_query`。
|
||||
|
||||
主动查单快照只保存状态、说明、渠道单号、时间和次数,不保存完整上游响应;完整回调仍以 `ma_pay_callback_log` 为准。
|
||||
|
||||
## 7. 插件 `notify()` 返回值
|
||||
|
||||
插件回调只负责验签和归一化结果。完整回调原文和该返回值会写入 `ma_pay_callback_log`,不会再回灌支付单 `ext_json`。
|
||||
|
||||
```php
|
||||
[
|
||||
'status' => 'success|failed|pending',
|
||||
'message' => '渠道状态说明',
|
||||
'channel_order_no' => '渠道订单号',
|
||||
'channel_trade_no' => '渠道交易号',
|
||||
'channel_status' => 'TRADE_SUCCESS',
|
||||
'channel_error_code' => '',
|
||||
'channel_error_msg' => '',
|
||||
'paid_at' => '2026-04-25 12:00:00',
|
||||
'failed_at' => null,
|
||||
'fee_actual_amount' => null,
|
||||
'ext_json' => [
|
||||
// 插件私有轻量补充信息,可选
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
`status=pending` 只记录回调日志,不推进支付单终态。`success` 和 `failed` 会推进支付单状态,并按需要创建商户通知任务。
|
||||
|
||||
## 8. 商户通知任务事件模型
|
||||
|
||||
`ma_notify_task` 不再以 `pay_no` 作为唯一业务键。通知任务统一按事件建模:
|
||||
|
||||
```php
|
||||
[
|
||||
'event_type' => 'PAY_SUCCESS|REFUND_SUCCESS|SETTLEMENT_SUCCESS',
|
||||
'ref_no' => '事件引用单号,例如 pay_no/refund_no/settle_no',
|
||||
'biz_no' => '业务单号',
|
||||
'pay_no' => '支付单号,支付相关事件保留',
|
||||
'notify_data' => [
|
||||
// 发送给商户的已签名参数
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
这样同一笔支付后续可以同时存在支付成功、退款成功、清算完成等多类通知,不会因为复用 `pay_no` 被唯一键挡住。
|
||||
|
||||
## 9. 回调日志留痕规则
|
||||
|
||||
渠道回调日志按“每次请求一条”写入,不做 `pay_no + callback_type` 覆盖或复用。
|
||||
|
||||
`request_hash` 是原始请求载荷的 SHA-256 摘要,用来在后台识别重复通知;重复通知是否推进业务状态,由支付单生命周期幂等控制。
|
||||
|
||||
## 10. 支付运行时维护
|
||||
|
||||
运行时维护使用 Webman 自定义进程 `payment-runtime`,对应 `app/process/PaymentRuntimeProcess.php`。进程只负责定时调度,具体业务由 `PaymentRuntimeMaintenanceService` 完成。
|
||||
|
||||
当前维护任务:
|
||||
|
||||
- 商户通知重试:扫描到期 `ma_notify_task` 并重新派发。
|
||||
- 支付单超时:扫描已过期且未终态的支付单,推进为超时并释放冻结手续费。
|
||||
- 主动查单:扫描支付中订单,调用插件 `query()` 补偿异步通知丢失或延迟。
|
||||
|
||||
当前阶段采用自定义进程直接执行,代码路径更短、部署依赖更少。后续如果商户通知量明显增大,建议引入 Redis 队列:支付成功监听器只负责投递队列,队列消费者负责 HTTP 通知派发和重试。
|
||||
|
||||
可在管理后台“支付配置”中维护以下系统配置:
|
||||
|
||||
- `pay_runtime_enabled`:运行时维护总开关。
|
||||
- `pay_notify_retry_scan_interval_seconds` / `pay_notify_retry_batch_size`:通知重试扫描间隔和批量。
|
||||
- `pay_order_timeout_scan_interval_seconds` / `pay_order_timeout_batch_size`:超时订单扫描间隔和批量。
|
||||
- `pay_active_query_enabled` / `pay_active_query_interval_seconds` / `pay_active_query_min_age_seconds` / `pay_active_query_batch_size`:主动查单开关、间隔、等待时间和批量。
|
||||
|
||||
系统配置保存后会触发 `system.config.changed` 事件刷新运行时缓存,维护进程下一轮心跳读取新值。
|
||||
|
||||
## 11. 支付域事件
|
||||
|
||||
支付生命周期服务只负责状态推进和资金动作。支付单首次进入成功态后,会发送事件:
|
||||
|
||||
```php
|
||||
PaymentEventConstant::PAY_ORDER_SUCCEEDED // payment.pay_order.succeeded
|
||||
```
|
||||
|
||||
当前监听器 `PayOrderSucceededListener` 负责创建并派发商户支付成功通知。后续如果引入 Redis 队列,可以只替换监听器内部实现,把通知派发入队,而不用改订单生命周期服务。
|
||||
47
doc/backend/routing.md
Normal file
47
doc/backend/routing.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 后端路由
|
||||
|
||||
后端只使用显式路由。`config/route.php` 加载三份路由文件后关闭默认路由。
|
||||
|
||||
## 路由文件
|
||||
|
||||
| 文件 | 覆盖范围 |
|
||||
| --- | --- |
|
||||
| `app/route/admin.php` | `/admin` 页面入口、`/adminapi` 管理后台 API |
|
||||
| `app/route/mer.php` | `/mer` 页面入口、`/merapi` 商户后台 API |
|
||||
| `app/route/api.php` | 收银台页面、收银台 API、ePay V1/V2 与开放 API |
|
||||
|
||||
## 当前入口
|
||||
|
||||
| 前缀 | 说明 | 中间件 |
|
||||
| --- | --- | --- |
|
||||
| `/admin` | 管理后台静态页面入口 | 无业务鉴权 |
|
||||
| `/adminapi` | 管理后台接口 | `Cors`,保护接口再走 `AdminAuthMiddleware` |
|
||||
| `/mer` | 商户后台静态页面入口 | 无业务鉴权 |
|
||||
| `/merapi` | 商户后台接口 | `Cors`,保护接口再走 `MerchantAuthMiddleware` |
|
||||
| `/cashier` | 收银台入口页 | 无业务鉴权 |
|
||||
| `/payment` | 支付页、中转页、结果页 | 无业务鉴权 |
|
||||
| `/api/cashier` | 收银台上下文、确认支付、支付单详情 | `Cors` |
|
||||
| `/api/pay` | ePay V2 支付、查询、退款、关闭、通道回调 | `Cors` |
|
||||
| `/api/merchant` | ePay V2 商户信息与订单查询 | `Cors` |
|
||||
| `/api/transfer` | ePay V2 转账提交、查询、余额 | `Cors` |
|
||||
| `/submit.php`、`/mapi.php`、`/api.php` | ePay V1 兼容入口 | `Cors` |
|
||||
|
||||
## 流转
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Req[HTTP 请求] --> Route[显式路由]
|
||||
Route --> Middleware[中间件]
|
||||
Middleware --> Controller[控制器]
|
||||
Controller --> Validator[参数校验]
|
||||
Validator --> Service[服务层]
|
||||
Service --> Repository[仓库层]
|
||||
Repository --> Model[模型层]
|
||||
Model --> DB[(MySQL)]
|
||||
```
|
||||
|
||||
## 维护要求
|
||||
|
||||
- 新增接口先改 `app/route/*`,再补对应 `docs/api/*`。
|
||||
- 页面兜底路由只返回前端入口,不承载业务逻辑。
|
||||
- 业务规则放服务层;路由文件只做 URL 到控制器方法的绑定。
|
||||
51
doc/backend/services.md
Normal file
51
doc/backend/services.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 后端服务层
|
||||
|
||||
服务层承载业务规则,控制器只负责入口、参数和响应包装。
|
||||
|
||||
## 目录职责
|
||||
|
||||
| 目录 | 主要职责 |
|
||||
| --- | --- |
|
||||
| `account/funds`、`account/ledger` | 商户账户、余额和资金流水 |
|
||||
| `bootstrap` | 启动期初始化 |
|
||||
| `file`、`file/storage` | 文件资产、上传、导入、预览、下载和存储驱动 |
|
||||
| `merchant` | 商户主体、总览、分组、策略、API 凭证 |
|
||||
| `merchant/auth` | 商户后台登录认证 |
|
||||
| `merchant/portal` | 商户后台资料、通道、路由预览、订单资金查询 |
|
||||
| `ops/log`、`ops/stat` | 通道通知、支付回调、商户通知任务和通道日统计 |
|
||||
| `payment/cashier` | 收银台上下文、确认支付、支付单展示 |
|
||||
| `payment/config` | 支付方式、插件、插件配置、通道、轮询组、绑定 |
|
||||
| `payment/epay` | ePay V1/V2 协议、MD5/RSA 签名 |
|
||||
| `payment/order` | 支付单、退款单、费用、派单、回调、生命周期 |
|
||||
| `payment/runtime` | 路由解析、插件装配、商户通知、运行时维护 |
|
||||
| `payment/settlement` | 清算单和清算生命周期 |
|
||||
| `payment/trace` | 交易追踪与报表 |
|
||||
| `payment/transfer` | 转账能力 |
|
||||
| `system/access`、`system/config`、`system/user` | 管理员认证、系统配置、管理员用户 |
|
||||
|
||||
## 命名规则
|
||||
|
||||
- `*Service`:对外门面。
|
||||
- `*QueryService`:查询与展示拼装。
|
||||
- `*CommandService`:写入、修改、删除。
|
||||
- `*LifecycleService`:状态流转。
|
||||
- `*CallbackService`:第三方回调。
|
||||
- `*SyncService`:同步、扫描、刷新。
|
||||
- `*SupportService`:业务辅助能力。
|
||||
|
||||
## 关键服务
|
||||
|
||||
- 支付:`PayOrderService`、`PayOrderLifecycleService`、`PayOrderChannelDispatchService`
|
||||
- 退款:`RefundService`、`RefundCreationService`、`RefundLifecycleService`
|
||||
- 清算:`SettlementService`、`SettlementLifecycleService`
|
||||
- 路由与插件:`PaymentRouteService`、`PaymentRouteResolverService`、`PaymentPluginManager`
|
||||
- 收银台:`CashierService`
|
||||
- 通知:`NotifyService`、`MerchantNotifyDispatcherService`
|
||||
- 商户:`MerchantService`、`MerchantPortalService`、`MerchantApiCredentialService`
|
||||
- 文件:`FileRecordService`、`StorageManager`
|
||||
|
||||
## 维护要求
|
||||
|
||||
- 查询和写入不要混在一个越来越胖的类里。
|
||||
- 回调、路由解析、插件装配、通知重试要保持独立职责。
|
||||
- `SupportService` 只放有业务含义的复用逻辑,不做基础工具的空转发。
|
||||
Reference in New Issue
Block a user