feat: 完善支付通道和收款监听链路

新增 ChannelNotifyPayloadInterface 等支付插件通知契约,规范 pay_no 定位和插件返回校验。

新增微信、支付宝、收钱吧、Postar 个人收款插件适配,支持余额识别与备注识别。

新增 receipt-watcher 后端进程、Redis 队列 job 和平台事件监听,覆盖收款流水通知、商户通知、退款派发、转账派发与清算完成。

补齐个人收款监听相关系统配置、仓储、服务费冻结明细、订单后台操作和通道测试能力。

重构支付单创建、回调、费用、风控、结算和通道统计链路,统一状态流转与幂等处理。
This commit is contained in:
技术老胡
2026-05-11 16:28:48 +08:00
parent 0e5de50337
commit fd1f53f2ee
136 changed files with 14416 additions and 3992 deletions

View File

@@ -5,6 +5,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\auth\MerchantAuthService;
use app\service\merchant\MerchantService;
use support\Request;
use support\Response;
@@ -25,7 +26,8 @@ class MerchantController extends BaseController
* @return void
*/
public function __construct(
protected MerchantService $merchantService
protected MerchantService $merchantService,
protected MerchantAuthService $merchantAuthService
) {
}
@@ -127,6 +129,34 @@ class MerchantController extends BaseController
return $this->success($this->merchantService->resetPassword((int) $data['id'], (string) $data['password']));
}
/**
* 签发商户后台临时登录令牌。
*
* 该入口只在管理后台登录态下可用,用于客服或运营从商户工作台直接进入当前商户后台。
*
* @param Request $request 请求对象
* @param string $id 商户ID
* @return Response 响应对象
*/
public function loginToken(Request $request, string $id): Response
{
$data = $this->validated(['id' => (int) $id], MerchantValidator::class, 'loginToken');
$merchant = $this->merchantService->ensureMerchantEnabled((int) $data['id']);
$issued = $this->merchantAuthService->issueToken(
(int) $merchant->id,
3600,
(string) $request->getRealIp(),
(string) $request->header('user-agent', '')
);
return $this->success([
'token' => (string) $issued['token'],
'expires_in' => (int) $issued['expires_in'],
'merchant_id' => (int) $merchant->id,
'merchant_no' => (string) $merchant->merchant_no,
]);
}
/**
* 生成或重置商户 API 凭证。
*

View File

@@ -5,6 +5,7 @@ namespace app\http\admin\controller\payment;
use app\common\base\BaseController;
use app\http\admin\validation\PaymentChannelValidator;
use app\service\payment\config\PaymentChannelService;
use app\service\payment\config\PaymentChannelTestService;
use support\Request;
use support\Response;
@@ -14,6 +15,7 @@ use support\Response;
* 负责支付通道的列表、详情、新增、修改和删除。
*
* @property PaymentChannelService $paymentChannelService 支付渠道服务
* @property PaymentChannelTestService $paymentChannelTestService 支付通道测试服务
*/
class PaymentChannelController extends BaseController
{
@@ -21,10 +23,12 @@ class PaymentChannelController extends BaseController
* 构造方法。
*
* @param PaymentChannelService $paymentChannelService 支付渠道服务
* @param PaymentChannelTestService $paymentChannelTestService 支付通道测试服务
* @return void
*/
public function __construct(
protected PaymentChannelService $paymentChannelService
protected PaymentChannelService $paymentChannelService,
protected PaymentChannelTestService $paymentChannelTestService
) {
}
@@ -157,6 +161,25 @@ class PaymentChannelController extends BaseController
return $this->success($this->paymentChannelService->searchOptions($request->all(), $page, $pageSize));
}
/**
* 发起当前通道测试支付。
*
* @param Request $request 请求对象
* @param string $id 支付渠道ID
* @return Response 响应对象
*/
public function test(Request $request, string $id): Response
{
$data = $this->validated(
array_merge($request->all(), ['id' => (int) $id]),
PaymentChannelValidator::class,
'test'
);
$data['client_ip'] = (string) $request->getRealIp();
return $this->success($this->paymentChannelTestService->submit((int) $data['id'], $data));
}
}

View File

