```
feat(shopro): 添加汇付延时分账对账功能 - 新增 huifu_delay_reconcile.sql 对账脚本,包含多个维度的分账核对查询 - 在 split_commission_verify.php 中添加 audit-delay 命令,支持实时查询汇付未确认金额 - 实现递归解析汇付查单响应的功能,支持嵌套JSON数据提取 - 提供延时分账订单审计功能,可对比本地状态与汇付侧 unconfirm_amt - 支持批量诊断延时分账待确认订单,便于排查资金卡顿问题 ```
这个提交包含在:
@@ -10,6 +10,7 @@
|
||||
* php addons/shopro/tests/split_commission_verify.php diagnose-missing [limit=20]
|
||||
* php addons/shopro/tests/split_commission_verify.php try-split {order_sn} --execute # 补跑汇付分账(慎用)
|
||||
* php addons/shopro/tests/split_commission_verify.php try-split {order_sn} --confirm-only # 仅延时分账确认、不分账(排查22000002)
|
||||
* php addons/shopro/tests/split_commission_verify.php audit-delay [limit=30] [--live] # 延迟户对账:本地+可选汇付查单 unconfirm_amt
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
@@ -62,9 +63,14 @@ switch ($command) {
|
||||
$confirmOnly = in_array('--confirm-only', $argv, true);
|
||||
trySplitOrder($sn, $execute, $confirmOnly);
|
||||
break;
|
||||
case 'audit-delay':
|
||||
$limit = max(1, min(200, intval($argv[2] ?? 30)));
|
||||
$live = in_array('--live', $argv, true);
|
||||
auditDelayUnconfirm($limit, $live);
|
||||
break;
|
||||
default:
|
||||
echo "未知命令: {$command}\n";
|
||||
echo "可用: summary | anomalies | order {order_sn} | recent-success [limit] | diagnose-missing [limit] | try-split {order_sn} [--execute|--confirm-only]\n";
|
||||
echo "可用: summary | anomalies | order {order_sn} | recent-success [limit] | diagnose-missing [limit] | try-split {order_sn} [--execute|--confirm-only] | audit-delay [limit] [--live]\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -718,6 +724,137 @@ function printOrderDetail($orderSn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟专户余额排查:列出 delay_acct=Y 订单,可选 live 查汇付 unconfirm_amt
|
||||
*/
|
||||
/** 从汇付查单响应(可能嵌套 JSON 字符串)取字段 */
|
||||
function huifuQueryFieldRecursive($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 huifuQueryFieldRecursive($decoded, $key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
return null;
|
||||
}
|
||||
if (array_key_exists($key, $data)) {
|
||||
return $data[$key];
|
||||
}
|
||||
foreach ($data as $v) {
|
||||
$found = huifuQueryFieldRecursive($v, $key);
|
||||
if ($found !== null && $found !== '') {
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function auditDelayUnconfirm($limit, $live)
|
||||
{
|
||||
$since = time() - 180 * 86400;
|
||||
$peFields = 'o.id, o.order_sn, o.pay_fee, o.paytime, o.status, pe.confirm_status, pe.confirm_hf_seq_id';
|
||||
if (tableHasColumn('shopro_huifu_pay_ext', 'split_timing_at_pay')) {
|
||||
$peFields .= ', pe.split_timing_at_pay';
|
||||
}
|
||||
$rows = Db::name('shopro_order')->alias('o')
|
||||
->join('shopro_huifu_pay_ext pe', 'pe.order_id = o.id')
|
||||
->where('o.pay_type', 'huifu_wechat')
|
||||
->where('o.paytime', '>=', $since)
|
||||
->whereRaw("UPPER(IFNULL(pe.delay_acct_flag, '')) = 'Y'")
|
||||
->field($peFields)
|
||||
->order('o.paytime', 'desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
|
||||
echo "========================================\n";
|
||||
echo " 延时分账订单审计(最多 {$limit} 笔,近180天 delay_acct=Y)\n";
|
||||
echo " --live 会调汇付支付查单,以 unconfirm_amt 为准\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
$querySvc = null;
|
||||
if ($live && class_exists('\\addons\\shopro_huifu\\library\\HuifuQueryService')) {
|
||||
$querySvc = new \addons\shopro_huifu\library\HuifuQueryService();
|
||||
} elseif ($live) {
|
||||
echo "⚠ 未安装 shopro_huifu,忽略 --live\n\n";
|
||||
}
|
||||
|
||||
$sumUnconfirm = '0.00';
|
||||
$pendingCount = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$oid = intval($row['id']);
|
||||
$split = Db::name('shopro_split_order')
|
||||
->where('order_id', $oid)
|
||||
->where('channel', 'huifu')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
$splitStatus = $split ? intval($split['status']) : -1;
|
||||
$splitLabel = $split ? (['0' => '待', '1' => '中', '2' => '成功', '3' => '部分', '4' => '失败'][$splitStatus] ?? $splitStatus) : '无记录';
|
||||
|
||||
$unconfirm = null;
|
||||
$remoteDelay = null;
|
||||
$freeze = null;
|
||||
$confirmResp = null;
|
||||
if ($querySvc) {
|
||||
try {
|
||||
$q = $querySvc->queryByOrderSn($row['order_sn']);
|
||||
$unconfirm = huifuQueryFieldRecursive($q, 'unconfirm_amt');
|
||||
$remoteDelay = huifuQueryFieldRecursive($q, 'delay_acct_flag');
|
||||
$freeze = huifuQueryFieldRecursive($q, 'fund_freeze_stat');
|
||||
if ($unconfirm !== null && $unconfirm !== '' && bccomp((string) $unconfirm, '0.00', 2) > 0) {
|
||||
$sumUnconfirm = bcadd($sumUnconfirm, number_format((float) $unconfirm, 2, '.', ''), 2);
|
||||
$pendingCount++;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$confirmResp = '查单失败: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$localClaimOk = ($row['confirm_status'] === 'S' || $splitStatus === 2);
|
||||
$flagMismatch = false;
|
||||
if ($unconfirm !== null && $unconfirm !== '' && bccomp((string) $unconfirm, '0.00', 2) > 0 && $localClaimOk) {
|
||||
$flagMismatch = true;
|
||||
}
|
||||
|
||||
echo str_repeat('-', 72) . "\n";
|
||||
echo "order_sn: {$row['order_sn']} id={$oid} pay_fee={$row['pay_fee']}\n";
|
||||
$timingAtPay = isset($row['split_timing_at_pay']) ? $row['split_timing_at_pay'] : '-';
|
||||
echo 'pay_time: ' . date('Y-m-d H:i:s', $row['paytime']) . " order_status={$row['status']} split_timing_at_pay={$timingAtPay}\n";
|
||||
echo "本地: confirm_status=" . ($row['confirm_status'] ?: 'NULL') . " confirm_hf_seq_id=" . ($row['confirm_hf_seq_id'] ?: '-') . "\n";
|
||||
echo "本地分账: {$splitLabel}" . ($split ? " split_amount={$split['split_amount']} id={$split['id']}" : '') . "\n";
|
||||
if ($querySvc && $confirmResp === null) {
|
||||
echo "汇付查单: delay_acct_flag={$remoteDelay} unconfirm_amt={$unconfirm} fund_freeze_stat={$freeze}\n";
|
||||
} elseif ($confirmResp) {
|
||||
echo "{$confirmResp}\n";
|
||||
}
|
||||
if ($flagMismatch) {
|
||||
echo ">>> ⚠ 本地显示已确认/分账成功,但汇付仍有 unconfirm_amt(符合汇付说的「返回链路/终态未对齐」)\n";
|
||||
} elseif ($splitLabel === '无记录') {
|
||||
echo ">>> ⚠ 从未分账:多为 split_timing=confirm 且未确认收货,或当时未触发分账\n";
|
||||
} elseif ($splitStatus === 4) {
|
||||
echo ">>> ⚠ 分账失败,资金可能仍在延迟户\n";
|
||||
} elseif ($unconfirm !== null && bccomp((string) $unconfirm, '0.00', 2) > 0) {
|
||||
echo ">>> ⚠ 汇付侧仍有待确认金额\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n========================================\n";
|
||||
if ($querySvc) {
|
||||
echo "汇付查单汇总: 仍有 unconfirm_amt>0 的订单 {$pendingCount} 笔,合计约 ¥{$sumUnconfirm}\n";
|
||||
echo "(可与控台「延迟户」余额对照;内扣手续费后单笔待确认略小于 pay_fee)\n";
|
||||
} else {
|
||||
echo "加 --live 调汇付查单 unconfirm_amt,才能精确对应延迟户余额。\n";
|
||||
}
|
||||
echo "处理: 无分账记录 → 确认收货或 try-split --execute;仅释款 → try-split --confirm-only\n";
|
||||
}
|
||||
|
||||
function printRecentSuccess($limit)
|
||||
{
|
||||
$rows = Db::name('shopro_split_order')->alias('so')
|
||||
|
||||
在新工单中引用
屏蔽一个用户