feat(shopro_huifu): 优化分账确认金额计算逻辑并增强调试功能

- 添加previewConfirmPayFee方法用于预览汇付确认基数
- 实现更精确的分账确认金额计算,支持多种场景:待确认金额、结算金额、交易金额减手续费
- 新增辅助方法pickTransAmt、pickFeeAmt、isPositiveMoney和queryFieldRecursive
- 支持递归查询嵌套数据结构中的字段值
- 在测试脚本中添加详细的调试输出,包括汇付确认基数对比和查单摘要
- 增强错误处理,显示最新的分账记录详情便于问题排查
- 改进分账执行结果的反馈信息,提供更清晰的成功/失败状态说明
```
这个提交包含在:
可乐加冰
2026-10-02 14:30:10 +08:00
父节点 f238ff212a
当前提交 41cd174686
共有 2 个文件被更改,包括 136 次插入 和 23 次删除

查看文件

@@ -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)

查看文件

@@ -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, '.', '');
}
$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) {
$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;
}
}
if (bccomp($transAmt, '0.00', 2) > 0) {
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) {