/** * dmepa 余额银行卡提现功能端到端全链路规格测试 * 覆盖范围: * 1. PartnerConfig.php 开关与最小金额读取方法、奖励字典 * 2. Config.php 控制器白名单、默认值、自愈与入参校验 * 3. config/index.html & config.js 后台表单项与初始化 * 4. partner_v2.sql 数据库升级配置行 * 5. Withdraw.php 服务层 applyMadow 提现发起、持仓校验、扣减流水、审核拒绝退款与失败退还闭环 * 6. partner/Index.php madow() 接口返回提现开关与最低金额 * 7. madow.vue 资产卡片提现按钮及动态显隐控制 * 8. withdraw.vue 路由参数、银行卡模式锁定、全部提现、实时汇率扣除估算与提现提交 */ const fs = require('fs'); const path = require('path'); let passCount = 0; let failCount = 0; function assert(condition, testName, detail = '') { if (condition) { console.log(` \x1b[32m[PASS]\x1b[0m ${testName}`); passCount++; } else { console.error(` \x1b[31m[FAIL]\x1b[0m ${testName} - ${detail}`); failCount++; } } console.log('========================================================'); console.log(' dmepa 余额银行卡提现功能端到端规格测试'); console.log('========================================================\n'); // 1. 验证 PartnerConfig.php console.log('--- 1. PartnerConfig 配置读取与字典注册 ---'); const partnerConfigPath = path.resolve(__dirname, '../../addons/shopro/service/partner/PartnerConfig.php'); assert(fs.existsSync(partnerConfigPath), 'PartnerConfig.php 文件存在'); const partnerConfigContent = fs.readFileSync(partnerConfigPath, 'utf-8'); assert( partnerConfigContent.includes('function isMadowWithdrawEnabled'), 'PartnerConfig 包含 isMadowWithdrawEnabled() 读取方法' ); assert( partnerConfigContent.includes('function madowWithdrawMin'), 'PartnerConfig 包含 madowWithdrawMin() 最低提现金额读取方法' ); assert( partnerConfigContent.includes("'madow_withdraw' => 'dmepa提现扣除'") && partnerConfigContent.includes("'madow_withdraw_refund' => 'dmepa提现退还'"), 'PartnerConfig rewardTypes 字典正确登记 madow_withdraw 与 madow_withdraw_refund' ); // 2. 验证 Config.php console.log('\n--- 2. 后台 Config.php 控制器白名单与自愈 ---'); const configPhpPath = path.resolve(__dirname, '../../application/admin/controller/shopro/Config.php'); assert(fs.existsSync(configPhpPath), 'Config.php 文件存在'); const configPhpContent = fs.readFileSync(configPhpPath, 'utf-8'); assert( configPhpContent.includes("'partner_madow_withdraw_enable'") && configPhpContent.includes("'partner_madow_withdraw_min'"), 'Config.php 白名单 $keys 包含 partner_madow_withdraw_enable 与 partner_madow_withdraw_min' ); assert( configPhpContent.includes("'partner_madow_withdraw_enable' => 1") && configPhpContent.includes("'partner_madow_withdraw_min' => 1"), 'Config.php $defaults 包含 partner_madow_withdraw_enable 与 partner_madow_withdraw_min 默认值' ); assert( configPhpContent.includes("shop.commission.partner_madow_withdraw_enable") && configPhpContent.includes("shop.commission.partner_madow_withdraw_min"), 'Config.php $ensureConfigs 包含提现开关与最低金额自愈创建机制' ); assert( configPhpContent.includes("intval($save['partner_madow_withdraw_enable'])") && configPhpContent.includes("floatval($save['partner_madow_withdraw_min'])"), 'Config.php POST 保存处理了整型与浮点安全类型转换' ); // 3. 验证 后台视图与 JS 脚本 console.log('\n--- 3. 后台配置视图与脚本 (index.html & config.js) ---'); const indexHtmlPath = path.resolve(__dirname, '../../application/admin/view/shopro/config/index.html'); assert(fs.existsSync(indexHtmlPath), 'config/index.html 视图文件存在'); const indexHtmlContent = fs.readFileSync(indexHtmlPath, 'utf-8'); assert( indexHtmlContent.includes('form.model.partner_madow_withdraw_enable') && (indexHtmlContent.includes('开启dmepa提现') || indexHtmlContent.includes('开启DMEPA提现')), '后台视图包含「开启DMEPA提现」switch 开关' ); assert( indexHtmlContent.includes('form.model.partner_madow_withdraw_min') && (indexHtmlContent.includes('dmepa最低提现金额') || indexHtmlContent.includes('DMEPA最低提现金额')), '后台视图包含「DMEPA最低提现金额」输入框' ); const configJsPath = path.resolve(__dirname, '../../public/assets/js/backend/shopro/config.js'); assert(fs.existsSync(configJsPath), 'config.js 脚本文件存在'); const configJsContent = fs.readFileSync(configJsPath, 'utf-8'); assert( configJsContent.includes('form.model.partner_madow_withdraw_enable') && configJsContent.includes('form.model.partner_madow_withdraw_min'), 'config.js 对 partner_madow_withdraw_enable 与 partner_madow_withdraw_min 进行默认初始化' ); // 4. 验证 partner_v2.sql console.log('\n--- 4. 数据库升级脚本 partner_v2.sql ---'); const sqlPath = path.resolve(__dirname, '../../addons/shopro/upgrade/partner_v2.sql'); assert(fs.existsSync(sqlPath), 'partner_v2.sql 文件存在'); const sqlContent = fs.readFileSync(sqlPath, 'utf-8'); assert( sqlContent.includes("'shop.commission.partner_madow_withdraw_enable'") && sqlContent.includes("'shop.commission.partner_madow_withdraw_min'"), 'partner_v2.sql 包含提现开关与最低金额初始配置行' ); // 5. 验证 Withdraw.php 服务层业务闭环 console.log('\n--- 5. 提现服务层 (Withdraw.php) dmepa提现业务闭环 ---'); const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service/Withdraw.php'); assert(fs.existsSync(withdrawServicePath), 'Withdraw.php 服务文件存在'); const withdrawServiceContent = fs.readFileSync(withdrawServicePath, 'utf-8'); assert( withdrawServiceContent.includes("params['wallet_type'] ?? 'commission'") && withdrawServiceContent.includes("applyMadow"), 'Withdraw.php::apply 支持 wallet_type=madow 并分流至 applyMadow' ); assert( withdrawServiceContent.includes("PartnerConfig::isMadowWithdrawEnabled()") && (withdrawServiceContent.includes('dmepa提现功能暂未开启') || withdrawServiceContent.includes('DMEPA提现功能暂未开启')), 'applyMadow 校验 DMEPA 提现开关,关闭时抛出面向用户的明确提示' ); assert( withdrawServiceContent.includes("$type !== 'bank'") && (withdrawServiceContent.includes('dmepa提现仅支持银行卡转账') || withdrawServiceContent.includes('DMEPA提现仅支持银行卡转账')), 'applyMadow 严格限制提现渠道仅支持 bank (银行卡转账)' ); assert( withdrawServiceContent.includes("PartnerConfig::madowWithdrawMin()"), 'applyMadow 校验最低提现金额' ); assert( withdrawServiceContent.includes("(new RateService())->currentRate()"), 'applyMadow 调用 RateService 实时获取当前大盘汇率' ); assert( withdrawServiceContent.includes("AgentModel::where('user_id', $this->user->id)->lock(true)->find()") && (withdrawServiceContent.includes("持仓dmepa不足,无法提现") || withdrawServiceContent.includes("持仓DMEPA不足,无法提现")), 'applyMadow 行级排他锁校验合伙人 DMEPA 持仓充足性' ); assert( withdrawServiceContent.includes("$agent->setDec('madow', $deductMadow)"), 'applyMadow 执行数据库原子扣减持仓 dmepa' ); assert( withdrawServiceContent.includes("'reward_type' => 'madow_withdraw'"), 'applyMadow 记录 PartnerRewardService madow_withdraw 负向提现流水' ); assert( withdrawServiceContent.includes("madow_withdraw_refund") && withdrawServiceContent.includes("$agent->setInc('madow', $madowRefund)"), 'handleRefuse 识别 dmepa 提现驳回并原子返还持仓 madow 记录 refund 流水' ); assert( withdrawServiceContent.includes("handleFail") && withdrawServiceContent.includes("madow_withdraw_refund"), 'handleFail 提现异常失败时同样支持安全返还 dmepa' ); // 6. 验证 用户端接口 partner/Index.php console.log('\n--- 6. 用户端 partner/Index.php 接口输出 ---'); const partnerIndexPath = path.resolve(__dirname, '../../addons/shopro/controller/partner/Index.php'); assert(fs.existsSync(partnerIndexPath), 'partner/Index.php 控制器存在'); const partnerIndexContent = fs.readFileSync(partnerIndexPath, 'utf-8'); assert( partnerIndexContent.includes("'can_withdraw_madow' => PartnerConfig::isMadowWithdrawEnabled() ? 1 : 0") && partnerIndexContent.includes("'madow_withdraw_min' => PartnerConfig::madowWithdrawMin()"), 'madow() 接口返回 can_withdraw_madow 开关与 madow_withdraw_min 门槛' ); // 7. 验证 前端 madow.vue 资产页 console.log('\n--- 7. 用户端 我的dmepa页面 (madow.vue) 提现入口 ---'); const madowVuePath = path.resolve(__dirname, '../../uniapp/pages/commission/madow.vue'); assert(fs.existsSync(madowVuePath), 'madow.vue 文件存在'); const madowVueContent = fs.readFileSync(madowVuePath, 'utf-8'); assert( madowVueContent.includes("v-if=\"state.info.can_withdraw_madow !== false && state.info.can_withdraw_madow !== 0 && state.info.can_withdraw_madow !== '0'\""), 'madow.vue 提现按钮受后台 can_withdraw_madow 开关动态控制显隐' ); assert( madowVueContent.includes("@tap=\"onWithdrawMadow\"") && madowVueContent.includes("sheep.$router.go('/pages/pay/withdraw', { type: 'madow' })"), 'madow.vue 点击提现跳转至 /pages/pay/withdraw?type=madow' ); assert( madowVueContent.includes(".topup-btn") && madowVueContent.includes(".hero-left"), 'madow.vue 包含与钱包对齐的黑金渐变提现按钮样式' ); assert( madowVueContent.includes("state.info.can_withdraw_madow = data.can_withdraw_madow"), 'madow.vue getMadow() 解析服务端下发的 can_withdraw_madow 状态' ); // 8. 验证 前端 withdraw.vue 提现申请页 console.log('\n--- 8. 用户端 提现申请页 (withdraw.vue) dmepa提现适配 ---'); const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw.vue'); assert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在'); const withdrawVueContent = fs.readFileSync(withdrawVuePath, 'utf-8'); assert( withdrawVueContent.includes(":title=\"state.walletType === 'madow' ? 'dmepa提现' : '申请提现'\"") || withdrawVueContent.includes(":title=\"state.walletType === 'madow' ? 'DMEPA提现' : '申请提现'\""), 'withdraw.vue 页面标题根据 walletType 自适应切换 DMEPA提现 / 申请提现' ); assert( withdrawVueContent.includes("state.walletType === 'madow'") && withdrawVueContent.includes("state.madowInfo.madow"), 'withdraw.vue 顶部资产卡片在 madow 模式下展示 dmepa 持仓与预估市值' ); assert( withdrawVueContent.includes("onWithdrawAllMadow") && withdrawVueContent.includes("全部提现"), 'withdraw.vue madow 模式下支持一键「全部提现」' ); assert( withdrawVueContent.includes("estimatedDeductMadow") && withdrawVueContent.includes("实时汇率"), 'withdraw.vue 支持输入金额时根据大盘实时汇率计算需扣除的 dmepa 数量预览' ); assert( withdrawVueContent.includes(":methods=\"state.walletType === 'madow' ? ['bank'] : state.rules.methods\""), 'withdraw.vue 在 madow 模式下提现方式严格锁定为 bank 银行卡' ); assert( withdrawVueContent.includes("payload.wallet_type = 'madow'") && withdrawVueContent.includes("payload.type = 'bank'"), 'withdraw.vue 提交申请时附带 wallet_type: madow 与 type: bank 参数' ); assert( withdrawVueContent.includes("fetchUserBankAccount"), 'withdraw.vue 进入 madow 提现时自动调取既有已绑定的银行卡信息' ); // 9. 验证 后台提现模型 (Withdraw.php) 属性追加与获取器清洗 console.log('\n--- 9. 后台提现模型 (Withdraw.php) 市值汇率与换算市值获取器 ---'); const adminWithdrawModelPath = path.resolve(__dirname, '../../application/admin/model/shopro/Withdraw.php'); assert(fs.existsSync(adminWithdrawModelPath), 'application/admin/model/shopro/Withdraw.php 存在'); const adminWithdrawModelContent = fs.readFileSync(adminWithdrawModelPath, 'utf-8'); assert( adminWithdrawModelContent.includes("'is_madow_withdraw'") && adminWithdrawModelContent.includes("'withdraw_info_display'"), 'Withdraw 模型 $append 包含 is_madow_withdraw 与 withdraw_info_display 属性' ); assert( adminWithdrawModelContent.includes("function getIsMadowWithdrawAttr"), 'Withdraw 模型提供 getIsMadowWithdrawAttr 获取器精准识别 DMEPA 提现单' ); assert( adminWithdrawModelContent.includes("function getWithdrawInfoDisplayAttr"), 'Withdraw 模型提供 getWithdrawInfoDisplayAttr 获取器' ); assert( adminWithdrawModelContent.includes("市值汇率") && adminWithdrawModelContent.includes("换算市值") && adminWithdrawModelContent.includes("1 DMEPA ≈"), 'getWithdrawInfoDisplayAttr 包含「市值汇率」与「换算市值」规范格式化' ); assert( adminWithdrawModelContent.includes("wallet_type") && adminWithdrawModelContent.includes("madow_amount") && adminWithdrawModelContent.includes("rate"), 'getWithdrawInfoDisplayAttr 过滤 wallet_type、madow_amount、rate 等英文技术键' ); // 10. 验证 后台提现视图 (index.html) 金额列与打款信息列展示 console.log('\n--- 10. 后台提现管理视图 (index.html) 市值汇率与换算市值高亮展示 ---'); const adminWithdrawHtmlPath = path.resolve(__dirname, '../../application/admin/view/shopro/withdraw/index.html'); assert(fs.existsSync(adminWithdrawHtmlPath), 'application/admin/view/shopro/withdraw/index.html 存在'); const adminWithdrawHtmlContent = fs.readFileSync(adminWithdrawHtmlPath, 'utf-8'); assert( adminWithdrawHtmlContent.includes("scope.row.is_madow_withdraw") && adminWithdrawHtmlContent.includes("DMEPA提现") && adminWithdrawHtmlContent.includes("换算市值: {{ scope.row.amount || '-' }}元"), '提现金额列识别 DMEPA 提现,展示 DMEPA提现 专属金标并明确标注「换算市值」' ); assert( adminWithdrawHtmlContent.includes("scope.row.withdraw_info_display || scope.row.withdraw_info"), '打款信息列优先读取规范清洗后的 withdraw_info_display' ); assert( adminWithdrawHtmlContent.includes("['wallet_type', 'madow_amount', 'rate'].includes(key)"), '打款信息列表格循环中过滤内部英文技术字段' ); assert( adminWithdrawHtmlContent.includes("['市值汇率', '换算市值', '提现资产', '扣除DMEPA'].includes(key)"), '打款信息列对「市值汇率」、「换算市值」等关键要素进行金黄色强调高亮' ); // 11. 验证 服务层 (Withdraw.php) 写入中文标准字典与退还稳健性 console.log('\n--- 11. 服务层 (Withdraw.php) 写入「市值汇率」与「换算市值」标准字典 ---'); assert( withdrawServiceContent.includes("$withdrawInfo['市值汇率'] = '1 DMEPA ≈ ' . $rateClean . ' 元';") && withdrawServiceContent.includes("$withdrawInfo['换算市值'] = $moneyClean . ' 元';"), 'applyMadow 提现单据中明确写入标准中文「市值汇率」与「换算市值」' ); assert( withdrawServiceContent.includes("$withdrawInfo['提现资产'] = 'DMEPA';") && withdrawServiceContent.includes("$withdrawInfo['扣除DMEPA'] = $deductMadow . ' DMEPA';"), 'applyMadow 写入规范大写 DMEPA 与扣除资产数量说明' ); console.log('--------------------------------------------------------'); console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`); console.log('========================================================\n'); if (failCount > 0) { process.exit(1); } else { process.exit(0); }