diff --git a/addons/shopro/library/Export.php b/addons/shopro/library/Export.php index f82ef94..168e03b 100755 --- a/addons/shopro/library/Export.php +++ b/addons/shopro/library/Export.php @@ -181,6 +181,9 @@ class Export $result = [ 'file_path' => str_replace(ROOT_PATH, '项目目录/', $save_file) ]; + + $writer = new $class_name($spreadsheet); + $writer->save($save_file); } else { $save_file = $fileName . '-' . date('YmdHis') . '.xlsx'; @@ -188,20 +191,37 @@ class Export 'file_name' => $save_file ]; + // 优先安全写入临时文件,确保 Excel 结构与数据完整无误后再输出下载头 + $tempDir = RUNTIME_PATH . 'temp/export/'; + if (!is_dir($tempDir)) { + @mkdir($tempDir, 0755, true); + } + $tempFile = $tempDir . 'exp_' . md5(uniqid((string)mt_rand(), true)) . '.xlsx'; + + $writer = new $class_name($spreadsheet); + $writer->save($tempFile); + + if (!file_exists($tempFile) || filesize($tempFile) <= 0) { + @unlink($tempFile); + error_stop('生成导出文件失败,请检查数据完整性'); + } + if (ob_get_level() > 0) { @ob_end_clean(); } - header('pragma:public'); - header("Content-type:application/octet-stream; charset=utf-8; name=" . urlencode($save_file)); - header("Content-Disposition:attachment; filename=" . urlencode($save_file)); //attachment新窗口打印inline本窗口打印 - $save_file = 'php://output'; - } - - $writer = new $class_name($spreadsheet); - $writer->save($save_file); + $fileSize = filesize($tempFile); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header("Content-Disposition: attachment; filename=" . urlencode($save_file) . "; filename*=utf-8''" . urlencode($save_file)); + header('Content-Length: ' . $fileSize); + header('Content-Transfer-Encoding: binary'); + + readfile($tempFile); + @unlink($tempFile); - if ($this->config['save_type'] != 'save') { if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } diff --git a/application/admin/controller/shopro/Withdraw.php b/application/admin/controller/shopro/Withdraw.php index ea435f9..27530d7 100644 --- a/application/admin/controller/shopro/Withdraw.php +++ b/application/admin/controller/shopro/Withdraw.php @@ -24,6 +24,9 @@ class Withdraw extends Common public function _initialize() { parent::_initialize(); + if (defined('ROOT_PATH') && is_file(ROOT_PATH . 'addons/shopro/helper/helper.php')) { + require_once ROOT_PATH . 'addons/shopro/helper/helper.php'; + } $this->model = new WithdrawModel; $this->logModel = new WithdrawLogModel; } diff --git a/composer.json b/composer.json index 1d77667..56d5047 100755 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "fastadminnet/fastadmin-mailer": "^2.0.0", "overtrue/pinyin": "^3.0", "phpoffice/phpspreadsheet": "^1.29.1", + "maennchen/zipstream-php": "^2.4", "overtrue/wechat": "^4.6", "ext-json": "*", "ext-curl": "*", diff --git a/tests/partner/test_withdraw_api_cases.php b/tests/partner/test_withdraw_api_cases.php index b80b9e1..68789ce 100644 --- a/tests/partner/test_withdraw_api_cases.php +++ b/tests/partner/test_withdraw_api_cases.php @@ -2,8 +2,8 @@ /** * 方案 B 提现 API 核心场景模拟测试(端到端) * 覆盖: - * 1. 正常场景:申请提现扣除商城余额 money,驳回原路退还 money - * 2. 业务失败场景:申请提现金额超过商城余额,捕获【商城可用余额不足】 + * 1. 正常场景:申请提现扣除钱包余额 money,驳回原路退还 money + * 2. 业务失败场景:申请提现金额超过钱包余额,捕获【钱包可用余额不足】 * 3. 参数错误场景:申请金额低于最低限额 100 元,捕获【提现金额不能少于 100元】 */ @@ -83,22 +83,22 @@ try { } // ------------------------------------------------------------------- -// 场景 2:业务失败场景(提现金额超出商城可用余额) +// 场景 2:业务失败场景(提现金额超出钱包可用余额) // ------------------------------------------------------------------- -echo "\n--- 2. 业务失败场景:提现金额超出商城可用余额 --- \n"; +echo "\n--- 2. 业务失败场景:提现金额超出钱包可用余额 --- \n"; try { $withdrawService->apply(array_merge($bankInfo, [ 'money' => 800, // 800+48=848,在单笔限额 1000 内,但当前可用余额为 554.65,必触发余额不足 ])); assertCondition(false, '未能拦截余额不足的提现请求'); } catch (ShoproException $e) { - assertCondition(strpos($e->getMessage(), '商城可用余额不足') !== false, "成功拦截超额业务拒绝: " . $e->getMessage()); + assertCondition(strpos($e->getMessage(), '钱包可用余额不足') !== false, "成功拦截超额业务拒绝: " . $e->getMessage()); } // ------------------------------------------------------------------- -// 场景 3:正常提现场景(内扣模式:提现 100 元,仅扣除 100 元商城余额,手续费内扣) +// 场景 3:正常提现场景(内扣模式:提现 100 元,仅扣除 100 元钱包余额,手续费内扣) // ------------------------------------------------------------------- -echo "\n--- 3. 正常业务场景:提现 100 元并校验商城余额内扣 --- \n"; +echo "\n--- 3. 正常业务场景:提现 100 元并校验钱包余额内扣 --- \n"; // 为确保测试环境用户余额充足,临时补充 200 元测试金(测试完成后全额扣回) $originalMoney = floatval($user->money); @@ -129,23 +129,23 @@ if ($testWithdraw) { $afterCommission = floatval($afterUser->commission); $expectedMoney = round($beforeMoney - 100.00, 2); - assertCondition(round($afterMoney, 2) === $expectedMoney, "【内扣】商城余额仅扣除本金 ¥100.00(¥{$beforeMoney} -> ¥{$afterMoney})"); + assertCondition(round($afterMoney, 2) === $expectedMoney, "【内扣】钱包余额仅扣除本金 ¥100.00(¥{$beforeMoney} -> ¥{$afterMoney})"); assertCondition($afterCommission === $beforeCommission, "佣金钱包分毫不差,保持 ¥{$beforeCommission}"); assertCondition($testWithdraw->withdraw_info['wallet_type'] === 'money', "提现订单记录标记为 wallet_type: money"); - assertCondition($testWithdraw->withdraw_info['提现资产'] === '商城余额', "提现订单记录提现资产: 商城余额"); + assertCondition($testWithdraw->withdraw_info['提现资产'] === '钱包余额', "提现订单记录提现资产: 钱包余额"); // 校验内扣净到账金额 (amount - charge_fee = 100 - 6 = 94.00) $expectedPaid = bcsub((string)$testWithdraw->amount, (string)$testWithdraw->charge_fee, 2); assertCondition($expectedPaid === '94.00', "内扣手续费 6.00 元后实际打款金额为 ¥{$expectedPaid}"); // 测试完成后原路驳回并退还余额,恢复环境数据 - echo "\n--- 4. 容灾闭环测试:审核驳回退还商城余额本金 --- \n"; + echo "\n--- 4. 容灾闭环测试:审核驳回退还钱包余额本金 --- \n"; Db::startTrans(); try { $withdrawService->handleRefuse($testWithdraw, 'API自动化测试驳回退还'); Db::commit(); $restoredUser = \app\admin\model\shopro\user\User::get($userId); - assertCondition(round(floatval($restoredUser->money), 2) === round($beforeMoney, 2), "驳回后商城余额原路退还本金 ¥100.00 至 ¥{$beforeMoney}"); + assertCondition(round(floatval($restoredUser->money), 2) === round($beforeMoney, 2), "驳回后钱包余额原路退还本金 ¥100.00 至 ¥{$beforeMoney}"); assertCondition(floatval($restoredUser->commission) === $beforeCommission, "佣金钱包继续保持 ¥{$beforeCommission}"); } catch (\Exception $e) { Db::rollback(); diff --git a/tests/partner/test_withdraw_export_fix_spec.js b/tests/partner/test_withdraw_export_fix_spec.js index 6ee6bfb..ddd099e 100644 --- a/tests/partner/test_withdraw_export_fix_spec.js +++ b/tests/partner/test_withdraw_export_fix_spec.js @@ -40,12 +40,20 @@ assert(modelContent.includes('is_numeric($rate)'), 'getChargeRateFormatAttr 包 assert(modelContent.includes('is_numeric($amount)'), 'getRealAmountAttr 包含 is_numeric 校验'); console.log('[测试点 2] Withdraw 模型源代码加固断言通过'); -// 3. 检查 addons/shopro/library/Export.php 包含缓冲检查与下载退出 +// 3. 检查 addons/shopro/library/Export.php 包含缓冲检查、临时文件生成与安全输出 const exportContent = fs.readFileSync(path.resolve(__dirname, '../../addons/shopro/library/Export.php'), 'utf8'); assert(exportContent.includes('ob_get_level() > 0'), 'Export.php 包含 ob_get_level() 检查防 Notice'); -assert(exportContent.includes("if ($this->config['save_type'] != 'save')"), 'Export.php 包含下载模式检查'); +assert(exportContent.includes('temp/export/'), 'Export.php 优先写入临时文件校验'); +assert(exportContent.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), 'Export.php 使用标准 xlsx mime 类型'); +assert(exportContent.includes('Content-Length'), 'Export.php 包含 Content-Length 响应头'); +assert(exportContent.includes("if ($this->config['save_type'] == 'save')"), 'Export.php 包含 save_type 判断'); assert(exportContent.includes('exit;'), 'Export.php 下载完成安全退出'); -console.log('[测试点 3] Export.php 缓冲区安全清理与下载模式防污染退出断言通过'); +console.log('[测试点 3] Export.php 临时文件安全写入、标准MIME与下载防污染退出断言通过'); + +// 3.1 检查 composer.json 锁定 maennchen/zipstream-php: ^2.4 防 PHP 8.1 readonly 语法错误 +const composerContent = fs.readFileSync(path.resolve(__dirname, '../../composer.json'), 'utf8'); +assert(composerContent.includes('"maennchen/zipstream-php": "^2.4"'), 'composer.json 锁定 zipstream-php: ^2.4'); +console.log('[测试点 3.1] composer.json 兼容性依赖锁定断言通过'); // 4. 检查 application/admin/controller/shopro/Withdraw.php 包含异常捕获、单 key 汇总、时间格式化 const controllerContent = fs.readFileSync(path.resolve(__dirname, '../../application/admin/controller/shopro/Withdraw.php'), 'utf8'); diff --git a/tests/partner/verify_admin_views_alignment_spec.js b/tests/partner/verify_admin_views_alignment_spec.js index 87cb3db..02a650f 100644 --- a/tests/partner/verify_admin_views_alignment_spec.js +++ b/tests/partner/verify_admin_views_alignment_spec.js @@ -30,7 +30,7 @@ assert(rewardModelContent.includes("'wallet_type_text'"), 'Reward 模型 $append assert(rewardModelContent.includes('function getWalletTypeAttr'), '包含 getWalletTypeAttr 属性获取器'); assert(rewardModelContent.includes('function getWalletTypeTextAttr'), '包含 getWalletTypeTextAttr 属性获取器'); assert(rewardModelContent.includes("PartnerConfig::rewardWalletType()"), '现金分润缺省联动 PartnerConfig::rewardWalletType()'); -assert(rewardModelContent.includes("'商城余额'"), 'wallet_type_text 映射包含 商城余额'); +assert(rewardModelContent.includes("'钱包余额'"), 'wallet_type_text 映射包含 钱包余额'); assert(rewardModelContent.includes("'佣金钱包'"), 'wallet_type_text 映射包含 佣金钱包'); assert(rewardModelContent.includes("'分红dmepa'"), 'wallet_type_text 映射包含 分红dmepa'); @@ -52,7 +52,7 @@ console.log('\n--- 3. 订单详情视图 (order/detail.html) 验证 ---'); const orderDetailViewPath = path.join(rootDir, 'application/admin/view/shopro/order/order/detail.html'); assert(fs.existsSync(orderDetailViewPath), 'order/detail.html 文件存在'); const orderDetailViewContent = fs.readFileSync(orderDetailViewPath, 'utf8'); -assert(orderDetailViewContent.includes('直入商城余额'), '分成顶部横幅包含「直入商城余额」专属 Tag 提示'); +assert(orderDetailViewContent.includes('直入钱包余额'), '分成顶部横幅包含「直入钱包余额」专属 Tag 提示'); assert(orderDetailViewContent.includes('label="入账账户"'), '分成明细表格包含「入账账户」列'); assert(orderDetailViewContent.includes('scope.row.wallet_type_text'), '入账账户列展示 scope.row.wallet_type_text'); @@ -86,7 +86,7 @@ assert(userControllerContent.includes("$data['commission'] = number_format"), 'U const userDetailViewPath = path.join(rootDir, 'application/admin/view/shopro/user/user/detail.html'); assert(fs.existsSync(userDetailViewPath), 'user/detail.html 文件存在'); const userDetailViewContent = fs.readFileSync(userDetailViewPath, 'utf8'); -assert(userDetailViewContent.includes('当前商城余额: ¥{{ state.detail.money'), '会员详情卡片1明确为「当前商城余额」'); +assert(userDetailViewContent.includes('当前钱包余额: ¥{{ state.detail.money'), '会员详情卡片1明确为「当前钱包余额」'); assert(userDetailViewContent.includes('历史未转佣金: ¥{{ state.detail.commission'), '会员详情卡片1明确为「历史未转佣金」'); assert(userDetailViewContent.includes('state.detail.total_income || state.detail.income_total_cash'), '卡片1优先绑定统一对齐的 total_income'); assert(userDetailViewContent.match(/log\.tabActive == 'partner_reward'[\s\S]*label="入账账户"/), 'Tab 1 收益中心流水包含「入账账户」列'); @@ -96,7 +96,7 @@ assert(fs.existsSync(userIndexViewPath), 'user/index.html 文件存在'); const userIndexViewContent = fs.readFileSync(userIndexViewPath, 'utf8'); assert(!userIndexViewContent.includes('可提现收益:'), '会员列表已彻底移除误导性文案「可提现收益」'); assert(userIndexViewContent.includes('累计收益:'), '会员列表展示「累计收益」'); -assert(userIndexViewContent.includes('商城余额: ¥{{ scope.row.money'), '会员列表展示「商城余额」'); +assert(userIndexViewContent.includes('钱包余额: ¥{{ scope.row.money'), '会员列表展示「钱包余额」'); assert(userIndexViewContent.includes('未转佣金: ¥{{ scope.row.commission'), '会员列表展示「未转佣金」'); // --- 6. 分销商详情与佣金明细 (commission) 验证 --- @@ -111,15 +111,15 @@ assert(agentControllerContent.includes('$detail->user_commission = number_format const agentDetailViewPath = path.join(rootDir, 'application/admin/view/shopro/commission/agent/detail.html'); assert(fs.existsSync(agentDetailViewPath), 'commission/agent/detail.html 文件存在'); const agentDetailViewContent = fs.readFileSync(agentDetailViewPath, 'utf8'); -assert(agentDetailViewContent.includes('直入商城余额'), '分销商详情收益卡片带有「直入商城余额」Tag'); -assert(agentDetailViewContent.includes('商城可用余额:'), '分销商资产信息包含「商城可用余额」'); +assert(agentDetailViewContent.includes('直入钱包余额'), '分销商详情收益卡片带有「直入钱包余额」Tag'); +assert(agentDetailViewContent.includes('钱包可用余额:'), '分销商资产信息包含「钱包可用余额」'); assert(agentDetailViewContent.includes('历史未转佣金:'), '分销商资产信息包含「历史未转佣金」'); assert(agentDetailViewContent.includes('label="历史分销佣金"'), 'Tab 3 明确标注为「历史分销佣金」'); const commRewardViewPath = path.join(rootDir, 'application/admin/view/shopro/commission/reward/index.html'); assert(fs.existsSync(commRewardViewPath), 'commission/reward/index.html 文件存在'); const commRewardViewContent = fs.readFileSync(commRewardViewPath, 'utf8'); -assert(commRewardViewContent.includes('当前收益分成已全额直入商城余额'), '历史佣金明细顶部包含直入商城余额提示说明'); +assert(commRewardViewContent.includes('当前收益分成已全额直入钱包余额'), '历史佣金明细顶部包含直入钱包余额提示说明'); const commRewardControllerPath = path.join(rootDir, 'application/admin/controller/shopro/commission/Reward.php'); assert(fs.existsSync(commRewardControllerPath), 'commission/Reward.php 文件存在'); diff --git a/tests/partner/verify_income_criteria_alignment_spec.js b/tests/partner/verify_income_criteria_alignment_spec.js index 1cd6d6b..e0ede60 100644 --- a/tests/partner/verify_income_criteria_alignment_spec.js +++ b/tests/partner/verify_income_criteria_alignment_spec.js @@ -1,5 +1,5 @@ /** - * 累计收益统计口径与用户商城余额统一专项测试 + * 累计收益统计口径与用户钱包余额统一专项测试 * 覆盖范围: * 1. IdentityService attachAgent / attachProfile 累计收益口径统一与兜底 * 2. User profile 接口统一映射 balance = money 及累计收益补偿 @@ -7,9 +7,9 @@ * 4. WalletLog 收益统计扣除互转重复项,真实反映收益与扣减(防误导) * 5. uniapp 视图端全面对齐: * - rewards.vue 累计已结算收益卡片与本月统计 - * - account-info.vue 累计收益与商城余额 - * - commission.vue 累计总收益、周期收益与商城可用余额 - * - withdraw.vue 商城可用余额提示与收益入账说明 + * - account-info.vue 累计收益与钱包余额 + * - commission.vue 累计总收益、周期收益与钱包可用余额 + * - withdraw.vue 钱包可用余额提示与收益入账说明 * 6. 算法逻辑模拟:无重复统计、真实收益判定与资金守恒 */ @@ -30,7 +30,7 @@ function assert(condition, message) { } console.log('========================================================'); -console.log(' 累计收益统计口径与用户商城余额统一专项测试'); +console.log(' 累计收益统计口径与用户钱包余额统一专项测试'); console.log('========================================================\n'); // ---------------------------------------------------- @@ -56,7 +56,7 @@ const userControllerPath = path.resolve(__dirname, '../../addons/shopro/controll assert(fs.existsSync(userControllerPath), 'addons/shopro/controller/user/User.php 文件存在'); if (fs.existsSync(userControllerPath)) { const content = fs.readFileSync(userControllerPath, 'utf-8'); - assert(content.includes("$data['balance'] = $user->money"), 'profile 明确将 balance 映射为商城余额 money'); + assert(content.includes("$data['balance'] = $user->money"), 'profile 明确将 balance 映射为钱包余额 money'); assert(content.includes('$userRewardCash > 0'), '非分销商身份但有分成收益时,执行累计收益兜底统计'); } @@ -108,7 +108,7 @@ assert(fs.existsSync(accountInfoVuePath), 'account-info.vue 文件存在'); if (fs.existsSync(accountInfoVuePath)) { const content = fs.readFileSync(accountInfoVuePath, 'utf-8'); assert(content.includes("agentInfo?.total_income || userInfo?.total_income"), '累计收益具备 agentInfo 与 userInfo 双重对齐兜底'); - assert(content.includes("userInfo?.money || '0.00'"), '商城余额绑定 userInfo.money'); + assert(content.includes("userInfo?.money || '0.00'"), '钱包余额绑定 userInfo.money'); } // 5.3 commission.vue @@ -125,8 +125,8 @@ const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw assert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在'); if (fs.existsSync(withdrawVuePath)) { const content = fs.readFileSync(withdrawVuePath, 'utf-8'); - assert(content.includes('商城可用余额:¥{{ userInfo.money'), 'withdraw.vue 同步展示商城可用余额'); - assert(content.includes('收益分成已全额直接入账商城余额'), 'withdraw.vue 显著提示收益已全额直接入账商城余额'); + assert(content.includes('钱包可用余额:¥{{ userInfo.money'), 'withdraw.vue 同步展示钱包可用余额'); + assert(content.includes('收益分成已全额直接入账钱包余额'), 'withdraw.vue 显著提示收益已全额直接入账钱包余额'); } // ---------------------------------------------------- diff --git a/tests/partner/verify_internal_charge_withdraw_spec.js b/tests/partner/verify_internal_charge_withdraw_spec.js index 2549002..14f7375 100644 --- a/tests/partner/verify_internal_charge_withdraw_spec.js +++ b/tests/partner/verify_internal_charge_withdraw_spec.js @@ -36,7 +36,7 @@ const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service testAssert(fs.existsSync(withdrawServicePath), 'Withdraw.php 服务文件存在'); if (fs.existsSync(withdrawServicePath)) { const content = fs.readFileSync(withdrawServicePath, 'utf-8'); - testAssert(content.includes("WalletService::change($this->user, 'money', -$money, 'withdraw'"), 'apply 提现申请仅从商城余额扣除本金 -$money(内扣模式)'); + testAssert(content.includes("WalletService::change($this->user, 'money', -$money, 'withdraw'"), 'apply 提现申请仅从钱包余额扣除本金 -$money(内扣模式)'); testAssert(content.includes("bccomp((string)($this->user->money ?? 0), (string)$money, 2) < 0"), 'checkApply 校验余额只需大于等于提现本金 money(无需累加手续费)'); testAssert(content.includes("ShoproException('扣除手续费后实际到账金额必须大于0元')"), 'checkApply 严格拦截扣除手续费后实际到账小于等于0的请求'); testAssert(content.includes("$withdraw->paid_fee = bcsub((string)$withdraw->amount, (string)$withdraw->charge_fee, 2);"), 'handleWithdraw 实际打款金额为 (amount - charge_fee)'); @@ -80,7 +80,7 @@ const userA = { function applyWithdrawInternal(user, amount) { if (user.money < amount) { - throw new Error('商城可用余额不足'); + throw new Error('钱包可用余额不足'); } const charge = Math.round(amount * chargeRate * 100) / 100; const realPaid = Math.round((amount - charge) * 100) / 100; @@ -113,16 +113,16 @@ testAssert(userA.commission === 500.00, '佣金钱包完全不受影响保持 50 // 场景 B:审核驳回 refuseWithdrawInternal(userA, orderA); -testAssert(userA.money === 1000.00, '审核驳回后商城余额原路全额退还至 1000.00 元'); +testAssert(userA.money === 1000.00, '审核驳回后钱包余额原路全额退还至 1000.00 元'); // 场景 C:超额提现拦截 let hasIntercept = false; try { applyWithdrawInternal(userA, 1500.00); } catch (e) { - hasIntercept = (e.message === '商城可用余额不足'); + hasIntercept = (e.message === '钱包可用余额不足'); } -testAssert(hasIntercept, '超额提现精准拦截并抛出【商城可用余额不足】'); +testAssert(hasIntercept, '超额提现精准拦截并抛出【钱包可用余额不足】'); console.log(`\n========================================================`); console.log(` 测试结果: 通过 ${passCount} 项, 失败 ${failCount} 项`); diff --git a/tests/partner/verify_money_withdraw_flow_spec.js b/tests/partner/verify_money_withdraw_flow_spec.js index 4a30140..467d76e 100644 --- a/tests/partner/verify_money_withdraw_flow_spec.js +++ b/tests/partner/verify_money_withdraw_flow_spec.js @@ -1,11 +1,11 @@ /** - * 商城可用余额(money)提现全链路规格与算法模拟测试 + * 钱包可用余额(money)提现全链路规格与算法模拟测试 * 覆盖范围: - * 1. uniapp/pages/pay/withdraw.vue 前端商城余额提现默认绑定、全部提现与参数透传 + * 1. uniapp/pages/pay/withdraw.vue 前端钱包余额提现默认绑定、全部提现与参数透传 * 2. addons/shopro/service/Withdraw.php 服务层 apply 余额扣除、checkApply 拦截与驳回/失败退还闭环 * 3. application/admin/model/shopro/Withdraw.php 后台模型 is_money_withdraw 与 display 资产标识 * 4. application/admin/model/shopro/user/WalletLog.php 钱包日志字典增加 withdraw / withdraw_error - * 5. application/admin/view/shopro/withdraw/index.html 后台表格专属「商城余额」标签 + * 5. application/admin/view/shopro/withdraw/index.html 后台表格专属「钱包余额」标签 * 6. 提现扣款、手续费计算、超额拦截、审核驳回原路退款守恒性算法模拟断言 */ @@ -27,7 +27,7 @@ function testAssert(condition, message) { } console.log('========================================================'); -console.log(' 商城可用余额(money)提现全链路专项测试'); +console.log(' 钱包可用余额(money)提现全链路专项测试'); console.log('========================================================\n'); // ---------------------------------------------------- @@ -39,11 +39,11 @@ testAssert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在'); if (fs.existsSync(withdrawVuePath)) { const content = fs.readFileSync(withdrawVuePath, 'utf-8'); testAssert(content.includes("walletType: 'money'"), 'state.walletType 默认初始化为 money'); - testAssert(content.includes("userInfo.money || '0.00'"), '顶部主卡片绑定用户商城可用余额 userInfo.money'); - testAssert(content.includes("onWithdrawAllMoney"), '提供一键全部提现商城余额方法 onWithdrawAllMoney'); + testAssert(content.includes("userInfo.money || '0.00'"), '顶部主卡片绑定用户钱包可用余额 userInfo.money'); + testAssert(content.includes("onWithdrawAllMoney"), '提供一键全部提现钱包余额方法 onWithdrawAllMoney'); testAssert(content.includes("onWithdrawAllCommission"), '兼容提供全部提现存量佣金方法 onWithdrawAllCommission'); testAssert(content.includes("payload.wallet_type = 'money'"), '提交时对齐传递 wallet_type: money'); - testAssert(content.includes("Number(payload.money) > Number(userInfo.value?.money || 0)"), '严格校验提现金额不得超过商城可用余额'); + testAssert(content.includes("Number(payload.money) > Number(userInfo.value?.money || 0)"), '严格校验提现金额不得超过钱包可用余额'); } // ---------------------------------------------------- @@ -57,7 +57,7 @@ if (fs.existsSync(withdrawServicePath)) { testAssert(content.includes("$walletType = $params['wallet_type'] ?? 'money';"), '提现钱包默认缺省读取为 money'); testAssert(content.includes("$walletType = 'money';"), '支持自动升级或使用 money 提现类型'); testAssert(content.includes("WalletService::change($this->user, 'money'"), 'apply 支持扣除用户 money 钱包'); - testAssert(content.includes("ShoproException('商城可用余额不足')"), 'checkApply 校验余额不足抛出明确中文异常'); + testAssert(content.includes("ShoproException('钱包可用余额不足')"), 'checkApply 校验余额不足抛出明确中文异常'); testAssert(content.includes("$isMoney = (isset($info['wallet_type']) && $info['wallet_type'] === 'money')"), '识别 isMoney 提现类型'); testAssert(content.includes("'提现驳回退还余额'"), 'handleRefuse 识别余额提现驳回并原路退回余额'); testAssert(content.includes("'提现失败退还余额'"), 'handleFail 识别余额提现打款失败并原路退回余额'); @@ -73,7 +73,7 @@ if (fs.existsSync(adminWithdrawModelPath)) { const content = fs.readFileSync(adminWithdrawModelPath, 'utf-8'); testAssert(content.includes("'is_money_withdraw'"), '$append 追加属性包含 is_money_withdraw'); testAssert(content.includes("public function getIsMoneyWithdrawAttr"), '提供 getIsMoneyWithdrawAttr 获取器方法'); - testAssert(content.includes("$display['提现资产'] = '商城余额'"), 'getWithdrawInfoDisplayAttr 注入 提现资产: 商城余额'); + testAssert(content.includes("$display['提现资产'] = '钱包余额'"), 'getWithdrawInfoDisplayAttr 注入 提现资产: 钱包余额'); } const adminWithdrawViewPath = path.resolve(__dirname, '../../application/admin/view/shopro/withdraw/index.html'); @@ -81,7 +81,7 @@ testAssert(fs.existsSync(adminWithdrawViewPath), 'application/admin/view/shopro/ if (fs.existsSync(adminWithdrawViewPath)) { const content = fs.readFileSync(adminWithdrawViewPath, 'utf-8'); testAssert(content.includes("scope.row.is_money_withdraw"), '表格金额列支持判断 is_money_withdraw'); - testAssert(content.includes("商城余额"), '展示专属 商城余额 标签'); + testAssert(content.includes("钱包余额"), '展示专属 钱包余额 标签'); } // ---------------------------------------------------- @@ -125,7 +125,7 @@ function simulateApplyWithdraw(user, amount, walletType) { if (walletType === 'money') { if (user.money < totalDeduct) { - throw new Error('商城可用余额不足'); + throw new Error('钱包可用余额不足'); } user.money = Math.round((user.money - totalDeduct) * 100) / 100; } else { @@ -157,7 +157,7 @@ const initialMoney = userState.money; const withdrawRecord = simulateApplyWithdraw(userState, 1000.00, 'money'); testAssert(withdrawRecord.amount === 1000.00, '申请提现本金 1000.00 元'); testAssert(withdrawRecord.charge_fee === 10.00, '1% 提现手续费 10.00 元'); -testAssert(userState.money === 47592.00, '用户商城余额原子扣减 1010.00 元,剩余 47592.00 元'); +testAssert(userState.money === 47592.00, '用户钱包余额原子扣减 1010.00 元,剩余 47592.00 元'); testAssert(userState.commission === 0.00, '佣金账户不受影响保持 0.00 元'); // 模拟场景 B:提现驳回退款 @@ -181,7 +181,7 @@ try { } catch (e) { balanceError = e.message; } -testAssert(balanceError === '商城可用余额不足', '余额不足提现请求被严密拦截并输出友好提示'); +testAssert(balanceError === '钱包可用余额不足', '余额不足提现请求被严密拦截并输出友好提示'); console.log('\n========================================================'); console.log(`测试完成!通过: ${passCount}, 失败: ${failCount}`); diff --git a/tests/partner/verify_money_withdraw_scheme_b_spec.js b/tests/partner/verify_money_withdraw_scheme_b_spec.js index b365f27..e546853 100644 --- a/tests/partner/verify_money_withdraw_scheme_b_spec.js +++ b/tests/partner/verify_money_withdraw_scheme_b_spec.js @@ -1,10 +1,10 @@ /** * 方案 B 核心业务规范自动化断言测试: - * 规则:收益进入哪个钱包,提现就走哪个钱包提现(统一为「商城余额 money」) + * 规则:收益进入哪个钱包,提现就走哪个钱包提现(统一为「钱包余额 money」) * 覆盖范围: * 1. addons/shopro/service/Withdraw.php 服务层 apply 默认 money 提现、checkApply 校验 money、驳回/失败退还 money * 2. uniapp/pages/pay/withdraw.vue 前端默认 money 提现与 payload 校验 - * 3. application/admin/model/shopro/Withdraw.php 与 index.html 专属商城余额标签与获取器 + * 3. application/admin/model/shopro/Withdraw.php 与 index.html 专属钱包余额标签与获取器 * 4. application/admin/model/shopro/user/WalletLog.php 字典对齐 * 5. 扣款、费率、超额拦截、驳回退回、失败退回与数据校正守恒算法模拟断言 */ @@ -27,7 +27,7 @@ function testAssert(condition, message) { } console.log('========================================================'); -console.log(' 方案 B(收益与提现统一走商城余额 money)全链路测试'); +console.log(' 方案 B(收益与提现统一走钱包余额 money)全链路测试'); console.log('========================================================\n'); // 1. 服务层 Withdraw.php 验证 @@ -36,13 +36,13 @@ const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service testAssert(fs.existsSync(withdrawServicePath), 'Withdraw.php 服务文件存在'); if (fs.existsSync(withdrawServicePath)) { const content = fs.readFileSync(withdrawServicePath, 'utf-8'); - testAssert(content.includes("$walletType = $params['wallet_type'] ?? 'money';"), '提现钱包默认缺省值为 money(商城余额)'); + testAssert(content.includes("$walletType = $params['wallet_type'] ?? 'money';"), '提现钱包默认缺省值为 money(钱包余额)'); testAssert(content.includes("WalletService::change($this->user, 'money'"), 'apply 提现成功扣除 money 钱包'); - testAssert(content.includes("ShoproException('商城可用余额不足')"), 'checkApply 严格校验商城余额不足并提示明确异常'); + testAssert(content.includes("ShoproException('钱包可用余额不足')"), 'checkApply 严格校验钱包余额不足并提示明确异常'); testAssert(content.includes("bccomp((string)($this->user->money ?? 0)"), '使用 bccomp 高精度比较用户余额'); testAssert(content.includes("$isMoney = (isset($info['wallet_type']) && $info['wallet_type'] === 'money')"), '正确识别 isMoney 提现类型'); - testAssert(content.includes("'提现驳回退还余额'"), 'handleRefuse 驳回退款精准退回用户商城余额'); - testAssert(content.includes("'提现失败退还余额'"), 'handleFail 失败退款精准退回用户商城余额'); + testAssert(content.includes("'提现驳回退还余额'"), 'handleRefuse 驳回退款精准退回用户钱包余额'); + testAssert(content.includes("'提现失败退还余额'"), 'handleFail 失败退款精准退回用户钱包余额'); } // 2. 前端提现页面 withdraw.vue 验证 @@ -52,7 +52,7 @@ testAssert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在'); if (fs.existsSync(withdrawVuePath)) { const content = fs.readFileSync(withdrawVuePath, 'utf-8'); testAssert(content.includes("walletType: 'money'"), 'state.walletType 默认初始化为 money'); - testAssert(content.includes("userInfo.money || '0.00'"), '顶部展示用户商城余额 userInfo.money'); + testAssert(content.includes("userInfo.money || '0.00'"), '顶部展示用户钱包余额 userInfo.money'); testAssert(content.includes("payload.wallet_type = 'money'"), '提现提交显式指定 wallet_type: money'); } @@ -63,7 +63,7 @@ testAssert(fs.existsSync(withdrawModelPath), 'Withdraw.php 模型存在'); if (fs.existsSync(withdrawModelPath)) { const content = fs.readFileSync(withdrawModelPath, 'utf-8'); testAssert(content.includes("getIsMoneyWithdrawAttr"), '提供 getIsMoneyWithdrawAttr 获取器'); - testAssert(content.includes("$display['提现资产'] = '商城余额'"), '注入 提现资产: 商城余额'); + testAssert(content.includes("$display['提现资产'] = '钱包余额'"), '注入 提现资产: 钱包余额'); } const walletLogPath = path.resolve(__dirname, '../../application/admin/model/shopro/user/WalletLog.php'); @@ -87,7 +87,7 @@ function applyWithdrawMoney(user, amount) { const charge = Math.round(amount * chargeRate * 100) / 100; const totalDeduct = Math.round((amount + charge) * 100) / 100; if (user.money < totalDeduct) { - throw new Error('商城可用余额不足'); + throw new Error('钱包可用余额不足'); } user.money = Math.round((user.money - totalDeduct) * 100) / 100; return { amount, charge, totalDeduct, wallet_type: 'money' }; @@ -102,7 +102,7 @@ const snapCommission = testUser.commission; const order1 = applyWithdrawMoney(testUser, 1000.00); testAssert(order1.amount === 1000.00, '提现本金 1000.00 元'); testAssert(order1.charge === 60.00, '6% 手续费 60.00 元'); -testAssert(testUser.money === 554.65, '商城余额由 1614.65 扣减至 554.65'); +testAssert(testUser.money === 554.65, '钱包余额由 1614.65 扣减至 554.65'); testAssert(testUser.commission === snapCommission, '佣金钱包未受任何触碰,保持 806.00'); // 场景 2:超额提现拦截 @@ -110,13 +110,13 @@ let interceptPassed = false; try { applyWithdrawMoney(testUser, 600.00); // 需扣 636,剩余 554.65 不足 } catch (e) { - interceptPassed = (e.message === '商城可用余额不足'); + interceptPassed = (e.message === '钱包可用余额不足'); } -testAssert(interceptPassed, '余额不足拦截有效,抛出【商城可用余额不足】'); +testAssert(interceptPassed, '余额不足拦截有效,抛出【钱包可用余额不足】'); // 场景 3:审核驳回退回 refundWithdrawMoney(testUser, order1); -testAssert(testUser.money === 1614.65, '提现驳回后商城余额原路全额退还至 1614.65'); +testAssert(testUser.money === 1614.65, '提现驳回后钱包余额原路全额退还至 1614.65'); testAssert(testUser.commission === snapCommission, '佣金钱包继续保持 806.00 不受影响'); // 场景 4:针对订单 W202601240278270146002000 的数据校正逻辑断言 @@ -130,7 +130,7 @@ const fixDeduct = 1060.00; user20BeforeFix.money = Math.round((user20BeforeFix.money - fixDeduct) * 100) / 100; user20BeforeFix.commission = Math.round((user20BeforeFix.commission + fixDeduct) * 100) / 100; -testAssert(user20BeforeFix.money === 554.65, '校正后用户 20 商城余额为 554.65(扣除了提现的 1060 元)'); +testAssert(user20BeforeFix.money === 554.65, '校正后用户 20 钱包余额为 554.65(扣除了提现的 1060 元)'); testAssert(user20BeforeFix.commission === 1866.00, '校正后用户 20 佣金返还为 1866.00(恢复了历史存量佣金)'); console.log(`\n========================================================`); diff --git a/tests/partner/verify_partner_reward_to_money_spec.js b/tests/partner/verify_partner_reward_to_money_spec.js index d6b62f5..1c361cd 100644 --- a/tests/partner/verify_partner_reward_to_money_spec.js +++ b/tests/partner/verify_partner_reward_to_money_spec.js @@ -1,12 +1,12 @@ /** - * 收益分成直接加到商城余额专项验证测试 + * 收益分成直接加到钱包余额专项验证测试 * 覆盖范围: * 1. PartnerConfig (rewardWalletType 默认 money) * 2. PartnerRewardService (account 走 money 入账,refund 智能回滚) * 3. DailySettleService (cashShare 走 money 入账,clearDaily 智能回滚) * 4. 后台 Reward 控制器 (add 走 money 入账,back 智能回滚) * 5. WalletLog 模型 (eventMap['money'] 补全收益事件,getEventTextAttr 动态解析) - * 6. 用户端组件 account-info.vue (展示商城余额并跳转 /pages/user/wallet/money) + * 6. 用户端组件 account-info.vue (展示钱包余额并跳转 /pages/user/wallet/money) * 7. 业务逻辑与回滚算法行为模拟断言 */ @@ -27,7 +27,7 @@ function assert(condition, message) { } console.log('========================================================'); -console.log(' 收益分成直接加到商城余额专项测试'); +console.log(' 收益分成直接加到钱包余额专项测试'); console.log('========================================================\n'); // ---------------------------------------------------- @@ -39,7 +39,7 @@ assert(fs.existsSync(partnerConfigPath), 'PartnerConfig.php 文件存在'); if (fs.existsSync(partnerConfigPath)) { const content = fs.readFileSync(partnerConfigPath, 'utf-8'); assert(content.includes('public static function rewardWalletType()'), '包含 rewardWalletType() 收益入账钱包解析方法'); - assert(content.includes("self::get('partner_reward_wallet', 'money')"), '默认收益入账钱包缺省为 money (商城余额)'); + assert(content.includes("self::get('partner_reward_wallet', 'money')"), '默认收益入账钱包缺省为 money (钱包余额)'); assert(content.includes("in_array($type, ['money', 'commission'])"), '安全限定钱包类型在合法区间'); } @@ -52,7 +52,7 @@ assert(fs.existsSync(partnerRewardServicePath), 'PartnerRewardService.php 文件 if (fs.existsSync(partnerRewardServicePath)) { const content = fs.readFileSync(partnerRewardServicePath, 'utf-8'); assert(content.includes('$walletType = PartnerConfig::rewardWalletType();'), 'account() 动态获取收益入账钱包类型'); - assert(content.includes('Wallet::change($userId, $walletType, $amount, $type,'), 'account() 使用动态钱包入账(直接入商城余额)'); + assert(content.includes('Wallet::change($userId, $walletType, $amount, $type,'), 'account() 使用动态钱包入账(直接入钱包余额)'); assert(content.includes("AgentModel::where('user_id', $userId)->setInc('total_income', $amount);"), '保留分销商累计收益 total_income 累加'); assert(content.includes('$walletLog = \\app\\admin\\model\\shopro\\user\\WalletLog::where('), 'refund() 退款时查询原入账钱包记录'); assert(content.includes("$targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType();"), 'refund() 智能根据原入账类型回滚对应钱包'); @@ -68,7 +68,7 @@ assert(fs.existsSync(dailySettleServicePath), 'DailySettleService.php 文件存 if (fs.existsSync(dailySettleServicePath)) { const content = fs.readFileSync(dailySettleServicePath, 'utf-8'); assert(content.includes('$walletType = PartnerConfig::rewardWalletType();'), 'cashShare() 获取收益入账钱包类型'); - assert(content.includes('Wallet::change($userId, $walletType, $each, $type,'), 'cashShare() 日结酒道馆/区县分成直接加到商城余额'); + assert(content.includes('Wallet::change($userId, $walletType, $each, $type,'), 'cashShare() 日结酒道馆/区县分成直接加到钱包余额'); assert(content.includes("AgentModel::where('user_id', $userId)->setInc('total_income', $each);"), 'cashShare() 累计更新 total_income'); assert(content.includes("$targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType();"), 'clearDaily() 重跑日结时智能识别并回滚对应钱包'); } @@ -82,7 +82,7 @@ assert(fs.existsSync(rewardCtrlPath), 'Reward.php 文件存在'); if (fs.existsSync(rewardCtrlPath)) { const content = fs.readFileSync(rewardCtrlPath, 'utf-8'); assert(content.includes('$walletType = PartnerConfig::rewardWalletType();'), 'add() 手工补发获取收益入账钱包类型'); - assert(content.includes('Wallet::change($params[\'user_id\'], $walletType, $amount, \'reward_income\''), 'add() 手工补发直接加到商城余额'); + assert(content.includes('Wallet::change($params[\'user_id\'], $walletType, $amount, \'reward_income\''), 'add() 手工补发直接加到钱包余额'); assert(content.includes("$targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType();"), 'back() 手工撤销智能回滚对应钱包'); } @@ -117,9 +117,9 @@ const accountInfoPath = path.resolve(__dirname, '../../uniapp/pages/commission/c assert(fs.existsSync(accountInfoPath), 'account-info.vue 文件存在'); if (fs.existsSync(accountInfoPath)) { const content = fs.readFileSync(accountInfoPath, 'utf-8'); - assert(content.includes('商城余额(元)'), '卡片标签已变更为「商城余额(元)」'); - assert(content.includes("userInfo?.money || '0.00'"), '读取 userInfo.money 商城余额'); - assert(content.includes("sheep.$router.go('/pages/user/wallet/money')"), '点击商城余额卡片直达 /pages/user/wallet/money'); + assert(content.includes('钱包余额(元)'), '卡片标签已变更为「钱包余额(元)」'); + assert(content.includes("userInfo?.money || '0.00'"), '读取 userInfo.money 钱包余额'); + assert(content.includes("sheep.$router.go('/pages/user/wallet/money')"), '点击钱包余额卡片直达 /pages/user/wallet/money'); assert(content.includes("sheep.$router.go('/pages/commission/rewards')"), '查看明细直达收益明细'); } @@ -159,7 +159,7 @@ const mockUser = { id: 10, money: '100.00', commission: '0.00' }; const mockAgent = { user_id: 10, total_income: '500.00' }; const log1 = mockAccountReward(mockUser, mockAgent, 398.00, 'push3'); -assert(mockUser.money === '498.00', '模拟即时奖入账:用户商城余额从 100 增至 498.00'); +assert(mockUser.money === '498.00', '模拟即时奖入账:用户钱包余额从 100 增至 498.00'); assert(mockUser.commission === '0.00', '模拟即时奖入账:佣金钱包维持 0.00 未动'); assert(mockAgent.total_income === '898.00', '模拟即时奖入账:分销商累计收益从 500 累加至 898.00'); assert(log1.type === 'money' && log1.event === 'push3', '产生正确的 money 钱包变更流水'); diff --git a/tests/partner/verify_wallet_migration_spec.js b/tests/partner/verify_wallet_migration_spec.js index c18f793..9416ba8 100644 --- a/tests/partner/verify_wallet_migration_spec.js +++ b/tests/partner/verify_wallet_migration_spec.js @@ -1,5 +1,5 @@ /** - * 历史收益全量结转至商城余额专项测试 + * 历史收益全量结转至钱包余额专项测试 * 覆盖范围: * 1. WalletMigrationService 业务服务契约与原子性逻辑 * 2. PartnerTransferCommission 控制台命令 @@ -26,7 +26,7 @@ function assert(condition, message) { } console.log('========================================================'); -console.log(' 历史收益全量结转至商城余额专项测试'); +console.log(' 历史收益全量结转至钱包余额专项测试'); console.log('========================================================\n'); // ---------------------------------------------------- @@ -42,7 +42,7 @@ if (fs.existsSync(migrationServicePath)) { assert(content.includes('Db::transaction(function ()'), '结转过程使用数据库事务'); assert(content.includes('lock(true)'), '读取用户使用排他行锁 lock(true) 防并发脏读'); assert(content.includes("Wallet::change($user, 'commission', -$amount, 'transfer_to_money'"), '调用 Wallet::change 扣减佣金钱包'); - assert(content.includes("Wallet::change($user, 'money', $amount, 'transfer_by_commission'"), '调用 Wallet::change 增加商城余额'); + assert(content.includes("Wallet::change($user, 'money', $amount, 'transfer_by_commission'"), '调用 Wallet::change 增加钱包余额'); assert(content.includes('bcadd('), '资金累计采用高精度 bcadd 避免浮点误差'); } @@ -167,9 +167,9 @@ assert(result.totalTransferred === '648.50', '结转总金额精确匹配 250.50 // 校验各个用户结转后的状态 assert(initialUsers[0].commission === '0.00', '张三佣金归零'); -assert(initialUsers[0].money === '350.50', '张三商城余额从 100 增至 350.50'); +assert(initialUsers[0].money === '350.50', '张三钱包余额从 100 增至 350.50'); assert(initialUsers[1].commission === '0.00', '李四佣金归零'); -assert(initialUsers[1].money === '398.00', '李四商城余额从 0 增至 398.00'); +assert(initialUsers[1].money === '398.00', '李四钱包余额从 0 增至 398.00'); assert(initialUsers[2].commission === '0.00' && initialUsers[2].money === '50.00', '王五佣金本为0,保持不变'); assert(initialUsers[3].commission === '120.00' && initialUsers[3].money === '80.00', '赵六发生异常,资金状态未被污染'); diff --git a/tests/partner/verify_wallet_records_alignment_spec.js b/tests/partner/verify_wallet_records_alignment_spec.js index 125179e..dc8cab0 100644 --- a/tests/partner/verify_wallet_records_alignment_spec.js +++ b/tests/partner/verify_wallet_records_alignment_spec.js @@ -5,7 +5,7 @@ * 2. application/admin/controller/shopro/user/WalletLog.php 后台佣金与收益流水联查 * 3. application/admin/view/shopro/user/user/detail.html 资产卡片与明细表格对齐 * 4. uniapp/pages/user/wallet/commission.vue 用户收益页面卡片/按钮/标签/提示文案对齐 - * 5. uniapp/pages/user/wallet/money.vue 商城余额明细页面 tab 筛选与动态指示胶囊对齐 + * 5. uniapp/pages/user/wallet/money.vue 钱包余额明细页面 tab 筛选与动态指示胶囊对齐 * 6. 核心行为算法模拟:历史流水+直入余额流水联查、收益分成专属过滤、收入支出统计守恒 */ @@ -40,7 +40,7 @@ if (fs.existsSync(userWalletLogPath)) { assert(content.includes("$type === 'commission'"), '包含对 commission 类型的特殊聚合处理'); assert(content.includes("where('type', 'commission')"), '查询条件包含历史佣金流水'); assert(content.includes("where('type', 'money')->whereIn('event', $rewardEvents)"), '查询条件联查直入余额的收益分成流水'); - assert(content.includes("'transfer_by_commission'"), 'rewardEvents 包含转入商城余额事件 transfer_by_commission'); + assert(content.includes("'transfer_by_commission'"), 'rewardEvents 包含转入钱包余额事件 transfer_by_commission'); assert(content.includes("'transfer_to_money'"), 'rewardEvents 包含结转事件 transfer_to_money'); assert(content.includes("case 'reward':"), '支持 tab=reward 收益分成专属筛选'); assert(content.includes("'reward_income' => $rewardIncome"), '接口返回收益分成累计金额统计'); @@ -69,11 +69,11 @@ assert(fs.existsSync(detailHtmlPath), 'detail.html 文件存在'); if (fs.existsSync(detailHtmlPath)) { const content = fs.readFileSync(detailHtmlPath, 'utf-8'); assert(content.includes('收益中心累计收益 (元)'), '资产卡片1标题更新为「收益中心累计收益 (元)」'); - assert(content.includes('当前商城余额: ¥{{ state.detail.money') || content.includes('已直入商城余额: ¥{{ state.detail.money'), '展示当前商城余额(已直入余额)字段'); + assert(content.includes('当前钱包余额: ¥{{ state.detail.money') || content.includes('已直入钱包余额: ¥{{ state.detail.money'), '展示当前钱包余额(已直入余额)字段'); assert(content.includes('历史未转佣金: ¥{{ state.detail.commission'), '展示历史未转佣金字段'); assert(content.includes('name="commission">佣金与收益流水') || content.includes('label="佣金与收益流水" name="commission"'), 'Tab 4 名称更新为「佣金与收益流水」'); assert(content.includes("label=\"钱包类别\" min-width=\"110\" v-if=\"log.tabActive=='commission'\""), '明细表格包含钱包类别列'); - assert(content.includes("scope.row.type == 'money' ? '商城余额' : '佣金钱包'"), '钱包类别使用 Tag 区分商城余额与佣金钱包'); + assert(content.includes("scope.row.type == 'money' ? '钱包余额' : '佣金钱包'"), '钱包类别使用 Tag 区分钱包余额与佣金钱包'); } // ---------------------------------------------------- @@ -84,7 +84,7 @@ const commissionVuePath = path.resolve(__dirname, '../../uniapp/pages/user/walle assert(fs.existsSync(commissionVuePath), 'commission.vue 文件存在'); if (fs.existsSync(commissionVuePath)) { const content = fs.readFileSync(commissionVuePath, 'utf-8'); - assert(content.includes('商城可用余额(元)'), '顶部卡片主金额标题变更为「商城可用余额(元)」'); + assert(content.includes('钱包可用余额(元)'), '顶部卡片主金额标题变更为「钱包可用余额(元)」'); assert(content.includes('userInfo.money'), '顶部卡片主金额绑定为 userInfo.money'); assert(content.includes('/pages/goods/list'), '卡片操作区提供「去消费」直达按钮'); assert(content.includes('Number(userInfo.commission || 0) > 0'), '历史佣金大于0时才显示「转余额」操作'); @@ -93,7 +93,7 @@ if (fs.existsSync(commissionVuePath)) { assert(content.includes("name: '收益分成'") && content.includes("value: 'reward'"), 'tabMaps 增加「收益分成」(value: reward) 选项'); assert(content.includes("item.type === 'money'"), '流水记录列表对直入余额项进行识别'); assert(content.includes('已入余额'), '直入余额项显示「已入余额」专属胶囊标签'); - assert(content.includes('收益分成已全额直接入账商城可用余额'), '底部提示文案明确说明收益直接入账商城余额'); + assert(content.includes('收益分成已全额直接入账钱包可用余额'), '底部提示文案明确说明收益直接入账钱包余额'); } // ---------------------------------------------------- @@ -123,7 +123,7 @@ const rewardEvents = [ // 模拟钱包日志混合数据池 const mockLogs = [ { id: 1, type: 'commission', event: 'push3', amount: 398.00, memo: '推三返一' }, - { id: 2, type: 'commission', event: 'transfer_to_money', amount: -398.00, memo: '结转至商城余额' }, + { id: 2, type: 'commission', event: 'transfer_to_money', amount: -398.00, memo: '结转至钱包余额' }, { id: 3, type: 'money', event: 'transfer_by_commission', amount: 398.00, memo: '收益结转入账' }, { id: 4, type: 'money', event: 'recharge', amount: 100.00, memo: '微信充值' }, { id: 5, type: 'money', event: 'team_venue', amount: 50.00, memo: '酒道馆订单奖' }, diff --git a/tests/partner/verify_withdraw_100_multiple_spec.js b/tests/partner/verify_withdraw_100_multiple_spec.js index f63ecc5..d49ab31 100644 --- a/tests/partner/verify_withdraw_100_multiple_spec.js +++ b/tests/partner/verify_withdraw_100_multiple_spec.js @@ -142,7 +142,7 @@ function validateWithdrawMoney(moneyInput, userBalance) { return { valid: false, message: '提现金额必须为100的整数倍' }; } if (floatMoney > userBalance) { - return { valid: false, message: '商城可用余额不足' }; + return { valid: false, message: '钱包可用余额不足' }; } return { valid: true, message: '验证通过' }; } @@ -190,7 +190,7 @@ const valNegative = validateWithdrawMoney(-100, 500); testAssert(valNegative.valid === false && valNegative.message === '请输入正确的提现金额', '校验用例:金额 -100 元被拦截'); const valExceed = validateWithdrawMoney(200, 150); -testAssert(valExceed.valid === false && valExceed.message === '商城可用余额不足', '校验用例:金额 200 元(余额 150 元)超额拦截'); +testAssert(valExceed.valid === false && valExceed.message === '钱包可用余额不足', '校验用例:金额 200 元(余额 150 元)超额拦截'); console.log('\n========================================================'); console.log(` 测试结果汇总:共 ${passCount + failCount} 项测试,通过: ${passCount},失败: ${failCount}`); diff --git a/tests/verify_admin_withdraw_export_spec.js b/tests/verify_admin_withdraw_export_spec.js index e543ba9..747becc 100644 --- a/tests/verify_admin_withdraw_export_spec.js +++ b/tests/verify_admin_withdraw_export_spec.js @@ -134,7 +134,7 @@ function formatExportRow(row) { accountNo = withdrawInfo.openid ? `\t${withdrawInfo.openid}` : ''; } - let assetName = '商城余额'; + let assetName = '钱包余额'; let assetAmountDetail = '-'; let amountDisplay = Number(row.money || 0).toFixed(2); let actualAmountDisplay = Number(row.actual_money || 0).toFixed(2); @@ -172,7 +172,7 @@ function formatExportRow(row) { }; } -// 模拟场景 A:普通商城余额提现至银行卡 +// 模拟场景 A:普通钱包余额提现至银行卡 const mockRowA = { id: 101, sn: 'TX202609210001', @@ -194,7 +194,7 @@ const mockRowA = { }; const formattedA = formatExportRow(mockRowA); -testAssert(formattedA.asset_name === '商城余额', '普通余额提现资产显示为 商城余额'); +testAssert(formattedA.asset_name === '钱包余额', '普通余额提现资产显示为 钱包余额'); testAssert(formattedA.mobile === '\t13800138000', '手机号添加制表符防失真'); testAssert(formattedA.card_no === '\t6222021000112233445', '银行卡号添加制表符防科学计数法'); testAssert(formattedA.money === '1000.00', '提现金额精准显示 1000.00');