feat(shopro_huifu): 完善汇付分账确认功能

- 添加收款方尾款余额处理逻辑,在主商户与收款户相同时,
  分账confirm后对剩余金额进行补确认操作
- 新增executeConfirmOnlyByOrderId方法支持同步支付扩展信息参数
- 增加resolveDelayConfirmStatus方法动态解析确认状态,
  根据未确认金额判断返回P或S状态
- 优化confirm流程中序列号更新逻辑,确保使用最终确认序列号
- 添加异常处理机制,记录收款方余额确认失败日志
```
这个提交包含在:
可乐加冰
2026-10-23 15:24:01 +08:00
父节点 0d1256a679
当前提交 cea79e59fe

查看文件

@@ -106,16 +106,32 @@ class HuifuSplitService
}
$confirmResult = $this->confirm($ext, $acctInfos, $outOrderNo, $confirmBase);
$finalConfirmSeq = $confirmResult['hf_seq_id'] ?? ($confirmResult['confirm_hf_seq_id'] ?? '');
try {
$remainderResult = $this->releaseCollectorRemainderIfNeeded($orderId, $order['order_sn'], $confirmBase, $acctInfos);
if ($remainderResult !== null) {
$confirmResult['collector_remainder_confirm'] = $remainderResult;
$seq = $remainderResult['hf_seq_id'] ?? '';
if ($seq !== '') {
$finalConfirmSeq = $seq;
}
}
} catch (\Exception $remainderEx) {
\think\Log::error('[shopro_huifu] collector remainder confirm failed order_id=' . $orderId
. ' msg=' . $remainderEx->getMessage());
$confirmResult['collector_remainder_error'] = $remainderEx->getMessage();
}
$splitOrder->status = 2;
$splitOrder->confirm_hf_seq_id = $confirmResult['hf_seq_id'] ?? ($confirmResult['confirm_hf_seq_id'] ?? '');
$splitOrder->confirm_hf_seq_id = $finalConfirmSeq;
$splitOrder->hf_req_seq_id = $outOrderNo;
$splitOrder->wx_result = json_encode($confirmResult, JSON_UNESCAPED_UNICODE);
$splitOrder->save();
SplitOrderReceiver::where('split_order_id', $splitOrder->id)->update(['status' => 1]);
HuifuPayService::savePayExt($orderId, $order['order_sn'], [
'confirm_hf_seq_id' => $splitOrder->confirm_hf_seq_id,
'confirm_status' => 'S',
'confirm_hf_seq_id' => $finalConfirmSeq,
'confirm_status' => $this->resolveDelayConfirmStatus($order['order_sn']),
]);
Db::commit();
$manualService->finishPendingRetryLog($orderId, true, '', $splitOrder->id);
@@ -185,7 +201,7 @@ class HuifuSplitService
/**
* 仅交易确认、不传分账串(用于排查 22000002 是否来自分账接收方配置)
*/
public function executeConfirmOnlyByOrderId($orderId)
public function executeConfirmOnlyByOrderId($orderId, $syncPayExt = true)
{
$order = Order::get($orderId);
if (!$order || $order['pay_type'] !== 'huifu_wechat') {
@@ -213,7 +229,68 @@ class HuifuSplitService
\think\Log::write('[shopro_huifu] delaytrans confirm-only extend=' . json_encode($extend, JSON_UNESCAPED_UNICODE), 'info');
$request->setExtendInfo($extend);
$result = $this->client->postRequest($request);
return $this->client->parseResponse($result);
$data = $this->client->parseResponse($result);
if ($syncPayExt) {
$this->syncPayExtAfterDelayConfirm($orderId, $order['order_sn'], $data);
}
return $data;
}
/**
* 主商户=收款户时,分账 confirm 只确认 acct_infos 部分,尾款需再调一次无分账串的 confirm。
*
* @return array|null 汇付返回;无需补确认时 null
*/
protected function releaseCollectorRemainderIfNeeded($orderId, $orderSn, $confirmBase, array $acctInfos)
{
$paymentHuifuId = trim((string) $this->config->get('huifu_id'));
$platformHuifuId = trim((string) ((new SplitConfig())->all()['platform_huifu_id'] ?? $paymentHuifuId));
if ($platformHuifuId === '' || $platformHuifuId !== $paymentHuifuId) {
return null;
}
$confirmBase = number_format((float) $confirmBase, 2, '.', '');
$splitSum = '0.00';
foreach ($acctInfos as $info) {
$splitSum = bcadd($splitSum, number_format((float) ($info['div_amt'] ?? 0), 2, '.', ''), 2);
}
$expectedRemain = bcsub($confirmBase, $splitSum, 2);
if (bccomp($expectedRemain, '0.00', 2) <= 0) {
return null;
}
$query = (new HuifuQueryService())->queryByOrderSn($orderSn);
$unconfirm = $this->queryFieldRecursive($query, 'unconfirm_amt');
if (!$this->isPositiveMoney($unconfirm)) {
return null;
}
\think\Log::info('[shopro_huifu] delaytrans collector remainder confirm order_id=' . $orderId
. ' expected_remain=' . $expectedRemain . ' unconfirm_amt=' . $unconfirm);
return $this->executeConfirmOnlyByOrderId($orderId, false);
}
protected function resolveDelayConfirmStatus($orderSn)
{
try {
$query = (new HuifuQueryService())->queryByOrderSn($orderSn);
$unconfirm = $this->queryFieldRecursive($query, 'unconfirm_amt');
if ($this->isPositiveMoney($unconfirm)) {
return 'P';
}
} catch (\Throwable $e) {
\think\Log::warning('[shopro_huifu] resolveDelayConfirmStatus query failed: ' . $e->getMessage());
}
return 'S';
}
protected function syncPayExtAfterDelayConfirm($orderId, $orderSn, array $confirmData)
{
$seq = $confirmData['hf_seq_id'] ?? ($confirmData['confirm_hf_seq_id'] ?? '');
HuifuPayService::savePayExt($orderId, $orderSn, [
'confirm_hf_seq_id' => $seq,
'confirm_status' => $this->resolveDelayConfirmStatus($orderSn),
]);
}
protected function confirm($ext, array $acctInfos, $confirmReqSeqId, $confirmBase = null)