1. 维护代码健壮

2. 更新项目结构文档
This commit is contained in:
技术老胡
2026-04-27 16:20:41 +08:00
parent 9a16a88640
commit 0e5de50337
198 changed files with 21038 additions and 702 deletions

View File

@@ -3,6 +3,7 @@
namespace app\http\admin\controller\merchant;
use app\common\base\BaseController;
use app\http\admin\validation\MerchantApiCredentialValidator;
use app\http\admin\validation\MerchantValidator;
use app\service\merchant\MerchantService;
use support\Request;
@@ -136,8 +137,9 @@ class MerchantController extends BaseController
public function issueCredential(Request $request, string $id): Response
{
$merchantId = (int) $id;
$data = $this->validated($request->all(), MerchantApiCredentialValidator::class, 'issueCredential');
return $this->success($this->merchantService->issueCredential($merchantId));
return $this->success($this->merchantService->issueCredential($merchantId, $data));
}
/**

View File

@@ -0,0 +1,85 @@
<?php
namespace app\http\admin\controller\ops;
use app\common\base\BaseController;
use app\http\admin\validation\MerchantNotifyTaskValidator;
use app\service\ops\log\MerchantNotifyTaskService;
use support\Request;
use support\Response;
/**
* 商户通知任务控制器。
*
* @property MerchantNotifyTaskService $merchantNotifyTaskService 商户通知任务服务
*/
class MerchantNotifyTaskController extends BaseController
{
/**
* 构造方法。
*
* @param MerchantNotifyTaskService $merchantNotifyTaskService 商户通知任务服务
* @return void
*/
public function __construct(
protected MerchantNotifyTaskService $merchantNotifyTaskService
) {
}
/**
* 查询商户通知任务列表。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function index(Request $request): Response
{
$data = $this->validated($request->all(), MerchantNotifyTaskValidator::class, 'index');
return $this->page(
$this->merchantNotifyTaskService->paginate(
$data,
(int) ($data['page'] ?? 1),
(int) ($data['page_size'] ?? 10)
)
);
}
/**
* 查询商户通知任务详情。
*
* @param Request $request 请求对象
* @param string $notifyNo 通知号
* @return Response 响应对象
*/
public function show(Request $request, string $notifyNo): Response
{
$data = $this->validated(['notify_no' => $notifyNo], MerchantNotifyTaskValidator::class, 'show');
$task = $this->merchantNotifyTaskService->findByNotifyNo((string) $data['notify_no']);
if (!$task) {
return $this->fail('商户通知任务不存在', 404);
}
return $this->success($task);
}
/**
* 手动重试商户通知任务。
*
* @param Request $request 请求对象
* @param string $notifyNo 通知号
* @return Response 响应对象
*/
public function retry(Request $request, string $notifyNo): Response
{
$data = $this->validated(['notify_no' => $notifyNo], MerchantNotifyTaskValidator::class, 'retry');
$task = $this->merchantNotifyTaskService->retry((string) $data['notify_no']);
if (!$task) {
return $this->fail('商户通知任务不存在', 404);
}
return $this->success($task, '商户通知任务已执行重试');
}
}

View File

