/** * 普通商品购买禁止兑换券抵扣 & 积分商城专享兑换券规范专项自动化测试 * 覆盖范围:confirm.vue, OrderCreate.php, voucher.vue, 算法防注入模拟 */ const fs = require('fs'); const path = require('path'); let passCount = 0; let failCount = 0; function assert(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. UniApp 确认订单页 confirm.vue 验证 const confirmVuePath = path.resolve(__dirname, '../uniapp/pages/order/confirm.vue'); assert(fs.existsSync(confirmVuePath), 'confirm.vue 文件存在'); if (fs.existsSync(confirmVuePath)) { const content = fs.readFileSync(confirmVuePath, 'utf-8'); // 兑换券整栏限制 const voucherIdx = content.indexOf('兑换券'); assert(voucherIdx !== -1, 'confirm.vue 包含兑换券标题栏'); const voucherBlock = content.slice(voucherIdx - 120, voucherIdx); assert(voucherBlock.includes("state.orderPayload.order_type === 'score'"), 'confirm.vue 兑换券栏严格限制仅在 order_type === score 时展示'); // 弹层 popup 限制 const popupMatch = content.includes(":show=\"state.showVoucher && state.orderPayload.order_type === 'score'\""); assert(popupMatch, 'confirm.vue 兑换券弹窗绑定 order_type === score 显隐安全保护'); // openVoucher 拦截 assert(content.includes("if (state.orderPayload.order_type !== 'score')"), 'confirm.vue openVoucher() 包含非积分订单直接拦截'); // getOrderInfo 清理 assert( content.includes("else if (state.orderPayload.order_type !== 'score')") && content.includes("delete state.orderPayload.voucher_fee"), 'confirm.vue getOrderInfo() 对非积分订单主动清理 voucher_fee' ); // submitOrder 清理 const submitOrderIdx = content.indexOf('async function submitOrder()'); const submitOrderBody = content.slice(submitOrderIdx, submitOrderIdx + 500); assert( submitOrderBody.includes("state.orderPayload.order_type !== 'score'") && submitOrderBody.includes("delete state.orderPayload.voucher_fee"), 'confirm.vue submitOrder() 对非积分订单提交前剔除 voucher_fee' ); } // 2. 后端 OrderCreate.php 核心隔离验证 const orderCreatePath = path.resolve(__dirname, '../addons/shopro/service/order/OrderCreate.php'); assert(fs.existsSync(orderCreatePath), 'OrderCreate.php 文件存在'); if (fs.existsSync(orderCreatePath)) { const content = fs.readFileSync(orderCreatePath, 'utf-8'); // calcParams 白名单解析 assert(content.includes("\$this->voucher_fee = (\$this->order_type === 'score' && isset(\$params['voucher_fee']) && \$params['voucher_fee'] !== '')"), 'calcParams 仅允许 score 积分订单解析 voucher_fee 参数'); // calcVoucher 严格前置隔离 assert(content.includes("if (\$this->order_type !== 'score') {\n \$this->orderData['voucher_available'] = 0;\n \$this->orderData['voucher_fee'] = 0;\n return;\n }"), 'calcVoucher 增加严格隔离:非 score 订单 voucher_available 与 voucher_fee 均强制为 0'); // ext 持久化防护 assert(content.includes("\$ext['voucher_fee'] = (\$this->order_type === 'score') ? (\$result['voucher_fee'] ?? 0) : 0;"), '订单 ext[\'voucher_fee\'] 仅允许积分商城订单记录,普通订单强制为 0'); } // 3. voucher.vue 文案优化验证 const voucherVuePath = path.resolve(__dirname, '../uniapp/pages/commission/voucher.vue'); assert(fs.existsSync(voucherVuePath), 'voucher.vue 文件存在'); if (fs.existsSync(voucherVuePath)) { const content = fs.readFileSync(voucherVuePath, 'utf-8'); assert(content.includes('积分兑换专享'), 'voucher.vue 胶囊标签展示「积分兑换专享」'); assert(!content.includes('全场通用'), 'voucher.vue 已彻底移除「全场通用」误导文案'); } // 4. 算法与防注入场景模拟验证 console.log('\n--- 场景模拟与抵扣安全性测试 ---'); function mockOrderCalc(orderType, userVoucherAvailable, orderPayFee, inputVoucherFee = null) { let orderData = { voucher_available: 0, voucher_fee: 0, pay_fee: orderPayFee, total_discount_fee: 0 }; // 模拟 OrderCreate::calcVoucher 隔离机制 if (orderType !== 'score') { orderData.voucher_available = 0; orderData.voucher_fee = 0; return orderData; } let available = userVoucherAvailable; orderData.voucher_available = available; let want = (inputVoucherFee === null) ? available : Math.max(0, Number(inputVoucherFee)); let apply = Math.min(want, available, orderData.pay_fee); apply = apply > 0 ? apply : 0; orderData.voucher_fee = apply; if (apply > 0) { orderData.pay_fee = Math.round((orderData.pay_fee - apply) * 100) / 100; orderData.total_discount_fee = Math.round((orderData.total_discount_fee + apply) * 100) / 100; } return orderData; } // 场景 1:普通商品购买(¥1398.00),用户拥有 27 个兑换券,正常下单(不传 voucher_fee) const scene1 = mockOrderCalc('goods', 27, 1398.00); assert(scene1.voucher_fee === 0, '场景 1:普通商品购买 voucher_fee 为 0'); assert(scene1.voucher_available === 0, '场景 1:普通商品购买 voucher_available 为 0(不向前端泄露抵扣额)'); assert(scene1.pay_fee === 1398.00, '场景 1:普通商品购买应付现金全额保持 1398.00 元'); // 场景 2:普通商品购买(¥1398.00),攻击者尝试越权传参 voucher_fee = 27 const scene2 = mockOrderCalc('goods', 27, 1398.00, 27); assert(scene2.voucher_fee === 0, '场景 2(恶意注入拦截):普通商品强行传 voucher_fee=27,被严格拦截置零'); assert(scene2.pay_fee === 1398.00, '场景 2(恶意注入拦截):应付金额分文不少,保持 1398.00 元'); // 场景 3:积分商城兑换(商品 50 元 + 10 积分),用户拥有 27 个兑换券,自动抵扣 const scene3 = mockOrderCalc('score', 27, 50.00); assert(scene3.voucher_fee === 27, '场景 3:积分商城兑换成功抵扣 27 个兑换券'); assert(scene3.voucher_available === 27, '场景 3:积分商城兑换正常返回可用额度 27'); assert(scene3.pay_fee === 23.00, '场景 3:积分商品剩余 23.00 元现金需自付'); // 场景 4:积分商城兑换(商品 50 元 + 10 积分),用户拥有 100 个兑换券,全额抵扣现金 const scene4 = mockOrderCalc('score', 100, 50.00); assert(scene4.voucher_fee === 50, '场景 4:积分商城兑换成功全额抵扣 50 个兑换券'); assert(scene4.pay_fee === 0.00, '场景 4:应付现金降至 0 元,结合 10 积分达成完全免现金兑换'); // 场景 5:模拟订单 ext 持久化安全 function mockSaveOrderExt(orderType, calcResultVoucherFee) { return (orderType === 'score') ? (calcResultVoucherFee || 0) : 0; } assert(mockSaveOrderExt('goods', 100) === 0, '场景 5:普通订单 ext.voucher_fee 强制为 0'); assert(mockSaveOrderExt('score', 50) === 50, '场景 5:积分订单 ext.voucher_fee 正确记录为 50'); console.log('\n--------------------------------------------------------'); console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`); console.log('========================================================\n'); if (failCount > 0) { process.exit(1); }