From 4094ff5cf70ae3d29df6ff818a5503358643396e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=AF=E4=B9=90=E5=8A=A0=E5=86=B0?= <11869599+addie66@user.noreply.gitee.com> Date: Mon, 5 Oct 2026 10:33:14 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(shopro):=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=B1=87=E4=BB=98=E5=88=86=E8=B4=A6=E5=8A=9F=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加主商户号验证逻辑,当platform_huifu_id为空时使用插件主商户 - 改进分账剩余金额显示逻辑,区分主商户留存和平台分账情况 - 更新错误提示信息,明确2200002错误的处理方式 - 新增acct_infos预览功能,便于调试分账配置 - 优化分账服务中的商户号处理,避免主商户重复分账 fix(shopro_huifu): 修复汇付分账配置错误提示和分账逻辑 - 更新22000002错误排查说明,简化表述并突出关键点 - 修复分账服务中构建分账信息的逻辑,确保不包含收款主商户 - 添加日志记录,当跳过分账方为自己时进行警告 - 优化剩余金额处理,当日志平台即收款主商户时剩余款项保留在主商户 - 增强分账验证,当无有效分账接收方时抛出异常 ``` --- .../shopro/tests/split_commission_verify.php | 53 +++++++++++++++++-- addons/shopro_huifu/library/HuifuClient.php | 2 +- .../library/HuifuSplitService.php | 40 +++++++++++--- 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/addons/shopro/tests/split_commission_verify.php b/addons/shopro/tests/split_commission_verify.php index 9592452..f1b30ea 100644 --- a/addons/shopro/tests/split_commission_verify.php +++ b/addons/shopro/tests/split_commission_verify.php @@ -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) diff --git a/addons/shopro_huifu/library/HuifuClient.php b/addons/shopro_huifu/library/HuifuClient.php index c4b3914..efd9302 100644 --- a/addons/shopro_huifu/library/HuifuClient.php +++ b/addons/shopro_huifu/library/HuifuClient.php @@ -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'; } diff --git a/addons/shopro_huifu/library/HuifuSplitService.php b/addons/shopro_huifu/library/HuifuSplitService.php index 8ef7a40..e201f5a 100644 --- a/addons/shopro_huifu/library/HuifuSplitService.php +++ b/addons/shopro_huifu/library/HuifuSplitService.php @@ -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; }