```
feat(shopro_huifu): 完善汇付分账服务功能 - 实现分账确认金额解析逻辑,确保与汇付待确认金额一致 - 添加分账项目缩放调整功能,防止分配总额超出限制 - 优化分账金额计算精度处理,使用BC数学函数保证准确性 - 改进后台重试接口错误提示,提供详细的失败原因信息 ```
这个提交包含在:
@@ -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');
|
||||
|
||||
@@ -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)
|
||||
|
||||
在新工单中引用
屏蔽一个用户