重构初始化

This commit is contained in:
技术老胡
2026-04-15 11:45:46 +08:00
parent 72d72d735b
commit 7612026773
381 changed files with 28287 additions and 14717 deletions

View File

@@ -1,98 +0,0 @@
<?php
namespace app\http\api\controller;
use app\common\base\BaseController;
use app\services\api\EpayProtocolService;
use support\Request;
use support\Response;
/**
* 易支付控制器
*/
class EpayController extends BaseController
{
public function __construct(
protected EpayProtocolService $epayProtocolService
) {
}
/**
* 页面跳转支付
*/
public function submit(Request $request)
{
try {
$result = $this->epayProtocolService->handleSubmit($request);
$type = $result['response_type'] ?? '';
if ($type === 'redirect' && !empty($result['url'])) {
return redirect($result['url']);
}
if ($type === 'form_html') {
return response((string)($result['html'] ?? ''))
->withHeaders(['Content-Type' => 'text/html; charset=UTF-8']);
}
if ($type === 'form_params') {
return $this->renderForm((array)($result['form'] ?? []));
}
return $this->fail('支付参数生成失败');
} catch (\Throwable $e) {
return $this->fail($e->getMessage());
}
}
/**
* API接口支付
*/
public function mapi(Request $request)
{
try {
return json($this->epayProtocolService->handleMapi($request));
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
}
}
/**
* API接口
*/
public function api(Request $request)
{
try {
return json($this->epayProtocolService->handleApi($request));
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
}
}
/**
* 渲染表单提交 HTML用于页面跳转支付
*/
private function renderForm(array $formParams): Response
{
$html = '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>跳转支付</title></head><body>';
$html .= '<form id="payForm" method="' . htmlspecialchars($formParams['method'] ?? 'POST') . '" action="' . htmlspecialchars($formParams['action'] ?? '') . '">';
if (isset($formParams['fields']) && is_array($formParams['fields'])) {
foreach ($formParams['fields'] as $name => $value) {
$html .= '<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars((string)$value) . '">';
}
}
$html .= '</form>';
$html .= '<script>document.getElementById("payForm").submit();</script>';
$html .= '</body></html>';
return response($html)->withHeaders(['Content-Type' => 'text/html; charset=UTF-8']);
}
}

View File