@@ -86,6 +86,24 @@ class AuthController extends BaseController
(string) $this->requestAttribute($request, 'auth.admin_username', '')
));
}
/**
* 修改当前登录管理员密码。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function changePassword(Request $request): Response
{
$adminId = $this->currentAdminId($request);
if ($adminId <= 0) {
return $this->fail('未获取到当前管理员信息', 401);
}
$data = $this->validated($request->all(), AuthValidator::class, 'changePassword');
return $this->success($this->adminUserService->changePassword($adminId, $data));
}
}

View File

@@ -11,7 +11,7 @@ use support\Response;
/**
* 支付订单管理控制器。
*
* 当前提供列表查询,后续可以继续扩展支付单详情、关闭、重试等管理操作
* 当前提供列表查询和详情查看,便于后台直接排查支付链路
*
* @property PayOrderService $payOrderService 支付订单服务
*/
@@ -42,6 +42,24 @@ class PayOrderController extends BaseController
return $this->success($this->payOrderService->paginate($data, $page, $pageSize));
}
/**
* 查询支付订单详情。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function show(Request $request, string $payNo): Response
{
$this->validated(
array_merge($request->all(), ['pay_no' => $payNo]),
PayOrderValidator::class,
'show'
);
return $this->success($this->payOrderService->detail($payNo));
}
}

View File

@@ -2,6 +2,7 @@
namespace app\http\admin\middleware;
use app\exception\UnauthorizedException;
use app\service\system\access\AdminAuthService;
use support\Context;
use Webman\Http\Request;
@@ -34,6 +35,7 @@ class AdminAuthMiddleware implements MiddlewareInterface
* @param Request $request 请求对象
* @param callable $handler handler
* @return Response 响应对象
* @throws UnauthorizedException
*/
public function process(Request $request, callable $handler): Response
{
@@ -42,11 +44,7 @@ class AdminAuthMiddleware implements MiddlewareInterface
if ($token === '') {
if ((int) env('AUTH_MIDDLEWARE_STRICT', 1) === 1) {
return json([
'code' => 401,
'msg' => 'admin unauthorized',
'data' => null,
]);
throw new UnauthorizedException('管理员未授权');
}
} else {
$admin = $this->adminAuthService->authenticateToken(
@@ -55,11 +53,7 @@ class AdminAuthMiddleware implements MiddlewareInterface
$request->header('user-agent', '')
);
if (!$admin) {
return json([
'code' => 401,
'msg' => 'admin unauthorized',
'data' => null,
]);
throw new UnauthorizedException('管理员未授权');
}
Context::set('auth.admin_id', (int) $admin->id);

View File

@@ -19,6 +19,8 @@ class AuthValidator extends Validator
protected array $rules = [
'username' => 'required|string|min:1|max:32',
'password' => 'required|string|min:6|max:100',
'current_password' => 'required|string|min:6|max:100',
'password_confirm' => 'required|string|min:6|max:100|same:password',
];
/**
@@ -29,6 +31,8 @@ class AuthValidator extends Validator
protected array $attributes = [
'username' => '用户名',
'password' => '密码',
'current_password' => '旧密码',
'password_confirm' => '确认密码',
];
/**
@@ -38,6 +42,7 @@ class AuthValidator extends Validator
*/
protected array $scenes = [
'login' => ['username', 'password'],
'changePassword' => ['current_password', 'password', 'password_confirm'],
];
}

View File

@@ -18,8 +18,11 @@ class MerchantApiCredentialValidator extends Validator
'id' => 'sometimes|integer|min:1',
'keyword' => 'sometimes|string|max:128',
'merchant_id' => 'sometimes|integer|min:1|exists:ma_merchant,id',
'sign_type' => 'sometimes|integer|in:0',
'sign_type' => 'sometimes|integer|in:0,1',
'rotate_v1' => 'sometimes|integer|in:0,1',
'rotate_v2' => 'sometimes|integer|in:0,1',
'api_key' => 'nullable|string|max:128',
'merchant_public_key' => 'nullable|string|max:65535',
'status' => 'sometimes|integer|in:0,1',
'page' => 'sometimes|integer|min:1',
'page_size' => 'sometimes|integer|min:1|max:100',
@@ -35,7 +38,10 @@ class MerchantApiCredentialValidator extends Validator
'keyword' => '关键词',
'merchant_id' => '所属商户',
'sign_type' => '签名类型',
'rotate_v1' => '是否重置 V1',
'rotate_v2' => '是否重置 V2',
'api_key' => '接口凭证值',
'merchant_public_key' => '商户公钥',
'status' => '接口凭证状态',
'page' => '页码',
'page_size' => '每页条数',
@@ -48,10 +54,11 @@ class MerchantApiCredentialValidator extends Validator
*/
protected array $scenes = [
'index' => ['keyword', 'merchant_id', 'status', 'page', 'page_size'],
'store' => ['merchant_id', 'sign_type', 'api_key', 'status'],
'update' => ['id', 'sign_type', 'api_key', 'status'],
'store' => ['merchant_id', 'sign_type', 'api_key', 'merchant_public_key', 'status'],
'update' => ['id', 'sign_type', 'api_key', 'merchant_public_key', 'status'],
'show' => ['id'],
'destroy' => ['id'],
'issueCredential' => ['rotate_v1', 'rotate_v2', 'sign_type', 'status'],
];
/**
@@ -63,7 +70,8 @@ class MerchantApiCredentialValidator extends Validator
{
return $this->appendRules([
'merchant_id' => 'required|integer|min:1|exists:ma_merchant,id',
'sign_type' => 'required|integer|in:0',
'sign_type' => 'required|integer|in:0,1',
'merchant_public_key' => 'nullable|string|max:65535',
'status' => 'required|integer|in:0,1',
]);
}
@@ -77,8 +85,25 @@ class MerchantApiCredentialValidator extends Validator
{
return $this->appendRules([
'id' => 'required|integer|min:1',
'sign_type' => 'required|integer|in:0',
'status' => 'required|integer|in:0,1',
'sign_type' => 'sometimes|integer|in:0,1',
'api_key' => 'nullable|string|max:128',
'merchant_public_key' => 'nullable|string|max:65535',
'status' => 'sometimes|integer|in:0,1',
]);
}
/**
* 配置生成接口凭证场景规则。
*
* @return static 校验器实例
*/
public function sceneIssueCredential(): static
{
return $this->appendRules([
'rotate_v1' => 'sometimes|integer|in:0,1',
'rotate_v2' => 'sometimes|integer|in:0,1',
'sign_type' => 'sometimes|integer|in:0,1',
'status' => 'sometimes|integer|in:0,1',
]);
}

View File

@@ -0,0 +1,72 @@
<?php
namespace app\http\admin\validation;
use support\validation\Validator;
/**
* 商户通知任务参数校验器。
*/
class MerchantNotifyTaskValidator extends Validator
{
/**
* 校验规则。
*
* @var array
*/
protected array $rules = [
'notify_no' => 'sometimes|string|max:64',
'keyword' => 'sometimes|string|max:128',
'merchant_id' => 'sometimes|integer|min:1',
'status' => 'sometimes|integer|in:0,1,2',
'page' => 'sometimes|integer|min:1',
'page_size' => 'sometimes|integer|min:1|max:100',
];
/**
* 字段别名。
*
* @var array
*/
protected array $attributes = [
'notify_no' => '通知号',
'keyword' => '关键词',
'merchant_id' => '所属商户',
'status' => '任务状态',
'page' => '页码',
'page_size' => '每页条数',
];
/**
* 校验场景。
*
* @var array
*/
protected array $scenes = [
'index' => ['keyword', 'merchant_id', 'status', 'page', 'page_size'],
'show' => ['notify_no'],
'retry' => ['notify_no'],
];
/**
* 配置详情场景规则。
*
* @return static
*/
public function sceneShow(): static
{
return $this->appendRules([
'notify_no' => 'required|string|max:64',
]);
}
/**
* 配置重试场景规则。
*
* @return static
*/
public function sceneRetry(): static
{
return $this->sceneShow();
}
}

View File

@@ -17,6 +17,7 @@ class PayOrderValidator extends Validator
* @var array
*/
protected array $rules = [
'pay_no' => 'sometimes|string|max:32',
'keyword' => 'sometimes|string|max:128',
'merchant_id' => 'sometimes|integer|min:1',
'pay_type_id' => 'sometimes|integer|min:1',
@@ -33,6 +34,7 @@ class PayOrderValidator extends Validator
* @var array
*/
protected array $attributes = [
'pay_no' => '支付单号',
'keyword' => '关键字',
'merchant_id' => '商户ID',
'pay_type_id' => '支付方式',
@@ -50,6 +52,6 @@ class PayOrderValidator extends Validator
*/
protected array $scenes = [
'index' => ['keyword', 'merchant_id', 'pay_type_id', 'status', 'channel_mode', 'callback_status', 'page', 'page_size'],
'show' => ['pay_no'],
];
}

View File

@@ -19,6 +19,7 @@ class PaymentPluginConfValidator extends Validator
protected array $rules = [
'id' => 'sometimes|integer|min:1',
'keyword' => 'sometimes|string|max:128',
'merchant_id' => 'sometimes|integer|min:0',
'plugin_code' => 'sometimes|string|alpha_dash|min:2|max:32',
'config' => 'nullable|array',
'settlement_cycle_type' => 'sometimes|integer|in:0,1,2,3,4',
@@ -37,6 +38,7 @@ class PaymentPluginConfValidator extends Validator
protected array $attributes = [
'id' => '配置ID',
'keyword' => '关键字',
'merchant_id' => '所属商户',
'plugin_code' => '插件编码',
'config' => '插件配置',
'settlement_cycle_type' => '结算周期',
@@ -53,9 +55,9 @@ class PaymentPluginConfValidator extends Validator
* @var array
*/
protected array $scenes = [
'index' => ['keyword', 'plugin_code', 'page', 'page_size'],
'store' => ['plugin_code', 'config', 'settlement_cycle_type', 'settlement_cutoff_time', 'remark'],
'update' => ['id', 'plugin_code', 'config', 'settlement_cycle_type', 'settlement_cutoff_time', 'remark'],
'index' => ['keyword', 'merchant_id', 'plugin_code', 'page', 'page_size'],
'store' => ['merchant_id', 'plugin_code', 'config', 'settlement_cycle_type', 'settlement_cutoff_time', 'remark'],
'update' => ['id', 'merchant_id', 'plugin_code', 'config', 'settlement_cycle_type', 'settlement_cutoff_time', 'remark'],
'show' => ['id'],
'destroy' => ['id'],
'options' => ['plugin_code'],
@@ -87,4 +89,3 @@ class PaymentPluginConfValidator extends Validator
}
}

View File

@@ -19,6 +19,7 @@ class PaymentPluginValidator extends Validator
protected array $rules = [
'code' => 'sometimes|string|alpha_dash|min:2|max:32',
'status' => 'sometimes|integer|in:0,1',
'allow_merchant' => 'sometimes|integer|in:0,1',
'remark' => 'nullable|string|max:500',
'keyword' => 'sometimes|string|max:128',
'name' => 'sometimes|string|max:50',
@@ -37,6 +38,7 @@ class PaymentPluginValidator extends Validator
'code' => '插件编码',
'name' => '插件名称',
'status' => '插件状态',
'allow_merchant' => '商户端可用',
'remark' => '备注',
'keyword' => '关键字',
'page' => '页码',
@@ -52,10 +54,9 @@ class PaymentPluginValidator extends Validator
*/
protected array $scenes = [
'index' => ['keyword', 'code', 'name', 'status', 'page', 'page_size'],
'update' => ['code', 'status', 'remark'],
'update' => ['code', 'status', 'allow_merchant', 'remark'],
'updateStatus' => ['code', 'status'],
'show' => ['code'],
'selectOptions' => ['keyword', 'page', 'page_size', 'pay_type_code', 'ids'],
];
}

View File

@@ -0,0 +1,67 @@
<?php
namespace app\http\api\controller\cashier;
use app\common\base\BaseController;
use app\http\api\validation\CashierValidator;
use app\service\payment\cashier\CashierService;
use support\Request;
use support\Response;
/**
* 收银台控制器。
*
* 提供收银台上下文查询和支付确认入口。
*/
class CashierController extends BaseController
{
public function __construct(
protected CashierService $cashierService
) {
}
/**
* 查询收银台上下文。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function context(Request $request): Response
{
$payload = $this->validated($request->all(), CashierValidator::class, 'context');
return $this->success(
$this->cashierService->context((string) ($payload['biz_no'] ?? ''))
);
}
/**
* 确认收银台支付方式。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function confirm(Request $request): Response
{
$payload = $this->validated($request->all(), CashierValidator::class, 'confirm');
return $this->success(
$this->cashierService->confirm($payload, $request)
);
}
/**
* 查询支付页详情。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function payOrder(Request $request): Response
{
$payload = $this->validated($request->all(), CashierValidator::class, 'pay_order');
return $this->success(
$this->cashierService->payOrderDetail((string) ($payload['pay_no'] ?? ''))
);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace app\http\api\controller\epay;
use app\common\base\BaseController;
use app\service\payment\epay\EpayV1ProtocolService;
use app\http\api\validation\EpayV1Validator;
use support\Request;
use support\Response;
/**
* ePay V1 控制器。
*
* 负责承接旧版页面跳转、API 支付与旧接口兼容查询。
*/
class EpayV1Controller extends BaseController
{
/**
* 构造方法。
*
* @param EpayV1ProtocolService $epayV1ProtocolService V1 协议服务
*/
public function __construct(
protected EpayV1ProtocolService $epayV1ProtocolService
) {
}
/**
* 页面跳转支付入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function submit(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV1Validator::class, 'submit');
return $this->epayV1ProtocolService->submit($payload, $request);
}
/**
* API 支付入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function mapi(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV1Validator::class, 'mapi');
return json($this->epayV1ProtocolService->mapi($payload, $request));
}
/**
* 旧版兼容 API 入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function api(Request $request): Response
{
$payload = $request->all();
$scene = $this->resolveApiScene((string) ($payload['act'] ?? ''));
if ($scene === null) {
return json(['code' => 0, 'msg' => '不支持的操作类型']);
}
$payload = $this->validated($payload, EpayV1Validator::class, $scene);
return json($this->epayV1ProtocolService->api($payload));
}
/**
* 映射旧版 `act` 到验证场景。
*
* @param string $act 接口动作
* @return string|null 验证场景
*/
private function resolveApiScene(string $act): ?string
{
return match (strtolower(trim($act))) {
'query' => 'api_query',
'settle' => 'api_settle',
'order' => 'api_order',
'orders' => 'api_orders',
'refund' => 'api_refund',
default => null,
};
}
}

View File

@@ -0,0 +1,173 @@
<?php
namespace app\http\api\controller\epay;
use app\common\base\BaseController;
use app\http\api\validation\EpayV2Validator;
use app\service\payment\epay\EpayV2ProtocolService;
use app\service\payment\order\PayOrderService;
use support\Request;
use support\Response;
/**
* ePay V2 控制器。
*
* 负责承接新版支付、查询、退款、商户与转账接口。
*/
class EpayV2Controller extends BaseController
{
/**
* 构造方法。
*
* @param EpayV2ProtocolService $epayV2ProtocolService V2 协议服务
*/
public function __construct(
protected EpayV2ProtocolService $epayV2ProtocolService,
protected PayOrderService $payOrderService
) {
}
/**
* 页面跳转支付入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function submit(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'submit');
return $this->epayV2ProtocolService->submit($payload, $request);
}
/**
* API 下单入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function create(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'create');
return json($this->epayV2ProtocolService->create($payload, $request));
}
/**
* 支付单查询入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function query(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'query');
return json($this->epayV2ProtocolService->query($payload));
}
/**
* 退款发起入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function refund(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'refund');
return json($this->epayV2ProtocolService->refund($payload));
}
/**
* 退款查询入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function refundQuery(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'refund_query');
return json($this->epayV2ProtocolService->refundQuery($payload));
}
/**
* 关闭订单入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function close(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'close');
return json($this->epayV2ProtocolService->close($payload));
}
/**
* 商户信息查询入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function merchantInfo(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'merchant_info');
return json($this->epayV2ProtocolService->merchantInfo($payload));
}
/**
* 商户订单列表入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function merchantOrders(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'merchant_orders');
return json($this->epayV2ProtocolService->merchantOrders($payload));
}
/**
* 转账发起入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function transferSubmit(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'transfer_submit');
return json($this->epayV2ProtocolService->transferSubmit($payload));
}
/**
* 转账查询入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function transferQuery(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'transfer_query');
return json($this->epayV2ProtocolService->transferQuery($payload));
}
/**
* 转账余额查询入口。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function transferBalance(Request $request): Response
{
$payload = $this->validated($request->all(), EpayV2Validator::class, 'transfer_balance');
return json($this->epayV2ProtocolService->transferBalance($payload));
}
/**
* 渠道回调入口。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return string|Response
*/
public function callback(Request $request, string $payNo): string|Response
{
return $this->payOrderService->handlePluginCallback($payNo, $request);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 收银台请求验证器。
*
* 定义收银台上下文查询与确认支付场景规则。
*/
class CashierValidator extends Validator
{
protected array $rules = [
'biz_no' => 'required|string|max:32',
'pay_no' => 'required|string|max:32',
'type' => 'nullable|string|max:32',
];
protected array $attributes = [
'biz_no' => '业务单号',
'pay_no' => '支付单号',
'type' => '支付方式',
];
protected array $scenes = [
'context' => ['biz_no'],
'confirm' => ['biz_no', 'type'],
'pay_order' => ['pay_no'],
];
/**
* 收银台上下文场景。
*
* @return static
*/
public function sceneContext(): static
{
return $this->appendRules([
'biz_no' => 'required|string|max:32',
]);
}
/**
* 收银台确认场景。
*
* @return static
*/
public function sceneConfirm(): static
{
return $this->appendRules([
'biz_no' => 'required|string|max:32',
'type' => 'required|string|max:32',
]);
}
/**
* 支付页详情场景。
*
* @return static
*/
public function scenePayOrder(): static
{
return $this->appendRules([
'pay_no' => 'required|string|max:32',
]);
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* ePay V1 请求验证器。
*
* 定义旧版提交、查询、退款与兼容接口的场景规则。
*/
class EpayV1Validator extends Validator
{
protected array $rules = [
'act' => 'sometimes|string|in:query,settle,order,orders,refund',
'pid' => 'required|integer|min:1',
'key' => 'nullable|string|max:128',
'type' => 'nullable|string|max:32',
'trade_no' => 'nullable|string|max:64',
'out_trade_no' => 'nullable|string|max:64',
'notify_url' => 'nullable|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'nullable|string|max:255',
'money' => 'nullable|regex:/^\d+(?:\.\d{1,2})?$/',
'param' => 'nullable',
'clientip' => 'nullable|ip',
'device' => 'nullable|string|in:pc,mobile,qq,wechat,alipay,jump',
'sign' => 'nullable|string|max:255',
'sign_type' => 'sometimes|string|in:MD5',
'page' => 'sometimes|integer|min:1',
'limit' => 'sometimes|integer|min:1|max:50',
];
protected array $attributes = [
'act' => '操作类型',
'pid' => '商户ID',
'key' => '商户密钥',
'type' => '支付方式',
'trade_no' => '平台订单号',
'out_trade_no' => '商户订单号',
'notify_url' => '异步通知地址',
'return_url' => '跳转通知地址',
'name' => '商品名称',
'money' => '商品金额',
'param' => '业务扩展参数',
'clientip' => '用户 IP',
'device' => '设备类型',
'sign' => '签名字符串',
'sign_type' => '签名类型',
'page' => '页码',
'limit' => '数量',
];
protected array $scenes = [
'submit' => ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'param', 'sign', 'sign_type'],
'mapi' => ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'param', 'clientip', 'device', 'sign', 'sign_type'],
'api_query' => ['act', 'pid', 'key'],
'api_settle' => ['act', 'pid', 'key'],
'api_order' => ['act', 'pid', 'key', 'trade_no', 'out_trade_no'],
'api_orders' => ['act', 'pid', 'key', 'page', 'limit'],
'api_refund' => ['act', 'pid', 'key', 'trade_no', 'out_trade_no', 'money'],
];
/**
* 页面跳转支付场景。
*
* @return static
*/
public function sceneSubmit(): static
{
return $this->appendRules([
'out_trade_no' => 'required|string|max:64',
'notify_url' => 'required|string|max:255',
'return_url' => 'required|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'sign_type' => 'required|string|in:MD5',
'sign' => 'required|string|max:255',
]);
}
/**
* API 支付场景。
*
* @return static
*/
public function sceneMapi(): static
{
return $this->appendRules([
'type' => 'required|string|max:32',
'out_trade_no' => 'required|string|max:64',
'notify_url' => 'required|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'clientip' => 'required|ip',
'sign_type' => 'required|string|in:MD5',
'sign' => 'required|string|max:255',
]);
}
/**
* 商户信息查询场景。
*
* @return static
*/
public function sceneApiQuery(): static
{
return $this->appendRules([
'key' => 'required|string|max:128',
]);
}
/**
* 结算记录查询场景。
*
* @return static
*/
public function sceneApiSettle(): static
{
return $this->appendRules([
'key' => 'required|string|max:128',
]);
}
/**
* 单个订单查询场景。
*
* @return static
*/
public function sceneApiOrder(): static
{
return $this->appendRules([
'key' => 'required|string|max:128',
'trade_no' => 'nullable|string|max:64|required_without:out_trade_no',
'out_trade_no' => 'nullable|string|max:64|required_without:trade_no',
]);
}
/**
* 订单列表查询场景。
*
* @return static
*/
public function sceneApiOrders(): static
{
return $this->appendRules([
'key' => 'required|string|max:128',
'page' => 'sometimes|integer|min:1',
'limit' => 'sometimes|integer|min:1|max:50',
]);
}
/**
* 退款申请场景。
*
* @return static
*/
public function sceneApiRefund(): static
{
return $this->appendRules([
'key' => 'required|string|max:128',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'trade_no' => 'nullable|string|max:64|required_without:out_trade_no',
'out_trade_no' => 'nullable|string|max:64|required_without:trade_no',
]);
}
}

View File

@@ -0,0 +1,216 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* ePay V2 请求验证器。
*
* 定义新版支付、退款、商户与转账接口的场景规则。
*/
class EpayV2Validator extends Validator
{
protected array $rules = [
'pid' => 'required|integer|min:1',
'timestamp' => 'required|integer|min:1',
// 兼容旧版 SDK 里使用的 `RSA` 简写,同时内部统一按 SHA256WithRSA 验签。
'sign_type' => 'required|string|in:SHA256WithRSA,RSA',
// RSA 签名是 Base64 文本,长度会明显超过 MD5不能沿用 255 的短限制。
'sign' => 'required|string|max:2048',
'type' => 'nullable|string|max:32',
'method' => 'nullable|string|in:web,jump,jsapi,app,scan,applet',
'trade_no' => 'nullable|string|max:64',
'out_trade_no' => 'nullable|string|max:64',
'notify_url' => 'nullable|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'nullable|string|max:255',
'money' => 'nullable|regex:/^\d+(?:\.\d{1,2})?$/',
'param' => 'nullable',
'auth_code' => 'nullable|string|max:128',
'sub_openid' => 'nullable|string|max:128',
'sub_appid' => 'nullable|string|max:64',
'clientip' => 'nullable|ip',
'device' => 'nullable|string|in:pc,mobile,qq,wechat,alipay',
'channel_id' => 'nullable|integer|min:0',
'offset' => 'sometimes|integer|min:0',
'limit' => 'sometimes|integer|min:1|max:50',
'status' => 'nullable|integer|min:0|max:5',
'refund_no' => 'nullable|string|max:64',
'out_refund_no' => 'nullable|string|max:64',
'biz_no' => 'nullable|string|max:32',
'out_biz_no' => 'nullable|string|max:64',
'account' => 'nullable|string|max:100',
'bookid' => 'nullable|string|max:64',
'remark' => 'nullable|string|max:255',
];
protected array $attributes = [
'pid' => '商户ID',
'timestamp' => '时间戳',
'sign_type' => '签名类型',
'sign' => '签名字符串',
'type' => '支付方式',
'method' => '接口类型',
'trade_no' => '平台订单号',
'out_trade_no' => '商户订单号',
'notify_url' => '异步通知地址',
'return_url' => '跳转通知地址',
'name' => '商品名称',
'money' => '商品金额',
'param' => '业务扩展参数',
'auth_code' => '授权码',
'sub_openid' => '子用户 OPENID',
'sub_appid' => '子应用 APPID',
'clientip' => '用户 IP',
'device' => '设备类型',
'channel_id' => '渠道ID',
'offset' => '偏移量',
'limit' => '数量',
'status' => '状态',
'refund_no' => '退款单号',
'out_refund_no' => '商户退款单号',
'biz_no' => '平台业务号',
'out_biz_no' => '商户转账单号',
'account' => '收款账号',
'bookid' => '书签ID',
'remark' => '备注',
];
protected array $scenes = [
'submit' => ['pid', 'timestamp', 'sign_type', 'sign', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'param', 'channel_id'],
'create' => ['pid', 'timestamp', 'sign_type', 'sign', 'type', 'method', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'param', 'auth_code', 'sub_openid', 'sub_appid', 'clientip', 'device', 'channel_id'],
'query' => ['pid', 'timestamp', 'sign_type', 'sign', 'trade_no', 'out_trade_no'],
'refund' => ['pid', 'timestamp', 'sign_type', 'sign', 'trade_no', 'out_trade_no', 'money', 'out_refund_no'],
'refund_query' => ['pid', 'timestamp', 'sign_type', 'sign', 'refund_no', 'out_refund_no'],
'close' => ['pid', 'timestamp', 'sign_type', 'sign', 'trade_no', 'out_trade_no'],
'merchant_info' => ['pid', 'timestamp', 'sign_type', 'sign'],
'merchant_orders' => ['pid', 'timestamp', 'sign_type', 'sign', 'offset', 'limit', 'status'],
'transfer_submit' => ['pid', 'timestamp', 'sign_type', 'sign', 'type', 'account', 'name', 'money', 'out_biz_no', 'remark', 'bookid'],
'transfer_query' => ['pid', 'timestamp', 'sign_type', 'sign', 'biz_no', 'out_biz_no'],
'transfer_balance' => ['pid', 'timestamp', 'sign_type', 'sign'],
];
/**
* 页面跳转支付场景。
*
* @return static
*/
public function sceneSubmit(): static
{
return $this->appendRules([
'type' => 'nullable|string|max:32',
'out_trade_no' => 'required|string|max:64',
'notify_url' => 'required|string|max:255',
'return_url' => 'required|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'sign_type' => 'required|string|in:SHA256WithRSA,RSA',
'sign' => 'required|string|max:2048',
]);
}
/**
* API 下单场景。
*
* @return static
*/
public function sceneCreate(): static
{
return $this->appendRules([
'type' => 'required|string|max:32',
'method' => 'required|string|in:web,jump,jsapi,app,scan,applet',
'out_trade_no' => 'required|string|max:64',
'notify_url' => 'required|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'device' => 'nullable|string|in:pc,mobile,qq,wechat,alipay',
'sign_type' => 'required|string|in:SHA256WithRSA,RSA',
'sign' => 'required|string|max:2048',
]);
}
/**
* 退款发起场景。
*
* @return static
*/
public function sceneRefund(): static
{
return $this->appendRules([
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'trade_no' => 'nullable|string|max:64|required_without:out_trade_no',
'out_trade_no' => 'nullable|string|max:64|required_without:trade_no',
'out_refund_no' => 'nullable|string|max:64',
'sign_type' => 'required|string|in:SHA256WithRSA,RSA',
'sign' => 'required|string|max:2048',
]);
}
/**
* 支付单查询场景。
*
* @return static
*/
public function sceneQuery(): static
{
return $this->appendRules([
'trade_no' => 'nullable|string|max:64|required_without:out_trade_no',
'out_trade_no' => 'nullable|string|max:64|required_without:trade_no',
]);
}
/**
* 关闭订单场景。
*
* @return static
*/
public function sceneClose(): static
{
return $this->sceneQuery();
}
/**
* 退款查询场景。
*
* @return static
*/
public function sceneRefundQuery(): static
{
return $this->appendRules([
'refund_no' => 'nullable|string|max:64|required_without:out_refund_no',
'out_refund_no' => 'nullable|string|max:64|required_without:refund_no',
]);
}
/**
* 转账查询场景。
*
* @return static
*/
public function sceneTransferQuery(): static
{
return $this->appendRules([
'biz_no' => 'nullable|string|max:32|required_without:out_biz_no',
'out_biz_no' => 'nullable|string|max:64|required_without:biz_no',
]);
}
/**
* 转账发起场景。
*
* @return static
*/
public function sceneTransferSubmit(): static
{
return $this->appendRules([
'type' => 'required|string|in:alipay,wxpay,qqpay,bank',
'account' => 'required|string|max:100',
'name' => 'required|string|max:100',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'sign_type' => 'required|string|in:SHA256WithRSA,RSA',
'sign' => 'required|string|max:2048',
]);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace app\http\mer\controller\file;
use app\common\base\BaseController;
use app\exception\ValidationException;
use app\http\admin\validation\FileRecordValidator;
use app\service\file\FileRecordService;
use support\Request;
use support\Response;
use Webman\Http\UploadFile;
/**
* 商户端文件控制器。
*
* 供插件配置动态表单中的上传字段使用。
*
* @property FileRecordService $fileRecordService 文件记录服务
*/
class FileRecordController extends BaseController
{
/**
* 构造方法。
*
* @param FileRecordService $fileRecordService 文件记录服务
* @return void
*/
public function __construct(
protected FileRecordService $fileRecordService
) {
}
/**
* 上传文件记录。
*
* @param Request $request 请求对象
* @return Response 响应对象
* @throws ValidationException
*/
public function upload(Request $request): Response
{
$data = $this->validated(array_merge($this->payload($request), ['scene' => $request->input('scene')]), FileRecordValidator::class, 'store');
$uploadedFile = $request->file('file');
if ($uploadedFile === null) {
throw new ValidationException('请先选择上传文件');
}
$createdBy = $this->currentMerchantId($request);
$createdByName = $this->currentMerchantNo($request);
if (is_array($uploadedFile)) {
$items = [];
foreach ($uploadedFile as $file) {
if ($file instanceof UploadFile) {
$items[] = $this->fileRecordService->upload($file, $data, $createdBy, $createdByName);
}
}
if ($items === []) {
throw new ValidationException('上传文件无效');
}
return $this->success([
'list' => $items,
'total' => count($items),
]);
}
if (!$uploadedFile instanceof UploadFile) {
throw new ValidationException('上传文件无效');
}
return $this->success($this->fileRecordService->upload($uploadedFile, $data, $createdBy, $createdByName));
}
/**
* 获取文件预览响应。
*
* @param Request $request 请求对象
* @param string $id 文件记录ID
* @return Response 响应对象
*/
public function preview(Request $request, string $id): Response
{
$data = $this->validated(['id' => (int) $id], FileRecordValidator::class, 'preview');
return $this->fileRecordService->previewResponse((int) $data['id']);
}
/**
* 获取文件下载响应。
*
* @param Request $request 请求对象
* @param string $id 文件记录ID
* @return Response 响应对象
*/
public function download(Request $request, string $id): Response
{
$data = $this->validated(['id' => (int) $id], FileRecordValidator::class, 'download');
return $this->fileRecordService->downloadResponse((int) $data['id']);
}
}

View File

@@ -100,6 +100,138 @@ class MerchantPortalController extends BaseController
return $this->success($this->merchantPortalService->myChannels($payload, $merchantId, $page, $pageSize));
}
public function channelCreateMeta(Request $request): Response
{
return $this->success($this->merchantPortalService->channelCreateMeta());
}
public function createChannel(Request $request): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$data = $this->validated($this->payload($request), MerchantPortalValidator::class, 'channelStore');
return $this->success($this->merchantPortalService->createChannel($merchantId, $data));
}
public function updateChannel(Request $request, string $id): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$data = $this->validated(
array_merge($this->payload($request), ['id' => (int) $id]),
MerchantPortalValidator::class,
'channelUpdate'
);
$channel = $this->merchantPortalService->updateChannel($merchantId, (int) $data['id'], $data);
if (!$channel) {
return $this->fail('通道不存在', 404);
}
return $this->success($channel);
}
public function deleteChannel(Request $request, string $id): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$data = $this->validated(['id' => (int) $id], MerchantPortalValidator::class, 'channelDestroy');
if (!$this->merchantPortalService->deleteChannel($merchantId, (int) $data['id'])) {
return $this->fail('通道不存在', 404);
}
return $this->success(true);
}
public function pluginConfigs(Request $request): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$payload = $this->validated($this->payload($request), MerchantPortalValidator::class, 'pluginConfigIndex');
$page = max(1, (int) ($payload['page'] ?? 1));
$pageSize = max(1, (int) ($payload['page_size'] ?? 10));
return $this->success($this->merchantPortalService->pluginConfigs($payload, $merchantId, $page, $pageSize));
}
public function createPluginConfig(Request $request): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$data = $this->validated($this->payload($request), MerchantPortalValidator::class, 'pluginConfigStore');
return $this->success($this->merchantPortalService->createPluginConfig($merchantId, $data));
}
public function updatePluginConfig(Request $request, string $id): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$data = $this->validated(
array_merge($this->payload($request), ['id' => (int) $id]),
MerchantPortalValidator::class,
'pluginConfigUpdate'
);
$config = $this->merchantPortalService->updatePluginConfig($merchantId, (int) $data['id'], $data);
if (!$config) {
return $this->fail('插件配置不存在', 404);
}
return $this->success($config);
}
public function deletePluginConfig(Request $request, string $id): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
$data = $this->validated(['id' => (int) $id], MerchantPortalValidator::class, 'pluginConfigDestroy');
if (!$this->merchantPortalService->deletePluginConfig($merchantId, (int) $data['id'])) {
return $this->fail('插件配置不存在', 404);
}
return $this->success(true);
}
public function pluginConfigOptions(Request $request): Response
{
$merchantId = $this->currentMerchantId($request);
if ($merchantId <= 0) {
return $this->fail('未获取到当前商户信息', 401);
}
return $this->success([
'configs' => $this->merchantPortalService->pluginConfigOptions($merchantId, (string) $request->get('plugin_code', '')),
]);
}
public function pluginSchema(Request $request, string $code): Response
{
return $this->success($this->merchantPortalService->pluginSchema($code));
}
/**
* 获取路由解析结果。
*
@@ -113,7 +245,12 @@ class MerchantPortalController extends BaseController
return $this->fail('未获取到当前商户信息', 401);
}
$payload = $this->validated($this->payload($request), MerchantPortalValidator::class, 'routePreview');
$rawPayload = $this->payload($request);
if (empty($rawPayload['pay_type_id']) || empty($rawPayload['pay_amount'])) {
return $this->success($this->merchantPortalService->routePreview($merchantId, 0, 0));
}
$payload = $this->validated($rawPayload, MerchantPortalValidator::class, 'routePreview');
$payTypeId = (int) ($payload['pay_type_id'] ?? 0);
$payAmount = (int) ($payload['pay_amount'] ?? 0);
$statDate = trim((string) ($payload['stat_date'] ?? ''));
@@ -150,7 +287,9 @@ class MerchantPortalController extends BaseController
return $this->fail('未获取到当前商户信息', 401);
}
return $this->success($this->merchantPortalService->issueCredential($merchantId));
$data = $this->validated($this->payload($request), MerchantPortalValidator::class, 'issueCredential');
return $this->success($this->merchantPortalService->issueCredential($merchantId, $data));
}
/**

View File

@@ -2,6 +2,7 @@
namespace app\http\mer\middleware;
use app\exception\UnauthorizedException;
use app\service\merchant\auth\MerchantAuthService;
use support\Context;
use Webman\Http\Request;
@@ -34,6 +35,7 @@ class MerchantAuthMiddleware implements MiddlewareInterface
* @param Request $request 请求对象
* @param callable $handler handler
* @return Response 响应对象
* @throws UnauthorizedException
*/
public function process(Request $request, callable $handler): Response
{
@@ -42,11 +44,7 @@ class MerchantAuthMiddleware implements MiddlewareInterface
if ($token === '') {
if ((int) env('AUTH_MIDDLEWARE_STRICT', 1) === 1) {
return json([
'code' => 401,
'msg' => 'merchant unauthorized',
'data' => null,
]);
throw new UnauthorizedException('商户未授权');
}
} else {
$result = $this->merchantAuthService->authenticateToken(
@@ -55,11 +53,7 @@ class MerchantAuthMiddleware implements MiddlewareInterface
$request->header('user-agent', '')
);
if (!$result) {
return json([
'code' => 401,
'msg' => 'merchant unauthorized',
'data' => null,
]);
throw new UnauthorizedException('商户未授权');
}
Context::set('auth.merchant_id', (int) $result['merchant']->id);

View File

@@ -29,6 +29,26 @@ class MerchantPortalValidator extends Validator
'pay_type_id' => 'required|integer|min:1',
'pay_amount' => 'required|integer|min:1',
'stat_date' => 'sometimes|date',
'id' => 'sometimes|integer|min:1',
'keyword' => 'sometimes|string|max:128',
'plugin_code' => 'sometimes|string|alpha_dash|min:2|max:32',
'config' => 'nullable|array',
'settlement_cycle_type' => 'sometimes|integer|in:0,1,2,3,4',
'settlement_cutoff_time' => 'nullable|date_format:H:i:s',
'name' => 'sometimes|string|min:2|max:128',
'api_config_id' => 'sometimes|integer|min:1',
'daily_limit_amount' => 'nullable|integer|min:0',
'daily_limit_count' => 'nullable|integer|min:0',
'min_amount' => 'nullable|integer|min:0',
'max_amount' => 'nullable|integer|min:0',
'remark' => 'nullable|string|max:500',
'status' => 'sometimes|integer|in:0,1',
'rotate_v1' => 'sometimes|integer|in:0,1',
'rotate_v2' => 'sometimes|integer|in:0,1',
'sign_type' => 'sometimes|integer|in:0,1',
'sort_no' => 'nullable|integer|min:0',
'page' => 'sometimes|integer|min:1',
'page_size' => 'sometimes|integer|min:1|max:100',
];
/**
@@ -51,6 +71,26 @@ class MerchantPortalValidator extends Validator
'pay_type_id' => '支付方式',
'pay_amount' => '支付金额',
'stat_date' => '统计日期',
'id' => '记录ID',
'keyword' => '关键字',
'plugin_code' => '支付插件',
'config' => '插件配置',
'settlement_cycle_type' => '结算周期',
'settlement_cutoff_time' => '结算截止时间',
'name' => '通道名称',
'api_config_id' => '插件配置',
'daily_limit_amount' => '单日限额',
'daily_limit_count' => '单日限笔',
'min_amount' => '单笔最小金额',
'max_amount' => '单笔最大金额',
'remark' => '备注',
'status' => '状态',
'rotate_v1' => 'V1 凭证',
'rotate_v2' => 'V2 凭证',
'sign_type' => '签名类型',
'sort_no' => '排序',
'page' => '页码',
'page_size' => '每页条数',
];
/**
@@ -71,7 +111,47 @@ class MerchantPortalValidator extends Validator
],
'passwordUpdate' => ['current_password', 'password', 'password_confirm'],
'routePreview' => ['pay_type_id', 'pay_amount', 'stat_date'],
'pluginConfigIndex' => ['keyword', 'plugin_code', 'page', 'page_size'],
'pluginConfigStore' => ['plugin_code', 'config', 'settlement_cycle_type', 'settlement_cutoff_time', 'remark'],
'pluginConfigUpdate' => ['id', 'plugin_code', 'config', 'settlement_cycle_type', 'settlement_cutoff_time', 'remark'],
'pluginConfigDestroy' => ['id'],
'channelStore' => ['name', 'pay_type_id', 'plugin_code', 'api_config_id', 'daily_limit_amount', 'daily_limit_count', 'min_amount', 'max_amount', 'remark', 'status', 'sort_no'],
'channelUpdate' => ['id', 'name', 'pay_type_id', 'plugin_code', 'api_config_id', 'daily_limit_amount', 'daily_limit_count', 'min_amount', 'max_amount', 'remark', 'status', 'sort_no'],
'channelDestroy' => ['id'],
'issueCredential' => ['rotate_v1', 'rotate_v2', 'sign_type', 'status'],
];
public function rules(): array
{
$rules = parent::rules();
return match ($this->scene()) {
'pluginConfigStore' => array_merge($rules, [
'plugin_code' => 'required|string|alpha_dash|min:2|max:32',
]),
'pluginConfigUpdate' => array_merge($rules, [
'id' => 'required|integer|min:1',
'plugin_code' => 'required|string|alpha_dash|min:2|max:32',
]),
'pluginConfigDestroy', 'channelDestroy' => array_merge($rules, [
'id' => 'required|integer|min:1',
]),
'channelStore' => array_merge($rules, [
'name' => 'required|string|min:2|max:128',
'pay_type_id' => 'required|integer|min:1',
'plugin_code' => 'required|string|alpha_dash|min:2|max:32',
'api_config_id' => 'required|integer|min:1',
'status' => 'required|integer|in:0,1',
]),
'channelUpdate' => array_merge($rules, [
'id' => 'required|integer|min:1',
'name' => 'required|string|min:2|max:128',
'pay_type_id' => 'required|integer|min:1',
'plugin_code' => 'required|string|alpha_dash|min:2|max:32',
'api_config_id' => 'required|integer|min:1',
'status' => 'required|integer|in:0,1',
]),
default => $rules,
};
}
}