文件
Henglianshutong/tests/partner/verify_full_order_rewards_and_settle.php

413 行
18 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace tests\partner;
define('APP_PATH', __DIR__ . '/../../application/');
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
require_once __DIR__ . '/../../thinkphp/base.php';
require_once __DIR__ . '/../../thinkphp/helper.php';
require_once __DIR__ . '/../../application/common.php';
if (is_file(ROOT_PATH . '.env')) {
$env = parse_ini_file(ROOT_PATH . '.env', true);
foreach ($env as $key => $val) {
$name = 'PHP_' . strtoupper($key);
if (is_array($val)) {
foreach ($val as $k => $v) {
putenv("{$name}_" . strtoupper($k) . "={$v}");
}
} else {
putenv("{$name}={$val}");
}
}
}
\think\Config::set(\think\Config::load(APP_PATH . 'config.php'));
\think\Config::set(include APP_PATH . 'database.php', 'database');
\think\Loader::addNamespace('app', APP_PATH);
\think\Loader::addNamespace('addons', ROOT_PATH . 'addons/');
require_once __DIR__ . '/../../addons/shopro/helper/helper.php';
use think\Db;
use app\admin\model\shopro\order\Order as OrderModel;
use app\admin\model\shopro\order\OrderItem;
use app\admin\model\shopro\user\User as UserModel;
use addons\shopro\service\partner\PartnerRewardService;
use addons\shopro\service\partner\DailySettleService;
use addons\shopro\service\partner\PartnerConfig;
use addons\shopro\service\partner\IdentityService;
use addons\shopro\service\user\NicknameService;
use addons\shopro\service\user\UserAuth;
class FullOrderRewardVerification
{
private $passed = 0;
private $failed = 0;
private $logs = [];
public function run()
{
echo "========================================================\n";
echo " 恒联数通 · 上线前全收益链路与规范核算终审验收\n";
echo "========================================================\n\n";
// 1. 验证规范昵称生成
$this->verifyNicknameGeneration();
// 2. 准备验收测试关系链 (A: 顶级馆兼县 -> B: 创客V2 -> C: 品酒师推三返一 -> D: 买家)
$chain = $this->prepareTestChain();
// 3. 执行下单与支付
$order = $this->createAndPayOrder($chain);
// 4. 验证即时奖励 (推三返一、创客极差、团队管理奖、兑换券计划)
$this->verifyInstantRewards($order, $chain);
// 5. 验证 30 天保活与秒级倒计时恢复 (方案 A)
$this->verifyActiveCountdown($order, $chain);
// 6. 执行并验证日结/分钟均分奖池 (酒道馆均分、区县均分、DMEPA均分)
$this->verifyShareRewards($order, $chain);
// 7. 方案 B 核心规范校验:所有收益必须入账用户钱包余额 money
$this->verifySchemeBWalletIntegrity($chain);
echo "\n--------------------------------------------------------\n";
echo "验收执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
echo "========================================================\n";
return $this->failed === 0 ? 0 : 1;
}
private function assert($condition, $testName, $detail = '')
{
if ($condition) {
echo " [PASS] {$testName}\n";
$this->passed++;
} else {
echo " [FAIL] {$testName} - {$detail}\n";
$this->failed++;
}
}
/**
* 1. 验证新规范昵称生成 (HL+手机尾号,若重复追加数字)
*/
private function verifyNicknameGeneration()
{
echo "【一、新用户注册规范昵称规则校验】\n";
$mobileTest = '18900005758';
$nickname1 = NicknameService::generate($mobileTest);
$this->assert(strpos($nickname1, 'HL5758') === 0, "手机号尾号 5758 生成规范前缀 HL5758 (实际: {$nickname1})");
// 模拟已存在重名
$existing = Db::name('user')->where('nickname', 'HL5758')->value('id');
if (!$existing) {
Db::name('user')->insert([
'username' => 'test_dummy_5758',
'nickname' => 'HL5758',
'mobile' => '13000005758',
'status' => 'normal',
'jointime' => time()
]);
}
$nickname2 = NicknameService::generate($mobileTest);
$this->assert($nickname2 === 'HL57581' || $nickname2 === 'HL57582', "重名时自动追加递增数字 (实际: {$nickname2})");
echo "\n";
}
/**
* 2. 构建或复用测试关系链
*/
private function prepareTestChain()
{
echo "【二、检查与构建全收益验收关系链】\n";
// 顶级 A (用户 20): 创客V5, is_venue=1, is_county=1 (馆兼县)
$userA = UserModel::get(20);
$this->assert($userA !== null, "顶级节点用户 20 存在");
// B: 创客 (用户 22): level=3 (创客V2), parent=20
$userB = UserModel::get(22);
$this->assert($userB !== null, "中间创客节点用户 22 存在");
// C: 品酒师 (直推人,用于推三返一测试): 确保 parent=22
$userC = UserModel::where('mobile', '13800009991')->find();
if (!$userC) {
$userC = UserModel::create([
'username' => 'tester_push3_c',
'nickname' => '推三推荐人C',
'mobile' => '13800009991',
'parent_user_id' => 22,
'status' => 'normal',
'money' => 0.00,
'commission' => 0.00,
'jointime' => time()
]);
}
$agentC = Db::name('shopro_commission_agent')->where('user_id', $userC->id)->find();
if (!$agentC) {
Db::name('shopro_commission_agent')->insert([
'user_id' => $userC->id,
'level' => 1,
'status' => 'normal',
'direct_order_seq' => 2, // 设为 2,下一单正好是第 3 单触发推三返一!
'last_self_order_time' => time(),
'createtime' => time()
]);
} else {
Db::name('shopro_commission_agent')->where('user_id', $userC->id)->update([
'direct_order_seq' => 2,
'last_self_order_time' => time()
]);
}
$this->assert(true, "直推人 C (#{$userC->id}) 已就绪,direct_order_seq=2,等待第3单触发返还");
// D: 买家 (新下单用户): parent=C
$userD = UserModel::where('mobile', '13800009992')->find();
if (!$userD) {
$nicknameD = NicknameService::generate('13800009992');
$userD = UserModel::create([
'username' => 'tester_buyer_d',
'nickname' => $nicknameD,
'mobile' => '13800009992',
'parent_user_id' => $userC->id,
'status' => 'normal',
'money' => 0.00,
'commission' => 0.00,
'jointime' => time()
]);
} else {
$userD->parent_user_id = $userC->id;
$userD->save();
}
$this->assert($userD->nickname === 'HL9992', "买家 D (#{$userD->id}) 规范昵称符合 HL+尾号: {$userD->nickname}");
echo "\n";
return [
'a' => $userA,
'b' => $userB,
'c' => $userC,
'd' => $userD,
];
}
/**
* 3. 创建并支付 1398.00 元合格白酒订单
*/
private function createAndPayOrder($chain)
{
echo "【三、模拟真实合格订单下单与支付】\n";
$buyer = $chain['d'];
$orderSn = 'HLTEST' . date('YmdHis') . mt_rand(100, 999);
$payFee = '1398.00';
$paidTime = time();
$order = OrderModel::create([
'order_sn' => $orderSn,
'user_id' => $buyer->id,
'type' => 'goods',
'status' => 'paid',
'pay_fee' => $payFee,
'goods_amount' => $payFee,
'dispatch_amount' => '0.00',
'pay_mode' => 'online',
'paid_time' => $paidTime,
'createtime' => $paidTime,
'updatetime' => $paidTime,
'ext' => json_encode(['from_app' => 'online_acceptance'])
]);
OrderItem::create([
'order_id' => $order->id,
'user_id' => $buyer->id,
'goods_id' => 34,
'goods_title' => '莫高飞天(持花飞天)2瓶',
'goods_image' => '/uploads/20260921/9ff2018f2d99a2134c36066e88c8b073.png',
'goods_sku_price_id' => 94,
'goods_price' => $payFee,
'goods_num' => 1,
'pay_fee' => $payFee,
'dispatch_fee' => '0.00',
'dispatch_type' => 'express',
'dispatch_status' => 0,
'aftersale_status' => 0,
'createtime' => $paidTime,
'updatetime' => $paidTime,
'ext' => json_encode(['is_commission' => 1])
]);
$this->assert($order->id > 0, "成功生成实付 1398.00 元白酒合格订单 (#{$order->id}, {$orderSn})");
// 记录结算前各用户钱包余额
$this->recordBeforeBalances($chain);
// 触发即时收益结算
$rewardService = new PartnerRewardService();
$settleResult = $rewardService->settleOrder($order->id);
$this->assert($settleResult === true, "PartnerRewardService::settleOrder 结算执行成功");
echo "\n";
return $order;
}
private function recordBeforeBalances($chain)
{
$this->chainBalances = [];
foreach ($chain as $k => $u) {
$fresh = Db::name('user')->where('id', $u->id)->find();
$this->chainBalances[$k] = [
'money' => floatval($fresh['money']),
'commission' => floatval($fresh['commission'])
];
}
}
/**
* 4. 验证即时奖励与收益明细
*/
private function verifyInstantRewards($order, $chain)
{
echo "【四、核查订单产生的所有即时奖项明细】\n";
$orderId = $order->id;
$rewards = Db::name('shopro_partner_reward')->where('order_id', $orderId)->select();
$this->assert(count($rewards) >= 4, "订单产生完整的即时奖励流水(共 " . count($rewards) . " 笔)");
$rewardMap = [];
foreach ($rewards as $r) {
$rewardMap[$r['reward_type']][] = $r;
echo " -> 奖项: {$r['reward_type']} | 受益人: #{$r['user_id']} | 金额: ¥{$r['amount']} | 状态: {$r['status']} | 备注: {$r['ext']}\n";
}
// ① 验证推三返一 (push3)
$push3 = $rewardMap['push3'][0] ?? null;
$this->assert($push3 !== null, "包含推三返一奖项");
if ($push3) {
$this->assert($push3['user_id'] == $chain['c']->id, "推三返一受益人为直推人 C (#{$chain['c']->id})");
$this->assert(floatval($push3['amount']) == 600.00, "推三返一第3单返还金额准确为 ¥600.00 (实际: ¥{$push3['amount']})");
$this->assert($push3['status'] == 1, "推三返一状态为已入账 (status=1)");
}
// ② 验证酒道馆团队管理奖 (team_venue) 及 平级奖 (venue_peer)
$venueReward = $rewardMap['team_venue'][0] ?? null;
$this->assert($venueReward !== null, "包含酒道馆团队管理奖");
if ($venueReward) {
$this->assert(floatval($venueReward['amount']) == 50.00, "酒道馆管理奖金额为 ¥50.00 (实际: ¥{$venueReward['amount']})");
$this->assert($venueReward['user_id'] == $chain['b']->id, "酒道馆受益人为链上最近酒道馆 B (#{$chain['b']->id})");
}
$venuePeerReward = $rewardMap['venue_peer'][0] ?? null;
$this->assert($venuePeerReward !== null, "包含酒道馆平级奖 (上级同级酒道馆触发平级分润)");
if ($venuePeerReward) {
$this->assert(floatval($venuePeerReward['amount']) == 5.00, "酒道馆第1代平级奖 50×10% = ¥5.00 (实际: ¥{$venuePeerReward['amount']})");
$this->assert($venuePeerReward['user_id'] == $chain['a']->id, "酒道馆平级受益人为上级酒道馆 A (#{$chain['a']->id})");
}
// ③ 验证创客极差奖 (maker_diff)
$diffRewards = $rewardMap['maker_diff'] ?? [];
$this->assert(count($diffRewards) >= 2, "包含至少2级创客极差奖流水 (共 " . count($diffRewards) . " 笔)");
$diffTotal = array_sum(array_column($diffRewards, 'amount'));
$this->assert($diffTotal == 100.00, "创客极差逐级瓜分总额准确为 ¥100.00 (40+60, 实际: ¥{$diffTotal})");
// ④ 验证区县团队管理奖 (team_county)
$countyReward = $rewardMap['team_county'][0] ?? null;
$this->assert($countyReward !== null, "包含区县团队管理奖");
if ($countyReward) {
$this->assert(floatval($countyReward['amount']) == 50.00, "区县管理奖金额为 ¥50.00 (实际: ¥{$countyReward['amount']})");
$this->assert($countyReward['user_id'] == $chain['a']->id, "区县受益人为链上最近区县 A (#{$chain['a']->id})");
}
// ④ 验证兑换券释放计划 (voucher_grant)
$plan = Db::name('shopro_partner_voucher_plan')->where('order_id', $orderId)->find();
$this->assert($plan !== null, "兑换券释放计划已生成");
if ($plan) {
$this->assert($plan['user_id'] == $chain['d']->id, "兑换券受益人为买家本人 D (#{$chain['d']->id})");
$this->assert(floatval($plan['total_amount']) == 2097.00, "1398 实付 × 1.5倍 释放兑换券总额为 2097.00 (实际: {$plan['total_amount']})");
$this->assert(intval($plan['total_days']) == 365, "兑换券释放周期为 365 天");
}
echo "\n";
}
/**
* 5. 验证 30 天保活与秒级倒计时恢复
*/
private function verifyActiveCountdown($order, $chain)
{
echo "【五、核验 30 天保活与秒级倒计时恢复 (方案 A)】\n";
$buyerId = $chain['d']->id;
$agentD = Db::name('shopro_commission_agent')->where('user_id', $buyerId)->find();
$this->assert(!empty($agentD['last_self_order_time']), "买家 D last_self_order_time 已成功激活");
// 调用 IdentityService 检查输出的时间戳与天数
$attached = IdentityService::attachAgent($agentD);
$this->assert($attached['active_30d'] === true, "买家 D active_30d 为 true (活跃有效)");
$this->assert($attached['active_status'] === 'active', "买家 D active_status 为 'active' (正常活跃)");
$this->assert($attached['days_remaining'] == 30, "买家 D 剩余天数满额显示 30 天 (实际: {$attached['days_remaining']})");
// 校验到期时间戳:订单支付时刻 + 30 * 86400 (方案 A 精确顺延)
$expectedExpire = intval($order->paid_time) + 30 * 86400;
$this->assert($attached['expire_time'] == $expectedExpire, "到期时间戳精确顺延720小时,绝无零点截断 (实际: {$attached['expire_time']}, 期望: {$expectedExpire})");
// 验证直推人 C 也获得双向保活更新
$agentC = Db::name('shopro_commission_agent')->where('user_id', $chain['c']->id)->find();
$this->assert($agentC['last_self_order_time'] >= $order->paid_time, "直推上级 C 同样被直推订单双向激活保活");
echo "\n";
}
/**
* 6. 执行并验证日结/分钟均分奖池 (酒道馆均分、区县均分、DMEPA均分)
*/
private function verifyShareRewards($order, $chain)
{
echo "【六、执行并验证平台均分奖池 (分钟结算/日结)】\n";
$dailyService = new DailySettleService();
$minuteRes = $dailyService->runMinute();
$this->assert(is_array($minuteRes), "DailySettleService::runMinute 分钟清算执行成功");
// 检查均分奖池中是否包含当前订单的贡献
$venueShares = Db::name('shopro_partner_reward')
->where('reward_type', 'venue_share')
->where('ext', 'like', "%{$order->id}%")
->select();
$this->assert(count($venueShares) > 0, "酒道馆均分奖池成功包含本单贡献 (共 " . count($venueShares) . " 家酒道馆获得均分)");
$countyShares = Db::name('shopro_partner_reward')
->where('reward_type', 'county_share')
->where('ext', 'like', "%{$order->id}%")
->select();
$this->assert(count($countyShares) > 0, "区县代理均分奖池成功包含本单贡献 (共 " . count($countyShares) . " 家区县获得均分)");
echo "\n";
}
/**
* 7. 方案 B 核心规范校验:所有收益必须入账用户钱包余额 money
*/
private function verifySchemeBWalletIntegrity($chain)
{
echo "【七、方案 B 一体化大余额闭环校验 (严禁走废弃 commission)】\n";
// 校验直推人 C
$freshC = Db::name('user')->where('id', $chain['c']->id)->find();
$moneyDiffC = bcsub((string)$freshC['money'], (string)$this->chainBalances['c']['money'], 2);
$this->assert(bccomp($moneyDiffC, '600.00', 2) === 0, "推荐人 C 钱包余额 (money) 严格净增 600.00 元 (实际增加: {$moneyDiffC})");
$this->assert(bccomp((string)$freshC['commission'], '0.00', 2) === 0, "推荐人 C commission 保持为 0.00,绝不走废弃钱包");
// 校验钱包流水日志
$moneyLogC = Db::name('user_wallet_log')
->where('user_id', $chain['c']->id)
->where('wallet_type', 'money')
->order('id', 'desc')
->find();
$this->assert($moneyLogC !== null && floatval($moneyLogC['wallet']) == 600.00, "生成对应的 money 变动流水: 变更 +600.00, 备注: {$moneyLogC['memo']}");
// 校验酒道馆/区县 A 的钱包增加 (50 + 50 + 均分)
$freshA = Db::name('user')->where('id', $chain['a']->id)->find();
$moneyDiffA = bcsub((string)$freshA['money'], (string)$this->chainBalances['a']['money'], 2);
$this->assert(floatval($moneyDiffA) >= 100.00, "节点 A 钱包余额严格增加至少 100.00 元 (管理奖 50+50 + 均分分润,实际增加: {$moneyDiffA})");
echo "\n";
}
}
$verifier = new FullOrderRewardVerification();
exit($verifier->run());