@@ -16,7 +16,7 @@ use support\Response;
class SystemConfigPageController extends BaseController
{
/**
* 构造方法。
* 构造方法。
*
* @param SystemConfigPageService $systemConfigPageService 系统配置页面服务
* @return void

View File

@@ -4,6 +4,7 @@ namespace app\http\admin\controller\system;
use app\common\base\BaseController;
use app\service\bootstrap\SystemBootstrapService;
use app\service\system\config\SystemPublicConfigService;
use support\Request;
use support\Response;
@@ -11,17 +12,20 @@ use support\Response;
* 管理后台系统数据控制器。
*
* @property SystemBootstrapService $systemBootstrapService 系统引导服务
* @property SystemPublicConfigService $systemPublicConfigService 系统公开配置服务
*/
class SystemController extends BaseController
{
/**
* 构造方法。
* 构造方法。
*
* @param SystemBootstrapService $systemBootstrapService 系统引导服务
* @param SystemPublicConfigService $systemPublicConfigService 系统公开配置服务
* @return void
*/
public function __construct(
protected SystemBootstrapService $systemBootstrapService
protected SystemBootstrapService $systemBootstrapService,
protected SystemPublicConfigService $systemPublicConfigService
) {
}
@@ -46,6 +50,17 @@ class SystemController extends BaseController
{
return $this->success($this->systemBootstrapService->getDictItems((string) $request->get('code', '')));
}
/**
* 获取管理后台公开展示配置。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function publicConfig(Request $request): Response
{
return $this->success($this->systemPublicConfigService->adminPortal());
}
}

View File

@@ -3,7 +3,9 @@
namespace app\http\admin\controller\trade;
use app\common\base\BaseController;
use app\http\admin\validation\PayOrderActionValidator;
use app\http\admin\validation\PayOrderValidator;
use app\service\payment\order\PayOrderAdminActionService;
use app\service\payment\order\PayOrderService;
use support\Request;
use support\Response;
@@ -14,6 +16,7 @@ use support\Response;
* 当前提供列表查询和详情查看,便于后台直接排查支付链路。
*
* @property PayOrderService $payOrderService 支付订单服务
* @property PayOrderAdminActionService $payOrderAdminActionService 支付订单后台操作服务
*/
class PayOrderController extends BaseController
{
@@ -21,10 +24,12 @@ class PayOrderController extends BaseController
* 构造方法。
*
* @param PayOrderService $payOrderService 支付订单服务
* @param PayOrderAdminActionService $payOrderAdminActionService 支付订单后台操作服务
* @return void
*/
public function __construct(
protected PayOrderService $payOrderService
protected PayOrderService $payOrderService,
protected PayOrderAdminActionService $payOrderAdminActionService
) {
}
@@ -40,7 +45,7 @@ class PayOrderController extends BaseController
$page = max(1, (int) ($data['page'] ?? 1));
$pageSize = max(1, (int) ($data['page_size'] ?? 10));
return $this->success($this->payOrderService->paginate($data, $page, $pageSize));
return $this->success($this->payOrderService->paginate($data, $page, $pageSize, null, true));
}
/**
@@ -58,7 +63,151 @@ class PayOrderController extends BaseController
'show'
);
return $this->success($this->payOrderService->detail($payNo));
return $this->success($this->payOrderService->detail($payNo, null, true));
}
/**
* 查询支付订单可操作项。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function actions(Request $request, string $payNo): Response
{
$this->validated(
array_merge($request->all(), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'actions'
);
return $this->success($this->payOrderAdminActionService->actions($payNo));
}
/**
* 重新通知商户。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function renotify(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'renotify'
);
return $this->success($this->payOrderAdminActionService->renotify($payNo, $data, $this->currentAdminId($request)));
}
/**
* 主动查询上游支付结果。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function activeQuery(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'active_query'
);
return $this->success($this->payOrderAdminActionService->activeQuery($payNo, $data, $this->currentAdminId($request)));
}
/**
* 发起 API 退款。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function apiRefund(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'api_refund'
);
return $this->success($this->payOrderAdminActionService->apiRefund($payNo, $data, $this->currentAdminId($request)));
}
/**
* 手动退款。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function manualRefund(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'manual_refund'
);
return $this->success($this->payOrderAdminActionService->manualRefund($payNo, $data, $this->currentAdminId($request)));
}
/**
* 手动补单。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function manualSuccess(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'manual_success'
);
return $this->success($this->payOrderAdminActionService->manualSuccess($payNo, $data, $this->currentAdminId($request)));
}
/**
* 冻结支付订单。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function freeze(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'freeze'
);
return $this->success($this->payOrderAdminActionService->freeze($payNo, $data, $this->currentAdminId($request)));
}
/**
* 解冻支付订单。
*
* @param Request $request 请求对象
* @param string $payNo 支付单号
* @return Response 响应对象
*/
public function unfreeze(Request $request, string $payNo): Response
{
$data = $this->validated(
array_merge($this->payload($request), ['pay_no' => $payNo]),
PayOrderActionValidator::class,
'unfreeze'
);
return $this->success($this->payOrderAdminActionService->unfreeze($payNo, $data, $this->currentAdminId($request)));
}
}

View File

@@ -3,9 +3,11 @@
namespace app\http\admin\controller\trade;
use app\common\base\BaseController;
use app\exception\BusinessStateException;
use app\exception\ResourceNotFoundException;
use app\http\admin\validation\SettlementOrderValidator;
use app\service\payment\settlement\SettlementOrderQueryService;
use app\service\payment\settlement\SettlementService;
use support\Request;
use support\Response;
@@ -13,6 +15,7 @@ use support\Response;
* 清算订单控制器。
*
* @property SettlementOrderQueryService $settlementOrderQueryService 结算订单查询服务
* @property SettlementService $settlementService 清算服务
*/
class SettlementOrderController extends BaseController
{
@@ -20,10 +23,12 @@ class SettlementOrderController extends BaseController
* 构造方法。
*
* @param SettlementOrderQueryService $settlementOrderQueryService 结算订单查询服务
* @param SettlementService $settlementService 清算服务
* @return void
*/
public function __construct(
protected SettlementOrderQueryService $settlementOrderQueryService
protected SettlementOrderQueryService $settlementOrderQueryService,
protected SettlementService $settlementService
) {
}
@@ -62,6 +67,53 @@ class SettlementOrderController extends BaseController
return $this->fail('清算订单不存在', 404);
}
}
/**
* 清算入账。
*
* @param Request $request 请求对象
* @param string $settleNo 清算单号
* @return Response 响应对象
*/
public function complete(Request $request, string $settleNo): Response
{
$data = $this->validated(['settle_no' => $settleNo], SettlementOrderValidator::class, 'show');
try {
return $this->success($this->settlementService->completeSettlement((string) $data['settle_no']));
} catch (ResourceNotFoundException $e) {
return $this->fail($e->getMessage(), $e->getCode());
} catch (BusinessStateException $e) {
return $this->fail($e->getMessage(), $e->getCode());
}
}
/**
* 标记清算失败。
*
* @param Request $request 请求对象
* @param string $settleNo 清算单号
* @return Response 响应对象
*/
public function markFailed(Request $request, string $settleNo): Response
{
$data = $this->validated(
array_merge($request->all(), ['settle_no' => $settleNo]),
SettlementOrderValidator::class,
'fail'
);
try {
return $this->success($this->settlementService->failSettlement(
(string) $data['settle_no'],
(string) ($data['reason'] ?? '')
));
} catch (ResourceNotFoundException $e) {
return $this->fail($e->getMessage(), $e->getCode());
} catch (BusinessStateException $e) {
return $this->fail($e->getMessage(), $e->getCode());
}
}
}

