From 40676e3e2a00256ae8528d0c998087a707e4aa26 Mon Sep 17 00:00:00 2001 From: chen Date: Sat, 26 Sep 2026 14:17:30 +0800 Subject: [PATCH] =?UTF-8?q?test(partner):=20=E5=A2=9E=E5=8A=A0=E4=B8=8A?= =?UTF-8?q?=E7=BA=BF=E5=89=8D=E5=85=A8=E6=94=B6=E7=9B=8A=E9=93=BE=E8=B7=AF?= =?UTF-8?q?=E4=B8=8E=E7=9C=9F=E5=AE=9E=E4=B8=8B=E5=8D=95=E6=94=B6=E7=9B=8A?= =?UTF-8?q?=E6=A0=B8=E7=AE=97=E7=BB=88=E5=AE=A1=E9=AA=8C=E6=94=B6=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../verify_full_order_rewards_and_settle.php | 397 ++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 tests/partner/verify_full_order_rewards_and_settle.php diff --git a/tests/partner/verify_full_order_rewards_and_settle.php b/tests/partner/verify_full_order_rewards_and_settle.php new file mode 100644 index 0000000..abac858 --- /dev/null +++ b/tests/partner/verify_full_order_rewards_and_settle.php @@ -0,0 +1,397 @@ + $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_type' => 'wechat', + '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_sku_price_id' => 94, + 'goods_price' => $payFee, + 'goods_num' => 1, + 'pay_fee' => $payFee, + 'dispatch_fee' => '0.00', + 'status' => 'paid', + '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']) == 398.00, "推三返一第3单返还金额准确为 ¥398.00 (实际: ¥{$push3['amount']})"); + $this->assert($push3['status'] == 1, "推三返一状态为已入账 (status=1)"); + } + + // ② 验证酒道馆团队管理奖 (team_venue) + $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['a']->id, "酒道馆受益人为链上最近酒道馆 A (#{$chain['a']->id})"); + } + + // ③ 验证区县团队管理奖 (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, '398.00', 2) === 0, "推荐人 C 钱包余额 (money) 严格净增 398.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']) == 398.00, "生成对应的 money 变动流水: 变更 +398.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());