/** * 新用户 E2E 扣费验证脚本 * * 前置:用一个**没用过**的手机号在 https://app.fmode.cn/dev/apig-pay/ 登录+支付 * 把登录后的 sessionToken 填到下面 NEW_TOKEN * * 运行: * node scripts/tools/verify-newuser-e2e.js * * 脚本动作: * 1. GET /parse/users/me?include=company → 验证新 user 有没有 company * 2. 列出该 user 名下所有 APIGAuth(应当只有 user 指针,无 company) * 3. 调用 /api/voc-ecom/forward → 预期 200(符合陈经理说的正常逻辑) * 4. 再次查 APIGAuth → 验证 count-1 / used+1 */ const TOKEN = process.argv[2]; if (!TOKEN) { console.error('用法: node verify-newuser-e2e.js '); process.exit(1); } const API = 'https://server.fmode.cn'; const H = { 'X-Parse-Application-Id': 'ncloudmaster', 'X-Parse-Session-Token': TOKEN }; function line() { console.log('━'.repeat(60)); } (async () => { line(); console.log(' STEP 1. 查看新 user 信息'); line(); const me = await (await fetch(`${API}/parse/users/me?include=company`, { headers: H })).json(); if (!me.objectId) { console.error('❌ Token 无效:', me); process.exit(1); } console.log(` userId: ${me.objectId}`); console.log(` username: ${me.username}`); console.log(` mobile: ${me.mobile || '(无)'}`); console.log(` company: ${me.company ? me.company.objectId : '(无) ✅ 正是我们要的新用户'}`); const userId = me.objectId; const companyId = me.company?.objectId; line(); console.log(' STEP 2. 查该 user 名下所有 APIGAuth'); line(); const q = encodeURIComponent(JSON.stringify({ user: { __type: 'Pointer', className: '_User', objectId: userId } })); const auths = await (await fetch( `${API}/parse/classes/APIGAuth?where=${q}&include=api&limit=20`, { headers: H } )).json(); if (!auths.results || auths.results.length === 0) { console.log(' (该 user 还没有任何 APIGAuth —— 需要先去 apig-pay 支付一次)'); } else { auths.results.forEach(a => { console.log(` ${a.objectId} api=${a.api?.objectId}(${a.api?.title}) count=${a.count} used=${a.used} company=${a.company?.objectId || '(无)'}`); }); } const ecomAuth = auths.results?.find(a => a.api?.objectId === '7HwdQZk55B'); if (!ecomAuth) { console.log('\n⚠️ 未找到电商 APIG(7HwdQZk55B) 的 APIGAuth,请先在 apig-pay 充值电商服务再跑此脚本'); process.exit(0); } console.log(`\n → 使用 APIGAuth ${ecomAuth.objectId}, 余额 ${ecomAuth.count}`); line(); console.log(' STEP 3. 调用 /api/voc-ecom/forward'); line(); const t0 = Date.now(); const callResp = await fetch(`${API}/api/voc-ecom/forward`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + TOKEN }, body: JSON.stringify({ path: '/api/KeywordQuery', method: 'POST', query: { domain: 1 }, body: { Keyword: 'natural reed diffuser' } }) }); const cost = Date.now() - t0; const result = await callResp.text(); console.log(` HTTP ${callResp.status} (${cost}ms)`); console.log(` Body: ${result.substring(0, 400)}`); if (callResp.status !== 200) { console.log('\n❌ 接口没打通。分析:'); if (callResp.status === 403) { if (companyId) { console.log(' - user 有 company → 后端走 company 分支'); console.log(` - company ${companyId} 下应该查不到 APIGAuth → 403 符合后端正常逻辑`); console.log(` - 解决:apig-pay 应该按 company 维度写 APIGAuth(带 company 指针)`); } else { console.log(' - user 无 company → 后端应走 user 分支'); console.log(' - 但仍 403 表示 user 维度 APIGAuth 也查不到,或者后端还有其他校验'); } } process.exit(1); } line(); console.log(' STEP 4. 再查余额,验证扣费'); line(); await new Promise(r => setTimeout(r, 1500)); const after = await (await fetch( `${API}/parse/classes/APIGAuth/${ecomAuth.objectId}?keys=count,used`, { headers: H } )).json(); const dCount = after.count - ecomAuth.count; const dUsed = after.used - ecomAuth.used; console.log(` count: ${ecomAuth.count} → ${after.count} (Δ=${dCount})`); console.log(` used: ${ecomAuth.used} → ${after.used} (Δ=${dUsed})`); if (dCount === -1 && dUsed === 1) { console.log('\n🎉 扣费链路打通!新用户流程端到端验证通过'); } else { console.log('\n⚠️ 接口 200 但余额未按预期变动,检查后端扣费逻辑'); } })().catch(e => console.error('[FATAL]', e));