View File

@@ -18,7 +18,6 @@ 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,1',
'rotate_v1' => 'sometimes|integer|in:0,1',
'rotate_v2' => 'sometimes|integer|in:0,1',
'api_key' => 'nullable|string|max:128',
@@ -37,7 +36,6 @@ class MerchantApiCredentialValidator extends Validator
'id' => '凭证ID',
'keyword' => '关键词',
'merchant_id' => '所属商户',
'sign_type' => '签名类型',
'rotate_v1' => '是否重置 V1',
'rotate_v2' => '是否重置 V2',
'api_key' => '接口凭证值',
@@ -54,11 +52,11 @@ class MerchantApiCredentialValidator extends Validator
*/
protected array $scenes = [
'index' => ['keyword', 'merchant_id', 'status', 'page', 'page_size'],
'store' => ['merchant_id', 'sign_type', 'api_key', 'merchant_public_key', 'status'],
'update' => ['id', 'sign_type', 'api_key', 'merchant_public_key', 'status'],
'store' => ['merchant_id', 'api_key', 'merchant_public_key', 'status'],
'update' => ['id', 'api_key', 'merchant_public_key', 'status'],
'show' => ['id'],
'destroy' => ['id'],
'issueCredential' => ['rotate_v1', 'rotate_v2', 'sign_type', 'status'],
'issueCredential' => ['rotate_v1', 'rotate_v2', 'status'],
];
/**
@@ -70,7 +68,6 @@ class MerchantApiCredentialValidator extends Validator
{
return $this->appendRules([
'merchant_id' => 'required|integer|min:1|exists:ma_merchant,id',
'sign_type' => 'required|integer|in:0,1',
'merchant_public_key' => 'nullable|string|max:65535',
'status' => 'required|integer|in:0,1',
]);
@@ -85,7 +82,6 @@ class MerchantApiCredentialValidator extends Validator
{
return $this->appendRules([
'id' => 'required|integer|min: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',
@@ -102,7 +98,6 @@ class MerchantApiCredentialValidator extends Validator
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

@@ -112,6 +112,7 @@ class MerchantValidator extends Validator
'remark',
],
'updateStatus' => ['id', 'status'],
'loginToken' => ['id'],
'resetPassword' => ['id', 'password', 'password_confirm'],
'destroy' => ['id'],
];
@@ -180,6 +181,18 @@ class MerchantValidator extends Validator
]);
}
/**
* 配置商户后台登录令牌场景规则。
*
* @return static 校验器实例
*/
public function sceneLoginToken(): static
{
return $this->appendRules([
'id' => 'required|integer|min:1',
]);
}
/**
* 配置删除商户场景规则。
*
@@ -192,4 +205,3 @@ class MerchantValidator extends Validator
]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace app\http\admin\validation;
use support\validation\Validator;
/**
* 支付订单后台操作参数校验器。
*
* 操作接口只做字段形态校验,状态、金额和冻结等业务规则由服务层加锁后最终判断。
*/
class PayOrderActionValidator extends Validator
{
/**
* 校验规则。
*
* @var array
*/
protected array $rules = [
'pay_no' => 'required|string|max:64',
'reason' => 'sometimes|string|max:255',
'refund_amount' => 'sometimes|integer|min:1',
'paid_amount' => 'sometimes|integer|min:1',
'money' => 'sometimes|string|max:32|regex:/^\d+(\.\d{1,2})?$/',
'channel_order_no' => 'sometimes|string|max:64',
'channel_trade_no' => 'sometimes|string|max:64',
'paid_at' => 'sometimes|date_format:Y-m-d H:i:s',
];
/**
* 字段别名。
*
* @var array
*/
protected array $attributes = [
'pay_no' => '支付单号',
'reason' => '操作原因',
'refund_amount' => '退款金额',
'paid_amount' => '实付金额',
'money' => '金额',
'channel_order_no' => '渠道订单号',
'channel_trade_no' => '渠道交易号',
'paid_at' => '支付时间',
];
/**
* 校验场景。
*
* @var array
*/
protected array $scenes = [
'actions' => ['pay_no'],
'renotify' => ['pay_no', 'reason'],
'active_query' => ['pay_no'],
'api_refund' => ['pay_no'],
'manual_refund' => ['pay_no', 'reason', 'refund_amount', 'money'],
'manual_success' => ['pay_no', 'reason'],
'freeze' => ['pay_no', 'reason'],
'unfreeze' => ['pay_no', 'reason'],
];
/**
* 根据场景补充动态规则。
*
* webman/validation 只会按 scenes 截取字段规则,不会自动调用 sceneXxx 方法,
* 因此需要在 rules() 里把高风险动作的必填原因显式补上。
*
* @return array<string, mixed> 校验规则
*/
public function rules(): array
{
$rules = parent::rules();
if (in_array($this->scene(), ['manual_refund', 'manual_success', 'freeze', 'unfreeze'], true)) {
$rules['reason'] = 'required|string|max:255';
}
return $rules;
}
}