@@ -1,74 +0,0 @@
<?php
namespace app\http\api\controller;
use app\common\base\BaseController;
use app\services\PayNotifyService;
use app\services\PluginService;
use support\Request;
use support\Response;
/**
* 支付控制器OpenAPI
*/
class PayController extends BaseController
{
public function __construct(
protected PayNotifyService $payNotifyService,
protected PluginService $pluginService
) {
}
/**
* 创建订单
*/
public function create(Request $request)
{
return $this->fail('not implemented', 501);
}
/**
* 查询订单
*/
public function query(Request $request)
{
return $this->fail('not implemented', 501);
}
/**
* 关闭订单
*/
public function close(Request $request)
{
return $this->fail('not implemented', 501);
}
/**
* 订单退款
*/
public function refund(Request $request)
{
return $this->fail('not implemented', 501);
}
/**
* 异步通知
*/
public function notify(Request $request, string $pluginCode)
{
try {
$plugin = $this->pluginService->getPluginInstance($pluginCode);
$result = $this->payNotifyService->handleNotify($pluginCode, $request);
$ackSuccess = method_exists($plugin, 'notifySuccess') ? $plugin->notifySuccess() : 'success';
$ackFail = method_exists($plugin, 'notifyFail') ? $plugin->notifyFail() : 'fail';
if (!($result['ok'] ?? false)) {
return $ackFail instanceof Response ? $ackFail : response((string)$ackFail);
}
return $ackSuccess instanceof Response ? $ackSuccess : response((string)$ackSuccess);
} catch (\Throwable $e) {
return response('fail');
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace app\http\api\controller\adapter;
use app\common\base\BaseController;
use app\exception\ValidationException;
use app\http\api\validation\EpayValidator;
use app\service\payment\compat\EpayCompatService;
use support\Request;
use support\Response;
/**
* Epay 协议兼容控制器。
*
* 负责 submit.php、mapi.php 和 api.php 的入口场景校验与结果分发。
*/
class EpayController extends BaseController
{
/**
* 构造函数,注入兼容服务。
*/
public function __construct(
protected EpayCompatService $epayCompatService
) {}
/**
* 页面跳转支付入口。
*/
public function submit(Request $request): Response
{
try {
$payload = $this->validated($request->all(), EpayValidator::class, 'submit');
return $this->epayCompatService->submit($payload, $request);
} catch (ValidationException $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage() ?: '提交失败',
]);
}
}
/**
* API 接口支付入口。
*/
public function mapi(Request $request): Response
{
try {
$payload = $this->validated($request->all(), EpayValidator::class, 'mapi');
return json($this->epayCompatService->mapi($payload, $request));
} catch (ValidationException $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage() ?: '提交失败',
]);
}
}
/**
* 标准 API 接口入口。
*/
public function api(Request $request): Response
{
try {
$payload = $request->all();
$act = strtolower(trim((string) ($payload['act'] ?? '')));
$scene = match ($act) {
'settle' => 'settle',
'orders' => 'orders',
'order' => trim((string) ($payload['trade_no'] ?? '')) !== '' ? 'order_trade_no' : 'order_out_trade_no',
'refund' => trim((string) ($payload['trade_no'] ?? '')) !== '' ? 'refund_trade_no' : 'refund_out_trade_no',
default => 'query',
};
$payload = $this->validated($payload, EpayValidator::class, $scene);
return json($this->epayCompatService->api($payload));
} catch (ValidationException $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage() ?: '请求失败',
]);
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace app\http\api\controller\notify;
use app\common\base\BaseController;
use app\http\api\validation\NotifyChannelValidator;
use app\http\api\validation\NotifyMerchantValidator;
use app\service\payment\runtime\NotifyService;
use support\Request;
use support\Response;
/**
* 通知与回调记录控制器。
*
* 负责渠道通知日志和商户通知任务的接入。
*/
class NotifyController extends BaseController
{
/**
* 构造函数,注入通知服务。
*/
public function __construct(
protected NotifyService $notifyService
) {
}
/**
* POST /api/notify/channel
*
* 记录渠道通知日志。
*/
public function channel(Request $request): Response
{
$data = $this->validated($request->all(), NotifyChannelValidator::class, 'store');
return $this->success($this->notifyService->recordChannelNotify($data));
}
/**
* POST /api/notify/merchant
*
* 创建商户通知任务。
*/
public function merchant(Request $request): Response
{
$data = $this->validated($request->all(), NotifyMerchantValidator::class, 'store');
return $this->success($this->notifyService->enqueueMerchantNotify($data));
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace app\http\api\controller\route;
use app\common\base\BaseController;
use app\http\api\validation\RouteResolveValidator;
use app\service\payment\runtime\PaymentRouteService;
use support\Request;
use support\Response;
/**
* 路由预览控制器。
*
* 用于返回指定商户分组、支付方式和金额条件下的路由解析结果。
*/
class RouteController extends BaseController
{
/**
* 构造函数,注入路由服务。
*/
public function __construct(
protected PaymentRouteService $paymentRouteService
) {
}
/**
* GET /api/routes/resolve
*
* 解析支付路由。
*/
public function resolve(Request $request): Response
{
$data = $this->validated($request->all(), RouteResolveValidator::class, 'resolve');
return $this->success($this->paymentRouteService->resolveByMerchantGroup(
(int) $data['merchant_group_id'],
(int) $data['pay_type_id'],
(int) $data['pay_amount'],
$data
));
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace app\http\api\controller\settlement;
use app\common\base\BaseController;
use app\exception\ResourceNotFoundException;
use app\http\api\validation\SettlementActionValidator;
use app\http\api\validation\SettlementCreateValidator;
use app\service\payment\settlement\SettlementService;
use support\Request;
use support\Response;
/**
* 清算接口控制器。
*
* 负责清算单创建、查询和清算状态推进。
*/
class SettlementController extends BaseController
{
/**
* 构造函数,注入清算相关依赖。
*/
public function __construct(
protected SettlementService $settlementService,
) {
}
/**
* 创建清结算单。
*/
public function create(Request $request): Response
{
$data = $this->validated($request->all(), SettlementCreateValidator::class, 'store');
$items = (array) ($data['items'] ?? []);
return $this->success($this->settlementService->createSettlementOrder($data, $items));
}
/**
* 查询清结算单详情。
*/
public function show(Request $request, string $settleNo): Response
{
try {
return $this->success($this->settlementService->detail($settleNo));
} catch (ResourceNotFoundException) {
return $this->fail('清算单不存在', 404);
}
}
/**
* 标记清结算成功。
*/
public function complete(Request $request, string $settleNo): Response
{
$this->validated(
array_merge($request->all(), ['settle_no' => $settleNo]),
SettlementActionValidator::class,
'complete'
);
return $this->success($this->settlementService->completeSettlement($settleNo));
}
/**
* 标记清结算失败。
*/
public function failSettlement(Request $request, string $settleNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['settle_no' => $settleNo]),
SettlementActionValidator::class,
'fail'
);
return $this->success($this->settlementService->failSettlement($settleNo, (string) ($data['reason'] ?? '')));
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace app\http\api\controller\trace;
use app\common\base\BaseController;
use app\http\api\validation\TraceQueryValidator;
use app\service\payment\trace\TradeTraceService;
use support\Request;
use support\Response;
/**
* 统一追踪查询控制器。
*/
class TraceController extends BaseController
{
public function __construct(
protected TradeTraceService $tradeTraceService
) {
}
/**
* 查询指定追踪号对应的完整交易链路。
*/
public function show(Request $request, string $traceNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['trace_no' => $traceNo]),
TraceQueryValidator::class,
'show'
);
$result = $this->tradeTraceService->queryByTraceNo((string) $data['trace_no']);
if (empty($result)) {
return $this->fail('追踪单不存在', 404);
}
return $this->success($result);
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace app\http\api\controller\trade;
use app\common\base\BaseController;
use app\exception\ResourceNotFoundException;
use app\http\api\validation\PayCallbackValidator;
use app\http\api\validation\PayCloseValidator;
use app\http\api\validation\PayPrepareValidator;
use app\http\api\validation\PayTimeoutValidator;
use app\service\merchant\security\MerchantApiCredentialService;
use app\service\payment\config\PaymentTypeService;
use app\service\payment\order\PayOrderService;
use support\Request;
use support\Response;
/**
* 支付接口控制器。
*
* 负责支付预下单、支付查询、支付关闭和渠道回调入口。
*/
class PayController extends BaseController
{
/**
* 构造函数,注入支付单相关依赖。
*/
public function __construct(
protected PayOrderService $payOrderService,
protected MerchantApiCredentialService $merchantApiCredentialService,
protected PaymentTypeService $paymentTypeService
) {
}
/**
* 支付预下单。
*/
public function prepare(Request $request): Response
{
$data = $this->validated(
$this->normalizePreparePayload($request, $request->all()),
PayPrepareValidator::class,
'prepare'
);
return $this->success($this->payOrderService->preparePayAttempt($data));
}
/**
* 查询支付单详情。
*/
public function show(Request $request, string $payNo): Response
{
try {
return $this->success($this->payOrderService->detail($payNo));
} catch (ResourceNotFoundException) {
return $this->fail('支付单不存在', 404);
}
}
/**
* 关闭支付单。
*/
public function close(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['pay_no' => $payNo]),
PayCloseValidator::class,
'close'
);
return $this->success($this->payOrderService->closePayOrder($payNo, $data));
}
/**
* 标记支付单超时。
*/
public function timeout(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['pay_no' => $payNo]),
PayTimeoutValidator::class,
'timeout'
);
return $this->success($this->payOrderService->timeoutPayOrder($payNo, $data));
}
/**
* 处理渠道回调。
*/
public function callback(Request $request, string $payNo = ''): Response|string
{
if ($payNo !== '') {
return $this->payOrderService->handlePluginCallback($payNo, $request);
}
$data = $this->validated($request->all(), PayCallbackValidator::class, 'callback');
return $this->success($this->payOrderService->handleChannelCallback($data));
}
/**
* 归一化外部支付下单参数并完成签名校验。
*
* 这层逻辑保留在控制器内,避免中间件承担业务验签职责。
*/
private function normalizePreparePayload(Request $request, array $payload): array
{
$this->merchantApiCredentialService->verifyMd5Sign($payload);
$typeCode = trim((string) ($payload['type'] ?? ''));
$paymentType = $this->paymentTypeService->resolveEnabledType($typeCode);
$typeCode = (string) $paymentType->code;
$money = (string) ($payload['money'] ?? '0');
$amount = (int) round(((float) $money) * 100);
return [
'merchant_id' => (int) ($payload['pid'] ?? 0),
'merchant_order_no' => (string) ($payload['out_trade_no'] ?? ''),
'pay_type_id' => (int) $paymentType->id,
'pay_amount' => $amount,
'subject' => (string) ($payload['name'] ?? ''),
'body' => (string) ($payload['name'] ?? ''),
'ext_json' => [
'type_code' => $typeCode,
'notify_url' => (string) ($payload['notify_url'] ?? ''),
'return_url' => (string) ($payload['return_url'] ?? ''),
'param' => $payload['param'] ?? null,
'clientip' => (string) ($payload['clientip'] ?? ''),
'device' => (string) ($payload['device'] ?? ''),
'sign_type' => (string) ($payload['sign_type'] ?? 'MD5'),
'channel_callback_base_url' => (string) sys_config('site_url') . '/api/pay',
],
];
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace app\http\api\controller\trade;
use app\common\base\BaseController;
use app\exception\ResourceNotFoundException;
use app\http\api\validation\RefundActionValidator;
use app\http\api\validation\RefundCreateValidator;
use app\service\payment\order\RefundService;
use support\Request;
use support\Response;
/**
* 退款接口控制器。
*
* 负责退款单创建与退款单查询。
*/
class RefundController extends BaseController
{
/**
* 构造函数,注入退款相关依赖。
*/
public function __construct(
protected RefundService $refundService,
) {
}
/**
* 创建退款单。
*/
public function create(Request $request): Response
{
$data = $this->validated($request->all(), RefundCreateValidator::class, 'store');
return $this->success($this->refundService->createRefund($data));
}
/**
* 查询退款单详情。
*/
public function show(Request $request, string $refundNo): Response
{
try {
return $this->success($this->refundService->detail($refundNo));
} catch (ResourceNotFoundException) {
return $this->fail('退款单不存在', 404);
}
}
/**
* 标记退款处理中。
*/
public function processing(Request $request, string $refundNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['refund_no' => $refundNo]),
RefundActionValidator::class,
'processing'
);
return $this->success($this->refundService->markRefundProcessing($refundNo, $data));
}
/**
* 退款重试。
*/
public function retry(Request $request, string $refundNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['refund_no' => $refundNo]),
RefundActionValidator::class,
'retry'
);
return $this->success($this->refundService->retryRefund($refundNo, $data));
}
/**
* 标记退款失败。
*/
public function markFail(Request $request, string $refundNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['refund_no' => $refundNo]),
RefundActionValidator::class,
'fail'
);
return $this->success($this->refundService->markRefundFailed($refundNo, $data));
}
}

