文件
xionggeshop/addons/shopro_huifu/library/onboard/HuifuOnboardValidator.php
可乐加冰 38c66b8f5e ```
feat(shopro_huifu): 完善汇付进件功能的表单验证和错误处理

- 增加支行搜索功能的城市筛选支持,优化搜索逻辑
- 添加前端表单验证机制,包括身份证、银行卡、区域等必填字段校验
- 改进支行选择验证,确保必须从搜索结果中选择带联行号的支行
- 优化后端保存逻辑,添加强制保存参数控制
- 统一异常处理机制,增加HttpResponseException的正确抛出
- 完善用户界面提示信息,明确操作指引和错误反馈
- 修复表单提交前的数据准备逻辑,确保数据完整性
```
2026-09-29 16:08:44 +08:00

270 行
11 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace addons\shopro_huifu\library\onboard;
class HuifuOnboardValidator
{
protected static $fieldLabels = [
'account_type' => '结算账户类型',
'name' => '真实姓名',
'cert_no' => '身份证号',
'reg_name' => '商户名称',
'license_code' => '统一社会信用代码',
'legal_name' => '法人姓名',
'legal_mobile' => '法人手机号',
'legal_cert_no' => '法人身份证号',
'reg_detail' => '详细地址',
'mobile_no' => '手机号',
'card_no' => '银行卡号',
'branch_name' => '开户银行',
'branch_code' => '支行联行号',
'prov_id' => '所在区域',
'id_card_front_url' => '身份证人像面',
'id_card_back_url' => '身份证国徽面',
'license_url' => '营业执照照片',
'legal_id_front_url' => '法人身份证人像面',
'legal_id_back_url' => '法人身份证国徽面',
];
public static function validateSubmit(array $data)
{
$type = self::normalizeAccountType($data['account_type'] ?? 'indv');
$data['account_type'] = $type;
HuifuOnboardAreaHelper::applyRegionCodes($data);
self::resolveBranch($data);
if ($type === 'ent') {
self::validateEnterprise($data);
} else {
self::validateIndividual($data);
}
self::validateBank($data);
return self::normalize($data);
}
protected static function validateIndividual(array $data)
{
foreach (['name', 'cert_no', 'mobile_no', 'reg_detail', 'id_card_front_url', 'id_card_back_url'] as $field) {
if (empty($data[$field])) {
throw new \InvalidArgumentException('请填写' . self::fieldLabel($field));
}
}
if (!preg_match('/^\d{17}[\dXx]$/', $data['cert_no'])) {
throw new \InvalidArgumentException('身份证号格式不正确');
}
if (!preg_match('/^1\d{10}$/', $data['mobile_no'])) {
throw new \InvalidArgumentException('手机号格式不正确');
}
self::assertCertDates($data, 'cert');
}
protected static function validateEnterprise(array $data)
{
foreach ([
'reg_name', 'license_code', 'reg_detail', 'legal_name', 'legal_mobile', 'legal_cert_no',
'license_url', 'legal_id_front_url', 'legal_id_back_url',
] as $field) {
if (empty($data[$field])) {
throw new \InvalidArgumentException('请填写' . self::fieldLabel($field));
}
}
if (!preg_match('/^[0-9A-Z]{15}$|^[0-9A-Z]{18}$/', strtoupper($data['license_code']))) {
throw new \InvalidArgumentException('统一社会信用代码格式不正确');
}
if (!preg_match('/^\d{17}[\dXx]$/', $data['legal_cert_no'])) {
throw new \InvalidArgumentException('法人身份证号格式不正确');
}
if (!preg_match('/^1\d{10}$/', $data['legal_mobile'])) {
throw new \InvalidArgumentException('法人手机号格式不正确');
}
self::assertCertDates($data, 'license');
self::assertCertDates($data, 'legal_cert');
}
protected static function validateBank(array $data)
{
if (empty($data['card_no'])) {
throw new \InvalidArgumentException('请填写银行卡号');
}
if (!preg_match('/^\d{12,19}$/', preg_replace('/\s+/', '', $data['card_no']))) {
throw new \InvalidArgumentException('银行卡号格式不正确');
}
if (empty($data['branch_code']) && empty($data['branch_name'])) {
throw new \InvalidArgumentException('请选择开户银行支行');
}
if (empty($data['branch_code']) && !empty($data['branch_name'])) {
throw new \InvalidArgumentException('请从搜索结果中点击选择开户银行支行(需联行号)');
}
if (empty($data['prov_id']) || empty($data['area_id'])) {
throw new \InvalidArgumentException('请选择所在区域');
}
$bankAccountType = (string) ($data['bank_account_type'] ?? '0');
if ($bankAccountType === '0') {
$mp = trim((string) ($data['mp'] ?? $data['mobile_no'] ?? $data['legal_mobile'] ?? ''));
if ($mp === '' || !preg_match('/^1\d{10}$/', $mp)) {
throw new \InvalidArgumentException('请填写银行预留手机号');
}
}
}
protected static function assertCertDates(array $data, $prefix)
{
$typeKey = $prefix . '_validity_type';
$beginKey = $prefix . '_begin_date';
$endKey = $prefix . '_end_date';
$type = (string) ($data[$typeKey] ?? '');
if (!in_array($type, ['0', '1'], true)) {
throw new \InvalidArgumentException('证件有效期类型无效');
}
if (empty($data[$beginKey])) {
throw new \InvalidArgumentException('请选择证件开始日期');
}
if ($type === '0' && empty($data[$endKey])) {
throw new \InvalidArgumentException('请选择证件截止日期');
}
if ($type === '0' && !empty($data[$endKey])) {
self::assertFutureDate($data[$endKey], '证件有效期');
}
}
protected static function assertFutureDate($ymd, $label)
{
$ymd = trim((string) $ymd);
if (strlen($ymd) !== 8) {
return;
}
$endTs = strtotime(substr($ymd, 0, 4) . '-' . substr($ymd, 4, 2) . '-' . substr($ymd, 6, 2));
$todayTs = strtotime(date('Y-m-d'));
if ($endTs !== false && $endTs <= $todayTs) {
throw new \InvalidArgumentException($label . '须晚于今天,长期有效请选择 2099-12-31');
}
}
protected static function resolveBranch(array &$data)
{
if (!empty($data['branch_code'])) {
return;
}
$name = trim((string) ($data['branch_name'] ?? ''));
if ($name === '') {
return;
}
$city = $data['area_id'] ?? '';
$matches = HuifuOnboardBankBranch::search($name, $city, 1);
if ($matches) {
$data['branch_code'] = $matches[0]['branch_code'];
$data['branch_name'] = $matches[0]['branch_name'];
if (empty($data['prov_id']) && !empty($matches[0]['prov_id'])) {
$data['prov_id'] = $matches[0]['prov_id'];
}
if (empty($data['area_id']) && !empty($matches[0]['area_id'])) {
$data['area_id'] = $matches[0]['area_id'];
}
}
}
public static function normalize(array $data)
{
$data['account_type'] = self::normalizeAccountType($data['account_type'] ?? 'indv');
HuifuOnboardAreaHelper::applyRegionCodes($data);
self::resolveBranch($data);
$data['bank_account_type'] = (string) ($data['bank_account_type'] ?? '0');
if ($data['account_type'] === 'indv') {
$data['bank_account_type'] = '0';
}
$data['name'] = trim($data['name'] ?? '');
$data['cert_no'] = strtoupper(trim($data['cert_no'] ?? ''));
$data['mobile_no'] = trim($data['mobile_no'] ?? '');
$data['reg_name'] = trim($data['reg_name'] ?? '');
$data['license_code'] = strtoupper(trim($data['license_code'] ?? ''));
$data['legal_name'] = trim($data['legal_name'] ?? '');
$data['legal_mobile'] = trim($data['legal_mobile'] ?? '');
$data['legal_cert_no'] = strtoupper(trim($data['legal_cert_no'] ?? ''));
$data['reg_detail'] = trim($data['reg_detail'] ?? '');
$data['card_no'] = preg_replace('/\s+/', '', $data['card_no'] ?? '');
$data['branch_code'] = trim($data['branch_code'] ?? '');
$data['branch_name'] = trim($data['branch_name'] ?? '');
$data['prov_id'] = HuifuOnboardAreaHelper::toCode($data['prov_id'] ?? '');
$data['area_id'] = HuifuOnboardAreaHelper::toCode($data['area_id'] ?? '');
$data['district_id'] = HuifuOnboardAreaHelper::toCode($data['district_id'] ?? '');
foreach (['cert', 'license', 'legal_cert'] as $prefix) {
$beginKey = $prefix . '_begin_date';
$endKey = $prefix . '_end_date';
$typeKey = $prefix . '_validity_type';
if (!empty($data[$beginKey])) {
$data[$beginKey] = self::toYmd($data[$beginKey]);
}
if (isset($data[$typeKey])) {
$data[$typeKey] = (string) $data[$typeKey];
}
if (($data[$typeKey] ?? '') === '1') {
unset($data[$endKey]);
} elseif (!empty($data[$endKey])) {
$data[$endKey] = self::toYmd($data[$endKey]);
}
}
if ($data['account_type'] === 'indv') {
// 汇付对私法人:card_name 必须与个人开户姓名 name 完全一致
$data['card_name'] = $data['name'];
$data['mp'] = trim($data['mp'] ?? $data['mobile_no']);
} else {
if ($data['bank_account_type'] === '1') {
$data['card_name'] = trim($data['card_name'] ?: $data['reg_name']);
} else {
// 企业/个体户对私结算:card_name 必须与法人姓名一致
$data['card_name'] = $data['legal_name'];
$data['cert_no'] = $data['legal_cert_no'];
$data['cert_validity_type'] = $data['legal_cert_validity_type'];
$data['cert_begin_date'] = $data['legal_cert_begin_date'];
$data['cert_end_date'] = $data['legal_cert_end_date'] ?? '';
}
$data['mp'] = trim($data['mp'] ?? $data['legal_mobile']);
$data['mobile_no'] = $data['legal_mobile'];
$data['name'] = $data['legal_name'];
}
return $data;
}
public static function maskPayload(array $data)
{
$copy = $data;
foreach (['cert_no', 'legal_cert_no'] as $key) {
if (!empty($copy[$key])) {
$copy[$key] = substr($copy[$key], 0, 6) . '********' . substr($copy[$key], -4);
}
}
if (!empty($copy['card_no'])) {
$len = strlen($copy['card_no']);
$copy['card_no'] = str_repeat('*', max(0, $len - 4)) . substr($copy['card_no'], -4);
}
return $copy;
}
protected static function normalizeAccountType($type)
{
$type = strtolower(trim((string) $type));
return in_array($type, ['indv', 'ent'], true) ? $type : 'indv';
}
protected static function toYmd($value)
{
$value = trim((string) $value);
$value = str_replace(['-', '/', '.'], '', $value);
if (strlen($value) === 8 && ctype_digit($value)) {
return $value;
}
throw new \InvalidArgumentException('日期格式应为 yyyyMMdd 或 yyyy-MM-dd');
}
protected static function fieldLabel($field)
{
return self::$fieldLabels[$field] ?? $field;
}
}