View File

@@ -34,6 +34,7 @@ class PaymentChannelValidator extends Validator
'remark' => 'nullable|string|max:255',
'status' => 'sometimes|integer|in:0,1',
'sort_no' => 'nullable|integer|min:0',
'money' => 'sometimes|numeric|min:0.01',
'page' => 'sometimes|integer|min:1',
'page_size' => 'sometimes|integer|min:1|max:100',
];
@@ -48,8 +49,8 @@ class PaymentChannelValidator extends Validator
'keyword' => '关键字',
'merchant_id' => '所属商户',
'name' => '通道名称',
'split_rate_bp' => '分成比例',
'cost_rate_bp' => '通道成本',
'split_rate_bp' => '商户分账比例',
'cost_rate_bp' => '第三方通道成本',
'channel_mode' => '通道模式',
'pay_type_id' => '支付方式',
'plugin_code' => '支付插件',
@@ -61,6 +62,7 @@ class PaymentChannelValidator extends Validator
'remark' => '备注',
'status' => '通道状态',
'sort_no' => '排序',
'money' => '测试金额',
'page' => '页码',
'page_size' => '每页条数',
];
@@ -77,6 +79,7 @@ class PaymentChannelValidator extends Validator
'updateStatus' => ['id', 'status'],
'show' => ['id'],
'destroy' => ['id'],
'test' => ['id', 'name', 'money'],
];
/**
@@ -117,6 +120,11 @@ class PaymentChannelValidator extends Validator
'show', 'destroy' => array_merge($rules, [
'id' => 'required|integer|min:1',
]),
'test' => array_merge($rules, [
'id' => 'required|integer|min:1',
'name' => 'required|string|min:1|max:128',
'money' => 'required|numeric|min:0.01',
]),
default => $rules,
};
}

View File

@@ -21,6 +21,7 @@ class SettlementOrderValidator extends Validator
'channel_id' => 'sometimes|integer|min:1',
'status' => 'sometimes|integer|min:0',
'cycle_type' => 'sometimes|integer|min:0',
'reason' => 'sometimes|string|max:255',
'page' => 'sometimes|integer|min:1',
'page_size' => 'sometimes|integer|min:1|max:100',
];
@@ -37,6 +38,7 @@ class SettlementOrderValidator extends Validator
'channel_id' => '所属通道',
'status' => '清算单状态',
'cycle_type' => '结算周期类型',
'reason' => '失败原因',
'page' => '页码',
'page_size' => '每页条数',
];
@@ -49,6 +51,6 @@ class SettlementOrderValidator extends Validator
protected array $scenes = [
'index' => ['keyword', 'merchant_id', 'channel_id', 'status', 'cycle_type', 'page', 'page_size'],
'show' => ['settle_no'],
'fail' => ['settle_no', 'reason'],
];
}

View File

@@ -64,4 +64,19 @@ class CashierController extends BaseController
$this->cashierService->payOrderDetail((string) ($payload['pay_no'] ?? ''))
);
}
/**
* 查询支付单状态。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function payOrderStatus(Request $request): Response
{
$payload = $this->validated($request->all(), CashierValidator::class, 'pay_order_status');
return $this->success(
$this->cashierService->payOrderStatus((string) ($payload['pay_no'] ?? ''))
);
}
}

View File

@@ -170,4 +170,16 @@ class EpayV2Controller extends BaseController
{
return $this->payOrderService->handlePluginCallback($payNo, $request);
}
/**
* 通道级通知入口。
*
* @param Request $request 请求对象
* @param int $chanId 通道ID
* @return string|Response
*/
public function channelNotify(Request $request, int $chanId): string|Response
{
return $this->payOrderService->handleChannelNotify($chanId, $request);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace app\http\api\controller\system;
use app\common\base\BaseController;
use app\service\system\config\SystemPublicConfigService;
use support\Request;
use support\Response;
/**
* 公开系统配置控制器。
*/
class SystemPublicConfigController extends BaseController
{
/**
* 构造方法。
*
* @param SystemPublicConfigService $systemPublicConfigService 系统公开配置服务
*/
public function __construct(
protected SystemPublicConfigService $systemPublicConfigService
) {
}
/**
* 查询收银台公开配置。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function cashier(Request $request): Response
{
return $this->success($this->systemPublicConfigService->cashier());
}
}

View File

@@ -27,6 +27,7 @@ class CashierValidator extends Validator
'context' => ['biz_no'],
'confirm' => ['biz_no', 'type'],
'pay_order' => ['pay_no'],
'pay_order_status' => ['pay_no'],
];
/**
@@ -65,4 +66,16 @@ class CashierValidator extends Validator
'pay_no' => 'required|string|max:32',
]);
}
/**
* 支付状态查询场景。
*
* @return static
*/
public function scenePayOrderStatus(): static
{
return $this->appendRules([
'pay_no' => 'required|string|max:32',
]);
}
}

View File

@@ -21,7 +21,7 @@ class EpayV1Validator extends Validator
'notify_url' => 'nullable|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'nullable|string|max:255',
'money' => 'nullable|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'nullable|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'param' => 'nullable',
'clientip' => 'nullable|ip',
'device' => 'nullable|string|in:pc,mobile,qq,wechat,alipay,jump',
@@ -73,7 +73,7 @@ class EpayV1Validator extends Validator
'notify_url' => 'required|string|max:255',
'return_url' => 'required|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'required|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'sign_type' => 'required|string|in:MD5',
'sign' => 'required|string|max:255',
]);
@@ -92,7 +92,7 @@ class EpayV1Validator extends Validator
'notify_url' => 'required|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'required|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'clientip' => 'required|ip',
'sign_type' => 'required|string|in:MD5',
'sign' => 'required|string|max:255',
@@ -160,7 +160,7 @@ class EpayV1Validator extends Validator
{
return $this->appendRules([
'key' => 'required|string|max:128',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'required|regex:/^(?=.*[1-9])\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

@@ -14,8 +14,7 @@ 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',
'sign_type' => 'required|string|in:RSA',
// RSA 签名是 Base64 文本,长度会明显超过 MD5不能沿用 255 的短限制。
'sign' => 'required|string|max:2048',
'type' => 'nullable|string|max:32',
@@ -25,7 +24,7 @@ class EpayV2Validator extends Validator
'notify_url' => 'nullable|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'nullable|string|max:255',
'money' => 'nullable|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'nullable|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'param' => 'nullable',
'auth_code' => 'nullable|string|max:128',
'sub_openid' => 'nullable|string|max:128',
@@ -104,8 +103,8 @@ class EpayV2Validator extends Validator
'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',
'money' => 'required|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'sign_type' => 'required|string|in:RSA',
'sign' => 'required|string|max:2048',
]);
}
@@ -124,9 +123,9 @@ class EpayV2Validator extends Validator
'notify_url' => 'required|string|max:255',
'return_url' => 'nullable|string|max:255',
'name' => 'required|string|max:255',
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'required|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'device' => 'nullable|string|in:pc,mobile,qq,wechat,alipay',
'sign_type' => 'required|string|in:SHA256WithRSA,RSA',
'sign_type' => 'required|string|in:RSA',
'sign' => 'required|string|max:2048',
]);
}
@@ -139,11 +138,11 @@ class EpayV2Validator extends Validator
public function sceneRefund(): static
{
return $this->appendRules([
'money' => 'required|regex:/^\d+(?:\.\d{1,2})?$/',
'money' => 'required|regex:/^(?=.*[1-9])\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_type' => 'required|string|in:RSA',
'sign' => 'required|string|max:2048',
]);
}
@@ -208,8 +207,8 @@ class EpayV2Validator extends Validator
'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',
'money' => 'required|regex:/^(?=.*[1-9])\d+(?:\.\d{1,2})?$/',
'sign_type' => 'required|string|in:RSA',
'sign' => 'required|string|max:2048',
]);
}

