From 246935728e47eed571858bb89c143a8cfe4f382f 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:21:59 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(shopro=5Fhuifu):=20=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E6=B1=87=E4=BB=98=E5=88=86=E8=B4=A6=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现分账确认金额解析逻辑,确保与汇付待确认金额一致 - 添加分账项目缩放调整功能,防止分配总额超出限制 - 优化分账金额计算精度处理,使用BC数学函数保证准确性 - 改进后台重试接口错误提示,提供详细的失败原因信息 ``` --- .../library/HuifuSplitService.php | 85 ++++++++++++++++++- .../admin/controller/shopro/split/Order.php | 30 ++++++- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/addons/shopro_huifu/library/HuifuSplitService.php b/addons/shopro_huifu/library/HuifuSplitService.php index b9bb28a..8d29c37 100644 --- a/addons/shopro_huifu/library/HuifuSplitService.php +++ b/addons/shopro_huifu/library/HuifuSplitService.php @@ -45,10 +45,22 @@ class HuifuSplitService } $payFee = number_format((float) $order['pay_fee'], 2, '.', ''); - $acctInfos = $this->buildAcctInfos($items, $payFee); + $confirmBase = $this->resolveConfirmPayFee($order['order_sn'], $payFee); + if (bccomp($confirmBase, $payFee, 2) !== 0) { + \think\Log::info('[shopro_huifu] split confirm base: order_id=' . $orderId + . ' pay_fee=' . $payFee . ' confirm_base=' . $confirmBase); + } + $allocated = '0.00'; + foreach ($items as $item) { + $allocated = bcadd($allocated, number_format((float) $item['amount'], 2, '.', ''), 2); + } + if (bccomp($allocated, $confirmBase, 2) > 0) { + $items = $this->scaleSplitItemsToCap($items, $allocated, $confirmBase); + } + $acctInfos = $this->buildAcctInfos($items, $confirmBase); $splitAmount = '0.00'; - foreach ($acctInfos as $info) { - $splitAmount = bcadd($splitAmount, $info['div_amt'], 2); + foreach ($items as $item) { + $splitAmount = bcadd($splitAmount, number_format((float) $item['amount'], 2, '.', ''), 2); } $outOrderNo = 'HFPS' . date('YmdHis') . $orderId; @@ -163,6 +175,73 @@ class HuifuSplitService return $infos; } + /** + * 延时分账确认总额须与汇付「待确认/交易金额」一致;内扣手续费时往往小于商城 pay_fee。 + */ + 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; + } + } + $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; + } + } + } catch (\Throwable $e) { + \think\Log::warning('[shopro_huifu] split confirm amount query fallback: ' . $e->getMessage()); + } + return $fallback; + } + + protected function scaleSplitItemsToCap(array $items, $allocated, $cap) + { + if (empty($items) || bccomp($allocated, '0.00', 2) <= 0) { + return $items; + } + $cap = number_format((float) $cap, 2, '.', ''); + $allocated = number_format((float) $allocated, 2, '.', ''); + if (bccomp($allocated, $cap, 2) <= 0) { + return $items; + } + $scaled = []; + $sum = '0.00'; + $count = count($items); + foreach ($items as $index => $item) { + if ($index === $count - 1) { + $item['amount'] = (float) bcsub($cap, $sum, 2); + } else { + $ratio = bcdiv($item['amount'], $allocated, 8); + $part = bcmul($cap, $ratio, 2); + $item['amount'] = (float) $part; + $sum = bcadd($sum, $part, 2); + } + if ($item['amount'] > 0) { + $scaled[] = $item; + } + } + return $scaled; + } + public function querySplit($hfSeqId) { SdkLoader::requireRequest('V2TradeTransSplitQueryRequest.php'); diff --git a/application/admin/controller/shopro/split/Order.php b/application/admin/controller/shopro/split/Order.php index f232e6b..27b1dfc 100644 --- a/application/admin/controller/shopro/split/Order.php +++ b/application/admin/controller/shopro/split/Order.php @@ -31,8 +31,36 @@ class Order extends Base public function retry($ids = null) { $id = $ids ?: $this->request->post('id'); + $row = $this->model->get($id); $result = (new SplitExecutor())->retry($id); - $result ? $this->success('重试成功') : $this->error('重试失败,请查看记录'); + if ($result) { + $this->success('重试成功'); + } + $hint = ''; + if ($row) { + $latest = $this->model->where('order_id', $row['order_id']) + ->where('channel', $row['channel'] ?? 'wechat') + ->order('id', 'desc') + ->find(); + if ($latest && !empty($latest['wx_result'])) { + $raw = is_string($latest['wx_result']) ? $latest['wx_result'] : json_encode($latest['wx_result'], JSON_UNESCAPED_UNICODE); + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $parts = array_filter([ + $decoded['resp_desc'] ?? '', + $decoded['resp_code'] ?? '', + $decoded['bank_message'] ?? '', + ]); + $hint = $parts ? (':' . implode(' ', $parts)) : (':' . mb_substr($raw, 0, 300)); + } else { + $hint = ':' . mb_substr($raw, 0, 300); + } + } + } + if ($hint === '') { + $hint = ':常见为分账明细为空(佣金/规则未匹配到汇付接收方)、汇付未开通分账/延时分账权限、接收方未绑定分账关系'; + } + $this->error('重试失败,请查看分账记录 wx_result' . $hint); } public function query($ids = null)