View File

@@ -1,69 +0,0 @@
<?php
namespace app\http\api\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Request;
use Webman\Http\Response;
use app\exceptions\UnauthorizedException;
use app\repositories\MerchantAppRepository;
/**
* OpenAPI 签名认证中间件
*
* 验证 AppId + 签名
*/
class EpayAuthMiddleware implements MiddlewareInterface
{
protected MerchantAppRepository $merchantAppRepository;
public function __construct()
{
// 延迟加载,避免循环依赖
$this->merchantAppRepository = new MerchantAppRepository();
}
public function process(Request $request, callable $handler): Response
{
$appId = $request->header('X-App-Id', '') ?: ($request->post('app_id', '') ?: $request->get('app_id', ''));
$timestamp = $request->header('X-Timestamp', '') ?: ($request->post('timestamp', '') ?: $request->get('timestamp', ''));
$nonce = $request->header('X-Nonce', '') ?: ($request->post('nonce', '') ?: $request->get('nonce', ''));
$signature = $request->header('X-Signature', '') ?: ($request->post('signature', '') ?: $request->get('signature', ''));
if (empty($appId) || empty($timestamp) || empty($nonce) || empty($signature)) {
throw new UnauthorizedException('缺少认证参数');
}
// 验证时间戳5分钟内有效
if (abs(time() - (int)$timestamp) > 300) {
throw new UnauthorizedException('请求已过期');
}
// 查询应用
$app = $this->merchantAppRepository->findByAppId($appId);
if (!$app) {
throw new UnauthorizedException('应用不存在或已禁用');
}
// 验证签名
$method = $request->method();
$path = $request->path();
$body = $request->rawBody();
$bodySha256 = hash('sha256', $body);
$signString = "app_id={$appId}&timestamp={$timestamp}&nonce={$nonce}&method={$method}&path={$path}&body_sha256={$bodySha256}";
$expectedSignature = hash_hmac('sha256', $signString, $app->app_secret);
if (!hash_equals($expectedSignature, $signature)) {
throw new UnauthorizedException('签名验证失败');
}
// 将应用信息注入到请求对象
$request->app = $app;
$request->merchantId = $app->merchant_id;
$request->appId = $app->id;
return $handler($request);
}
}

