mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-04-22 10:04:27 +08:00
更新数据库结构
This commit is contained in:
145
doc/auth_strategy_design.md
Normal file
145
doc/auth_strategy_design.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# 认证策略设计说明
|
||||
|
||||
## 设计理念
|
||||
|
||||
采用**策略模式**替代中间件方式处理认证,具有以下优势:
|
||||
|
||||
1. **灵活扩展**:可以轻松添加新的接口标准(如易支付、OpenAPI、自定义标准等)
|
||||
2. **按需使用**:控制器可以根据需要选择认证策略,而不是在路由层面强制
|
||||
3. **易于测试**:策略类可以独立测试,不依赖中间件
|
||||
4. **代码复用**:不同接口可以共享相同的认证逻辑
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 1. 核心接口
|
||||
|
||||
**`AuthStrategyInterface`** - 认证策略接口
|
||||
```php
|
||||
interface AuthStrategyInterface
|
||||
{
|
||||
public function authenticate(Request $request): MerchantApp;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 策略实现
|
||||
|
||||
#### EpayAuthStrategy(易支付认证)
|
||||
- 使用 `pid` + `key` + `MD5签名`
|
||||
- 参数格式:`application/x-www-form-urlencoded`
|
||||
- 签名算法:MD5(排序后的参数字符串 + KEY)
|
||||
|
||||
#### OpenApiAuthStrategy(OpenAPI认证)
|
||||
- 使用 `app_id` + `timestamp` + `nonce` + `HMAC-SHA256签名`
|
||||
- 支持请求头或参数传递
|
||||
- 签名算法:HMAC-SHA256(签名字符串, app_secret)
|
||||
|
||||
### 3. 认证服务
|
||||
|
||||
**`AuthService`** - 认证服务,负责:
|
||||
- 自动检测接口标准类型
|
||||
- 根据类型选择对应的认证策略
|
||||
- 支持手动注册新的认证策略
|
||||
|
||||
```php
|
||||
// 自动检测
|
||||
$app = $authService->authenticate($request);
|
||||
|
||||
// 指定策略类型
|
||||
$app = $authService->authenticate($request, 'epay');
|
||||
|
||||
// 注册新策略
|
||||
$authService->registerStrategy('custom', CustomAuthStrategy::class);
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 控制器中使用
|
||||
|
||||
```php
|
||||
class PayController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
protected PayOrderService $payOrderService,
|
||||
protected AuthService $authService
|
||||
) {
|
||||
}
|
||||
|
||||
public function submit(Request $request)
|
||||
{
|
||||
// 自动检测或指定策略类型
|
||||
$app = $this->authService->authenticate($request, 'epay');
|
||||
|
||||
// 使用 $app 进行后续业务处理
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 添加新的认证策略
|
||||
|
||||
1. **实现策略接口**
|
||||
```php
|
||||
class CustomAuthStrategy implements AuthStrategyInterface
|
||||
{
|
||||
public function authenticate(Request $request): MerchantApp
|
||||
{
|
||||
// 实现自定义认证逻辑
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **注册策略**
|
||||
```php
|
||||
// 在服务提供者或启动文件中
|
||||
$authService = new AuthService();
|
||||
$authService->registerStrategy('custom', CustomAuthStrategy::class);
|
||||
```
|
||||
|
||||
3. **在控制器中使用**
|
||||
```php
|
||||
$app = $this->authService->authenticate($request, 'custom');
|
||||
```
|
||||
|
||||
## 自动检测机制
|
||||
|
||||
`AuthService` 会根据请求特征自动检测接口标准:
|
||||
|
||||
- **易支付**:检测到 `pid` 参数
|
||||
- **OpenAPI**:检测到 `X-App-Id` 请求头或 `app_id` 参数
|
||||
|
||||
如果无法自动检测,可以手动指定策略类型。
|
||||
|
||||
## 优势对比
|
||||
|
||||
### 中间件方式(旧方案)
|
||||
- ❌ 路由配置复杂,每个接口标准需要不同的中间件
|
||||
- ❌ 难以在同一路由支持多种认证方式
|
||||
- ❌ 扩展新标准需要修改路由配置
|
||||
|
||||
### 策略模式(新方案)
|
||||
- ✅ 控制器按需选择认证策略
|
||||
- ✅ 同一路由可以支持多种认证方式(通过参数区分)
|
||||
- ✅ 扩展新标准只需实现策略接口并注册
|
||||
- ✅ 代码更清晰,职责分离
|
||||
|
||||
## 路由配置
|
||||
|
||||
由于不再使用中间件,路由配置更简洁:
|
||||
|
||||
```php
|
||||
// 易支付接口
|
||||
Route::any('/submit.php', [PayController::class, 'submit']);
|
||||
Route::post('/mapi.php', [PayController::class, 'mapi']);
|
||||
Route::get('/api.php', [PayController::class, 'queryOrder']);
|
||||
|
||||
// 所有接口都在控制器内部进行认证,无需中间件
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过策略模式重构认证逻辑,系统具备了:
|
||||
- **高扩展性**:轻松添加新的接口标准
|
||||
- **高灵活性**:控制器可以自由选择认证方式
|
||||
- **高可维护性**:代码结构清晰,易于理解和维护
|
||||
|
||||
216
doc/epay.md
Normal file
216
doc/epay.md
Normal file
@@ -0,0 +1,216 @@
|
||||
协议规则
|
||||
请求数据格式:application/x-www-form-urlencoded
|
||||
|
||||
返回数据格式:JSON
|
||||
|
||||
签名算法:MD5
|
||||
|
||||
字符编码:UTF-8
|
||||
|
||||
页面跳转支付
|
||||
此接口可用于用户前台直接发起支付,使用form表单跳转或拼接成url跳转。
|
||||
|
||||
URL地址:http://192.168.31.200:4000/submit.php
|
||||
|
||||
请求方式:POST 或 GET(推荐POST,不容易被劫持或屏蔽)
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
商户ID pid 是 Int 1001
|
||||
支付方式 type 否 String alipay 支付方式列表
|
||||
商户订单号 out_trade_no 是 String 20160806151343349
|
||||
异步通知地址 notify_url 是 String http://www.pay.com/notify_url.php 服务器异步通知地址
|
||||
跳转通知地址 return_url 是 String http://www.pay.com/return_url.php 页面跳转通知地址
|
||||
商品名称 name 是 String VIP会员 如超过127个字节会自动截取
|
||||
商品金额 money 是 String 1.00 单位:元,最大2位小数
|
||||
业务扩展参数 param 否 String 没有请留空 支付后原样返回
|
||||
签名字符串 sign 是 String 202cb962ac59075b964b07152d234b70 签名算法点此查看
|
||||
签名类型 sign_type 是 String MD5 默认为MD5
|
||||
支付方式(type)不传会跳转到收银台支付
|
||||
|
||||
API接口支付
|
||||
此接口可用于服务器后端发起支付请求,会返回支付二维码链接或支付跳转url。
|
||||
|
||||
URL地址:http://192.168.31.200:4000/mapi.php
|
||||
|
||||
请求方式:POST
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
商户ID pid 是 Int 1001
|
||||
支付方式 type 是 String alipay 支付方式列表
|
||||
商户订单号 out_trade_no 是 String 20160806151343349
|
||||
异步通知地址 notify_url 是 String http://www.pay.com/notify_url.php 服务器异步通知地址
|
||||
跳转通知地址 return_url 否 String http://www.pay.com/return_url.php 页面跳转通知地址
|
||||
商品名称 name 是 String VIP会员 如超过127个字节会自动截取
|
||||
商品金额 money 是 String 1.00 单位:元,最大2位小数
|
||||
用户IP地址 clientip 是 String 192.168.1.100 用户发起支付的IP地址
|
||||
设备类型 device 否 String pc 根据用户浏览器的UA判断,
|
||||
传入用户所使用的浏览器
|
||||
或设备类型,默认为pc
|
||||
设备类型列表
|
||||
业务扩展参数 param 否 String 没有请留空 支付后原样返回
|
||||
签名字符串 sign 是 String 202cb962ac59075b964b07152d234b70 签名算法点此查看
|
||||
签名类型 sign_type 是 String MD5 默认为MD5
|
||||
返回结果(json):
|
||||
|
||||
字段名 变量名 类型 示例值 描述
|
||||
返回状态码 code Int 1 1为成功,其它值为失败
|
||||
返回信息 msg String 失败时返回原因
|
||||
订单号 trade_no String 20160806151343349 支付订单号
|
||||
支付跳转url payurl String http://192.168.31.200:4000/pay/wxpay/202010903/ 如果返回该字段,则直接跳转到该url支付
|
||||
二维码链接 qrcode String weixin://wxpay/bizpayurl?pr=04IPMKM 如果返回该字段,则根据该url生成二维码
|
||||
小程序跳转url urlscheme String weixin://dl/business/?ticket=xxx 如果返回该字段,则使用js跳转该url,可发起微信小程序支付
|
||||
注:payurl、qrcode、urlscheme 三个参数只会返回其中一个
|
||||
|
||||
支付结果通知
|
||||
通知类型:服务器异步通知(notify_url)、页面跳转通知(return_url)
|
||||
|
||||
请求方式:GET
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
商户ID pid 是 Int 1001
|
||||
易支付订单号 trade_no 是 String 20160806151343349021 聚合支付平台订单号
|
||||
商户订单号 out_trade_no 是 String 20160806151343349 商户系统内部的订单号
|
||||
支付方式 type 是 String alipay 支付方式列表
|
||||
商品名称 name 是 String VIP会员
|
||||
商品金额 money 是 String 1.00
|
||||
支付状态 trade_status 是 String TRADE_SUCCESS 只有TRADE_SUCCESS是成功
|
||||
业务扩展参数 param 否 String
|
||||
签名字符串 sign 是 String 202cb962ac59075b964b07152d234b70 签名算法点此查看
|
||||
签名类型 sign_type 是 String MD5 默认为MD5
|
||||
收到异步通知后,需返回success以表示服务器接收到了订单通知
|
||||
|
||||
MD5签名算法
|
||||
1、将发送或接收到的所有参数按照参数名ASCII码从小到大排序(a-z),sign、sign_type、和空值不参与签名!
|
||||
|
||||
2、将排序后的参数拼接成URL键值对的格式,例如 a=b&c=d&e=f,参数值不要进行url编码。
|
||||
|
||||
3、再将拼接好的字符串与商户密钥KEY进行MD5加密得出sign签名参数,sign = md5 ( a=b&c=d&e=f + KEY ) (注意:+ 为各语言的拼接符,不是字符!),md5结果为小写。
|
||||
|
||||
4、具体签名与发起支付的示例代码可下载SDK查看。
|
||||
|
||||
支付方式列表
|
||||
调用值 描述
|
||||
alipay 支付宝
|
||||
wxpay 微信支付
|
||||
qqpay QQ钱包
|
||||
设备类型列表
|
||||
调用值 描述
|
||||
pc 电脑浏览器
|
||||
mobile 手机浏览器
|
||||
qq 手机QQ内浏览器
|
||||
wechat 微信内浏览器
|
||||
alipay 支付宝客户端
|
||||
jump 仅返回支付跳转url
|
||||
[API]查询商户信息
|
||||
URL地址:http://192.168.31.200:4000/api.php?act=query&pid={商户ID}&key={商户密钥}
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
操作类型 act 是 String query 此API固定值
|
||||
商户ID pid 是 Int 1001
|
||||
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
|
||||
返回结果:
|
||||
|
||||
字段名 变量名 类型 示例值 描述
|
||||
返回状态码 code Int 1 1为成功,其它值为失败
|
||||
商户ID pid Int 1001 商户ID
|
||||
商户密钥 key String(32) 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i 商户密钥
|
||||
商户状态 active Int 1 1为正常,0为封禁
|
||||
商户余额 money String 0.00 商户所拥有的余额
|
||||
结算方式 type Int 1 1:支付宝,2:微信,3:QQ,4:银行卡
|
||||
结算账号 account String admin@pay.com 结算的支付宝账号
|
||||
结算姓名 username String 张三 结算的支付宝姓名
|
||||
订单总数 orders Int 30 订单总数统计
|
||||
今日订单 order_today Int 15 今日订单数量
|
||||
昨日订单 order_lastday Int 15 昨日订单数量
|
||||
[API]查询结算记录
|
||||
URL地址:http://192.168.31.200:4000/api.php?act=settle&pid={商户ID}&key={商户密钥}
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
操作类型 act 是 String settle 此API固定值
|
||||
商户ID pid 是 Int 1001
|
||||
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
|
||||
返回结果:
|
||||
|
||||
字段名 变量名 类型 示例值 描述
|
||||
返回状态码 code Int 1 1为成功,其它值为失败
|
||||
返回信息 msg String 查询结算记录成功!
|
||||
结算记录 data Array 结算记录列表
|
||||
[API]查询单个订单
|
||||
URL地址:http://192.168.31.200:4000/api.php?act=order&pid={商户ID}&key={商户密钥}&out_trade_no={商户订单号}
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
操作类型 act 是 String order 此API固定值
|
||||
商户ID pid 是 Int 1001
|
||||
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
|
||||
系统订单号 trade_no 选择 String 20160806151343312
|
||||
商户订单号 out_trade_no 选择 String 20160806151343349
|
||||
提示:系统订单号 和 商户订单号 二选一传入即可,如果都传入以系统订单号为准!
|
||||
|
||||
返回结果:
|
||||
|
||||
字段名 变量名 类型 示例值 描述
|
||||
返回状态码 code Int 1 1为成功,其它值为失败
|
||||
返回信息 msg String 查询订单号成功!
|
||||
易支付订单号 trade_no String 2016080622555342651 聚合支付平台订单号
|
||||
商户订单号 out_trade_no String 20160806151343349 商户系统内部的订单号
|
||||
第三方订单号 api_trade_no String 20160806151343349 支付宝微信等接口方订单号
|
||||
支付方式 type String alipay 支付方式列表
|
||||
商户ID pid Int 1001 发起支付的商户ID
|
||||
创建订单时间 addtime String 2016-08-06 22:55:52
|
||||
完成交易时间 endtime String 2016-08-06 22:55:52
|
||||
商品名称 name String VIP会员
|
||||
商品金额 money String 1.00
|
||||
支付状态 status Int 0 1为支付成功,0为未支付
|
||||
业务扩展参数 param String 默认留空
|
||||
支付者账号 buyer String 默认留空
|
||||
[API]批量查询订单
|
||||
URL地址:http://192.168.31.200:4000/api.php?act=orders&pid={商户ID}&key={商户密钥}
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
操作类型 act 是 String orders 此API固定值
|
||||
商户ID pid 是 Int 1001
|
||||
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
|
||||
查询订单数量 limit 否 Int 20 返回的订单数量,最大50
|
||||
页码 page 否 Int 1 当前查询的页码
|
||||
返回结果:
|
||||
|
||||
字段名 变量名 类型 示例值 描述
|
||||
返回状态码 code Int 1 1为成功,其它值为失败
|
||||
返回信息 msg String 查询结算记录成功!
|
||||
订单列表 data Array 订单列表
|
||||
[API]提交订单退款
|
||||
需要先在商户后台开启订单退款API接口开关,才能调用该接口发起订单退款
|
||||
|
||||
URL地址:http://192.168.31.200:4000/api.php?act=refund
|
||||
|
||||
请求方式:POST
|
||||
|
||||
请求参数说明:
|
||||
|
||||
字段名 变量名 必填 类型 示例值 描述
|
||||
商户ID pid 是 Int 1001
|
||||
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
|
||||
易支付订单号 trade_no 特殊可选 String 20160806151343349021 易支付订单号
|
||||
商户订单号 out_trade_no 特殊可选 String 20160806151343349 订单支付时传入的商户订单号,商家自定义且保证商家系统中唯一
|
||||
退款金额 money 是 String 1.50 少数通道需要与原订单金额一致
|
||||
注:trade_no、out_trade_no 不能同时为空,如果都传了以trade_no为准
|
||||
|
||||
返回结果:
|
||||
|
||||
字段名 变量名 类型 示例值 描述
|
||||
返回状态码 code Int 0 0为成功,其它值为失败
|
||||
返回信息 msg String 退款成功
|
||||
214
doc/order_table_design.md
Normal file
214
doc/order_table_design.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 支付订单表设计说明
|
||||
|
||||
## 一、订单表设计原因
|
||||
|
||||
### 1.1 订单号设计(双重订单号)
|
||||
|
||||
**系统订单号 (`pay_order_id`)**
|
||||
- **作用**:系统内部唯一标识,用于查询、对账、退款等操作
|
||||
- **生成规则**:`P` + `YYYYMMDDHHmmss` + `6位随机数`(如:P20240101120000123456)
|
||||
- **唯一性**:通过 `uk_pay_order_id` 唯一索引保证
|
||||
- **优势**:
|
||||
- 全局唯一,不受商户影响
|
||||
- 便于系统内部查询和关联
|
||||
- 对账时作为主键
|
||||
|
||||
**商户订单号 (`mch_order_no`)**
|
||||
- **作用**:商户传入的订单号,用于幂等性校验
|
||||
- **唯一性**:通过 `uk_mch_order_no(merchant_id, mch_order_no)` 联合唯一索引保证
|
||||
- **优势**:
|
||||
- 同一商户下订单号唯一,防止重复提交
|
||||
- 商户侧可以自定义订单号规则
|
||||
- 支持商户订单号查询订单
|
||||
|
||||
**为什么需要两个订单号?**
|
||||
- 系统订单号:保证全局唯一,便于系统内部管理
|
||||
- 商户订单号:保证商户侧唯一,防止重复支付(幂等性)
|
||||
|
||||
### 1.2 关联关系设计
|
||||
|
||||
**商户与应用关联 (`merchant_id` + `app_id`)**
|
||||
- **作用**:标识订单所属商户和应用
|
||||
- **用途**:
|
||||
- 权限控制(商户只能查询自己的订单)
|
||||
- 对账统计(按商户/应用维度)
|
||||
- 通知路由(根据应用配置的通知地址)
|
||||
|
||||
**支付通道关联 (`channel_id`)**
|
||||
- **作用**:记录实际使用的支付通道
|
||||
- **用途**:
|
||||
- 退款时找到对应的插件和配置
|
||||
- 对账时关联通道信息
|
||||
- 统计通道使用情况
|
||||
|
||||
**支付方式与产品 (`method_code` + `product_code`)**
|
||||
- **method_code**:支付方式(alipay/wechat/unionpay)
|
||||
- 用于统计、筛选、报表
|
||||
- **product_code**:支付产品(alipay_h5/alipay_life/wechat_jsapi等)
|
||||
- 由插件根据用户环境自动选择
|
||||
- 用于记录实际使用的支付产品
|
||||
|
||||
### 1.3 金额字段设计
|
||||
|
||||
**订单金额 (`amount`)**
|
||||
- 商户实际收款金额(扣除手续费前)
|
||||
- 用于退款金额校验、对账
|
||||
|
||||
**手续费 (`fee`)**
|
||||
- 可选字段,记录通道手续费
|
||||
- 用于对账、结算、利润统计
|
||||
- 如果不需要详细记录手续费,可以留空或通过 `extra` 存储
|
||||
|
||||
**币种 (`currency`)**
|
||||
- 默认 CNY,支持国际化扩展
|
||||
- 预留字段,便于后续支持多币种
|
||||
|
||||
### 1.4 状态流转设计
|
||||
|
||||
```
|
||||
PENDING(待支付)
|
||||
├─> SUCCESS(支付成功)← 收到渠道回调并验签通过
|
||||
├─> FAIL(支付失败)← 用户取消、超时、渠道返回失败
|
||||
└─> CLOSED(已关闭)← 全额退款后
|
||||
```
|
||||
|
||||
**状态说明**:
|
||||
- **PENDING**:订单创建后,等待用户支付
|
||||
- **SUCCESS**:支付成功,已收到渠道回调并验签通过
|
||||
- **FAIL**:支付失败(用户取消、订单超时、渠道返回失败等)
|
||||
- **CLOSED**:已关闭(全额退款后)
|
||||
|
||||
### 1.5 渠道信息设计
|
||||
|
||||
**渠道订单号 (`channel_order_no`)**
|
||||
- 渠道返回的订单号
|
||||
- 用于查询订单状态、退款等操作
|
||||
|
||||
**渠道交易号 (`channel_trade_no`)**
|
||||
- 部分渠道有交易号概念(如支付宝的 trade_no)
|
||||
- 用于对账、查询等
|
||||
|
||||
### 1.6 通知机制设计
|
||||
|
||||
**通知状态 (`notify_status`)**
|
||||
- 0:未通知
|
||||
- 1:已通知成功
|
||||
|
||||
**通知次数 (`notify_count`)**
|
||||
- 记录通知次数,用于重试控制
|
||||
- 配合 `ma_notify_task` 表实现异步通知
|
||||
|
||||
### 1.7 扩展性设计
|
||||
|
||||
**扩展字段 (`extra`)**
|
||||
- JSON 格式,存储:
|
||||
- 支付参数(`pay_params`):前端支付所需的参数
|
||||
- 退款信息(`refund_info`):退款结果
|
||||
- 自定义字段:业务扩展字段
|
||||
|
||||
**订单过期时间 (`expire_time`)**
|
||||
- 用于自动关闭超时订单
|
||||
- 默认 30 分钟,可配置
|
||||
|
||||
## 二、索引设计说明
|
||||
|
||||
### 2.1 唯一索引
|
||||
|
||||
- **`uk_pay_order_id`**:保证系统订单号唯一
|
||||
- **`uk_mch_order_no(merchant_id, mch_order_no)`**:保证同一商户下商户订单号唯一(幂等性)
|
||||
|
||||
### 2.2 普通索引
|
||||
|
||||
- **`idx_merchant_app(merchant_id, app_id)`**:商户/应用维度查询
|
||||
- **`idx_channel_id`**:通道维度查询
|
||||
- **`idx_method_code`**:支付方式维度统计
|
||||
- **`idx_status`**:状态筛选
|
||||
- **`idx_pay_time`**:按支付时间查询(对账、统计)
|
||||
- **`idx_created_at`**:按创建时间查询(分页、统计)
|
||||
|
||||
## 三、可能遗漏的字段(后续扩展)
|
||||
|
||||
### 3.1 退款相关字段
|
||||
|
||||
如果后续需要支持**部分退款**或**多次退款**,可以考虑添加:
|
||||
|
||||
```sql
|
||||
`refund_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '已退款金额(累计)',
|
||||
`refund_status` varchar(20) NOT NULL DEFAULT '' COMMENT '退款状态:PENDING-退款中, SUCCESS-退款成功, FAIL-退款失败',
|
||||
`refund_time` datetime DEFAULT NULL COMMENT '最后退款时间',
|
||||
```
|
||||
|
||||
**当前设计**:
|
||||
- 退款信息存储在 `extra['refund_info']` 中
|
||||
- 全额退款后订单状态改为 `CLOSED`
|
||||
- 如果只需要全额退款,当前设计已足够
|
||||
|
||||
### 3.2 结算相关字段
|
||||
|
||||
如果后续需要**分账/结算**功能,可以考虑添加:
|
||||
|
||||
```sql
|
||||
`settlement_status` varchar(20) NOT NULL DEFAULT '' COMMENT '结算状态:PENDING-待结算, SUCCESS-已结算, FAIL-结算失败',
|
||||
`settlement_time` datetime DEFAULT NULL COMMENT '结算时间',
|
||||
`settlement_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '结算金额',
|
||||
```
|
||||
|
||||
**当前设计**:
|
||||
- 结算信息可以通过 `extra` 存储
|
||||
- 如果不需要复杂的结算流程,当前设计已足够
|
||||
|
||||
### 3.3 风控相关字段
|
||||
|
||||
如果需要**风控功能**,可以考虑添加:
|
||||
|
||||
```sql
|
||||
`risk_level` varchar(20) NOT NULL DEFAULT '' COMMENT '风险等级:LOW-低, MEDIUM-中, HIGH-高',
|
||||
`risk_score` int(11) NOT NULL DEFAULT 0 COMMENT '风险评分',
|
||||
`risk_reason` varchar(255) NOT NULL DEFAULT '' COMMENT '风险原因',
|
||||
```
|
||||
|
||||
**当前设计**:
|
||||
- 风控信息可以通过 `extra` 存储
|
||||
- 如果不需要复杂的风控系统,当前设计已足够
|
||||
|
||||
### 3.4 其他扩展字段
|
||||
|
||||
- **`user_id`**:用户ID(如果需要关联用户)
|
||||
- **`device_info`**:设备信息(用于风控)
|
||||
- **`remark`**:备注(管理员备注)
|
||||
- **`close_reason`**:关闭原因(用户取消/超时/管理员关闭等)
|
||||
|
||||
## 四、设计原则总结
|
||||
|
||||
1. **幂等性**:通过 `uk_mch_order_no` 保证同一商户下订单号唯一
|
||||
2. **可追溯性**:记录完整的订单信息、渠道信息、时间信息
|
||||
3. **可扩展性**:通过 `extra` JSON 字段存储扩展信息
|
||||
4. **性能优化**:合理的索引设计,支持常见查询场景
|
||||
5. **业务完整性**:覆盖订单全生命周期(创建→支付→退款→关闭)
|
||||
|
||||
## 五、与代码的对应关系
|
||||
|
||||
| SQL 字段 | 代码字段 | 说明 |
|
||||
|---------|---------|------|
|
||||
| `pay_order_id` | `pay_order_id` | 系统订单号 |
|
||||
| `merchant_id` | `merchant_id` | 商户ID |
|
||||
| `app_id` | `app_id` | 应用ID |
|
||||
| `mch_order_no` | `mch_order_no` | 商户订单号 |
|
||||
| `method_code` | `method_code` | 支付方式 |
|
||||
| `product_code` | `product_code` | 支付产品 |
|
||||
| `channel_id` | `channel_id` | 通道ID |
|
||||
| `amount` | `amount` | 订单金额 |
|
||||
| `currency` | `currency` | 币种 |
|
||||
| `status` | `status` | 订单状态 |
|
||||
| `channel_order_no` | `channel_order_no` | 渠道订单号 |
|
||||
| `channel_trade_no` | `channel_trade_no` | 渠道交易号 |
|
||||
| `extra` | `extra` | 扩展字段(JSON) |
|
||||
|
||||
## 六、注意事项
|
||||
|
||||
1. **字段命名统一**:SQL 和代码中的字段名必须一致
|
||||
2. **索引维护**:定期检查索引使用情况,优化慢查询
|
||||
3. **数据归档**:历史订单数据量大时,考虑归档策略
|
||||
4. **JSON 字段**:`extra` 字段使用 JSON 类型,便于扩展但查询性能略低
|
||||
5. **时间字段**:`pay_time`、`expire_time` 等时间字段使用 `datetime` 类型,便于查询和统计
|
||||
|
||||
485
doc/payment_flow.md
Normal file
485
doc/payment_flow.md
Normal file
@@ -0,0 +1,485 @@
|
||||
# 支付订单发起流程说明
|
||||
|
||||
## 一、业务系统调用统一下单接口
|
||||
|
||||
### 1. 接口地址
|
||||
```
|
||||
POST /api/pay/unifiedOrder
|
||||
```
|
||||
|
||||
### 2. 请求头(签名认证)
|
||||
```
|
||||
X-App-Id: app001 # 应用ID
|
||||
X-Timestamp: 1704067200 # 时间戳(Unix秒)
|
||||
X-Nonce: abc123xyz # 随机字符串
|
||||
X-Signature: calculated_signature # 签名(HMAC-SHA256)
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
### 3. 签名算法
|
||||
|
||||
**待签名字符串**:
|
||||
```
|
||||
app_id={app_id}×tamp={timestamp}&nonce={nonce}&method=POST&path=/api/pay/unifiedOrder&body_sha256={body_sha256}
|
||||
```
|
||||
|
||||
**计算签名**:
|
||||
```php
|
||||
$bodySha256 = hash('sha256', json_encode($requestBody));
|
||||
$signString = "app_id={app_id}×tamp={timestamp}&nonce={nonce}&method=POST&path=/api/pay/unifiedOrder&body_sha256={bodySha256}";
|
||||
$signature = hash_hmac('sha256', $signString, $appSecret);
|
||||
```
|
||||
|
||||
### 4. 请求体示例
|
||||
|
||||
```json
|
||||
{
|
||||
"mch_order_no": "ORDER202401011200001",
|
||||
"pay_method": "alipay",
|
||||
"amount": 100.00,
|
||||
"currency": "CNY",
|
||||
"subject": "测试商品",
|
||||
"body": "测试商品描述"
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `mch_order_no`:商户订单号(必填,唯一,用于幂等)
|
||||
- `pay_method`:支付方式(必填,如:alipay、wechat、unionpay)
|
||||
- `amount`:订单金额(必填,单位:元)
|
||||
- `currency`:币种(可选,默认:CNY)
|
||||
- `subject`:订单标题(必填)
|
||||
- `body`:订单描述(可选)
|
||||
|
||||
### 5. 调用示例(cURL)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8787/api/pay/unifiedOrder \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-App-Id: app001" \
|
||||
-H "X-Timestamp: 1704067200" \
|
||||
-H "X-Nonce: abc123xyz" \
|
||||
-H "X-Signature: calculated_signature" \
|
||||
-d '{
|
||||
"mch_order_no": "ORDER202401011200001",
|
||||
"pay_method": "alipay",
|
||||
"amount": 100.00,
|
||||
"subject": "测试商品",
|
||||
"body": "测试商品描述"
|
||||
}'
|
||||
```
|
||||
|
||||
### 6. PHP调用示例
|
||||
|
||||
```php
|
||||
<?php
|
||||
$appId = 'app001';
|
||||
$appSecret = 'your_app_secret';
|
||||
$baseUrl = 'http://localhost:8787';
|
||||
|
||||
// 准备请求数据
|
||||
$requestBody = [
|
||||
'mch_order_no' => 'ORDER202401011200001',
|
||||
'pay_method' => 'alipay',
|
||||
'amount' => 100.00,
|
||||
'subject' => '测试商品',
|
||||
'body' => '测试商品描述'
|
||||
];
|
||||
|
||||
// 计算签名
|
||||
$timestamp = time();
|
||||
$nonce = uniqid();
|
||||
$bodyJson = json_encode($requestBody);
|
||||
$bodySha256 = hash('sha256', $bodyJson);
|
||||
$signString = "app_id={$appId}×tamp={$timestamp}&nonce={$nonce}&method=POST&path=/api/pay/unifiedOrder&body_sha256={$bodySha256}";
|
||||
$signature = hash_hmac('sha256', $signString, $appSecret);
|
||||
|
||||
// 发送请求
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $baseUrl . '/api/pay/unifiedOrder');
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
"X-App-Id: {$appId}",
|
||||
"X-Timestamp: {$timestamp}",
|
||||
"X-Nonce: {$nonce}",
|
||||
"X-Signature: {$signature}",
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
if ($httpCode === 200 && $result['code'] === 200) {
|
||||
echo "支付订单号:" . $result['data']['pay_order_id'] . "\n";
|
||||
echo "支付参数:" . json_encode($result['data']['pay_params'], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
} else {
|
||||
echo "错误:" . $result['msg'] . "\n";
|
||||
}
|
||||
```
|
||||
|
||||
## 二、服务端处理流程
|
||||
|
||||
### 流程图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant BizSystem as 业务系统
|
||||
participant OpenAPI as OpenAPI接口
|
||||
participant AuthMW as 签名中间件
|
||||
participant PayService as 订单服务
|
||||
participant ChannelRouter as 通道路由
|
||||
participant PluginFactory as 插件工厂
|
||||
participant Plugin as 支付插件
|
||||
participant Channel as 第三方渠道
|
||||
|
||||
BizSystem->>OpenAPI: POST /api/pay/unifiedOrder
|
||||
OpenAPI->>AuthMW: 验证签名
|
||||
AuthMW->>PayService: 调用统一下单
|
||||
PayService->>PayService: 1. 验证商户应用
|
||||
PayService->>PayService: 2. 幂等校验
|
||||
PayService->>PayService: 3. 创建支付订单
|
||||
PayService->>ChannelRouter: 4. 选择通道
|
||||
ChannelRouter-->>PayService: 返回通道信息
|
||||
PayService->>PluginFactory: 5. 实例化插件
|
||||
PluginFactory-->>PayService: 返回插件实例
|
||||
PayService->>Plugin: 6. 初始化插件(init)
|
||||
PayService->>Plugin: 7. 环境检测
|
||||
PayService->>Plugin: 8. 调用统一下单(unifiedOrder)
|
||||
Plugin->>Plugin: 8.1 根据环境选择产品
|
||||
Plugin->>Channel: 8.2 调用第三方接口
|
||||
Channel-->>Plugin: 返回支付参数
|
||||
Plugin-->>PayService: 返回支付结果
|
||||
PayService->>PayService: 9. 更新订单信息
|
||||
PayService-->>OpenAPI: 返回结果
|
||||
OpenAPI-->>BizSystem: 返回支付参数
|
||||
```
|
||||
|
||||
### 详细步骤说明
|
||||
|
||||
#### 步骤1:签名验证(中间件)
|
||||
- `OpenApiAuthMiddleware` 验证请求头中的签名
|
||||
- 验证时间戳(5分钟内有效)
|
||||
- 验证签名是否正确
|
||||
- 将应用信息注入到请求对象
|
||||
|
||||
#### 步骤2:验证商户应用
|
||||
- 根据 `app_id` 查询 `ma_merchant_app` 表
|
||||
- 检查应用状态是否启用
|
||||
|
||||
#### 步骤3:幂等校验
|
||||
- 根据 `merchant_id + mch_order_no` 查询是否已存在订单
|
||||
- 如果存在,直接返回已有订单信息(支持幂等)
|
||||
|
||||
#### 步骤4:创建支付订单
|
||||
- 生成支付订单号(格式:`P20240101120000123456`)
|
||||
- 创建 `ma_pay_order` 记录
|
||||
- 状态:`PENDING`(待支付)
|
||||
- 过期时间:30分钟后
|
||||
|
||||
#### 步骤5:通道路由选择
|
||||
- 根据 `merchant_id + app_id + method_code` 查找可用通道
|
||||
- 从 `ma_pay_channel` 表中查询
|
||||
- 选择第一个可用的通道(后续可扩展权重、容灾策略)
|
||||
|
||||
#### 步骤6:实例化插件
|
||||
- 在 `PayService` 中根据 `ma_pay_plugin` 注册表解析插件:优先使用表中的 `class_name`,否则按约定使用 `app\common\payment\{Code}Payment` 实例化插件
|
||||
- 例如:`plugin_code = 'lakala'` → 实例化 `LakalaPlugin`
|
||||
|
||||
#### 步骤7:初始化插件
|
||||
- 调用 `$plugin->init($methodCode, $channelConfig)`
|
||||
- 插件内部切换到指定支付方式的配置和逻辑
|
||||
- 例如:拉卡拉插件初始化到 `alipay` 模式
|
||||
|
||||
#### 步骤8:环境检测
|
||||
- 从请求头 `User-Agent` 判断用户环境
|
||||
- 环境类型:
|
||||
- `PC`:PC桌面浏览器
|
||||
- `H5`:H5手机浏览器
|
||||
- `WECHAT`:微信内浏览器
|
||||
- `ALIPAY_CLIENT`:支付宝客户端
|
||||
|
||||
#### 步骤9:调用插件统一下单
|
||||
- 调用 `$plugin->unifiedOrder($orderData, $channelConfig, $env)`
|
||||
- 插件内部处理:
|
||||
1. **产品选择**:从通道的 `enabled_products` 中,根据环境自动选择一个产品
|
||||
- 例如:H5环境 → 选择 `alipay_h5`
|
||||
- 例如:支付宝客户端 → 选择 `alipay_life`
|
||||
2. **调用第三方接口**:根据产品和支付方式,调用对应的第三方支付接口
|
||||
- 例如:拉卡拉插件的支付宝H5接口
|
||||
3. **返回支付参数**:返回给业务系统的支付参数
|
||||
|
||||
#### 步骤10:更新订单
|
||||
- 更新订单的 `product_code`(实际使用的产品)
|
||||
- 更新订单的 `channel_id`
|
||||
- 更新订单的 `channel_order_no`(渠道订单号)
|
||||
- 保存 `pay_params` 到 `extra` 字段
|
||||
|
||||
## 三、响应数据格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"pay_order_id": "P20240101120000123456",
|
||||
"status": "PENDING",
|
||||
"pay_params": {
|
||||
"type": "redirect",
|
||||
"url": "https://mapi.alipay.com/gateway.do?..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 支付参数类型
|
||||
|
||||
根据不同的支付产品和环境,`pay_params` 的格式不同:
|
||||
|
||||
#### 1. 跳转支付(H5/PC扫码)
|
||||
```json
|
||||
{
|
||||
"type": "redirect",
|
||||
"url": "https://mapi.alipay.com/gateway.do?xxx"
|
||||
}
|
||||
```
|
||||
业务系统需要:**跳转到该URL**
|
||||
|
||||
#### 2. 表单提交(H5)
|
||||
```json
|
||||
{
|
||||
"type": "form",
|
||||
"method": "POST",
|
||||
"action": "https://mapi.alipay.com/gateway.do",
|
||||
"fields": {
|
||||
"app_id": "xxx",
|
||||
"method": "alipay.trade.wap.pay",
|
||||
"biz_content": "{...}"
|
||||
}
|
||||
}
|
||||
```
|
||||
业务系统需要:**自动提交表单**
|
||||
|
||||
#### 3. JSAPI支付(微信内/支付宝生活号)
|
||||
```json
|
||||
{
|
||||
"type": "jsapi",
|
||||
"appId": "wx1234567890",
|
||||
"timeStamp": "1704067200",
|
||||
"nonceStr": "abc123",
|
||||
"package": "prepay_id=wx1234567890",
|
||||
"signType": "MD5",
|
||||
"paySign": "calculated_signature"
|
||||
}
|
||||
```
|
||||
业务系统需要:**调用微信/支付宝JSAPI**
|
||||
|
||||
#### 4. 二维码支付(PC扫码)
|
||||
```json
|
||||
{
|
||||
"type": "qrcode",
|
||||
"qrcode_url": "https://qr.alipay.com/xxx",
|
||||
"qrcode_data": "data:image/png;base64,..."
|
||||
}
|
||||
```
|
||||
业务系统需要:**展示二维码**
|
||||
|
||||
## 四、用户支付流程
|
||||
|
||||
### 1. 业务系统处理支付参数
|
||||
|
||||
根据 `pay_params.type` 进行不同处理:
|
||||
|
||||
```javascript
|
||||
// 前端处理示例
|
||||
const payParams = response.data.pay_params;
|
||||
|
||||
switch (payParams.type) {
|
||||
case 'redirect':
|
||||
// 跳转支付
|
||||
window.location.href = payParams.url;
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
// 表单提交
|
||||
const form = document.createElement('form');
|
||||
form.method = payParams.method;
|
||||
form.action = payParams.action;
|
||||
Object.keys(payParams.fields).forEach(key => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = key;
|
||||
input.value = payParams.fields[key];
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
break;
|
||||
|
||||
case 'jsapi':
|
||||
// 微信JSAPI支付
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||
appId: payParams.appId,
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
package: payParams.package,
|
||||
signType: payParams.signType,
|
||||
paySign: payParams.paySign
|
||||
}, function(res) {
|
||||
if (res.err_msg === "get_brand_wcpay_request:ok") {
|
||||
// 支付成功
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'qrcode':
|
||||
// 展示二维码
|
||||
document.getElementById('qrcode').src = payParams.qrcode_data;
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 用户完成支付
|
||||
|
||||
- 用户在第三方支付平台完成支付
|
||||
- 第三方平台异步回调到支付中心
|
||||
|
||||
### 3. 支付中心处理回调
|
||||
|
||||
- 接收回调:`POST /api/notify/alipay` 或 `/api/notify/wechat`
|
||||
- 验签:使用插件验证回调签名
|
||||
- 更新订单状态:`PENDING` → `SUCCESS` 或 `FAIL`
|
||||
- 创建通知任务:异步通知业务系统
|
||||
|
||||
### 4. 业务系统接收通知
|
||||
|
||||
- 支付中心异步通知业务系统的 `notify_url`
|
||||
- 业务系统验证签名并处理订单
|
||||
|
||||
## 五、查询订单接口
|
||||
|
||||
### 接口地址
|
||||
```
|
||||
GET /api/pay/query?pay_order_id=P20240101120000123456
|
||||
```
|
||||
|
||||
### 请求头(需要签名)
|
||||
```
|
||||
X-App-Id: app001
|
||||
X-Timestamp: 1704067200
|
||||
X-Nonce: abc123xyz
|
||||
X-Signature: calculated_signature
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"pay_order_id": "P20240101120000123456",
|
||||
"mch_order_no": "ORDER202401011200001",
|
||||
"status": "SUCCESS",
|
||||
"amount": 100.00,
|
||||
"pay_time": "2024-01-01 12:00:30"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、完整调用示例(Node.js)
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
|
||||
class PaymentClient {
|
||||
constructor(appId, appSecret, baseUrl) {
|
||||
this.appId = appId;
|
||||
this.appSecret = appSecret;
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
// 计算签名
|
||||
calculateSignature(method, path, body, timestamp, nonce) {
|
||||
const bodySha256 = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
|
||||
const signString = `app_id=${this.appId}×tamp=${timestamp}&nonce=${nonce}&method=${method}&path=${path}&body_sha256=${bodySha256}`;
|
||||
return crypto.createHmac('sha256', this.appSecret).update(signString).digest('hex');
|
||||
}
|
||||
|
||||
// 统一下单
|
||||
async unifiedOrder(orderData) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = Math.random().toString(36).substring(7);
|
||||
const method = 'POST';
|
||||
const path = '/api/pay/unifiedOrder';
|
||||
|
||||
const signature = this.calculateSignature(method, path, orderData, timestamp, nonce);
|
||||
|
||||
const response = await axios.post(`${this.baseUrl}${path}`, orderData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-App-Id': this.appId,
|
||||
'X-Timestamp': timestamp,
|
||||
'X-Nonce': nonce,
|
||||
'X-Signature': signature
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
async queryOrder(payOrderId) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = Math.random().toString(36).substring(7);
|
||||
const method = 'GET';
|
||||
const path = `/api/pay/query?pay_order_id=${payOrderId}`;
|
||||
|
||||
const signature = this.calculateSignature(method, path, {}, timestamp, nonce);
|
||||
|
||||
const response = await axios.get(`${this.baseUrl}${path}`, {
|
||||
headers: {
|
||||
'X-App-Id': this.appId,
|
||||
'X-Timestamp': timestamp,
|
||||
'X-Nonce': nonce,
|
||||
'X-Signature': signature
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const client = new PaymentClient('app001', 'your_app_secret', 'http://localhost:8787');
|
||||
|
||||
// 统一下单
|
||||
client.unifiedOrder({
|
||||
mch_order_no: 'ORDER202401011200001',
|
||||
pay_method: 'alipay',
|
||||
amount: 100.00,
|
||||
subject: '测试商品',
|
||||
body: '测试商品描述'
|
||||
}).then(result => {
|
||||
console.log('支付参数:', result.data.pay_params);
|
||||
// 根据 pay_params.type 处理支付
|
||||
}).catch(err => {
|
||||
console.error('下单失败:', err.message);
|
||||
});
|
||||
```
|
||||
|
||||
## 七、注意事项
|
||||
|
||||
1. **幂等性**:相同的 `mch_order_no` 多次调用,返回同一订单信息
|
||||
2. **签名有效期**:时间戳5分钟内有效
|
||||
3. **订单过期**:订单默认30分钟过期
|
||||
4. **环境检测**:系统自动根据UA判断环境,选择合适的产品
|
||||
5. **异步通知**:支付成功后,系统会异步通知业务系统的 `notify_url`
|
||||
6. **订单查询**:业务系统可通过查询接口主动查询订单状态
|
||||
|
||||
182
doc/payment_system_implementation.md
Normal file
182
doc/payment_system_implementation.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 支付系统核心实现说明
|
||||
|
||||
## 概述
|
||||
|
||||
已实现支付系统核心功能,包括:
|
||||
- 插件化支付通道系统(支持一个插件多个支付方式)
|
||||
- OpenAPI统一支付网关
|
||||
- 通道管理与配置
|
||||
- 订单管理与状态机
|
||||
- 异步通知机制
|
||||
|
||||
## 数据库初始化
|
||||
|
||||
执行以下SQL脚本创建表结构:
|
||||
|
||||
```bash
|
||||
mysql -u用户名 -p 数据库名 < database/mvp_payment_tables.sql
|
||||
```
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 1. 插件系统
|
||||
|
||||
- **插件接口**:`app/common/contracts/PayPluginInterface.php`
|
||||
- **抽象基类**:`app/common/contracts/AbstractPayPlugin.php`(提供环境检测、产品选择等通用功能)
|
||||
- **插件类示例**:`app/common/payment/LakalaPayment.php`(命名规范:`XxxPayment`)
|
||||
- **插件解析**:由 `PayService`、`PayOrderService`、`PluginService` 直接根据 `ma_pay_plugin` 注册表中配置的 `plugin_code` / `class_name` 解析并实例化插件(默认约定类名为 `app\common\payment\{Code}Payment`)
|
||||
|
||||
**插件特点**:
|
||||
- 一个插件可以支持多个支付方式(如拉卡拉插件支持 alipay/wechat/unionpay)
|
||||
- **支付产品由插件内部定义**,不需要数据库字典表
|
||||
- 插件根据用户环境(PC/H5/微信内/支付宝客户端)自动选择已开通的产品
|
||||
- 通道配置中,用户只需勾选确认开启了哪些产品(产品编码由插件定义)
|
||||
- 有些支付平台不区分产品,插件会根据通道配置自行处理
|
||||
- 通道配置表单由插件动态生成
|
||||
|
||||
### 2. 数据模型
|
||||
|
||||
- `Merchant`:商户
|
||||
- `MerchantApp`:商户应用(AppId/AppSecret)
|
||||
- `PayMethod`:支付方式(alipay/wechat等)
|
||||
- `PayChannel`:支付通道(绑定到"插件+支付方式",配置已开通的产品列表)
|
||||
- `PayOrder`:支付订单
|
||||
- `NotifyTask`:商户通知任务
|
||||
|
||||
**注意**:支付产品不由数据库管理,而是由插件通过 `getSupportedProducts()` 方法定义。通道配置中的 `enabled_products` 字段存储的是用户勾选的产品编码数组。
|
||||
|
||||
### 3. 服务层
|
||||
|
||||
- `PayOrderService`:订单业务编排(统一下单、查询)
|
||||
- `ChannelRouterService`:通道路由选择
|
||||
- `NotifyService`:商户通知服务
|
||||
|
||||
### 4. API接口
|
||||
|
||||
#### OpenAPI(对外支付网关)
|
||||
|
||||
- `POST /api/pay/unifiedOrder`:统一下单(需要签名认证)
|
||||
- `GET /api/pay/query`:查询订单(需要签名认证)
|
||||
- `POST /api/notify/alipay`:支付宝回调
|
||||
- `POST /api/notify/wechat`:微信回调
|
||||
|
||||
#### 管理后台API
|
||||
|
||||
- `GET /adminapi/channel/plugins`:获取所有可用插件
|
||||
- `GET /adminapi/channel/plugin/config-schema`:获取插件配置表单Schema
|
||||
- `GET /adminapi/channel/plugin/products`:获取插件支持的支付产品
|
||||
- `GET /adminapi/channel/list`:通道列表
|
||||
- `GET /adminapi/channel/detail`:通道详情
|
||||
- `POST /adminapi/channel/save`:保存通道
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 1. 创建商户和应用
|
||||
|
||||
```sql
|
||||
INSERT INTO ma_merchant (merchant_no, merchant_name, funds_mode, status)
|
||||
VALUES ('M001', '测试商户', 'direct', 1);
|
||||
|
||||
INSERT INTO ma_merchant_app (merchant_id, app_id, app_secret, app_name, notify_url, status)
|
||||
VALUES (1, 'app001', 'secret_key_here', '测试应用', 'https://example.com/notify', 1);
|
||||
```
|
||||
|
||||
### 2. 配置支付通道
|
||||
|
||||
**配置流程**:
|
||||
1. 创建通道:选择支付方式、支付插件,配置通道基本信息(显示名称、分成比例、通道成本、通道模式、限额等)
|
||||
2. 配置插件参数:通道创建后,再配置该通道的插件参数信息(通过插件的配置表单动态生成)
|
||||
|
||||
通过管理后台或直接操作数据库:
|
||||
|
||||
```sql
|
||||
INSERT INTO ma_pay_channel (
|
||||
merchant_id, app_id, channel_code, channel_name,
|
||||
plugin_code, method_code, enabled_products, config_json,
|
||||
split_ratio, channel_cost, channel_mode,
|
||||
daily_limit, daily_count, min_amount, max_amount,
|
||||
status
|
||||
) VALUES (
|
||||
1, 1, 'CH001', '拉卡拉-支付宝通道',
|
||||
'lakala', 'alipay',
|
||||
'["alipay_h5", "alipay_life"]',
|
||||
'{"merchant_id": "lakala_merchant", "secret_key": "xxx", "api_url": "https://api.lakala.com"}',
|
||||
100.00, 0.00, 'wallet',
|
||||
0.00, 0, NULL, NULL,
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
**通道字段说明**:
|
||||
- `split_ratio`: 分成比例(%),默认100.00
|
||||
- `channel_cost`: 通道成本(%),默认0.00
|
||||
- `channel_mode`: 通道模式,`wallet`-支付金额扣除手续费后加入商户余额,`direct`-直连到商户
|
||||
- `daily_limit`: 单日限额(元),0表示不限制
|
||||
- `daily_count`: 单日限笔,0表示不限制
|
||||
- `min_amount`: 单笔最小金额(元),NULL表示不限制
|
||||
- `max_amount`: 单笔最大金额(元),NULL表示不限制
|
||||
|
||||
### 3. 调用统一下单接口
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8787/api/pay/unifiedOrder \
|
||||
-H "X-App-Id: app001" \
|
||||
-H "X-Timestamp: 1234567890" \
|
||||
-H "X-Nonce: abc123" \
|
||||
-H "X-Signature: calculated_signature" \
|
||||
-d '{
|
||||
"mch_order_no": "ORDER001",
|
||||
"pay_method": "alipay",
|
||||
"amount": 100.00,
|
||||
"subject": "测试订单",
|
||||
"body": "测试订单描述"
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. 签名算法
|
||||
|
||||
```
|
||||
signString = "app_id={app_id}×tamp={timestamp}&nonce={nonce}&method={method}&path={path}&body_sha256={body_sha256}"
|
||||
signature = HMAC-SHA256(signString, app_secret)
|
||||
```
|
||||
|
||||
## 扩展新插件
|
||||
|
||||
1. 创建插件类,继承 `AbstractPayPlugin`,并按照 `XxxPayment` 命名放在 `app/common/payment` 目录:
|
||||
|
||||
```php
|
||||
namespace app\common\payment;
|
||||
|
||||
use app\common\contracts\AbstractPayPlugin;
|
||||
|
||||
class AlipayPayment extends AbstractPayPlugin
|
||||
{
|
||||
public static function getCode(): string { return 'alipay'; }
|
||||
public static function getName(): string { return '支付宝直连'; }
|
||||
public static function getSupportedMethods(): array { return ['alipay']; }
|
||||
// ... 实现其他方法
|
||||
}
|
||||
```
|
||||
|
||||
2. 在 `ma_pay_plugin` 表中注册插件信息(也可通过后台管理界面维护):
|
||||
|
||||
```sql
|
||||
INSERT INTO ma_pay_plugin (plugin_code, plugin_name, class_name, status)
|
||||
VALUES ('alipay', '支付宝直连', 'app\\common\\payment\\AlipayPayment', 1);
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **支付产品定义**:支付产品由插件内部通过 `getSupportedProducts()` 方法定义,不需要数据库字典表。通道配置时,用户只需勾选已开通的产品编码。
|
||||
2. **环境检测**:插件基类提供 `detectEnvironment()` 方法,可根据UA判断环境
|
||||
3. **产品选择**:插件根据环境从通道已开通产品中自动选择。如果通道配置为空或不区分产品,插件会根据配置自行处理。
|
||||
4. **通知重试**:使用 `NotifyMerchantJob` 异步重试通知,支持指数退避
|
||||
5. **幂等性**:统一下单接口支持幂等,相同 `mch_order_no` 返回已有订单
|
||||
|
||||
## 后续扩展
|
||||
|
||||
- 账务系统(账户、分录、余额)
|
||||
- 结算系统(可结算金额、结算批次、打款)
|
||||
- 对账系统(渠道账单导入、差异处理)
|
||||
- 风控系统(规则引擎、风险预警)
|
||||
|
||||
481
doc/skill.md
481
doc/skill.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理系统,提供完整的支付业务管理功能,包括用户认证、菜单管理、系统配置、财务管理、渠道管理和数据分析等核心模块。
|
||||
MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理系统,核心聚焦支付业务:商户管理、通道配置、统一支付、易支付兼容、商户通知等。管理后台提供管理员认证、菜单、系统配置、通道与插件管理;对外提供 OpenAPI 与易支付标准接口。
|
||||
|
||||
## 2. 技术架构
|
||||
|
||||
@@ -10,46 +10,38 @@ MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理
|
||||
|
||||
| 类别 | 技术/框架 | 版本 | 用途 | 来源 |
|
||||
|------|-----------|------|------|------|
|
||||
| 基础框架 | Webman | ^2.1 | 高性能HTTP服务框架 | composer.json:28 |
|
||||
| PHP版本 | PHP | >=8.1 | 开发语言 | composer.json:27 |
|
||||
| 数据库 | webman/database | ^2.1 | 数据库操作 | composer.json:31 |
|
||||
| 缓存 | Redis | ^2.1 | 缓存存储 | composer.json:32 |
|
||||
| 缓存 | webman/cache | ^2.1 | 缓存管理 | composer.json:34 |
|
||||
| 认证 | JWT | ^7.0 | 用户认证 | composer.json:42 |
|
||||
| 验证码 | webman/captcha | ^1.0 | 登录验证码 | composer.json:37 |
|
||||
| 事件系统 | webman/event | ^1.0 | 事件管理 | composer.json:38 |
|
||||
| 配置管理 | vlucas/phpdotenv | ^5.6 | 环境变量 | composer.json:39 |
|
||||
| 定时任务 | workerman/crontab | ^1.0 | 定时任务 | composer.json:40 |
|
||||
| 队列 | webman/redis-queue | ^2.1 | 消息队列 | composer.json:41 |
|
||||
| 验证 | topthink/think-validate | ^3.0 | 数据验证 | composer.json:36 |
|
||||
| 容器 | php-di/php-di | 7.0 | 依赖注入 | composer.json:30 |
|
||||
| 日志 | monolog/monolog | ^2.0 | 日志管理 | composer.json:29 |
|
||||
| 控制台 | webman/console | ^2.1 | 命令行工具 | composer.json:35 |
|
||||
| 基础框架 | Webman | ^2.1 | 高性能HTTP服务框架 | composer.json |
|
||||
| PHP版本 | PHP | >=8.1 | 开发语言 | composer.json |
|
||||
| 数据库 | webman/database | ^2.1 | 数据库操作 | composer.json |
|
||||
| 缓存 | Redis | ^2.1 | 缓存存储 | composer.json |
|
||||
| 缓存 | webman/cache | ^2.1 | 缓存管理 | composer.json |
|
||||
| 认证 | JWT | ^7.0 | 管理员认证 | composer.json |
|
||||
| 验证码 | webman/captcha | ^1.0 | 登录验证码 | composer.json |
|
||||
| 事件系统 | webman/event | ^1.0 | 事件管理 | composer.json |
|
||||
| 配置管理 | vlucas/phpdotenv | ^5.6 | 环境变量 | composer.json |
|
||||
| 定时任务 | workerman/crontab | ^1.0 | 定时任务 | composer.json |
|
||||
| 队列 | webman/redis-queue | ^2.1 | 消息队列 | composer.json |
|
||||
| 验证 | topthink/think-validate | ^3.0 | 数据验证 | composer.json |
|
||||
| 容器 | php-di/php-di | 7.0 | 依赖注入 | composer.json |
|
||||
| 日志 | monolog/monolog | ^2.0 | 日志管理 | composer.json |
|
||||
| 控制台 | webman/console | ^2.1 | 命令行工具 | composer.json |
|
||||
|
||||
### 2.2 前端技术栈
|
||||
|
||||
| 类别 | 技术/框架 | 版本 | 用途 | 来源 |
|
||||
|------|-----------|------|------|------|
|
||||
| 基础框架 | Vue | ^3.5.15 | 前端框架 | package.json:61 |
|
||||
| 语言 | TypeScript | ^5.2.2 | 开发语言 | package.json:103 |
|
||||
| 构建工具 | Vite | ^6.3.5 | 构建工具 | package.json:107 |
|
||||
| UI框架 | Arco Design | ^2.57.0 | 界面组件库 | package.json:72 |
|
||||
| 状态管理 | Pinia | ^2.3.0 | 状态管理 | package.json:53 |
|
||||
| 路由 | Vue Router | ^4.3.0 | 前端路由 | package.json:66 |
|
||||
| HTTP客户端 | Axios | ^1.6.8 | API调用 | package.json:47 |
|
||||
| 表单生成 | @form-create/arco-design | ^3.2.37 | 动态表单 | package.json:41 |
|
||||
| 图表 | @visactor/vchart | ^1.11.0 | 数据可视化 | package.json:42 |
|
||||
| 代码编辑器 | CodeMirror | ^6.0.1 | 代码编辑 | package.json:48 |
|
||||
| 富文本编辑器 | @wangeditor/editor | ^5.1.23 | 内容编辑 | package.json:45 |
|
||||
| 国际化 | vue-i18n | 10.0.0-alpha.3 | 多语言支持 | package.json:64 |
|
||||
| 工具库 | @vueuse/core | ^12.4.0 | 实用工具 | package.json:44 |
|
||||
| 指纹识别 | @fingerprintjs/fingerprintjs | ^4.6.2 | 设备识别 | package.json:40 |
|
||||
| 二维码 | qrcode | ^1.5.4 | 二维码生成 | package.json:57 |
|
||||
| 条码 | jsbarcode | ^3.11.6 | 条码生成 | package.json:51 |
|
||||
| 打印 | print-js | ^1.6.0 | 页面打印 | package.json:56 |
|
||||
| 进度条 | nprogress | ^0.2.0 | 加载进度 | package.json:52 |
|
||||
| 中文转拼音 | pinyin-pro | ^3.26.0 | 拼音转换 | package.json:55 |
|
||||
| 引导 | driver.js | ^1.3.1 | 功能引导 | package.json:49 |
|
||||
| 基础框架 | Vue | ^3.5.15 | 前端框架 | package.json |
|
||||
| 语言 | TypeScript | ^5.2.2 | 开发语言 | package.json |
|
||||
| 构建工具 | Vite | ^6.3.5 | 构建工具 | package.json |
|
||||
| UI框架 | Arco Design | ^2.57.0 | 界面组件库 | package.json |
|
||||
| 状态管理 | Pinia | ^2.3.0 | 状态管理 | package.json |
|
||||
| 路由 | Vue Router | ^4.3.0 | 前端路由 | package.json |
|
||||
| HTTP客户端 | Axios | ^1.6.8 | API调用 | package.json |
|
||||
| 表单生成 | @form-create/arco-design | ^3.2.37 | 动态表单 | package.json |
|
||||
| 图表 | @visactor/vchart | ^1.11.0 | 数据可视化 | package.json |
|
||||
| 国际化 | vue-i18n | 10.0.0-alpha.3 | 多语言支持 | package.json |
|
||||
| 工具库 | @vueuse/core | ^12.4.0 | 实用工具 | package.json |
|
||||
| 二维码 | qrcode | ^1.5.4 | 二维码生成 | package.json |
|
||||
|
||||
## 3. 项目结构
|
||||
|
||||
@@ -57,267 +49,264 @@ MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理
|
||||
|
||||
```
|
||||
d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\
|
||||
├── app/ # 应用代码
|
||||
│ ├── common/ # 通用代码
|
||||
│ │ ├── base/ # 基础类
|
||||
├── app/ # 应用代码
|
||||
│ ├── common/ # 通用代码
|
||||
│ │ ├── base/ # 基础类
|
||||
│ │ │ ├── BaseController.php
|
||||
│ │ │ ├── BaseModel.php
|
||||
│ │ │ ├── BaseRepository.php
|
||||
│ │ │ └── BaseService.php
|
||||
│ │ ├── constants/ # 常量
|
||||
│ │ │ └── YesNo.php
|
||||
│ │ ├── enums/ # 枚举
|
||||
│ │ │ └── MenuType.php
|
||||
│ │ ├── middleware/ # 中间件
|
||||
│ │ │ ├── Cors.php
|
||||
│ │ │ └── StaticFile.php
|
||||
│ │ └── utils/ # 工具类
|
||||
│ │ └── JwtUtil.php
|
||||
│ ├── events/ # 事件
|
||||
│ │ └── SystemConfig.php
|
||||
│ ├── exceptions/ # 异常处理
|
||||
│ │ └── ValidationException.php
|
||||
│ ├── http/ # HTTP相关
|
||||
│ │ ├── admin/ # 后台管理
|
||||
│ │ │ ├── controller/ # 控制器
|
||||
│ │ ├── contracts/ # 契约/接口
|
||||
│ │ │ ├── PayPluginInterface.php
|
||||
│ │ │ └── AbstractPayPlugin.php
|
||||
│ │ ├── constants/ # 常量
|
||||
│ │ ├── enums/ # 枚举
|
||||
│ │ ├── middleware/ # 中间件(Cors, StaticFile)
|
||||
│ │ ├── payment/ # 支付插件实现
|
||||
│ │ │ └── LakalaPayment.php
|
||||
│ │ └── utils/ # 工具类(JwtUtil 等)
|
||||
│ ├── events/ # 事件
|
||||
│ ├── exceptions/ # 异常(BadRequest, NotFound, Validation 等)
|
||||
│ ├── http/
|
||||
│ │ ├── admin/ # 管理后台
|
||||
│ │ │ ├── controller/
|
||||
│ │ │ │ ├── AuthController.php
|
||||
│ │ │ │ ├── AdminController.php
|
||||
│ │ │ │ ├── MenuController.php
|
||||
│ │ │ │ ├── SystemController.php
|
||||
│ │ │ │ └── UserController.php
|
||||
│ │ │ └── middleware/ # 中间件
|
||||
│ │ │ │ ├── ChannelController.php
|
||||
│ │ │ │ └── PluginController.php
|
||||
│ │ │ └── middleware/
|
||||
│ │ │ └── AuthMiddleware.php
|
||||
│ ├── models/ # 数据模型
|
||||
│ │ ├── SystemConfig.php
|
||||
│ │ └── User.php
|
||||
│ ├── process/ # 进程管理
|
||||
│ │ ├── Http.php
|
||||
│ │ └── Monitor.php
|
||||
│ ├── repositories/ # 数据仓库
|
||||
│ │ ├── SystemConfigRepository.php
|
||||
│ │ └── UserRepository.php
|
||||
│ ├── routes/ # 路由配置
|
||||
│ │ └── api/ # 对外 API
|
||||
│ │ ├── controller/
|
||||
│ │ │ ├── PayController.php # OpenAPI 支付接口(骨架)
|
||||
│ │ │ └── EpayController.php # 易支付接口(submit.php/mapi.php/api.php)
|
||||
│ │ └── middleware/
|
||||
│ │ ├── EpayAuthMiddleware.php
|
||||
│ │ └── OpenApiAuthMiddleware.php
|
||||
│ ├── jobs/ # 异步任务
|
||||
│ │ └── NotifyMerchantJob.php
|
||||
│ ├── models/ # 数据模型
|
||||
│ │ ├── Admin.php
|
||||
│ │ ├── Merchant.php
|
||||
│ │ ├── MerchantApp.php
|
||||
│ │ ├── PaymentMethod.php
|
||||
│ │ ├── PaymentPlugin.php
|
||||
│ │ ├── PaymentChannel.php
|
||||
│ │ ├── PaymentOrder.php
|
||||
│ │ ├── PaymentCallbackLog.php
|
||||
│ │ ├── PaymentNotifyTask.php
|
||||
│ │ └── SystemConfig.php
|
||||
│ ├── repositories/ # 数据仓储
|
||||
│ │ ├── AdminRepository.php
|
||||
│ │ ├── MerchantRepository.php
|
||||
│ │ ├── MerchantAppRepository.php
|
||||
│ │ ├── PaymentMethodRepository.php
|
||||
│ │ ├── PaymentPluginRepository.php
|
||||
│ │ ├── PaymentChannelRepository.php
|
||||
│ │ ├── PaymentOrderRepository.php
|
||||
│ │ ├── PaymentNotifyTaskRepository.php
|
||||
│ │ ├── PaymentCallbackLogRepository.php
|
||||
│ │ └── SystemConfigRepository.php
|
||||
│ ├── routes/ # 路由
|
||||
│ │ ├── admin.php
|
||||
│ │ ├── api.php
|
||||
│ │ └── mer.php
|
||||
│ ├── services/ # 业务逻辑
|
||||
│ ├── services/ # 业务逻辑
|
||||
│ │ ├── AuthService.php
|
||||
│ │ ├── AdminService.php
|
||||
│ │ ├── CaptchaService.php
|
||||
│ │ ├── MenuService.php
|
||||
│ │ ├── SystemConfigService.php
|
||||
│ │ ├── SystemSettingService.php
|
||||
│ │ └── UserService.php
|
||||
│ └── validation/ # 数据验证
|
||||
│ └── SystemConfigValidator.php
|
||||
├── config/ # 配置文件
|
||||
│ ├── base-config/ # 基础配置
|
||||
│ │ ├── basic.json
|
||||
│ │ ├── email.json
|
||||
│ │ ├── permission.json
|
||||
│ │ └── tabs.json
|
||||
│ ├── plugin/ # 插件配置
|
||||
│ │ ├── webman/
|
||||
│ │ │ ├── console/
|
||||
│ │ │ ├── event/
|
||||
│ │ │ ├── redis-queue/
|
||||
│ │ │ └── validation/
|
||||
│ ├── system-file/ # 系统文件
|
||||
│ │ ├── dict.json
|
||||
│ │ ├── menu.json
|
||||
│ │ └── menu.md
|
||||
│ ├── app.php
|
||||
│ ├── autoload.php
|
||||
│ ├── bootstrap.php
|
||||
│ ├── cache.php
|
||||
│ ├── container.php
|
||||
│ ├── database.php
|
||||
│ ├── dependence.php
|
||||
│ ├── event.php
|
||||
│ ├── exception.php
|
||||
│ ├── jwt.php
|
||||
│ ├── log.php
|
||||
│ ├── menu.php
|
||||
│ ├── middleware.php
|
||||
│ ├── process.php
|
||||
│ ├── redis.php
|
||||
│ ├── route.php
|
||||
│ ├── server.php
|
||||
│ ├── session.php
|
||||
│ ├── static.php
|
||||
│ ├── translation.php
|
||||
│ └── view.php
|
||||
├── database/ # 数据库文件
|
||||
│ └── ma_system_config.sql
|
||||
├── doc/ # 文档
|
||||
│ ├── event.md
|
||||
│ └── exception.md
|
||||
├── public/ # 静态资源
|
||||
│ └── favicon.ico
|
||||
├── resource/ # 资源文件
|
||||
│ └── mpay_v2_admin/ # 前端项目
|
||||
├── .env # 环境变量
|
||||
├── composer.json # PHP依赖
|
||||
└── composer.lock # 依赖锁定
|
||||
│ │ ├── PluginService.php # 插件注册与实例化
|
||||
│ │ ├── ChannelRouterService.php # 通道路由(按商户+应用+支付方式选通道)
|
||||
│ │ ├── PayOrderService.php # 订单创建、幂等、退款
|
||||
│ │ ├── PayService.php # 统一下单、调用插件
|
||||
│ │ ├── NotifyService.php # 商户通知、重试
|
||||
│ │ └── api/
|
||||
│ │ └── EpayService.php # 易支付业务封装
|
||||
│ ├── validation/ # 验证器
|
||||
│ │ ├── EpayValidator.php
|
||||
│ │ └── SystemConfigValidator.php
|
||||
│ └── process/ # 进程(Http, Monitor)
|
||||
├── config/ # 配置文件
|
||||
├── database/ # 数据库脚本
|
||||
│ └── mvp_payment_tables.sql # 支付系统核心表(ma_*)
|
||||
├── doc/ # 文档
|
||||
│ ├── skill.md
|
||||
│ ├── epay.md
|
||||
│ ├── payment_flow.md
|
||||
│ ├── validation.md
|
||||
│ └── payment_system_implementation.md
|
||||
├── public/
|
||||
├── resource/
|
||||
│ └── mpay_v2_admin/ # 前端项目
|
||||
├── .env
|
||||
└── composer.json
|
||||
```
|
||||
|
||||
### 3.2 前端目录结构
|
||||
### 3.2 数据库表结构(`database/mvp_payment_tables.sql`)
|
||||
|
||||
| 表名 | 说明 |
|
||||
|------|------|
|
||||
| ma_merchant | 商户表 |
|
||||
| ma_merchant_app | 商户应用表(api_type 区分 openapi/epay/custom) |
|
||||
| ma_pay_method | 支付方式字典(alipay/wechat/unionpay) |
|
||||
| ma_pay_plugin | 支付插件注册表(plugin_code 为主键) |
|
||||
| ma_pay_channel | 支付通道表(merchant_id, merchant_app_id, method_id 关联) |
|
||||
| ma_pay_order | 支付订单表(status: 0-PENDING, 1-SUCCESS, 2-FAIL, 3-CLOSED) |
|
||||
| ma_pay_callback_log | 支付回调日志表 |
|
||||
| ma_notify_task | 商户通知任务表(order_id, retry_cnt, next_retry_at) |
|
||||
| ma_system_config | 系统配置表 |
|
||||
| ma_admin | 管理员表 |
|
||||
|
||||
### 3.3 前端目录结构
|
||||
|
||||
```
|
||||
d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\resource\mpay_v2_admin\
|
||||
├── src/ # 源代码
|
||||
│ ├── api/ # API调用
|
||||
│ ├── assets/ # 静态资源
|
||||
│ ├── components/ # 组件
|
||||
│ ├── config/ # 配置
|
||||
│ ├── directives/ # 指令
|
||||
│ ├── hooks/ # 钩子
|
||||
│ ├── lang/ # 国际化
|
||||
│ ├── layout/ # 布局
|
||||
│ ├── mock/ # 模拟数据
|
||||
│ ├── router/ # 路由
|
||||
│ ├── store/ # 状态管理
|
||||
│ ├── style/ # 样式
|
||||
│ ├── typings/ # 类型定义
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── views/ # 页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ ├── auto-import.d.ts # 自动导入
|
||||
│ ├── components.d.ts # 组件声明
|
||||
│ ├── main.ts # 入口文件
|
||||
│ └── style.css # 全局样式
|
||||
├── build/ # 构建配置
|
||||
│ ├── optimize.ts
|
||||
│ └── vite-plugin.ts
|
||||
├── .env # 环境变量
|
||||
├── .env.development # 开发环境变量
|
||||
├── .env.production # 生产环境变量
|
||||
├── .env.test # 测试环境变量
|
||||
├── eslint.config.js # ESLint配置
|
||||
├── index.html # HTML模板
|
||||
├── package.json # 前端依赖
|
||||
└── vite.config.ts # Vite配置
|
||||
resource/mpay_v2_admin/
|
||||
├── src/
|
||||
│ ├── api/
|
||||
│ ├── components/
|
||||
│ ├── layout/
|
||||
│ ├── router/
|
||||
│ ├── store/
|
||||
│ ├── views/
|
||||
│ │ ├── login/
|
||||
│ │ ├── home/
|
||||
│ │ ├── finance/
|
||||
│ │ ├── channel/
|
||||
│ │ ├── analysis/
|
||||
│ │ └── system/
|
||||
│ ├── App.vue
|
||||
│ └── main.ts
|
||||
├── package.json
|
||||
└── vite.config.ts
|
||||
```
|
||||
|
||||
## 4. 核心功能模块
|
||||
|
||||
### 4.1 后端核心模块
|
||||
### 4.1 支付业务流程约定
|
||||
|
||||
| 模块 | 主要功能 | 文件位置 | 来源 |
|
||||
|------|----------|----------|------|
|
||||
| 认证模块 | 用户登录、验证码生成 | app/http/admin/controller/AuthController.php | app/routes/admin.php:20-21 |
|
||||
| 用户模块 | 获取用户信息 | app/http/admin/controller/UserController.php | app/routes/admin.php:26 |
|
||||
| 菜单模块 | 获取路由菜单 | app/http/admin/controller/MenuController.php | app/routes/admin.php:29 |
|
||||
| 系统模块 | 字典管理、配置管理 | app/http/admin/controller/SystemController.php | app/routes/admin.php:32-37 |
|
||||
1. **订单创建**:`PayOrderService::createOrder`,支持幂等(merchant_id + merchant_app_id + mch_order_no 唯一)
|
||||
2. **通道路由**:`ChannelRouterService::chooseChannel(merchantId, merchantAppId, methodId)` 按第一个可用通道
|
||||
3. **统一下单**:`PayService::unifiedPay` → 创建订单 → 选通道 → 实例化插件 → 调用 `unifiedOrder`
|
||||
4. **商户通知**:`NotifyService::createNotifyTask`,`notify_url` 从订单 `extra['notify_url']` 获取
|
||||
5. **通知重试**:`NotifyMerchantJob` 定时拉取待重试任务,指数退避
|
||||
|
||||
### 4.2 前端核心模块
|
||||
### 4.2 支付插件接口
|
||||
|
||||
| 模块 | 主要功能 | 文件位置 | 来源 |
|
||||
|------|----------|----------|------|
|
||||
| 布局模块 | 系统整体布局 | src/layout/ | resource/mpay_v2_admin/src/layout/ |
|
||||
| 认证模块 | 登录、权限控制 | src/views/login/ | resource/mpay_v2_admin/src/views/ |
|
||||
| 首页模块 | 数据概览 | src/views/home/ | resource/mpay_v2_admin/src/views/home/ |
|
||||
| 财务管理 | 结算、对账、发票 | src/views/finance/ | resource/mpay_v2_admin/src/views/finance/ |
|
||||
| 渠道管理 | 通道配置、支付方式 | src/views/channel/ | resource/mpay_v2_admin/src/views/channel/ |
|
||||
| 数据分析 | 交易分析、商户分析 | src/views/analysis/ | resource/mpay_v2_admin/src/views/analysis/ |
|
||||
| 系统设置 | 系统配置、字典管理 | src/views/system/ | resource/mpay_v2_admin/src/views/ |
|
||||
- `app/common/contracts/PayPluginInterface.php`
|
||||
- `app/common/contracts/AbstractPayPlugin.php`
|
||||
- 示例实现:`app/common/payment/LakalaPayment.php`
|
||||
|
||||
## 5. API接口设计
|
||||
插件需实现:`getName`、`getSupportedMethods`、`getConfigSchema`、`getSupportedProducts`、`init`、`unifiedOrder`、`refund`、`verifyNotify` 等。
|
||||
|
||||
### 5.1 认证接口
|
||||
### 4.3 后端核心模块
|
||||
|
||||
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|
||||
|------|------|-----------|------|------|------|
|
||||
| /adminapi/captcha | GET | AuthController | 获取验证码 | 无 | app/routes/admin.php:20 |
|
||||
| /adminapi/login | POST | AuthController | 用户登录 | 无 | app/routes/admin.php:21 |
|
||||
| 模块 | 主要功能 | 文件位置 |
|
||||
|------|----------|----------|
|
||||
| 认证 | 管理员登录、验证码 | AuthController, AuthService |
|
||||
| 管理员 | 获取管理员信息 | AdminController, AdminService, Admin 模型 |
|
||||
| 菜单 | 获取路由菜单 | MenuController, MenuService |
|
||||
| 系统 | 字典、配置管理 | SystemController, SystemConfigService |
|
||||
| 通道管理 | 通道列表、详情、保存 | ChannelController, PaymentChannelRepository |
|
||||
| 插件管理 | 插件列表、配置 Schema、产品列表 | PluginController, PluginService |
|
||||
| 易支付 | submit.php/mapi.php/api.php | EpayController, EpayService |
|
||||
|
||||
### 5.2 用户接口
|
||||
### 4.4 前端核心模块
|
||||
|
||||
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|
||||
|------|------|-----------|------|------|------|
|
||||
| /adminapi/user/getUserInfo | GET | UserController | 获取用户信息 | JWT | app/routes/admin.php:26 |
|
||||
| 模块 | 主要功能 | 位置 |
|
||||
|------|----------|------|
|
||||
| 布局 | 系统整体布局 | src/layout/ |
|
||||
| 认证 | 登录、权限控制 | src/views/login/ |
|
||||
| 首页 | 数据概览 | src/views/home/ |
|
||||
| 财务管理 | 结算、对账、发票 | src/views/finance/ |
|
||||
| 渠道管理 | 通道配置、支付方式 | src/views/channel/ |
|
||||
| 数据分析 | 交易分析、商户分析 | src/views/analysis/ |
|
||||
| 系统设置 | 系统配置、字典管理 | src/views/system/ |
|
||||
|
||||
### 5.3 菜单接口
|
||||
## 5. API 接口设计
|
||||
|
||||
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|
||||
|------|------|-----------|------|------|------|
|
||||
| /adminapi/menu/getRouters | GET | MenuController | 获取路由菜单 | JWT | app/routes/admin.php:29 |
|
||||
### 5.1 管理后台(/adminapi)
|
||||
|
||||
### 5.4 系统接口
|
||||
| 路径 | 方法 | 控制器 | 功能 | 权限 |
|
||||
|------|------|--------|------|------|
|
||||
| /adminapi/captcha | GET | AuthController | 获取验证码 | 无 |
|
||||
| /adminapi/login | POST | AuthController | 管理员登录 | 无 |
|
||||
| /adminapi/user/getUserInfo | GET | AdminController | 获取管理员信息 | JWT |
|
||||
| /adminapi/menu/getRouters | GET | MenuController | 获取路由菜单 | JWT |
|
||||
| /adminapi/system/getDict[/{code}] | GET | SystemController | 获取字典 | JWT |
|
||||
| /adminapi/system/base-config/tabs | GET | SystemController | 获取配置标签 | JWT |
|
||||
| /adminapi/system/base-config/form/{tabKey} | GET | SystemController | 获取表单配置 | JWT |
|
||||
| /adminapi/system/base-config/submit/{tabKey} | POST | SystemController | 提交配置 | JWT |
|
||||
| /adminapi/channel/list | GET | ChannelController | 通道列表 | JWT |
|
||||
| /adminapi/channel/detail | GET | ChannelController | 通道详情 | JWT |
|
||||
| /adminapi/channel/save | POST | ChannelController | 保存通道 | JWT |
|
||||
| /adminapi/channel/plugins | GET | PluginController | 插件列表 | JWT |
|
||||
| /adminapi/channel/plugin/config-schema | GET | PluginController | 插件配置 Schema | JWT |
|
||||
| /adminapi/channel/plugin/products | GET | PluginController | 插件产品列表 | JWT |
|
||||
|
||||
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|
||||
|------|------|-----------|------|------|------|
|
||||
| /adminapi/system/getDict[/{code}] | GET | SystemController | 获取字典数据 | JWT | app/routes/admin.php:32 |
|
||||
| /adminapi/system/base-config/tabs | GET | SystemController | 获取配置标签 | JWT | app/routes/admin.php:35 |
|
||||
| /adminapi/system/base-config/form/{tabKey} | GET | SystemController | 获取表单配置 | JWT | app/routes/admin.php:36 |
|
||||
| /adminapi/system/base-config/submit/{tabKey} | POST | SystemController | 提交配置 | JWT | app/routes/admin.php:37 |
|
||||
### 5.2 易支付接口(对外 API)
|
||||
|
||||
## 6. 技术特点
|
||||
| 路径 | 方法 | 控制器 | 功能 | 说明 |
|
||||
|------|------|--------|------|------|
|
||||
| /submit.php | ANY | EpayController | 页面跳转支付 | 参数:pid, key, out_trade_no, money, name, type, notify_url 等 |
|
||||
| /mapi.php | POST | EpayController | API 接口支付 | 返回 trade_no、payurl/qrcode/urlscheme |
|
||||
| /api.php | GET | EpayController | 订单查询/退款 | act=order 查询,act=refund 退款 |
|
||||
|
||||
### 6.1 后端特点
|
||||
易支付约定:`pid` 映射为 `app_id`(商户应用标识),`key` 为 `app_secret`。
|
||||
|
||||
1. **高性能架构**:基于 Webman 框架,使用 Workerman 作为底层,支持高并发处理
|
||||
2. **模块化设计**:采用分层架构,清晰分离控制器、服务、仓库和模型
|
||||
3. **JWT认证**:使用 JSON Web Token 实现无状态认证
|
||||
4. **中间件机制**:通过中间件实现请求拦截和权限控制
|
||||
5. **Redis集成**:使用 Redis 作为缓存和队列存储
|
||||
6. **事件系统**:支持事件驱动架构
|
||||
7. **定时任务**:内置定时任务管理功能
|
||||
8. **数据验证**:使用 think-validate 进行数据验证
|
||||
9. **依赖注入**:使用 PHP-DI 实现依赖注入
|
||||
10. **日志管理**:使用 Monolog 进行日志管理
|
||||
## 6. 命名与约定
|
||||
|
||||
### 6.2 前端特点
|
||||
### 6.1 模型与仓储命名
|
||||
|
||||
1. **Vue 3 + TypeScript**:使用最新的 Vue 3 组合式 API 和 TypeScript 提供类型安全
|
||||
2. **Arco Design**:采用字节跳动开源的 Arco Design UI 组件库,提供美观的界面
|
||||
3. **Pinia 状态管理**:使用 Pinia 替代 Vuex,提供更简洁的状态管理方案
|
||||
4. **Vite 构建工具**:使用 Vite 提供快速的开发体验和优化的构建输出
|
||||
5. **国际化支持**:内置多语言支持,可轻松切换语言
|
||||
6. **响应式设计**:适配不同屏幕尺寸的设备
|
||||
7. **丰富的功能组件**:集成多种实用组件,如二维码生成、条码生成、富文本编辑等
|
||||
8. **权限控制**:基于指令的权限控制机制
|
||||
9. **Mock 数据**:内置 Mock 数据,方便开发和测试
|
||||
- 业务语义命名:`PaymentMethod`、`PaymentOrder`、`PaymentChannel` 等,不使用 `ma` 前缀
|
||||
- 表名仍为 `ma_*`,通过模型 `$table` 映射
|
||||
|
||||
### 6.2 订单相关字段
|
||||
|
||||
- 系统订单号:`order_id`
|
||||
- 商户订单号:`mch_order_no`
|
||||
- 商户ID:`merchant_id`
|
||||
- 商户应用ID:`merchant_app_id`
|
||||
- 通道ID:`channel_id`
|
||||
- 支付方式ID:`method_id`(关联 ma_pay_method.id)
|
||||
|
||||
### 6.3 商户应用 api_type
|
||||
|
||||
用于区分不同 API 的验签与通知方式:`openapi`、`epay`、`custom` 等。
|
||||
|
||||
## 7. 开发流程
|
||||
|
||||
### 7.1 后端开发
|
||||
|
||||
1. **环境准备**:PHP 8.1+,Composer,MySQL,Redis
|
||||
2. **依赖安装**:`composer install`
|
||||
3. **配置环境**:复制 `.env.example` 为 `.env` 并配置相关参数
|
||||
4. **启动服务**:`php start.php start`
|
||||
5. **代码结构**:遵循 Webman 框架规范,按模块组织代码
|
||||
1. **环境**:PHP 8.1+,Composer,MySQL,Redis
|
||||
2. **依赖**:`composer install`
|
||||
3. **数据库**:执行 `database/mvp_payment_tables.sql`
|
||||
4. **配置**:复制 `.env.example` 为 `.env`
|
||||
5. **启动**:
|
||||
- Linux:`php start.php start`
|
||||
- Windows:`php windows.php start`
|
||||
|
||||
### 7.2 前端开发
|
||||
|
||||
1. **环境准备**:Node.js 18.12+,PNPM 8.7+
|
||||
2. **依赖安装**:`pnpm install`
|
||||
3. **开发模式**:`pnpm dev`
|
||||
4. **构建部署**:`pnpm build:prod`
|
||||
5. **代码结构**:遵循 Vue 3 项目规范,按功能模块组织代码
|
||||
1. **环境**:Node.js 18.12+,PNPM 8.7+
|
||||
2. **依赖**:`pnpm install`
|
||||
3. **开发**:`pnpm dev`
|
||||
4. **构建**:`pnpm build:prod`
|
||||
|
||||
## 8. 部署与配置
|
||||
## 8. 相关文档
|
||||
|
||||
### 8.1 后端部署
|
||||
|
||||
1. **服务器要求**:Linux/Unix 系统,PHP 8.1+,MySQL 5.7+,Redis 5.0+
|
||||
2. **Nginx 配置**:配置反向代理指向 Webman 服务
|
||||
3. **启动方式**:
|
||||
- 开发环境:`php start.php start`
|
||||
- 生产环境:`php start.php start -d`
|
||||
4. **监控管理**:可使用 Supervisor 管理进程
|
||||
|
||||
### 8.2 前端部署
|
||||
|
||||
1. **构建**:`pnpm build:prod`
|
||||
2. **部署**:将 `dist` 目录部署到 Web 服务器
|
||||
3. **Nginx 配置**:配置静态文件服务和路由重写
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| doc/epay.md | 易支付接口说明 |
|
||||
| doc/payment_flow.md | 支付流程说明 |
|
||||
| doc/payment_system_implementation.md | 支付系统实现说明 |
|
||||
| doc/validation.md | 验证规则说明 |
|
||||
| database/mvp_payment_tables.sql | 支付系统表结构 |
|
||||
|
||||
## 9. 总结
|
||||
|
||||
MPAY V2 项目采用现代化的技术栈和架构设计,后端使用 Webman 框架提供高性能的 API 服务,前端使用 Vue 3 + TypeScript + Arco Design 提供美观、响应式的用户界面。项目结构清晰,模块化程度高,便于维护和扩展。
|
||||
|
||||
核心功能覆盖了支付管理系统的主要业务场景,包括用户认证、菜单管理、系统配置、财务管理、渠道管理和数据分析等模块,为支付业务的运营和管理提供了完整的解决方案。
|
||||
|
||||
技术特点包括高性能架构、模块化设计、JWT认证、Redis集成、Vue 3组合式API、TypeScript类型安全、Arco Design UI组件库、Pinia状态管理、Vite构建工具等,确保了系统的稳定性、安全性和可扩展性。
|
||||
|
||||
该项目适合作为支付管理系统的基础框架,可根据具体业务需求进行定制和扩展。
|
||||
MPAY V2 以支付业务为核心,采用 Webman + Vue 3 技术栈,后端分层清晰(Controller → Service → Repository → Model),支持支付插件扩展与易支付兼容。管理后台基于 JWT 认证,提供通道、插件、系统配置等管理能力;对外提供易支付标准接口(submit/mapi/api),便于第三方商户接入。
|
||||
|
||||
395
doc/validation.md
Normal file
395
doc/validation.md
Normal file
@@ -0,0 +1,395 @@
|
||||
验证器 webman/validation
|
||||
基于 illuminate/validation,提供手动验证、注解验证、参数级验证,以及可复用的规则集。
|
||||
|
||||
安装
|
||||
composer require webman/validation
|
||||
基本概念
|
||||
规则集复用:通过继承 support\validation\Validator 定义可复用的 rules messages attributes scenes,可在手动与注解中复用。
|
||||
方法级注解(Attribute)验证:使用 PHP 8 属性注解 #[Validate] 绑定控制器方法。
|
||||
参数级注解(Attribute)验证:使用 PHP 8 属性注解 #[Param] 绑定控制器方法参数。
|
||||
异常处理:验证失败抛出 support\validation\ValidationException,异常类可通过配置自定义
|
||||
数据库验证:如果涉及数据库验证,需要安装 composer require webman/database
|
||||
手动验证
|
||||
基本用法
|
||||
use support\validation\Validator;
|
||||
|
||||
$data = ['email' => 'user@example.com'];
|
||||
|
||||
Validator::make($data, [
|
||||
'email' => 'required|email',
|
||||
])->validate();
|
||||
提示
|
||||
validate() 校验失败会抛出 support\validation\ValidationException。如果你不希望抛异常,请使用下方的 fails() 写法获取错误信息。
|
||||
|
||||
自定义 messages 与 attributes
|
||||
use support\validation\Validator;
|
||||
|
||||
$data = ['contact' => 'user@example.com'];
|
||||
|
||||
Validator::make(
|
||||
$data,
|
||||
['contact' => 'required|email'],
|
||||
['contact.email' => '邮箱格式不正确'],
|
||||
['contact' => '邮箱']
|
||||
)->validate();
|
||||
不抛异常并获取错误信息
|
||||
如果你不希望抛异常,可以使用 fails() 判断,并通过 errors()(返回 MessageBag)获取错误信息:
|
||||
|
||||
use support\validation\Validator;
|
||||
|
||||
$data = ['email' => 'bad-email'];
|
||||
|
||||
$validator = Validator::make($data, [
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$firstError = $validator->errors()->first(); // string
|
||||
$allErrors = $validator->errors()->all(); // array
|
||||
$errorsByField = $validator->errors()->toArray(); // array
|
||||
// 处理错误...
|
||||
}
|
||||
规则集复用(自定义 Validator)
|
||||
namespace app\validation;
|
||||
|
||||
use support\validation\Validator;
|
||||
|
||||
class UserValidator extends Validator
|
||||
{
|
||||
protected array $rules = [
|
||||
'id' => 'required|integer|min:1',
|
||||
'name' => 'required|string|min:2|max:20',
|
||||
'email' => 'required|email',
|
||||
];
|
||||
|
||||
protected array $messages = [
|
||||
'name.required' => '姓名必填',
|
||||
'email.required' => '邮箱必填',
|
||||
'email.email' => '邮箱格式不正确',
|
||||
];
|
||||
|
||||
protected array $attributes = [
|
||||
'name' => '姓名',
|
||||
'email' => '邮箱',
|
||||
];
|
||||
}
|
||||
手动验证复用
|
||||
use app\validation\UserValidator;
|
||||
|
||||
UserValidator::make($data)->validate();
|
||||
使用 scenes(可选)
|
||||
scenes 是可选能力,只有在你调用 withScene(...) 时,才会按场景只验证部分字段。
|
||||
|
||||
namespace app\validation;
|
||||
|
||||
use support\validation\Validator;
|
||||
|
||||
class UserValidator extends Validator
|
||||
{
|
||||
protected array $rules = [
|
||||
'id' => 'required|integer|min:1',
|
||||
'name' => 'required|string|min:2|max:20',
|
||||
'email' => 'required|email',
|
||||
];
|
||||
|
||||
protected array $scenes = [
|
||||
'create' => ['name', 'email'],
|
||||
'update' => ['id', 'name', 'email'],
|
||||
];
|
||||
}
|
||||
use app\validation\UserValidator;
|
||||
|
||||
// 不指定场景 -> 验证全部规则
|
||||
UserValidator::make($data)->validate();
|
||||
|
||||
// 指定场景 -> 只验证该场景包含的字段
|
||||
UserValidator::make($data)->withScene('create')->validate();
|
||||
注解验证(方法级)
|
||||
直接规则
|
||||
use support\Request;
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class AuthController
|
||||
{
|
||||
#[Validate(
|
||||
rules: [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string|min:6',
|
||||
],
|
||||
messages: [
|
||||
'email.required' => '邮箱必填',
|
||||
'password.required' => '密码必填',
|
||||
],
|
||||
attributes: [
|
||||
'email' => '邮箱',
|
||||
'password' => '密码',
|
||||
]
|
||||
)]
|
||||
public function login(Request $request)
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
复用规则集
|
||||
use app\validation\UserValidator;
|
||||
use support\Request;
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class UserController
|
||||
{
|
||||
#[Validate(validator: UserValidator::class, scene: 'create')]
|
||||
public function create(Request $request)
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
多重验证叠加
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class UserController
|
||||
{
|
||||
#[Validate(rules: ['email' => 'required|email'])]
|
||||
#[Validate(rules: ['token' => 'required|string'])]
|
||||
public function send()
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
验证数据来源
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class UserController
|
||||
{
|
||||
#[Validate(
|
||||
rules: ['email' => 'required|email'],
|
||||
in: ['query', 'body', 'path']
|
||||
)]
|
||||
public function send()
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
通过in参数来指定数据来源,其中:
|
||||
|
||||
query http请求的query参数,取自 $request->get()
|
||||
body http请求的包体,取自 $request->post()
|
||||
path http请求的路径参数,取自 $request->route->param()
|
||||
in可为字符串或数组;为数组时按顺序合并,后者覆盖前者。未传递in时默认等效于 ['query', 'body', 'path']。
|
||||
|
||||
参数级验证(Param)
|
||||
基本用法
|
||||
use support\validation\annotation\Param;
|
||||
|
||||
class MailController
|
||||
{
|
||||
public function send(
|
||||
#[Param(rules: 'required|email')] string $from,
|
||||
#[Param(rules: 'required|email')] string $to,
|
||||
#[Param(rules: 'required|string|min:1|max:500')] string $content
|
||||
) {
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
验证数据来源
|
||||
类似的,参数级也支持in参数指定来源
|
||||
|
||||
use support\validation\annotation\Param;
|
||||
|
||||
class MailController
|
||||
{
|
||||
public function send(
|
||||
#[Param(rules: 'required|email', in: ['body'])] string $from
|
||||
) {
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
rules 支持字符串或数组
|
||||
use support\validation\annotation\Param;
|
||||
|
||||
class MailController
|
||||
{
|
||||
public function send(
|
||||
#[Param(rules: ['required', 'email'])] string $from
|
||||
) {
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
自定义 messages / attribute
|
||||
use support\validation\annotation\Param;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function updateEmail(
|
||||
#[Param(
|
||||
rules: 'required|email',
|
||||
messages: ['email.email' => '邮箱格式不正确'],
|
||||
attribute: '邮箱'
|
||||
)]
|
||||
string $email
|
||||
) {
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
规则常量复用
|
||||
final class ParamRules
|
||||
{
|
||||
public const EMAIL = ['required', 'email'];
|
||||
}
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function send(
|
||||
#[Param(rules: ParamRules::EMAIL)] string $email
|
||||
) {
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
方法级 + 参数级混合
|
||||
use support\Request;
|
||||
use support\validation\annotation\Param;
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class UserController
|
||||
{
|
||||
#[Validate(rules: ['token' => 'required|string'])]
|
||||
public function send(
|
||||
Request $request,
|
||||
#[Param(rules: 'required|email')] string $from,
|
||||
#[Param(rules: 'required|integer')] int $id
|
||||
) {
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
||||
自动规则推导(基于参数签名)
|
||||
当方法上使用 #[Validate],或该方法的任意参数使用了 #[Param] 时,本组件会根据方法参数签名自动推导并补全基础验证规则,再与已有规则合并后执行验证。
|
||||
|
||||
示例:#[Validate] 等价展开
|
||||
1) 只开启 #[Validate],不手写规则:
|
||||
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class DemoController
|
||||
{
|
||||
#[Validate]
|
||||
public function create(string $content, int $uid)
|
||||
{
|
||||
}
|
||||
}
|
||||
等价于:
|
||||
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class DemoController
|
||||
{
|
||||
#[Validate(rules: [
|
||||
'content' => 'required|string',
|
||||
'uid' => 'required|integer',
|
||||
])]
|
||||
public function create(string $content, int $uid)
|
||||
{
|
||||
}
|
||||
}
|
||||
2) 只写了部分规则,其余由参数签名补全:
|
||||
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class DemoController
|
||||
{
|
||||
#[Validate(rules: [
|
||||
'content' => 'min:2',
|
||||
])]
|
||||
public function create(string $content, int $uid)
|
||||
{
|
||||
}
|
||||
}
|
||||
等价于:
|
||||
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class DemoController
|
||||
{
|
||||
#[Validate(rules: [
|
||||
'content' => 'required|string|min:2',
|
||||
'uid' => 'required|integer',
|
||||
])]
|
||||
public function create(string $content, int $uid)
|
||||
{
|
||||
}
|
||||
}
|
||||
3) 默认值/可空类型:
|
||||
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class DemoController
|
||||
{
|
||||
#[Validate]
|
||||
public function create(string $content = '默认值', ?int $uid = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
等价于:
|
||||
|
||||
use support\validation\annotation\Validate;
|
||||
|
||||
class DemoController
|
||||
{
|
||||
#[Validate(rules: [
|
||||
'content' => 'string',
|
||||
'uid' => 'integer|nullable',
|
||||
])]
|
||||
public function create(string $content = '默认值', ?int $uid = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
异常处理
|
||||
默认异常
|
||||
验证失败默认抛出 support\validation\ValidationException,继承 Webman\Exception\BusinessException,不会记录错误日志。
|
||||
|
||||
默认响应行为由 BusinessException::render() 处理:
|
||||
|
||||
普通请求:返回字符串消息,例如 token 为必填项。
|
||||
JSON 请求:返回 JSON 响应,例如 {"code": 422, "msg": "token 为必填项。", "data":....}
|
||||
通过自定义异常修改处理方式
|
||||
全局配置:config/plugin/webman/validation/app.php 的 exception
|
||||
多语言支持
|
||||
组件内置中英文语言包,并支持项目覆盖。加载顺序:
|
||||
|
||||
项目语言包 resource/translations/{locale}/validation.php
|
||||
组件内置 vendor/webman/validation/resources/lang/{locale}/validation.php
|
||||
Illuminate 内置英文(兜底)
|
||||
提示
|
||||
webman默认语言由 config/translation.php 配置,也可以通过函数 locale('en'); 更改。
|
||||
|
||||
本地覆盖示例
|
||||
resource/translations/zh_CN/validation.php
|
||||
|
||||
return [
|
||||
'email' => ':attribute 不是有效的邮件格式。',
|
||||
];
|
||||
中间件自动加载
|
||||
组件安装后会通过 config/plugin/webman/validation/middleware.php 自动加载验证中间件,无需手动注册。
|
||||
|
||||
命令行生成注解
|
||||
使用命令 make:validator 生成验证器类(默认生成到 app/validation 目录)。
|
||||
|
||||
提示
|
||||
需要安装 composer require webman/console
|
||||
|
||||
基础用法
|
||||
生成空模板
|
||||
php webman make:validator UserValidator
|
||||
覆盖已存在文件
|
||||
php webman make:validator UserValidator --force
|
||||
php webman make:validator UserValidator -f
|
||||
从表结构生成规则
|
||||
指定表名生成基础规则(会根据字段类型/可空/长度等推导 $rules;默认排除字段与 ORM 相关:laravel 为 created_at/updated_at/deleted_at,thinkorm 为 create_time/update_time/delete_time)
|
||||
php webman make:validator UserValidator --table=wa_users
|
||||
php webman make:validator UserValidator -t wa_users
|
||||
指定数据库连接(多连接场景)
|
||||
php webman make:validator UserValidator --table=wa_users --database=mysql
|
||||
php webman make:validator UserValidator -t wa_users -d mysql
|
||||
场景(scenes)
|
||||
生成 CRUD 场景:create/update/delete/detail
|
||||
php webman make:validator UserValidator --table=wa_users --scenes=crud
|
||||
php webman make:validator UserValidator -t wa_users -s crud
|
||||
update 场景会包含主键字段(用于定位记录)以及其余字段;delete/detail 默认仅包含主键字段。
|
||||
Reference in New Issue
Block a user