feat(shopro): 完善汇付分账功能的错误处理和调试信息

- 添加主商户号验证逻辑,当platform_huifu_id为空时使用插件主商户
- 改进分账剩余金额显示逻辑,区分主商户留存和平台分账情况
- 更新错误提示信息,明确2200002错误的处理方式
- 新增acct_infos预览功能,便于调试分账配置
- 优化分账服务中的商户号处理,避免主商户重复分账

fix(shopro_huifu): 修复汇付分账配置错误提示和分账逻辑

- 更新22000002错误排查说明,简化表述并突出关键点
- 修复分账服务中构建分账信息的逻辑,确保不包含收款主商户
- 添加日志记录,当跳过分账方为自己时进行警告
- 优化剩余金额处理,当日志平台即收款主商户时剩余款项保留在主商户
- 增强分账验证,当无有效分账接收方时抛出异常
```
这个提交包含在:
可乐加冰
2026-10-05 10:33:14 +08:00
父节点 da2ce90ae5
当前提交 4094ff5cf7
共有 3 个文件被更改,包括 81 次插入 和 14 次删除

查看文件

@@ -428,13 +428,18 @@ function diagnoseOrderSplitBlockers($orderId)
);
}
$payFee = number_format((float) $order['pay_fee'], 2, '.', '');
$payHuifuId = trim((string) Db::name('shopro_huifu_config')->where('name', 'huifu_id')->value('value'));
$platformId = trim((string) ($sc['platform_huifu_id'] ?? ''));
if ($platformId === '') {
$platformId = $payHuifuId;
}
$remain = bcsub($payFee, number_format($sum, 2, '.', ''), 2);
if (bccomp($remain, '0.00', 2) > 0) {
$reasons[] = ' → platform 剩余 huifu_id=' . ($platformId !== '' ? $platformId : '(未配 platform_huifu_id,将用插件主商户)') . ' 金额=' . $remain;
}
if ($platformId === '') {
$reasons[] = ' ⚠ platform_huifu_id 为空,请与汇付收款主商户 huifu_id 一致,否则易 22000002';
if ($platformId !== '' && $platformId === $payHuifuId) {
$reasons[] = ' → 主商户留存(不写 acct_infos)huifu_id=' . $payHuifuId . ' 金额=' . $remain;
} else {
$reasons[] = ' → 平台分账 huifu_id=' . $platformId . ' 金额=' . $remain;
}
}
}
@@ -488,7 +493,7 @@ function printTrySplitChannelHints(array $order)
if ($latest && intval($latest['status']) === 4 && !empty($latest['wx_result'])) {
$raw = is_string($latest['wx_result']) ? $latest['wx_result'] : json_encode($latest['wx_result'], JSON_UNESCAPED_UNICODE);
if (strpos($raw, '22000002') !== false || strpos($raw, '分账配置') !== false) {
echo "\n【最近失败】id={$latest['id']} → 22000002 分账配置有误:控台绑定上述 huifu_id 后再重试\n";
echo "\n【最近失败】id={$latest['id']} → 22000002:若已部署「主商户不写 acct_infos」仍失败,请在控台绑定子商户分账关系\n";
} elseif (strpos($raw, 'FREEZE') !== false) {
echo "\n【最近失败】id={$latest['id']} → 资金冻结,见上查单\n";
}
@@ -498,6 +503,44 @@ function printTrySplitChannelHints(array $order)
if ($payHuifuId) {
echo " · 插件支付 huifu_id={$payHuifuId}(应与 platform_huifu_id / 控台主商户一致)\n";
}
printHuifuAcctInfosPreview($order);
}
function printHuifuAcctInfosPreview(array $order)
{
if (!class_exists('\\addons\\shopro\\library\\split\\SplitCalculator')
|| !class_exists('\\addons\\shopro_huifu\\library\\HuifuSplitService')) {
return;
}
$items = (new \addons\shopro\library\split\SplitCalculator())->calculate($order['id']);
if (empty($items)) {
return;
}
$service = new \addons\shopro_huifu\library\HuifuSplitService();
$payFee = number_format((float) $order['pay_fee'], 2, '.', '');
$confirmBase = $service->previewConfirmPayFee($order['order_sn'], $payFee);
$ref = new \ReflectionClass($service);
$method = $ref->getMethod('buildAcctInfos');
$method->setAccessible(true);
try {
$acctInfos = $method->invoke($service, $items, $confirmBase);
} catch (\Throwable $e) {
echo "\n【acct_infos 预览】组装失败: " . $e->getMessage() . "\n";
return;
}
echo "\n【将提交汇付 acct_infos】confirm_base={$confirmBase}\n";
foreach ($acctInfos as $row) {
echo ' · huifu_id=' . ($row['huifu_id'] ?? '-') . ' div_amt=' . ($row['div_amt'] ?? '-') . "\n";
}
$splitSum = '0.00';
foreach ($acctInfos as $row) {
$splitSum = bcadd($splitSum, number_format((float) ($row['div_amt'] ?? 0), 2, '.', ''), 2);
}
$stay = bcsub($confirmBase, $splitSum, 2);
if (bccomp($stay, '0.00', 2) > 0) {
echo " · (其余 {$stay} 元留在收款主商户,不写入分账串)\n";
}
}
function diagnoseMissingSplitOrders($limit)

查看文件

@@ -399,7 +399,7 @@ class HuifuClient
if ($respCode === '22000002') {
$parts[] = '排查: 1) 汇付控台主商户已开通延时分账/分账业务 2) acct_split_bunch 中每个 huifu_id 已在控台绑定为分账接收方 3) 商城「平台汇付商户号 platform_huifu_id」= 收款主商户 huifu_id 4) 分账接收方账号为进件 huifu_id(非微信 openid)5) 推广员进件成功且与 receiver 一致';
$parts[] = '排查: 1) 控台主商户已开通延时分账 2) 子商户 huifu_id 已绑为分账接收方 3) acct_infos 勿含收款主商户(剩余款留主商户)4) platform_huifu_id=主商户时仅分出推广员等子户 5) receiver 为进件 huifu_id';
}

查看文件

@@ -158,6 +158,13 @@ class HuifuSplitService
protected function buildAcctInfos(array $items, $payFee)
{
$payFee = number_format((float) $payFee, 2, '.', '');
$paymentHuifuId = trim((string) $this->config->get('huifu_id'));
$platformHuifuId = trim((string) ((new SplitConfig())->all()['platform_huifu_id'] ?? $paymentHuifuId));
if ($platformHuifuId === '') {
$platformHuifuId = $paymentHuifuId;
}
$infos = [];
$allocated = '0.00';
foreach ($items as $item) {
@@ -165,28 +172,45 @@ class HuifuSplitService
if (bccomp($amt, '0.00', 2) <= 0) {
continue;
}
$receiverHuifuId = trim((string) ($item['receiver_account'] ?? ''));
if ($receiverHuifuId === '') {
continue;
}
// 汇付要求:分账方勿与收款主商户相同(剩余款留在主商户,勿写入 acct_infos)
if ($paymentHuifuId !== '' && $receiverHuifuId === $paymentHuifuId) {
\think\Log::warning('[shopro_huifu] split skip self receiver in acct_infos huifu_id=' . $receiverHuifuId);
continue;
}
$infos[] = [
'huifu_id' => $item['receiver_account'],
'huifu_id' => $receiverHuifuId,
'div_amt' => $amt,
];
$allocated = bcadd($allocated, $amt, 2);
}
$platformHuifuId = (new SplitConfig())->all()['platform_huifu_id'] ?? $this->config->get('huifu_id');
$remain = bcsub($payFee, $allocated, 2);
if (bccomp($remain, '0.00', 2) > 0) {
$infos[] = [
'huifu_id' => $platformHuifuId,
'div_amt' => $remain,
];
$platformIsCollector = ($platformHuifuId === $paymentHuifuId);
if (!$platformIsCollector) {
$infos[] = [
'huifu_id' => $platformHuifuId,
'div_amt' => $remain,
];
} else {
\think\Log::info('[shopro_huifu] split platform remainder stays on collector: remain=' . $remain
. ' huifu_id=' . $paymentHuifuId);
}
}
$sum = '0.00';
foreach ($infos as $info) {
$sum = bcadd($sum, $info['div_amt'], 2);
}
if (bccomp($sum, $payFee, 2) !== 0) {
throw new \RuntimeException('分账金额之和必须等于订单实付');
if (empty($infos)) {
throw new \RuntimeException('分账明细为空:无有效分账接收方(勿将主商户号作为分账方)');
}
if (bccomp($sum, $payFee, 2) > 0) {
throw new \RuntimeException('分账金额之和不能超过待确认金额');
}
return $infos;
}