View File

@@ -1,69 +0,0 @@
<?php
namespace app\http\api\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Request;
use Webman\Http\Response;
use app\exceptions\UnauthorizedException;
use app\repositories\MerchantAppRepository;
/**
* OpenAPI 签名认证中间件
*
* 验证 AppId + 签名
*/
class OpenApiAuthMiddleware implements MiddlewareInterface
{
protected MerchantAppRepository $merchantAppRepository;
public function __construct()
{
// 延迟加载,避免循环依赖
$this->merchantAppRepository = new MerchantAppRepository();
}
public function process(Request $request, callable $handler): Response
{
$appId = $request->header('X-App-Id', '') ?: ($request->post('app_id', '') ?: $request->get('app_id', ''));
$timestamp = $request->header('X-Timestamp', '') ?: ($request->post('timestamp', '') ?: $request->get('timestamp', ''));
$nonce = $request->header('X-Nonce', '') ?: ($request->post('nonce', '') ?: $request->get('nonce', ''));
$signature = $request->header('X-Signature', '') ?: ($request->post('signature', '') ?: $request->get('signature', ''));
if (empty($appId) || empty($timestamp) || empty($nonce) || empty($signature)) {
throw new UnauthorizedException('缺少认证参数');
}
// 验证时间戳5分钟内有效
if (abs(time() - (int)$timestamp) > 300) {
throw new UnauthorizedException('请求已过期');
}
// 查询应用
$app = $this->merchantAppRepository->findByAppId($appId);
if (!$app) {
throw new UnauthorizedException('应用不存在或已禁用');
}
// 验证签名
$method = $request->method();
$path = $request->path();
$body = $request->rawBody();
$bodySha256 = hash('sha256', $body);
$signString = "app_id={$appId}&timestamp={$timestamp}&nonce={$nonce}&method={$method}&path={$path}&body_sha256={$bodySha256}";
$expectedSignature = hash_hmac('sha256', $signString, $app->app_secret);
if (!hash_equals($expectedSignature, $signature)) {
throw new UnauthorizedException('签名验证失败');
}
// 将应用信息注入到请求对象
$request->app = $app;
$request->merchantId = $app->merchant_id;
$request->appId = $app->id;
return $handler($request);
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* ePay 兼容层请求校验器。
*
* 根据 submit、mapi、api.php 的不同入口场景做分场景校验,不依赖隐藏标记字段。
*/
class EpayValidator extends Validator
{
protected array $rules = [
'act' => 'required|string|in:query,settle,order,orders,refund',
'pid' => 'required|integer|gt:0',
'key' => 'required|string|min:1|max:255',
'type' => 'sometimes|string|max:32',
'out_trade_no' => 'sometimes|string|min:1|max:64',
'notify_url' => 'sometimes|url|max:255',
'return_url' => 'sometimes|url|max:255',
'name' => 'sometimes|string|min:1|max:255',
'money' => 'sometimes|numeric|gt:0|regex:/^\d+(?:\.\d{1,2})?$/',
'sign' => 'sometimes|string|min:1|max:255',
'sign_type' => 'sometimes|string|in:MD5,md5',
'device' => 'sometimes|string|in:pc,mobile,qq,wechat,alipay,jump',
'clientip' => 'sometimes|ip',
'param' => 'sometimes|string|max:2000',
'trade_no' => 'sometimes|string|min:1|max:64',
'refund_no' => 'sometimes|string|min:1|max:64',
'reason' => 'sometimes|string|max:255',
'limit' => 'sometimes|integer|gt:0|max:50',
'page' => 'sometimes|integer|gt:0',
];
protected array $attributes = [
'act' => '操作类型',
'pid' => '商户ID',
'key' => '商户密钥',
'type' => '支付方式',
'out_trade_no' => '商户订单号',
'trade_no' => '易支付订单号',
'notify_url' => '异步通知地址',
'return_url' => '跳转通知地址',
'name' => '商品名称',
'money' => '商品金额',
'sign' => '签名字符串',
'sign_type' => '签名类型',
'device' => '设备类型',
'clientip' => '用户IP地址',
'param' => '业务扩展参数',
'refund_no' => '退款单号',
'reason' => '退款原因',
'limit' => '查询订单数量',
'page' => '页码',
];
protected array $scenes = [
'submit' => ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'sign', 'sign_type', 'param'],
'mapi' => ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'clientip', 'device', 'sign', 'sign_type', 'param'],
'query' => ['act', 'pid', 'key'],
'settle' => ['act', 'pid', 'key'],
'order_trade_no' => ['act', 'pid', 'key', 'trade_no'],
'order_out_trade_no' => ['act', 'pid', 'key', 'out_trade_no'],
'orders' => ['act', 'pid', 'key', 'limit', 'page'],
'refund_trade_no' => ['act', 'pid', 'key', 'trade_no', 'money', 'refund_no', 'reason'],
'refund_out_trade_no' => ['act', 'pid', 'key', 'out_trade_no', 'money', 'refund_no', 'reason'],
];
public function rules(): array
{
$rules = parent::rules();
return match ($this->scene()) {
'submit' => array_merge($rules, [
'type' => 'sometimes|string|max:32',
'out_trade_no' => 'required|string|min:1|max:64',
'notify_url' => 'required|url|max:255',
'return_url' => 'required|url|max:255',
'name' => 'required|string|min:1|max:255',
'money' => 'required|numeric|gt:0|regex:/^\d+(?:\.\d{1,2})?$/',
'sign' => 'required|string|min:1|max:255',
'sign_type' => 'required|string|in:MD5,md5',
]),
'mapi' => array_merge($rules, [
'type' => 'required|string|max:32',
'out_trade_no' => 'required|string|min:1|max:64',
'notify_url' => 'required|url|max:255',
'return_url' => 'sometimes|url|max:255',
'name' => 'required|string|min:1|max:255',
'money' => 'required|numeric|gt:0|regex:/^\d+(?:\.\d{1,2})?$/',
'clientip' => 'required|ip',
'sign' => 'required|string|min:1|max:255',
'sign_type' => 'required|string|in:MD5,md5',
]),
'order_trade_no' => array_merge($rules, [
'trade_no' => 'required|string|min:1|max:64',
]),
'order_out_trade_no' => array_merge($rules, [
'out_trade_no' => 'required|string|min:1|max:64',
]),
'refund_trade_no' => array_merge($rules, [
'trade_no' => 'required|string|min:1|max:64',
'money' => 'required|numeric|gt:0|regex:/^\d+(?:\.\d{1,2})?$/',
]),
'refund_out_trade_no' => array_merge($rules, [
'out_trade_no' => 'required|string|min:1|max:64',
'money' => 'required|numeric|gt:0|regex:/^\d+(?:\.\d{1,2})?$/',
]),
default => $rules,
};
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 渠道通知参数校验器。
*
* 用于校验渠道通知日志入参。
*/
class NotifyChannelValidator extends Validator
{
protected array $rules = [
'notify_no' => 'sometimes|string|min:1|max:64',
'channel_id' => 'required|integer|min:1|exists:ma_payment_channel,id',
'notify_type' => 'sometimes|integer|in:0,1',
'biz_no' => 'required|string|min:1|max:64',
'pay_no' => 'sometimes|string|min:1|max:64',
'channel_request_no' => 'sometimes|string|min:1|max:64',
'channel_trade_no' => 'sometimes|string|min:1|max:64',
'raw_payload' => 'nullable|array',
'verify_status' => 'sometimes|integer|in:0,1,2',
'process_status' => 'sometimes|integer|in:0,1,2',
'retry_count' => 'sometimes|integer|min:0',
'next_retry_at' => 'nullable|date_format:Y-m-d H:i:s',
'last_error' => 'nullable|string|max:255',
];
protected array $attributes = [
'notify_no' => '通知单号',
'channel_id' => '通道ID',
'notify_type' => '通知类型',
'biz_no' => '业务单号',
'pay_no' => '支付单号',
'channel_request_no' => '渠道请求号',
'channel_trade_no' => '渠道交易号',
'raw_payload' => '原始通知数据',
'verify_status' => '验签状态',
'process_status' => '处理状态',
'retry_count' => '重试次数',
'next_retry_at' => '下次重试时间',
'last_error' => '最后错误',
];
protected array $scenes = [
'store' => ['notify_no', 'channel_id', 'notify_type', 'biz_no', 'pay_no', 'channel_request_no', 'channel_trade_no', 'raw_payload', 'verify_status', 'process_status', 'retry_count', 'next_retry_at', 'last_error'],
];
}

View File

@@ -0,0 +1,47 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 商户通知参数校验器。
*
* 用于校验商户通知任务入参。
*/
class NotifyMerchantValidator extends Validator
{
protected array $rules = [
'notify_no' => 'sometimes|string|min:1|max:64',
'merchant_id' => 'required|integer|min:1|exists:ma_merchant,id',
'merchant_group_id' => 'required|integer|min:1|exists:ma_merchant_group,id',
'biz_no' => 'required|string|min:1|max:64',
'pay_no' => 'sometimes|string|min:1|max:64',
'notify_url' => 'required|url|max:255',
'notify_data' => 'nullable|array',
'status' => 'sometimes|integer|min:0',
'retry_count' => 'sometimes|integer|min:0',
'next_retry_at' => 'nullable|date_format:Y-m-d H:i:s',
'last_notify_at' => 'nullable|date_format:Y-m-d H:i:s',
'last_response' => 'nullable|string|max:255',
];
protected array $attributes = [
'notify_no' => '通知单号',
'merchant_id' => '商户ID',
'merchant_group_id' => '商户分组ID',
'biz_no' => '业务单号',
'pay_no' => '支付单号',
'notify_url' => '通知地址',
'notify_data' => '通知内容',
'status' => '状态',
'retry_count' => '重试次数',
'next_retry_at' => '下次重试时间',
'last_notify_at' => '最后通知时间',
'last_response' => '最后响应',
];
protected array $scenes = [
'store' => ['notify_no', 'merchant_id', 'merchant_group_id', 'biz_no', 'pay_no', 'notify_url', 'notify_data', 'status', 'retry_count', 'next_retry_at', 'last_notify_at', 'last_response'],
];
}

View File

@@ -0,0 +1,53 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 支付回调参数校验器。
*
* 用于校验渠道回调和主动查单回传参数。
*/
class PayCallbackValidator extends Validator
{
protected array $rules = [
'pay_no' => 'required|string|min:1|max:64|exists:ma_pay_order,pay_no',
'success' => 'required|boolean',
'channel_id' => 'nullable|integer|min:1|exists:ma_payment_channel,id',
'callback_type' => 'nullable|integer|in:0,1',
'request_data' => 'nullable|array',
'verify_status' => 'nullable|integer|in:0,1,2',
'process_status' => 'nullable|integer|in:0,1,2',
'process_result' => 'nullable|array',
'channel_trade_no' => 'nullable|string|max:64',
'channel_order_no' => 'nullable|string|max:64',
'fee_actual_amount' => 'nullable|integer|min:0',
'paid_at' => 'nullable|date_format:Y-m-d H:i:s',
'channel_error_code' => 'nullable|string|max:64',
'channel_error_msg' => 'nullable|string|max:255',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'pay_no' => '支付单号',
'success' => '是否成功',
'channel_id' => '通道ID',
'callback_type' => '回调类型',
'request_data' => '原始回调数据',
'verify_status' => '验签状态',
'process_status' => '处理状态',
'process_result' => '处理结果',
'channel_trade_no' => '渠道交易号',
'channel_order_no' => '渠道订单号',
'fee_actual_amount' => '实际手续费',
'paid_at' => '支付时间',
'channel_error_code' => '错误码',
'channel_error_msg' => '错误信息',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'callback' => ['pay_no', 'success', 'channel_id', 'callback_type', 'request_data', 'verify_status', 'process_status', 'process_result', 'channel_trade_no', 'channel_order_no', 'fee_actual_amount', 'paid_at', 'channel_error_code', 'channel_error_msg', 'ext_json'],
];
}

View File

@@ -0,0 +1,31 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 支付关闭参数校验器。
*
* 用于校验关闭支付单所需参数。
*/
class PayCloseValidator extends Validator
{
protected array $rules = [
'pay_no' => 'required|string|min:1|max:64|exists:ma_pay_order,pay_no',
'reason' => 'nullable|string|max:255',
'closed_at' => 'nullable|date_format:Y-m-d H:i:s',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'pay_no' => '支付单号',
'reason' => '关闭原因',
'closed_at' => '关闭时间',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'close' => ['pay_no', 'reason', 'closed_at', 'ext_json'],
];
}

View File

@@ -0,0 +1,37 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 支付预下单参数校验器。
*
* 用于校验支付发起时的核心入参。
*/
class PayPrepareValidator extends Validator
{
protected array $rules = [
'merchant_id' => 'required|integer|min:1|exists:ma_merchant,id',
'merchant_order_no' => 'required|string|min:1|max:64',
'pay_type_id' => 'required|integer|min:1|exists:ma_payment_type,id',
'pay_amount' => 'required|integer|min:1',
'subject' => 'sometimes|string|max:255',
'body' => 'sometimes|string|max:500',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'merchant_id' => '商户ID',
'merchant_order_no' => '商户订单号',
'pay_type_id' => '支付方式',
'pay_amount' => '支付金额',
'subject' => '标题',
'body' => '描述',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'prepare' => ['merchant_id', 'merchant_order_no', 'pay_type_id', 'pay_amount', 'subject', 'body', 'ext_json'],
];
}

View File

@@ -0,0 +1,31 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 支付超时参数校验器。
*
* 用于校验超时关闭支付单所需参数。
*/
class PayTimeoutValidator extends Validator
{
protected array $rules = [
'pay_no' => 'required|string|min:1|max:64|exists:ma_pay_order,pay_no',
'reason' => 'nullable|string|max:255',
'timeout_at' => 'nullable|date_format:Y-m-d H:i:s',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'pay_no' => '支付单号',
'reason' => '超时原因',
'timeout_at' => '超时时间',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'timeout' => ['pay_no', 'reason', 'timeout_at', 'ext_json'],
];
}

View File

@@ -0,0 +1,37 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 退款动作参数校验器。
*
* 用于校验退款处理、失败和重试操作的公共参数。
*/
class RefundActionValidator extends Validator
{
protected array $rules = [
'refund_no' => 'required|string|min:1|max:64|exists:ma_refund_order,refund_no',
'reason' => 'nullable|string|max:255',
'last_error' => 'nullable|string|max:255',
'processing_at' => 'nullable|date_format:Y-m-d H:i:s',
'failed_at' => 'nullable|date_format:Y-m-d H:i:s',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'refund_no' => '退款单号',
'reason' => '原因',
'last_error' => '最近错误信息',
'processing_at' => '处理时间',
'failed_at' => '失败时间',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'processing' => ['refund_no', 'reason', 'last_error', 'processing_at', 'ext_json'],
'retry' => ['refund_no', 'reason', 'last_error', 'processing_at', 'ext_json'],
'fail' => ['refund_no', 'reason', 'last_error', 'failed_at', 'ext_json'],
];
}

View File

@@ -0,0 +1,33 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 退款创建参数校验器。
*
* 用于校验退款单创建参数。
*/
class RefundCreateValidator extends Validator
{
protected array $rules = [
'pay_no' => 'required|string|min:1|max:64|exists:ma_pay_order,pay_no',
'merchant_refund_no' => 'sometimes|string|min:1|max:64',
'refund_amount' => 'nullable|integer|min:1',
'reason' => 'nullable|string|max:255',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'pay_no' => '支付单号',
'merchant_refund_no' => '商户退款单号',
'refund_amount' => '退款金额',
'reason' => '退款原因',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'store' => ['pay_no', 'merchant_refund_no', 'refund_amount', 'reason', 'ext_json'],
];
}

View File

@@ -0,0 +1,31 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 路由解析参数校验器。
*
* 用于校验路由预览所需参数。
*/
class RouteResolveValidator extends Validator
{
protected array $rules = [
'merchant_group_id' => 'required|integer|min:1|exists:ma_merchant_group,id',
'pay_type_id' => 'required|integer|min:1|exists:ma_payment_type,id',
'pay_amount' => 'required|integer|min:1',
'stat_date' => 'nullable|date_format:Y-m-d',
];
protected array $attributes = [
'merchant_group_id' => '商户分组ID',
'pay_type_id' => '支付方式',
'pay_amount' => '支付金额',
'stat_date' => '统计日期',
];
protected array $scenes = [
'resolve' => ['merchant_group_id', 'pay_type_id', 'pay_amount', 'stat_date'],
];
}

View File

@@ -0,0 +1,30 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 清算动作参数校验器。
*
* 用于校验清算成功和失败动作的公共参数。
*/
class SettlementActionValidator extends Validator
{
protected array $rules = [
'settle_no' => 'required|string|min:1|max:64|exists:ma_settlement_order,settle_no',
'reason' => 'nullable|string|max:255',
'ext_json' => 'nullable|array',
];
protected array $attributes = [
'settle_no' => '清算单号',
'reason' => '原因',
'ext_json' => '扩展信息',
];
protected array $scenes = [
'complete' => ['settle_no'],
'fail' => ['settle_no', 'reason', 'ext_json'],
];
}

View File

@@ -0,0 +1,63 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 清算创建参数校验器。
*
* 用于校验清算单和清算明细的创建参数。
*/
class SettlementCreateValidator extends Validator
{
protected array $rules = [
'settle_no' => 'sometimes|string|min:1|max:64',
'merchant_id' => 'required|integer|min:1|exists:ma_merchant,id',
'merchant_group_id' => 'required|integer|min:1|exists:ma_merchant_group,id',
'channel_id' => 'required|integer|min:1|exists:ma_payment_channel,id',
'cycle_type' => 'required|integer|min:0',
'cycle_key' => 'required|string|min:1|max:64',
'status' => 'sometimes|integer|min:0',
'generated_at' => 'nullable|date_format:Y-m-d H:i:s',
'accounted_amount' => 'nullable|integer|min:0',
'gross_amount' => 'nullable|integer|min:0',
'fee_amount' => 'nullable|integer|min:0',
'refund_amount' => 'nullable|integer|min:0',
'fee_reverse_amount' => 'nullable|integer|min:0',
'net_amount' => 'nullable|integer|min:0',
'ext_json' => 'nullable|array',
'items' => 'nullable|array',
'items.*.pay_no' => 'sometimes|string|min:1|max:64',
'items.*.refund_no' => 'sometimes|string|min:1|max:64',
'items.*.pay_amount' => 'sometimes|integer|min:0',
'items.*.fee_amount' => 'sometimes|integer|min:0',
'items.*.refund_amount' => 'sometimes|integer|min:0',
'items.*.fee_reverse_amount' => 'sometimes|integer|min:0',
'items.*.net_amount' => 'sometimes|integer|min:0',
'items.*.item_status' => 'sometimes|integer|min:0',
];
protected array $attributes = [
'settle_no' => '清算单号',
'merchant_id' => '商户ID',
'merchant_group_id' => '商户分组ID',
'channel_id' => '通道ID',
'cycle_type' => '结算周期类型',
'cycle_key' => '结算周期键',
'status' => '状态',
'generated_at' => '生成时间',
'accounted_amount' => '入账金额',
'gross_amount' => '交易总额',
'fee_amount' => '手续费',
'refund_amount' => '退款金额',
'fee_reverse_amount' => '手续费冲回',
'net_amount' => '净额',
'ext_json' => '扩展信息',
'items' => '清算明细',
];
protected array $scenes = [
'store' => ['settle_no', 'merchant_id', 'merchant_group_id', 'channel_id', 'cycle_type', 'cycle_key', 'status', 'generated_at', 'accounted_amount', 'gross_amount', 'fee_amount', 'refund_amount', 'fee_reverse_amount', 'net_amount', 'ext_json', 'items', 'items.*.pay_no', 'items.*.refund_no', 'items.*.pay_amount', 'items.*.fee_amount', 'items.*.refund_amount', 'items.*.fee_reverse_amount', 'items.*.net_amount', 'items.*.item_status'],
];
}

View File

@@ -0,0 +1,23 @@
<?php
namespace app\http\api\validation;
use support\validation\Validator;
/**
* 统一追踪查询参数校验器。
*/
class TraceQueryValidator extends Validator
{
protected array $rules = [
'trace_no' => 'required|string|min:1|max:64',
];
protected array $attributes = [
'trace_no' => '追踪号',
];
protected array $scenes = [
'show' => ['trace_no'],
];
}