View File

@@ -4,6 +4,7 @@ namespace app\http\mer\controller\system;
use app\common\base\BaseController;
use app\service\bootstrap\SystemBootstrapService;
use app\service\system\config\SystemPublicConfigService;
use support\Request;
use support\Response;
@@ -11,17 +12,20 @@ use support\Response;
* 商户后台系统数据控制器。
*
* @property SystemBootstrapService $systemBootstrapService 系统引导服务
* @property SystemPublicConfigService $systemPublicConfigService 系统公开配置服务
*/
class SystemController extends BaseController
{
/**
* 构造方法。
* 构造方法。
*
* @param SystemBootstrapService $systemBootstrapService 系统引导服务
* @param SystemPublicConfigService $systemPublicConfigService 系统公开配置服务
* @return void
*/
public function __construct(
protected SystemBootstrapService $systemBootstrapService
protected SystemBootstrapService $systemBootstrapService,
protected SystemPublicConfigService $systemPublicConfigService
) {
}
@@ -46,6 +50,17 @@ class SystemController extends BaseController
{
return $this->success($this->systemBootstrapService->getDictItems((string) $request->get('code', '')));
}
/**
* 获取商户后台公开展示配置。
*
* @param Request $request 请求对象
* @return Response 响应对象
*/
public function publicConfig(Request $request): Response
{
return $this->success($this->systemPublicConfigService->merchantPortal());
}
}

View File

@@ -45,7 +45,6 @@ class MerchantPortalValidator extends Validator
'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',
@@ -87,7 +86,6 @@ class MerchantPortalValidator extends Validator
'status' => '状态',
'rotate_v1' => 'V1 凭证',
'rotate_v2' => 'V2 凭证',
'sign_type' => '签名类型',
'sort_no' => '排序',
'page' => '页码',
'page_size' => '每页条数',
@@ -118,7 +116,7 @@ class MerchantPortalValidator extends Validator
'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'],
'issueCredential' => ['rotate_v1', 'rotate_v2', 'status'],
];
public function rules(): array