分账核验脚本
这个提交包含在:
@@ -9,7 +9,7 @@
|
||||
--
|
||||
-- ⚠️ 执行前务必完整备份数据库!
|
||||
-- ⚠️ 默认种子会员 id=1 (13888888888) 不在白名单中,也会被删除
|
||||
-- ⚠️ 若某张表不存在(如未安装汇付插件),请注释对应 DELETE 语句
|
||||
-- ⚠️ 若某张表不存在(如未安装汇付/微信交易/商学院/运营公司模块),请注释对应 DELETE 语句
|
||||
--
|
||||
-- 用法:mysql -u用户名 -p 数据库名 < member_cleanup_keep_whitelist.sql
|
||||
-- =============================================================================
|
||||
@@ -36,6 +36,39 @@ WHERE mobile NOT IN (
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
);
|
||||
|
||||
-- 待删订单及插件扩展(需先构建 tmp_keep_users / tmp_delete_users 临时表后再跑)
|
||||
SELECT '待删商城订单' AS step, COUNT(*) AS cnt
|
||||
FROM fa_shopro_order o
|
||||
INNER JOIN fa_user u ON u.id = o.user_id
|
||||
LEFT JOIN (
|
||||
SELECT id FROM fa_user WHERE mobile IN (
|
||||
'13757629486','13732177165','13595350591','18188532666','19858299151',
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
)
|
||||
) k ON u.id = k.id
|
||||
WHERE k.id IS NULL;
|
||||
|
||||
SELECT '待删汇付支付扩展' AS step, COUNT(*) AS cnt
|
||||
FROM fa_shopro_huifu_pay_ext pe
|
||||
INNER JOIN fa_shopro_order o ON o.id = pe.order_id
|
||||
INNER JOIN fa_user u ON u.id = o.user_id
|
||||
WHERE u.mobile NOT IN (
|
||||
'13757629486','13732177165','13595350591','18188532666','19858299151',
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
);
|
||||
|
||||
SELECT '待删微信发货同步' AS step, COUNT(*) AS cnt
|
||||
FROM fa_shopro_wechat_trade_sync ws
|
||||
INNER JOIN fa_shopro_order o ON o.id = ws.order_id
|
||||
INNER JOIN fa_user u ON u.id = o.user_id
|
||||
WHERE u.mobile NOT IN (
|
||||
'13757629486','13732177165','13595350591','18188532666','19858299151',
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
);
|
||||
*/
|
||||
|
||||
START TRANSACTION;
|
||||
@@ -94,6 +127,58 @@ SELECT DISTINCT rao.order_id
|
||||
FROM fa_shopro_regional_agent_order rao
|
||||
INNER JOIN tmp_delete_users du ON rao.agent_user_id = du.id;
|
||||
|
||||
-- 插件/附属表反查订单(避免主订单已删但扩展数据残留)
|
||||
INSERT IGNORE INTO tmp_delete_orders (order_id)
|
||||
SELECT DISTINCT so.order_id
|
||||
FROM fa_shopro_split_order so
|
||||
INNER JOIN fa_shopro_order o ON o.id = so.order_id
|
||||
INNER JOIN tmp_delete_users du ON o.user_id = du.id;
|
||||
|
||||
INSERT IGNORE INTO tmp_delete_orders (order_id)
|
||||
SELECT DISTINCT pe.order_id
|
||||
FROM fa_shopro_huifu_pay_ext pe
|
||||
INNER JOIN fa_shopro_order o ON o.id = pe.order_id
|
||||
INNER JOIN tmp_delete_users du ON o.user_id = du.id;
|
||||
|
||||
INSERT IGNORE INTO tmp_delete_orders (order_id)
|
||||
SELECT DISTINCT ws.order_id
|
||||
FROM fa_shopro_wechat_trade_sync ws
|
||||
INNER JOIN fa_shopro_order o ON o.id = ws.order_id
|
||||
INNER JOIN tmp_delete_users du ON o.user_id = du.id
|
||||
WHERE ws.order_id > 0;
|
||||
|
||||
INSERT IGNORE INTO tmp_delete_orders (order_id)
|
||||
SELECT DISTINCT v.order_id
|
||||
FROM fa_shopro_verify v
|
||||
INNER JOIN tmp_delete_users du ON v.user_id = du.id
|
||||
WHERE v.order_id > 0;
|
||||
|
||||
INSERT IGNORE INTO tmp_delete_orders (order_id)
|
||||
SELECT DISTINCT gl.order_id
|
||||
FROM fa_shopro_activity_groupon_log gl
|
||||
INNER JOIN tmp_delete_users du ON gl.user_id = du.id
|
||||
WHERE gl.order_id > 0;
|
||||
|
||||
INSERT IGNORE INTO tmp_delete_orders (order_id)
|
||||
SELECT DISTINCT gc.order_id
|
||||
FROM fa_shopro_goods_comment gc
|
||||
INNER JOIN tmp_delete_users du ON gc.user_id = du.id
|
||||
WHERE gc.order_id > 0;
|
||||
|
||||
-- 收集待删订单号(退款/结算明细等按 order_sn 关联)
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_delete_order_sns;
|
||||
CREATE TEMPORARY TABLE tmp_delete_order_sns (order_sn VARCHAR(64) PRIMARY KEY);
|
||||
|
||||
INSERT INTO tmp_delete_order_sns (order_sn)
|
||||
SELECT DISTINCT o.order_sn
|
||||
FROM fa_shopro_order o
|
||||
INNER JOIN tmp_delete_orders d ON o.id = d.order_id;
|
||||
|
||||
INSERT IGNORE INTO tmp_delete_order_sns (order_sn)
|
||||
SELECT DISTINCT t.order_sn
|
||||
FROM fa_shopro_trade_order t
|
||||
INNER JOIN tmp_delete_users du ON t.user_id = du.id;
|
||||
|
||||
-- 收集待删区域代理记录 ID(用于分账接收方等)
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_delete_regional_agents;
|
||||
CREATE TEMPORARY TABLE tmp_delete_regional_agents (id INT PRIMARY KEY);
|
||||
@@ -171,6 +256,9 @@ INNER JOIN tmp_delete_orders o ON co.order_id = o.order_id;
|
||||
DELETE l FROM fa_shopro_commission_log l
|
||||
INNER JOIN tmp_delete_users du ON l.agent_id = du.id;
|
||||
|
||||
DELETE l FROM fa_shopro_commission_log l
|
||||
INNER JOIN tmp_delete_orders o ON l.event = 'order' AND l.event_id = o.order_id;
|
||||
|
||||
DELETE a FROM fa_shopro_commission_agent a
|
||||
INNER JOIN tmp_delete_users du ON a.user_id = du.id;
|
||||
|
||||
@@ -201,9 +289,32 @@ INNER JOIN tmp_delete_regional_agents ra ON ho.role_type = 'regional_agent' AND
|
||||
-- =============================================================================
|
||||
-- 8. 商城订单及附属
|
||||
-- =============================================================================
|
||||
-- 汇付支付扩展(若未安装汇付插件,注释本节)
|
||||
DELETE pe FROM fa_shopro_huifu_pay_ext pe
|
||||
INNER JOIN tmp_delete_orders o ON pe.order_id = o.order_id;
|
||||
|
||||
-- 微信发货/确认收货同步(若未安装 wechat_trade 插件,注释本节)
|
||||
DELETE ws FROM fa_shopro_wechat_trade_sync ws
|
||||
INNER JOIN tmp_delete_orders o ON ws.order_id = o.order_id;
|
||||
|
||||
DELETE rl FROM fa_shopro_refund_log rl
|
||||
INNER JOIN fa_shopro_order o ON rl.order_sn = o.order_sn
|
||||
INNER JOIN tmp_delete_orders d ON o.id = d.order_id;
|
||||
INNER JOIN tmp_delete_order_sns s ON rl.order_sn = s.order_sn;
|
||||
|
||||
DELETE v FROM fa_shopro_verify v
|
||||
INNER JOIN tmp_delete_orders o ON v.order_id = o.order_id;
|
||||
|
||||
DELETE gl FROM fa_shopro_activity_groupon_log gl
|
||||
INNER JOIN tmp_delete_orders o ON gl.order_id = o.order_id;
|
||||
|
||||
DELETE gc FROM fa_shopro_goods_comment gc
|
||||
INNER JOIN tmp_delete_orders o ON gc.order_id = o.order_id;
|
||||
|
||||
-- 商学院/运营公司结算明细(若未安装对应模块,注释本节)
|
||||
DELETE si FROM fa_shopro_business_school_settlement_item si
|
||||
INNER JOIN tmp_delete_order_sns s ON si.order_sn = s.order_sn;
|
||||
|
||||
DELETE si FROM fa_shopro_operating_company_settlement_item si
|
||||
INNER JOIN tmp_delete_order_sns s ON si.order_sn = s.order_sn;
|
||||
|
||||
DELETE inv FROM fa_shopro_order_invoice inv
|
||||
INNER JOIN tmp_delete_orders o ON inv.order_id = o.order_id;
|
||||
@@ -356,4 +467,33 @@ SELECT id, nickname, mobile, createtime
|
||||
FROM fa_user
|
||||
ORDER BY id;
|
||||
|
||||
SELECT '非白名单会员订单残留' AS info, COUNT(*) AS cnt
|
||||
FROM fa_shopro_order o
|
||||
INNER JOIN fa_user u ON u.id = o.user_id
|
||||
WHERE u.mobile NOT IN (
|
||||
'13757629486','13732177165','13595350591','18188532666','19858299151',
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
);
|
||||
|
||||
SELECT '汇付支付扩展残留' AS info, COUNT(*) AS cnt
|
||||
FROM fa_shopro_huifu_pay_ext pe
|
||||
LEFT JOIN fa_shopro_order o ON o.id = pe.order_id
|
||||
LEFT JOIN fa_user u ON u.id = o.user_id
|
||||
WHERE o.id IS NULL OR u.mobile NOT IN (
|
||||
'13757629486','13732177165','13595350591','18188532666','19858299151',
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
);
|
||||
|
||||
SELECT '微信发货同步残留' AS info, COUNT(*) AS cnt
|
||||
FROM fa_shopro_wechat_trade_sync ws
|
||||
LEFT JOIN fa_shopro_order o ON o.id = ws.order_id
|
||||
LEFT JOIN fa_user u ON u.id = o.user_id
|
||||
WHERE ws.order_id = 0 OR o.id IS NULL OR u.mobile NOT IN (
|
||||
'13757629486','13732177165','13595350591','18188532666','19858299151',
|
||||
'13985723672','13906609262','18768436875','13780074281','18230780880',
|
||||
'18285358372','13368531489','15329832578'
|
||||
);
|
||||
|
||||
SELECT '清理完成' AS info;
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
/**
|
||||
* 分账开关 & 已分账订单佣金去向 · CLI 核验
|
||||
*
|
||||
* 用法(项目根目录):
|
||||
* php addons/shopro/tests/split_commission_verify.php summary
|
||||
* php addons/shopro/tests/split_commission_verify.php anomalies
|
||||
* php addons/shopro/tests/split_commission_verify.php order {order_sn}
|
||||
* php addons/shopro/tests/split_commission_verify.php recent-success [limit=20]
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit("请通过命令行运行\n");
|
||||
}
|
||||
|
||||
define('APP_PATH', __DIR__ . '/../../../application/');
|
||||
define('ROOT_PATH', __DIR__ . '/../../../');
|
||||
|
||||
if (!is_file(APP_PATH . 'admin/command/Install/install.lock')) {
|
||||
fwrite(STDERR, "项目未安装,无法连接数据库。\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require ROOT_PATH . 'thinkphp/base.php';
|
||||
\think\App::initCommon();
|
||||
|
||||
use think\Db;
|
||||
|
||||
$argv = $GLOBALS['argv'] ?? [];
|
||||
$command = $argv[1] ?? 'summary';
|
||||
|
||||
switch ($command) {
|
||||
case 'summary':
|
||||
printSummary();
|
||||
break;
|
||||
case 'anomalies':
|
||||
printAnomalies();
|
||||
break;
|
||||
case 'order':
|
||||
$sn = $argv[2] ?? '';
|
||||
if ($sn === '') {
|
||||
fwrite(STDERR, "请指定订单号: order {order_sn}\n");
|
||||
exit(1);
|
||||
}
|
||||
printOrderDetail($sn);
|
||||
break;
|
||||
case 'recent-success':
|
||||
$limit = max(1, min(100, intval($argv[2] ?? 20)));
|
||||
printRecentSuccess($limit);
|
||||
break;
|
||||
default:
|
||||
echo "未知命令: {$command}\n";
|
||||
echo "可用: summary | anomalies | order {order_sn} | recent-success [limit]\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function splitConfigMap()
|
||||
{
|
||||
return Db::name('shopro_split_config')->column('value', 'name');
|
||||
}
|
||||
|
||||
function huifuConfigMap()
|
||||
{
|
||||
if (!Db::query("SHOW TABLES LIKE '" . config('database.prefix') . "shopro_huifu_config'")) {
|
||||
return [];
|
||||
}
|
||||
return Db::name('shopro_huifu_config')->column('value', 'name');
|
||||
}
|
||||
|
||||
function inferSplitPath(array $sc, array $hf)
|
||||
{
|
||||
$enable = ($sc['split_enable'] ?? '0') === '1';
|
||||
if (!$enable) {
|
||||
return '未走支付分账(split_enable=0)';
|
||||
}
|
||||
$mode = $sc['split_mode'] ?? 'wechat';
|
||||
if ($mode === 'wallet') {
|
||||
return '未走支付分账(split_mode=wallet 仅钱包)';
|
||||
}
|
||||
$channel = $hf['miniprogram_channel'] ?? 'yansongda';
|
||||
$hfOn = ($hf['status'] ?? '0') === '1';
|
||||
$provider = $sc['split_provider'] ?? 'auto';
|
||||
if ($hfOn && $channel === 'huifu') {
|
||||
if (in_array($mode, ['huifu', 'both'], true) || in_array($provider, ['auto', 'huifu'], true)) {
|
||||
return '汇付延时分账(huifu_wechat 订单,确认时 acct_split_bunch)';
|
||||
}
|
||||
}
|
||||
if (in_array($mode, ['wechat', 'both'], true)) {
|
||||
return '微信分账(pay_type=wechat 订单)';
|
||||
}
|
||||
return '分账已开,请结合 pay_type 与 split_mode/provider 人工确认';
|
||||
}
|
||||
|
||||
function printSummary()
|
||||
{
|
||||
$sc = splitConfigMap();
|
||||
$hf = huifuConfigMap();
|
||||
$commEvent = Db::name('shopro_commission_config')->where('name', 'commission_event')->value('value');
|
||||
$regEvent = Db::name('shopro_regional_agent_config')->where('name', 'commission_event')->value('value');
|
||||
|
||||
echo "========================================\n";
|
||||
echo " 分账 & 佣金核验 · 摘要\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
echo "【分账配置】\n";
|
||||
foreach (['split_enable', 'split_mode', 'split_provider', 'split_timing', 'split_amount_source', 'split_max_rate', 'delay_acct_enable', 'platform_huifu_id'] as $k) {
|
||||
echo " {$k}: " . ($sc[$k] ?? '(未配置)') . "\n";
|
||||
}
|
||||
echo "\n【汇付】\n";
|
||||
foreach (['status', 'miniprogram_channel', 'huifu_id', 'delay_acct_enable'] as $k) {
|
||||
echo " {$k}: " . ($hf[$k] ?? '(无表/未配置)') . "\n";
|
||||
}
|
||||
echo "\n【佣金结算事件】\n";
|
||||
echo " 分销 commission_event: " . ($commEvent ?: 'confirm') . "\n";
|
||||
echo " 区域 regional commission_event: " . ($regEvent ?: 'confirm') . "\n";
|
||||
echo " 分账 split_timing: " . ($sc['split_timing'] ?? 'payed') . "\n";
|
||||
if (($sc['split_timing'] ?? 'payed') !== ($commEvent ?: 'confirm')) {
|
||||
echo " ⚠ 分账时机与分销结算事件不一致,可能出现「先网关分账、后钱包入账」\n";
|
||||
}
|
||||
|
||||
echo "\n【推断】\n";
|
||||
echo " " . inferSplitPath($sc, $hf) . "\n";
|
||||
|
||||
echo "\n【代码行为说明】\n";
|
||||
echo " · 网关分账金额来自 commission/regional reward 或 split_rule\n";
|
||||
echo " · 汇付:推广员/区域 huifu_id 收 div_amt,余款进 platform_huifu_id 或主商户 huifu_id\n";
|
||||
echo " · 业务佣金结算仍写 shopro_user_wallet_log(commission_income / regional_agent_income)\n";
|
||||
echo " · Config::shouldSkipWallet() 未接入 Reward,分账开启时仍可能同时入账钱包 → 用 anomalies 命令排查\n";
|
||||
|
||||
$since = time() - 90 * 86400;
|
||||
$stats = Db::name('shopro_split_order')->alias('so')
|
||||
->join('shopro_order o', 'o.id = so.order_id')
|
||||
->where('o.paid_time', '>=', $since)
|
||||
->field('so.channel, so.status, COUNT(*) cnt, SUM(so.split_amount) split_sum')
|
||||
->group('so.channel, so.status')
|
||||
->select();
|
||||
echo "\n【近90天分账订单】\n";
|
||||
if (empty($stats)) {
|
||||
echo " (无记录)\n";
|
||||
} else {
|
||||
foreach ($stats as $row) {
|
||||
echo " channel={$row['channel']} status={$row['status']} cnt={$row['cnt']} split_sum={$row['split_sum']}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printAnomalies()
|
||||
{
|
||||
$since = time() - 90 * 86400;
|
||||
echo "========================================\n";
|
||||
echo " 异常检测(近90天)\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
$pfx = config('database.prefix');
|
||||
$double = Db::query("
|
||||
SELECT so.order_sn, so.split_amount, wl.wallet_sum
|
||||
FROM {$pfx}shopro_split_order so
|
||||
INNER JOIN {$pfx}shopro_order o ON o.id = so.order_id
|
||||
INNER JOIN (
|
||||
SELECT order_id, SUM(wallet_sum) AS wallet_sum FROM (
|
||||
SELECT cr.order_id, SUM(wl.wallet) AS wallet_sum
|
||||
FROM {$pfx}shopro_commission_reward cr
|
||||
INNER JOIN {$pfx}shopro_user_wallet_log wl
|
||||
ON wl.item_id = cr.id AND wl.type = 'commission_income' AND wl.wallet_type = 'money'
|
||||
WHERE cr.status = 1
|
||||
GROUP BY cr.order_id
|
||||
UNION ALL
|
||||
SELECT rr.order_id, SUM(wl.wallet)
|
||||
FROM {$pfx}shopro_regional_agent_reward rr
|
||||
INNER JOIN {$pfx}shopro_user_wallet_log wl
|
||||
ON wl.item_id = rr.id AND wl.type = 'regional_agent_income' AND wl.wallet_type = 'money'
|
||||
WHERE rr.status = 1
|
||||
GROUP BY rr.order_id
|
||||
) t GROUP BY order_id
|
||||
) wl ON wl.order_id = so.order_id
|
||||
WHERE so.status IN (2,3) AND so.split_amount > 0 AND wl.wallet_sum > 0
|
||||
AND o.paid_time >= ?
|
||||
ORDER BY so.id DESC
|
||||
LIMIT 30
|
||||
", [$since]);
|
||||
|
||||
echo "【可能双重发放】网关分账成功且钱包有佣金流水(最多30条)\n";
|
||||
if (empty($double)) {
|
||||
echo " ✓ 未发现\n\n";
|
||||
} else {
|
||||
foreach ($double as $r) {
|
||||
echo " {$r['order_sn']} split={$r['split_amount']} wallet={$r['wallet_sum']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
$failed = Db::name('shopro_split_order')->where('status', 4)->order('id desc')->limit(15)->select();
|
||||
echo "【分账失败 status=4】\n";
|
||||
if (empty($failed)) {
|
||||
echo " ✓ 无\n\n";
|
||||
} else {
|
||||
foreach ($failed as $r) {
|
||||
$msg = mb_substr($r['wx_result'] ?? '', 0, 80);
|
||||
echo " {$r['order_sn']} channel={$r['channel']} retry={$r['retry_count']} {$msg}\n";
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
$missing = Db::name('shopro_order')->alias('o')
|
||||
->where('o.pay_type', 'huifu_wechat')
|
||||
->where('o.paid_time', '>=', $since)
|
||||
->whereNotExists(function ($q) {
|
||||
$q->name('shopro_split_order')->alias('so')
|
||||
->whereRaw('so.order_id = o.id')
|
||||
->where('so.channel', 'huifu')
|
||||
->where('so.status', 'in', [1, 2, 3]);
|
||||
})
|
||||
->whereExists(function ($q) {
|
||||
$q->name('shopro_commission_reward')->alias('cr')
|
||||
->whereRaw('cr.order_id = o.id')
|
||||
->where('cr.status', '>=', 0);
|
||||
})
|
||||
->field('o.id, o.order_sn, o.pay_fee')
|
||||
->limit(20)
|
||||
->select();
|
||||
|
||||
echo "【汇付单有分销佣金但无成功/进行中分账记录】\n";
|
||||
if (empty($missing)) {
|
||||
echo " ✓ 未发现(或分账未开启/无佣金)\n";
|
||||
} else {
|
||||
foreach ($missing as $r) {
|
||||
echo " {$r['order_sn']} pay_fee={$r['pay_fee']}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printOrderDetail($orderSn)
|
||||
{
|
||||
$order = Db::name('shopro_order')->where('order_sn', $orderSn)->find();
|
||||
if (!$order) {
|
||||
echo "订单不存在: {$orderSn}\n";
|
||||
exit(1);
|
||||
}
|
||||
$oid = $order['id'];
|
||||
echo "订单 {$orderSn} id={$oid} pay_type={$order['pay_type']} pay_fee={$order['pay_fee']}\n\n";
|
||||
|
||||
$splits = Db::name('shopro_split_order')->where('order_id', $oid)->select();
|
||||
echo "【分账主表】\n";
|
||||
print_r($splits);
|
||||
|
||||
if ($splits) {
|
||||
$ids = array_column($splits, 'id');
|
||||
$lines = Db::name('shopro_split_order_receiver')->alias('sor')
|
||||
->join('shopro_split_receiver sr', 'sr.id = sor.receiver_id', 'LEFT')
|
||||
->where('sor.split_order_id', 'in', $ids)
|
||||
->field('sor.*, sr.receiver_account, sr.channel, sr.receiver_type')
|
||||
->select();
|
||||
echo "\n【分账明细 → 接收方账户(网关去向)】\n";
|
||||
print_r($lines);
|
||||
}
|
||||
|
||||
$cr = Db::name('shopro_commission_reward')->where('order_id', $oid)->select();
|
||||
$rr = Db::name('shopro_regional_agent_reward')->where('order_id', $oid)->select();
|
||||
echo "\n【分销/区域佣金记录(业务层)】\n";
|
||||
echo "commission_reward: " . count($cr) . " 条\n";
|
||||
echo "regional_reward: " . count($rr) . " 条\n";
|
||||
|
||||
$rewardIds = array_merge(
|
||||
array_column($cr, 'id'),
|
||||
array_column($rr, 'id')
|
||||
);
|
||||
$wl = [];
|
||||
if ($rewardIds) {
|
||||
$wl = Db::name('shopro_user_wallet_log')
|
||||
->where('item_id', 'in', $rewardIds)
|
||||
->where('wallet_type', 'money')
|
||||
->where('type', 'in', ['commission_income', 'regional_agent_income', 'commission_back', 'regional_agent_back'])
|
||||
->select();
|
||||
}
|
||||
echo "\n【钱包流水(用户可提现余额来源)】\n";
|
||||
print_r($wl);
|
||||
|
||||
$ext = Db::name('shopro_huifu_pay_ext')->where('order_id', $oid)->find();
|
||||
if ($ext) {
|
||||
echo "\n【汇付支付扩展】\n";
|
||||
print_r($ext);
|
||||
}
|
||||
}
|
||||
|
||||
function printRecentSuccess($limit)
|
||||
{
|
||||
$rows = Db::name('shopro_split_order')->alias('so')
|
||||
->join('shopro_order o', 'o.id = so.order_id')
|
||||
->where('so.status', 'in', [2, 3])
|
||||
->field('so.order_sn, so.channel, so.total_amount, so.split_amount, so.createtime, o.pay_type')
|
||||
->order('so.id desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
echo "最近 {$limit} 笔分账成功订单:\n";
|
||||
foreach ($rows as $r) {
|
||||
$remain = round($r['total_amount'] - $r['split_amount'], 2);
|
||||
echo date('Y-m-d H:i', $r['createtime']) . " {$r['order_sn']} {$r['channel']} pay={$r['total_amount']} split={$r['split_amount']} platform_remain≈{$remain}\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
-- =============================================================================
|
||||
-- 分账开关 & 已分账订单佣金去向 · 线上只读核验脚本
|
||||
-- =============================================================================
|
||||
-- 用途:
|
||||
-- 1) 判断当前是否走支付通道分账(微信分账 / 汇付延时分账)
|
||||
-- 2) 核对已分账订单:网关分账明细 vs 业务佣金/钱包入账是否一致、有无重复发放风险
|
||||
--
|
||||
-- 用法:
|
||||
-- mysql -u用户名 -p 数据库名 < split_commission_verify.sql
|
||||
-- 或在客户端中分段执行(建议先跑「一、全局配置」)
|
||||
--
|
||||
-- 表前缀默认 fa_,若不同请全局替换 fa_
|
||||
-- 本脚本仅 SELECT,不修改数据
|
||||
-- =============================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- =============================================================================
|
||||
-- 一、全局配置:是否开启分账、走哪条通道
|
||||
-- =============================================================================
|
||||
SELECT '=== 1. 分账总配置 shopro_split_config ===' AS section;
|
||||
|
||||
SELECT name, value, description
|
||||
FROM fa_shopro_split_config
|
||||
WHERE name IN (
|
||||
'split_enable', 'split_mode', 'split_provider', 'split_timing',
|
||||
'split_amount_source', 'split_max_rate', 'delay_acct_enable',
|
||||
'platform_receiver_account', 'platform_receiver_name',
|
||||
'platform_huifu_id', 'platform_acct_id'
|
||||
)
|
||||
ORDER BY name;
|
||||
|
||||
SELECT '=== 2. 汇付支付配置 shopro_huifu_config(关键项)===' AS section;
|
||||
|
||||
SELECT name, value
|
||||
FROM fa_shopro_huifu_config
|
||||
WHERE name IN (
|
||||
'status', 'env', 'huifu_id', 'upper_huifu_id',
|
||||
'miniprogram_channel', 'delay_acct_enable', 'onboard_required'
|
||||
)
|
||||
ORDER BY name;
|
||||
|
||||
SELECT '=== 3. 分销/区域代理结算事件(与分账时机对比)===' AS section;
|
||||
|
||||
SELECT 'commission' AS cfg, name, value
|
||||
FROM fa_shopro_commission_config
|
||||
WHERE name IN ('commission_event', 'level', 'self_buy')
|
||||
UNION ALL
|
||||
SELECT 'regional_agent', name, value
|
||||
FROM fa_shopro_regional_agent_config
|
||||
WHERE name IN ('commission_event', 'status');
|
||||
|
||||
-- 人工解读(见下方说明):
|
||||
-- split_enable=1 且 split_mode 非 wallet → 会尝试支付通道分账
|
||||
-- miniprogram_channel=huifu + split 开启 + pay_type=huifu_wechat → 汇付延时分账
|
||||
-- split_timing 默认 payed;commission_event 默认 confirm → 可能出现「先分账、后入账钱包」
|
||||
|
||||
SELECT '=== 4. 配置结论(自动推断,供对照)===' AS section;
|
||||
|
||||
SELECT
|
||||
sc_enable.value AS split_enable,
|
||||
sc_mode.value AS split_mode,
|
||||
sc_provider.value AS split_provider,
|
||||
sc_timing.value AS split_timing,
|
||||
sc_delay.value AS split_delay_acct_enable,
|
||||
hf_channel.value AS miniprogram_channel,
|
||||
hf_status.value AS huifu_plugin_status,
|
||||
CASE
|
||||
WHEN IFNULL(sc_enable.value, '0') <> '1' THEN '未走支付分账(总开关关闭)'
|
||||
WHEN sc_mode.value = 'wallet' THEN '未走支付分账(仅钱包模式)'
|
||||
WHEN hf_channel.value = 'huifu' AND IFNULL(hf_status.value, '0') = '1'
|
||||
AND sc_mode.value IN ('huifu', 'both')
|
||||
THEN '走汇付延时分账(huifu_wechat 订单)'
|
||||
WHEN hf_channel.value = 'huifu' AND IFNULL(hf_status.value, '0') = '1'
|
||||
AND IFNULL(sc_provider.value, 'auto') IN ('auto', 'huifu')
|
||||
THEN '走汇付延时分账(auto/huifu 提供方 + 小程序汇付通道)'
|
||||
WHEN sc_mode.value IN ('wechat', 'both') THEN '走微信分账(pay_type=wechat 订单)'
|
||||
ELSE '分账已开但通道需结合 pay_type 人工确认'
|
||||
END AS inferred_split_path
|
||||
FROM (SELECT value FROM fa_shopro_split_config WHERE name = 'split_enable' LIMIT 1) sc_enable
|
||||
CROSS JOIN (SELECT IFNULL(value, 'wechat') AS value FROM fa_shopro_split_config WHERE name = 'split_mode' LIMIT 1) sc_mode
|
||||
CROSS JOIN (SELECT IFNULL(value, 'auto') AS value FROM fa_shopro_split_config WHERE name = 'split_provider' LIMIT 1) sc_provider
|
||||
CROSS JOIN (SELECT IFNULL(value, 'payed') AS value FROM fa_shopro_split_config WHERE name = 'split_timing' LIMIT 1) sc_timing
|
||||
CROSS JOIN (SELECT IFNULL(value, '0') AS value FROM fa_shopro_split_config WHERE name = 'delay_acct_enable' LIMIT 1) sc_delay
|
||||
CROSS JOIN (SELECT IFNULL(value, 'yansongda') AS value FROM fa_shopro_huifu_config WHERE name = 'miniprogram_channel' LIMIT 1) hf_channel
|
||||
CROSS JOIN (SELECT IFNULL(value, '0') AS value FROM fa_shopro_huifu_config WHERE name = 'status' LIMIT 1) hf_status;
|
||||
|
||||
-- =============================================================================
|
||||
-- 二、分账订单统计(近 90 天,可改 INTERVAL)
|
||||
-- =============================================================================
|
||||
SELECT '=== 5. 分账订单按状态/通道汇总(近90天)===' AS section;
|
||||
|
||||
SELECT
|
||||
so.channel,
|
||||
so.status,
|
||||
CASE so.status
|
||||
WHEN 0 THEN '待分账'
|
||||
WHEN 1 THEN '分账中'
|
||||
WHEN 2 THEN '成功'
|
||||
WHEN 3 THEN '部分成功'
|
||||
WHEN 4 THEN '失败'
|
||||
ELSE CONCAT('未知(', so.status, ')')
|
||||
END AS status_label,
|
||||
COUNT(*) AS cnt,
|
||||
ROUND(SUM(so.split_amount), 2) AS sum_split_amount,
|
||||
ROUND(SUM(so.total_amount), 2) AS sum_order_pay
|
||||
FROM fa_shopro_split_order so
|
||||
INNER JOIN fa_shopro_order o ON o.id = so.order_id
|
||||
WHERE o.paid_time >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY))
|
||||
GROUP BY so.channel, so.status
|
||||
ORDER BY so.channel, so.status;
|
||||
|
||||
SELECT '=== 6. 已支付订单 pay_type 分布(近90天)===' AS section;
|
||||
|
||||
SELECT
|
||||
pay_type,
|
||||
COUNT(*) AS order_cnt,
|
||||
ROUND(SUM(pay_fee), 2) AS sum_pay_fee
|
||||
FROM fa_shopro_order
|
||||
WHERE status IN ('paid', 'completed', 'pending', 'nosend', 'nocomment')
|
||||
AND paid_time >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY))
|
||||
GROUP BY pay_type;
|
||||
|
||||
-- =============================================================================
|
||||
-- 三、已分账订单:佣金去了哪里(网关 vs 钱包)
|
||||
-- =============================================================================
|
||||
-- 网关分账:fa_shopro_split_order_receiver + receiver 表(汇付 huifu_id / 微信 openid)
|
||||
-- 业务佣金:fa_shopro_commission_reward / fa_shopro_regional_agent_reward
|
||||
-- 钱包入账:fa_shopro_user_wallet_log type=commission_income / regional_agent_income
|
||||
-- 代码说明:shouldSkipWallet() 尚未接入 Reward,分账开启时仍可能同时入账钱包(需重点核对)
|
||||
|
||||
SELECT '=== 7. 分账成功订单明细(最近 30 笔)===' AS section;
|
||||
|
||||
SELECT
|
||||
so.id AS split_order_id,
|
||||
so.order_id,
|
||||
so.order_sn,
|
||||
so.channel,
|
||||
so.status,
|
||||
so.total_amount AS order_pay_fee,
|
||||
so.split_amount AS split_to_receivers,
|
||||
ROUND(so.total_amount - so.split_amount, 2) AS implied_platform_remain,
|
||||
FROM_UNIXTIME(so.createtime) AS split_at,
|
||||
so.confirm_hf_seq_id,
|
||||
o.pay_type
|
||||
FROM fa_shopro_split_order so
|
||||
INNER JOIN fa_shopro_order o ON o.id = so.order_id
|
||||
WHERE so.status IN (2, 3)
|
||||
ORDER BY so.id DESC
|
||||
LIMIT 30;
|
||||
|
||||
SELECT '=== 8. 上述成功分账的接收方明细(最近 30 单)===' AS section;
|
||||
|
||||
SELECT
|
||||
so.order_sn,
|
||||
sor.role_type,
|
||||
sor.amount,
|
||||
sor.description,
|
||||
sor.status AS line_status,
|
||||
sr.receiver_account,
|
||||
sr.receiver_type,
|
||||
sr.channel AS receiver_channel,
|
||||
sr.receiver_name
|
||||
FROM fa_shopro_split_order so
|
||||
INNER JOIN fa_shopro_split_order_receiver sor ON sor.split_order_id = so.id
|
||||
LEFT JOIN fa_shopro_split_receiver sr ON sr.id = sor.receiver_id
|
||||
WHERE so.status IN (2, 3)
|
||||
AND so.id IN (
|
||||
SELECT id FROM (
|
||||
SELECT so2.id
|
||||
FROM fa_shopro_split_order so2
|
||||
WHERE so2.status IN (2, 3)
|
||||
ORDER BY so2.id DESC
|
||||
LIMIT 30
|
||||
) t
|
||||
)
|
||||
ORDER BY so.order_sn DESC, sor.id;
|
||||
|
||||
SELECT '=== 9. 分账成功订单 · 业务佣金与钱包对照 ===' AS section;
|
||||
|
||||
SELECT
|
||||
so.order_sn,
|
||||
so.channel,
|
||||
so.split_amount AS gateway_split_sum,
|
||||
IFNULL(cr.dist_commission, 0) AS dist_reward_sum,
|
||||
IFNULL(cr.dist_accounted, 0) AS dist_wallet_settled_cnt,
|
||||
IFNULL(rr.reg_commission, 0) AS regional_reward_sum,
|
||||
IFNULL(rr.reg_accounted, 0) AS regional_wallet_settled_cnt,
|
||||
IFNULL(wl.wallet_commission, 0) AS wallet_commission_income,
|
||||
IFNULL(wl.wallet_regional, 0) AS wallet_regional_income,
|
||||
CASE
|
||||
WHEN IFNULL(wl.wallet_commission, 0) + IFNULL(wl.wallet_regional, 0) > 0
|
||||
AND so.split_amount > 0
|
||||
THEN '⚠ 网关已分账且钱包也有佣金流水(可能双重发放,需业务确认)'
|
||||
WHEN so.split_amount > 0
|
||||
AND IFNULL(wl.wallet_commission, 0) + IFNULL(wl.wallet_regional, 0) = 0
|
||||
AND IFNULL(cr.dist_accounted, 0) + IFNULL(rr.reg_accounted, 0) = 0
|
||||
THEN '✓ 佣金主要在支付通道(未入钱包或尚未结算)'
|
||||
WHEN so.split_amount = 0 THEN '分账金额为0(检查接收方/规则)'
|
||||
ELSE '请人工核对'
|
||||
END AS check_hint
|
||||
FROM fa_shopro_split_order so
|
||||
INNER JOIN fa_shopro_order o ON o.id = so.order_id
|
||||
LEFT JOIN (
|
||||
SELECT order_id,
|
||||
SUM(commission) AS dist_commission,
|
||||
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS dist_accounted
|
||||
FROM fa_shopro_commission_reward
|
||||
WHERE status >= 0
|
||||
GROUP BY order_id
|
||||
) cr ON cr.order_id = so.order_id
|
||||
LEFT JOIN (
|
||||
SELECT order_id,
|
||||
SUM(commission) AS reg_commission,
|
||||
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS reg_accounted
|
||||
FROM fa_shopro_regional_agent_reward
|
||||
WHERE status >= 0
|
||||
GROUP BY order_id
|
||||
) rr ON rr.order_id = so.order_id
|
||||
LEFT JOIN (
|
||||
SELECT order_id,
|
||||
SUM(wallet_commission) AS wallet_commission,
|
||||
SUM(wallet_regional) AS wallet_regional
|
||||
FROM (
|
||||
SELECT cr.order_id,
|
||||
SUM(CASE WHEN wl.type = 'commission_income' THEN wl.wallet ELSE 0 END) AS wallet_commission,
|
||||
0 AS wallet_regional
|
||||
FROM fa_shopro_commission_reward cr
|
||||
INNER JOIN fa_shopro_user_wallet_log wl
|
||||
ON wl.item_id = cr.id AND wl.type = 'commission_income' AND wl.wallet_type = 'money'
|
||||
WHERE cr.status = 1
|
||||
GROUP BY cr.order_id
|
||||
UNION ALL
|
||||
SELECT rr.order_id, 0,
|
||||
SUM(CASE WHEN wl.type = 'regional_agent_income' THEN wl.wallet ELSE 0 END)
|
||||
FROM fa_shopro_regional_agent_reward rr
|
||||
INNER JOIN fa_shopro_user_wallet_log wl
|
||||
ON wl.item_id = rr.id AND wl.type = 'regional_agent_income' AND wl.wallet_type = 'money'
|
||||
WHERE rr.status = 1
|
||||
GROUP BY rr.order_id
|
||||
) wsum
|
||||
GROUP BY order_id
|
||||
) wl ON wl.order_id = so.order_id
|
||||
WHERE so.status IN (2, 3)
|
||||
ORDER BY so.id DESC
|
||||
LIMIT 50;
|
||||
|
||||
-- =============================================================================
|
||||
-- 四、异常检测(应排查项)
|
||||
-- =============================================================================
|
||||
SELECT '=== 10. 有佣金记录但分账失败/缺失(汇付单,近90天)===' AS section;
|
||||
|
||||
SELECT
|
||||
o.id AS order_id,
|
||||
o.order_sn,
|
||||
o.pay_type,
|
||||
o.pay_fee,
|
||||
IFNULL(so.status, -99) AS split_status,
|
||||
LEFT(so.wx_result, 120) AS split_fail_msg,
|
||||
IFNULL(cr.reward_cnt, 0) + IFNULL(rr.reward_cnt, 0) AS reward_cnt,
|
||||
IFNULL(cr.reward_sum, 0) + IFNULL(rr.reward_sum, 0) AS reward_sum
|
||||
FROM fa_shopro_order o
|
||||
LEFT JOIN (
|
||||
SELECT order_id, COUNT(*) AS reward_cnt, SUM(commission) AS reward_sum
|
||||
FROM fa_shopro_commission_reward WHERE status >= 0 GROUP BY order_id
|
||||
) cr ON cr.order_id = o.id
|
||||
LEFT JOIN (
|
||||
SELECT order_id, COUNT(*) AS reward_cnt, SUM(commission) AS reward_sum
|
||||
FROM fa_shopro_regional_agent_reward WHERE status >= 0 GROUP BY order_id
|
||||
) rr ON rr.order_id = o.id
|
||||
LEFT JOIN fa_shopro_split_order so ON so.order_id = o.id AND so.channel = 'huifu'
|
||||
WHERE o.pay_type = 'huifu_wechat'
|
||||
AND o.paid_time >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY))
|
||||
AND (IFNULL(cr.reward_cnt, 0) + IFNULL(rr.reward_cnt, 0)) > 0
|
||||
AND (so.id IS NULL OR so.status IN (0, 4))
|
||||
ORDER BY o.id DESC
|
||||
LIMIT 50;
|
||||
|
||||
SELECT '=== 11. 分账失败记录(status=4,最近 20 条)===' AS section;
|
||||
|
||||
SELECT
|
||||
so.order_id,
|
||||
so.order_sn,
|
||||
so.channel,
|
||||
so.split_amount,
|
||||
so.retry_count,
|
||||
FROM_UNIXTIME(so.next_retry_time) AS next_retry,
|
||||
LEFT(so.wx_result, 200) AS fail_reason
|
||||
FROM fa_shopro_split_order so
|
||||
WHERE so.status = 4
|
||||
ORDER BY so.id DESC
|
||||
LIMIT 20;
|
||||
|
||||
SELECT '=== 12. 有推广/区域佣金但缺少汇付接收方(导致无法分账)===' AS section;
|
||||
|
||||
SELECT DISTINCT
|
||||
r.order_id,
|
||||
o.order_sn,
|
||||
'commission' AS reward_source,
|
||||
a.user_id AS beneficiary_user_id,
|
||||
u.mobile,
|
||||
ho.huifu_id AS onboard_huifu_id,
|
||||
sr.id AS split_receiver_id
|
||||
FROM fa_shopro_commission_reward r
|
||||
INNER JOIN fa_shopro_order o ON o.id = r.order_id
|
||||
INNER JOIN fa_shopro_commission_agent a ON a.id = r.agent_id
|
||||
INNER JOIN fa_user u ON u.id = a.user_id
|
||||
LEFT JOIN fa_shopro_huifu_onboard ho ON ho.user_id = a.user_id AND ho.status = 'approved'
|
||||
LEFT JOIN fa_shopro_split_receiver sr ON sr.role_type = 'promoter'
|
||||
AND sr.role_id = a.user_id AND sr.channel = 'huifu' AND sr.status = 'normal'
|
||||
WHERE r.status >= 0
|
||||
AND o.pay_type = 'huifu_wechat'
|
||||
AND o.paid_time >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY))
|
||||
AND sr.id IS NULL
|
||||
LIMIT 30;
|
||||
|
||||
SELECT '=== 13. 延时分账标记:支付扩展 delay_acct(近90天汇付单抽样)===' AS section;
|
||||
|
||||
SELECT
|
||||
pe.order_id,
|
||||
o.order_sn,
|
||||
pe.delay_acct_flag,
|
||||
pe.confirm_status,
|
||||
pe.confirm_hf_seq_id,
|
||||
so.status AS split_order_status
|
||||
FROM fa_shopro_huifu_pay_ext pe
|
||||
INNER JOIN fa_shopro_order o ON o.id = pe.order_id
|
||||
LEFT JOIN fa_shopro_split_order so ON so.order_id = pe.order_id AND so.channel = 'huifu'
|
||||
WHERE o.pay_type = 'huifu_wechat'
|
||||
AND o.paid_time >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY))
|
||||
ORDER BY pe.order_id DESC
|
||||
LIMIT 30;
|
||||
|
||||
SELECT '=== 14. 分账接收方台账(汇付/微信)===' AS section;
|
||||
|
||||
SELECT channel, role_type, COUNT(*) AS cnt,
|
||||
SUM(CASE WHEN status = 'normal' THEN 1 ELSE 0 END) AS normal_cnt
|
||||
FROM fa_shopro_split_receiver
|
||||
GROUP BY channel, role_type;
|
||||
|
||||
-- =============================================================================
|
||||
-- 五、单笔订单深度核验(把 @order_sn 改成实际订单号后执行)
|
||||
-- =============================================================================
|
||||
/*
|
||||
SET @order_sn = '2025010100000001';
|
||||
|
||||
SELECT o.* FROM fa_shopro_order o WHERE o.order_sn = @order_sn;
|
||||
|
||||
SELECT * FROM fa_shopro_split_order WHERE order_sn = @order_sn;
|
||||
SELECT sor.*, sr.receiver_account, sr.channel
|
||||
FROM fa_shopro_split_order_receiver sor
|
||||
LEFT JOIN fa_shopro_split_receiver sr ON sr.id = sor.receiver_id
|
||||
WHERE sor.split_order_id IN (SELECT id FROM fa_shopro_split_order WHERE order_sn = @order_sn);
|
||||
|
||||
SELECT * FROM fa_shopro_commission_reward WHERE order_id = (SELECT id FROM fa_shopro_order WHERE order_sn = @order_sn LIMIT 1);
|
||||
SELECT * FROM fa_shopro_regional_agent_reward WHERE order_id = (SELECT id FROM fa_shopro_order WHERE order_sn = @order_sn LIMIT 1);
|
||||
|
||||
SELECT wl.*
|
||||
FROM fa_shopro_user_wallet_log wl
|
||||
WHERE wl.wallet_type = 'money'
|
||||
AND wl.type IN ('commission_income', 'regional_agent_income', 'commission_back', 'regional_agent_back')
|
||||
AND (
|
||||
wl.item_id IN (SELECT id FROM fa_shopro_commission_reward WHERE order_id = (SELECT id FROM fa_shopro_order WHERE order_sn = @order_sn LIMIT 1))
|
||||
OR wl.item_id IN (SELECT id FROM fa_shopro_regional_agent_reward WHERE order_id = (SELECT id FROM fa_shopro_order WHERE order_sn = @order_sn LIMIT 1))
|
||||
);
|
||||
|
||||
SELECT * FROM fa_shopro_huifu_pay_ext WHERE order_id = (SELECT id FROM fa_shopro_order WHERE order_sn = @order_sn LIMIT 1);
|
||||
*/
|
||||
在新工单中引用
屏蔽一个用户