/** * 提现手续费「内扣模式」核心规格断言与算法模拟测试 * * 核心业务规则: * 1. 用户提现申请本金为 M,手续费为 C(M * charge_rate); * 2. 用户余额仅扣除申请本金 M(支持余额有多少就提多少,全部提现不报错); * 3. 实际打款金额(paid_fee / transfer_amount / trans_amount)为 (M - C); * 4. 审核驳回或打款失败时,原路全额退还本金 M(平台不倒贴、用户不少退); * 5. 前端实时计算并展示内扣后预计到账金额。 */ const fs = require('fs'); const path = require('path'); const assert = require('assert'); let passCount = 0; let failCount = 0; function testAssert(condition, message) { if (condition) { console.log(` \x1b[32m[PASS]\x1b[0m ${message}`); passCount++; } else { console.error(` \x1b[31m[FAIL]\x1b[0m ${message}`); failCount++; } } console.log('========================================================'); console.log(' 提现手续费「内扣模式」全链路规格与资金守恒测试'); console.log('========================================================\n'); // 1. 服务层 Withdraw.php 静态代码结构验证 console.log('--- 1. addons/shopro/service/Withdraw.php 服务层内扣闭环验证 ---'); const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service/Withdraw.php'); 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("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)'); testAssert(content.includes("'transfer_amount' => bcsub((string)$withdraw->amount, (string)$withdraw->charge_fee, 2)"), 'handleWechatTransfer 微信转账金额为 (amount - charge_fee)'); testAssert(content.includes("'trans_amount' => bcsub((string)$withdraw->amount, (string)$withdraw->charge_fee, 2)"), 'handleTransfer 支付宝转账金额为 (amount - charge_fee)'); testAssert(content.includes("WalletService::change($this->user, 'money', $withdraw->amount, 'withdraw_error'"), 'handleRefuse 驳回退还金额精准为本金 amount(绝不多退或少退)'); } // 2. 数据模型层 Withdraw.php 验证 console.log('\n--- 2. application/admin/model/shopro/Withdraw.php 模型预计到账验证 ---'); const withdrawModelPath = path.resolve(__dirname, '../../application/admin/model/shopro/Withdraw.php'); testAssert(fs.existsSync(withdrawModelPath), 'Withdraw.php 模型存在'); if (fs.existsSync(withdrawModelPath)) { const content = fs.readFileSync(withdrawModelPath, 'utf-8'); testAssert(content.includes("'real_amount'"), '$append 追加 real_amount 属性'); testAssert(content.includes("getRealAmountAttr"), '提供 getRealAmountAttr 获取器计算 net 净到账金额'); testAssert(content.includes("$display['预计到账']"), 'withdraw_info_display 注入「预计到账」字段供后台审核核对'); } // 3. 前端提现界面 withdraw.vue 验证 console.log('\n--- 3. uniapp/pages/pay/withdraw.vue 前端内扣提示验证 ---'); const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw.vue'); testAssert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在'); if (fs.existsSync(withdrawVuePath)) { const content = fs.readFileSync(withdrawVuePath, 'utf-8'); testAssert(content.includes("estimatedArrivalMoney"), '提供 estimatedArrivalMoney 实时计算内扣到账金额'); testAssert(content.includes("estimatedChargeFee"), '提供 estimatedChargeFee 实时计算内扣手续费'); testAssert(content.includes("预计到账:¥{{ estimatedArrivalMoney }}"), '界面输入金额时实时展现预计到账'); testAssert(content.includes("手续费内扣"), '界面明确提示手续费从提现金额内扣'); } // 4. 内扣业务逻辑与资金守恒算法模拟 console.log('\n--- 4. 内扣模式算法与资金守恒严格断言 ---'); const chargeRate = 0.06; // 6% 手续费 // 场景 A:用户有 1000 元余额,执行“一键全部提现 1000 元” const userA = { money: 1000.00, commission: 500.00 }; function applyWithdrawInternal(user, amount) { if (user.money < amount) { throw new Error('钱包可用余额不足'); } const charge = Math.round(amount * chargeRate * 100) / 100; const realPaid = Math.round((amount - charge) * 100) / 100; if (realPaid <= 0) { throw new Error('扣除手续费后实际到账金额必须大于0元'); } // 内扣:仅扣除提现申请本金 user.money = Math.round((user.money - amount) * 100) / 100; return { amount, charge_fee: charge, paid_fee: realPaid, wallet_type: 'money' }; } function refuseWithdrawInternal(user, order) { // 驳回全额退还本金 user.money = Math.round((user.money + order.amount) * 100) / 100; } const orderA = applyWithdrawInternal(userA, 1000.00); testAssert(orderA.amount === 1000.00, '申请提现本金 1000.00 元'); testAssert(orderA.charge_fee === 60.00, '手续费 60.00 元(6%)'); testAssert(orderA.paid_fee === 940.00, '实际打款到账 940.00 元(1000 - 60)'); testAssert(userA.money === 0.00, '【核心】用户余额精准扣减 1000.00 元,剩余 0.00 元,全部提现零报错'); testAssert(userA.commission === 500.00, '佣金钱包完全不受影响保持 500.00 元'); // 场景 B:审核驳回 refuseWithdrawInternal(userA, orderA); testAssert(userA.money === 1000.00, '审核驳回后钱包余额原路全额退还至 1000.00 元'); // 场景 C:超额提现拦截 let hasIntercept = false; try { applyWithdrawInternal(userA, 1500.00); } catch (e) { hasIntercept = (e.message === '钱包可用余额不足'); } testAssert(hasIntercept, '超额提现精准拦截并抛出【钱包可用余额不足】'); console.log(`\n========================================================`); console.log(` 测试结果: 通过 ${passCount} 项, 失败 ${failCount} 项`); console.log(`========================================================\n`); assert.strictEqual(failCount, 0, '所有内扣测试必须全部通过');