const test = require('node:test'); const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const ts = require('typescript'); function load(file, deps = {}, globals = {}) { const exports = {}; const source = fs.readFileSync(path.resolve(__dirname, '../functions', file), 'utf8'); const code = ts.transpileModule(source, { compilerOptions: { target:ts.ScriptTarget.ES2020, module:ts.ModuleKind.CommonJS } }).outputText; vm.runInNewContext(code, { exports, require:name=>{ if (!(name in deps)) throw new Error('Missing test dependency: '+name); return deps[name]; }, Date, ...globals }); return exports; } const ids = [1,2,3,4,5,6].map(i=>'new_gold_'+i).concat([8,18,36,68,98].map(i=>'new_unlimited_health_bundle_'+i)); const prices = [600,3000,6800,12800,32800,64800,800,1800,3600,6800,9800]; function fixture() { const state={orders:[],requests:[],user:{_id:'u',openid:'o',session_key:'session'}}; const db={collection:()=>({add:async row=>{state.orders.push(row);return {};},where:()=>({getOne:async()=>({data:state.user}),update:async()=>({})})})}; const cloud={database:()=>db,shared:{get:()=> 'token'},storage:{bucket:()=>({readFile:async()=>({Body:{transformToString:async()=> 'synthetic-key'}})})}}; const deps={ '@lafjs/cloud':{default:cloud}, '@/limitedTimeEvent':{starterPackOrderSnapshot:()=>({})}, '@/passCheckUpgrade':{passOrderFields:()=>({}),PASS_PRICES:{}}, '@/jungleConfig':{getJunglePaidTierByProductId:()=>null}, crypto:{...crypto,default:{...crypto,createSign:()=>({update(){},sign:()=> 'synthetic-signature'})}} }; const globals={console:{log(){},error(){}},process:{env:{PAYMENT_APP_ENV:'test',WX_MIDAS_PAY_SIGN_KEY:'synthetic-key'}}, fetch:async(url,options)=>{state.requests.push({url,body:JSON.parse(options.body)});return {ok:true,json:async()=>({prepay_id:'synthetic-prepay',errcode:0})};}}; return {state,load:file=>load(file,deps,globals).default}; } test('both signing endpoints enforce all eleven B prices and quantity one', async()=>{ for(const file of ['wx/orderPaySig.ts','wx/iosorderPaySig.ts']) { const f=fixture(), create=f.load(file); for(const [i,id] of ids.entries()) { const result=await create({body:{uid:'u',itemid:id,itemCount:99,itemPrice:1}}); assert.equal(result.code,1); const signed=JSON.parse(result.data.signData), row=f.state.orders[i]; assert.equal(row.itemid,id);assert.equal(row.itemCount,1);assert.equal(row.goodsPrice,prices[i]); assert.equal(signed.productId,id);assert.equal(signed.buyQuantity,1);assert.equal(signed.goodsPrice,prices[i]); assert.equal(result.data.paySig,crypto.createHmac('sha256','synthetic-key').update('requestMidasPaymentGameItem&'+result.data.signData).digest('hex')); } const legacy=await create({body:{uid:'u',itemid:'gold_2',itemCount:1,itemPrice:3600}}); assert.equal(legacy.code,1);assert.equal(f.state.orders.at(-1).goodsPrice,3600); } }); test('legacy iOS cashier uses all B prices, display names and order IDs',async()=>{ const f=fixture(), create=f.load('wx/KeFuInfo.ts'); for(const [i,id] of ids.entries()) { await create({body:{FromUserName:'o',SessionFrom:JSON.stringify({tpye:'ios',propName:id,price:1,count:99,outTradeNo:'ios-'+i})}}); const row=f.state.orders[i];assert.ok(row,'customer-service request must create order'); assert.equal(row.goodsPrice,prices[i]);assert.equal(row.itemCount,1);assert.equal(row.itemid,id); const payments=f.state.requests.filter(r=>r.url.includes('/v3/pay/transactions/jsapi')); assert.equal(payments[i].body.amount.total,prices[i]); const link=f.state.requests.filter(r=>r.body.msgtype==='link')[i].body.link; const data=JSON.parse(decodeURIComponent(link.url.split('#data=')[1])); assert.equal(data.name,i < 6 ? '新版金币包'+(i+1) : '新版组合礼包'+[8,18,36,68,98][i-6]+'元');assert.equal(data.price,prices[i]);assert.equal(data.outTradeNo,'ios-'+i); } });