From 41cd174686c147345eb9f813db60d69d7f789e68 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: Fri, 2 Oct 2026 14:30:10 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(shopro=5Fhuifu):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=88=86=E8=B4=A6=E7=A1=AE=E8=AE=A4=E9=87=91=E9=A2=9D?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E9=80=BB=E8=BE=91=E5=B9=B6=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加previewConfirmPayFee方法用于预览汇付确认基数 - 实现更精确的分账确认金额计算,支持多种场景:待确认金额、结算金额、交易金额减手续费 - 新增辅助方法pickTransAmt、pickFeeAmt、isPositiveMoney和queryFieldRecursive - 支持递归查询嵌套数据结构中的字段值 - 在测试脚本中添加详细的调试输出,包括汇付确认基数对比和查单摘要 - 增强错误处理,显示最新的分账记录详情便于问题排查 - 改进分账执行结果的反馈信息,提供更清晰的成功/失败状态说明 ``` --- .../shopro/tests/split_commission_verify.php | 45 ++++++- .../library/HuifuSplitService.php | 114 ++++++++++++++---- 2 files changed, 136 insertions(+), 23 deletions(-) diff --git a/addons/shopro/tests/split_commission_verify.php b/addons/shopro/tests/split_commission_verify.php index e8f6ce6..eb1c9ae 100644 --- a/addons/shopro/tests/split_commission_verify.php +++ b/addons/shopro/tests/split_commission_verify.php @@ -467,8 +467,49 @@ function trySplitOrder($orderSn, $execute) fwrite(STDERR, "未安装 shopro_huifu\n"); exit(1); } - $ok = (new \addons\shopro_huifu\library\HuifuSplitService())->executeByOrderId($order['id']); - echo $ok ? "\n分账执行返回: 成功\n" : "\n分账执行返回: 失败(查日志 / shopro_split_order)\n"; + $service = new \addons\shopro_huifu\library\HuifuSplitService(); + $payFee = number_format((float) $order['pay_fee'], 2, '.', ''); + $confirmBase = $service->previewConfirmPayFee($order['order_sn'], $payFee); + echo "\n汇付确认基数: pay_fee={$payFee} → confirm_base={$confirmBase}"; + if (bccomp($confirmBase, $payFee, 2) < 0) { + echo "(已扣手续费/待确认金额)"; + } + echo "\n"; + + try { + $query = (new \addons\shopro_huifu\library\HuifuQueryService())->queryByOrderSn($order['order_sn']); + $snippet = [ + 'trans_amt' => $query['trans_amt'] ?? null, + 'unconfirm_amt' => $query['unconfirm_amt'] ?? null, + 'settlement_amt' => $query['settlement_amt'] ?? null, + 'delay_acct_flag'=> $query['delay_acct_flag'] ?? null, + 'trans_stat' => $query['trans_stat'] ?? null, + ]; + echo '查单摘要: ' . json_encode($snippet, JSON_UNESCAPED_UNICODE) . "\n"; + } catch (\Throwable $e) { + echo '查单摘要: 失败 ' . $e->getMessage() . "\n"; + } + + $ok = $service->executeByOrderId($order['id']); + if ($ok) { + echo "\n分账执行返回: 成功\n"; + return; + } + + $latest = Db::name('shopro_split_order') + ->where('order_id', $order['id']) + ->where('channel', 'huifu') + ->order('id', 'desc') + ->find(); + echo "\n分账执行返回: 失败\n"; + if ($latest) { + echo "最新分账记录 id={$latest['id']} status={$latest['status']} split_amount={$latest['split_amount']}\n"; + if (!empty($latest['wx_result'])) { + echo "wx_result: {$latest['wx_result']}\n"; + } + } else { + echo "未写入 shopro_split_order(可能明细为空或 pay_type 非 huifu_wechat)\n"; + } } function printOrderDetail($orderSn) diff --git a/addons/shopro_huifu/library/HuifuSplitService.php b/addons/shopro_huifu/library/HuifuSplitService.php index 50d6f9a..5d4f45a 100644 --- a/addons/shopro_huifu/library/HuifuSplitService.php +++ b/addons/shopro_huifu/library/HuifuSplitService.php @@ -189,34 +189,45 @@ class HuifuSplitService /** * 延时分账确认总额须与汇付「待确认/交易金额」一致;内扣手续费时往往小于商城 pay_fee。 */ + public function previewConfirmPayFee($orderSn, $orderPayFee) + { + return $this->resolveConfirmPayFee($orderSn, $orderPayFee); + } + protected function resolveConfirmPayFee($orderSn, $orderPayFee) { $fallback = number_format((float) $orderPayFee, 2, '.', ''); try { $query = (new HuifuQueryService())->queryByOrderSn($orderSn); - $unconfirm = trim((string) ($query['unconfirm_amt'] ?? '')); - if ($unconfirm !== '' && is_numeric($unconfirm)) { - $amt = number_format((float) $unconfirm, 2, '.', ''); - if (bccomp($amt, '0.00', 2) > 0) { - return $amt; + $delayAcct = strtoupper(trim((string) ($this->queryFieldRecursive($query, 'delay_acct_flag') ?? ''))) === 'Y'; + + $unconfirm = $this->queryFieldRecursive($query, 'unconfirm_amt'); + if ($this->isPositiveMoney($unconfirm)) { + return number_format((float) $unconfirm, 2, '.', ''); + } + + $settlement = $this->queryFieldRecursive($query, 'settlement_amt'); + if ($this->isPositiveMoney($settlement) && bccomp((string) $settlement, $fallback, 2) < 0) { + return number_format((float) $settlement, 2, '.', ''); + } + + $transAmt = $this->pickTransAmt($query); + $feeAmt = $this->pickFeeAmt($query); + if ($transAmt !== null && $feeAmt !== null) { + $net = bcsub($transAmt, $feeAmt, 2); + if (bccomp($net, '0.00', 2) > 0 && bccomp($net, $fallback, 2) <= 0) { + \think\Log::info('[shopro_huifu] split confirm base from trans-fee: trans=' . $transAmt . ' fee=' . $feeAmt . ' net=' . $net); + return $net; } } - $trans = trim((string) ($query['trans_amt'] ?? '')); - if ($trans !== '' && is_numeric($trans)) { - $transAmt = number_format((float) $trans, 2, '.', ''); - foreach (['fee_amt', 'fee_amount', 'trans_fee'] as $feeKey) { - $feeRaw = trim((string) ($query[$feeKey] ?? '')); - if ($feeRaw === '' || !is_numeric($feeRaw)) { - continue; - } - $net = bcsub($transAmt, number_format((float) $feeRaw, 2, '.', ''), 2); - if (bccomp($net, '0.00', 2) > 0 && bccomp($net, $fallback, 2) < 0) { - return $net; - } - } - if (bccomp($transAmt, '0.00', 2) > 0) { - return $transAmt; - } + + if ($transAmt !== null && !$delayAcct && bccomp($transAmt, '0.00', 2) > 0) { + return $transAmt; + } + + if ($delayAcct && $transAmt !== null && bccomp($transAmt, $fallback, 2) === 0) { + \think\Log::warning('[shopro_huifu] split confirm base: delay_acct=Y but unconfirm/fee missing, still using pay_fee=' . $fallback + . ' query_keys=' . implode(',', array_slice(array_keys($query), 0, 20))); } } catch (\Throwable $e) { \think\Log::warning('[shopro_huifu] split confirm amount query fallback: ' . $e->getMessage()); @@ -224,6 +235,67 @@ class HuifuSplitService return $fallback; } + protected function pickTransAmt(array $query) + { + $raw = $this->queryFieldRecursive($query, 'trans_amt'); + if (!$this->isPositiveMoney($raw)) { + return null; + } + return number_format((float) $raw, 2, '.', ''); + } + + protected function pickFeeAmt(array $query) + { + foreach (['actual_fee_amt', 'receivable_fee_amt', 'fee_amt', 'fee_amount', 'trans_fee', 'combinedpay_fee_amt'] as $key) { + $raw = $this->queryFieldRecursive($query, $key); + if ($this->isPositiveMoney($raw)) { + return number_format((float) $raw, 2, '.', ''); + } + } + return null; + } + + protected function isPositiveMoney($value) + { + if ($value === null || $value === '') { + return false; + } + if (!is_numeric($value)) { + return false; + } + return bccomp(number_format((float) $value, 2, '.', ''), '0.00', 2) > 0; + } + + /** + * @param array|string|mixed $data + */ + protected function queryFieldRecursive($data, $key) + { + if (is_string($data)) { + $trim = trim($data); + if ($trim !== '' && ($trim[0] === '{' || $trim[0] === '[')) { + $decoded = json_decode($trim, true); + if (is_array($decoded)) { + return $this->queryFieldRecursive($decoded, $key); + } + } + return null; + } + if (!is_array($data)) { + return null; + } + if (array_key_exists($key, $data) && $data[$key] !== '' && $data[$key] !== null) { + return $data[$key]; + } + foreach ($data as $value) { + $found = $this->queryFieldRecursive($value, $key); + if ($found !== null && $found !== '') { + return $found; + } + } + return null; + } + protected function scaleSplitItemsToCap(array $items, $allocated, $cap) { if (empty($items) || bccomp($allocated, '0.00', 2) <= 0) {