diff --git a/assets/Script/JiaZai.ts b/assets/Script/JiaZai.ts index b5038c4..b12d98c 100644 --- a/assets/Script/JiaZai.ts +++ b/assets/Script/JiaZai.ts @@ -2692,7 +2692,7 @@ export default class JiaZai extends cc.Component { coinTemp = 1000; } else if (/^starter_pack(?:_[2-5])?$/.test(order.itemid)) { - coinTemp = starterPackReward(order.starterPackOffer || res.starterPackOffer).coin; + coinTemp = starterPackReward(order.itemid).coin; } else if (order.itemid == "battlepass") { // 通行证订单 this.reissuePaaCheck(); diff --git a/assets/Script/module/Config/StarterPack.ts b/assets/Script/module/Config/StarterPack.ts index f760363..0fe83ed 100644 --- a/assets/Script/module/Config/StarterPack.ts +++ b/assets/Script/module/Config/StarterPack.ts @@ -1,7 +1,14 @@ -/** Use the order's offer for delivery; missing snapshots retain legacy rewards. */ -export function starterPackReward(offer?: any) { - if (offer?.reward) return { ...offer.reward }; - return { coin: 3000, hammer: 3, freeze: 3, magic_wand: 3, infinite_health: 30 * 60 }; +const STARTER_PACK_REWARDS = { + starter_pack: { coin: 3000, hammer: 1, freeze: 1, magic_wand: 1, infinite_health: 30 * 60 }, + starter_pack_2: { coin: 3000, hammer: 1, freeze: 1, magic_wand: 1, infinite_health: 30 * 60 }, + starter_pack_3: { coin: 4500, hammer: 3, freeze: 3, magic_wand: 3, infinite_health: 45 * 60 }, + starter_pack_4: { coin: 5000, hammer: 5, freeze: 5, magic_wand: 5, infinite_health: 60 * 60 }, + starter_pack_5: { coin: 8000, hammer: 8, freeze: 8, magic_wand: 8, infinite_health: 180 * 60 }, +}; + +/** Rewards are fixed by the client SKU; the server only selects the offer tier. */ +export function starterPackReward(productId = "starter_pack") { + return { ...(STARTER_PACK_REWARDS[productId] || STARTER_PACK_REWARDS.starter_pack) }; } export function starterPackNow(): number { diff --git a/assets/Script/module/Tool/GameTool.ts b/assets/Script/module/Tool/GameTool.ts index ad1a7a1..cbbdeb6 100644 --- a/assets/Script/module/Tool/GameTool.ts +++ b/assets/Script/module/Tool/GameTool.ts @@ -1003,7 +1003,6 @@ var GameTool = { //商城购买 shopBuy(productId, compensate, orderContext: any = null) { - if (/^starter_pack_[2-5]$/.test(productId) && !orderContext?.starterPackOffer?.reward) return false; const newProduct = getNewShopProduct(productId); const newOrderKey = newProduct && orderContext?.outTradeNo ? "ShopBGranted_" + cc.fx.GameConfig.GM_INFO.uid + "_" + orderContext.outTradeNo : ""; @@ -1304,7 +1303,7 @@ var GameTool = { case "starter_pack_3": case "starter_pack_4": case "starter_pack_5": { - const reward = starterPackReward(orderContext?.starterPackOffer); + const reward = starterPackReward(productId); coin = reward.coin; price = orderContext?.starterPackOffer?.priceFen || 300; cc.fx.GameTool.changeCoin(coin); diff --git a/assets/action_bundle/script/NewbieGift.ts b/assets/action_bundle/script/NewbieGift.ts index da2baf9..b6609bc 100644 --- a/assets/action_bundle/script/NewbieGift.ts +++ b/assets/action_bundle/script/NewbieGift.ts @@ -44,7 +44,7 @@ export default class NewbieGift extends cc.Component { private refreshOffer() { const price = Number(this.offer?.priceFen) || 300; for (const value of [3, 6, 12, 18, 30]) cc.find("btnContainer/" + value, this.node).active = value * 100 === price; - const reward = starterPackReward(this.offer); + const reward = starterPackReward(this.offer?.productId); const bundle = cc.assetManager.getBundle("action_bundle"); bundle.load("img/newbieGift/newbieGiftFont", cc.SpriteAtlas, (error, atlas: cc.SpriteAtlas) => { if (error || !cc.isValid(this)) return; @@ -110,13 +110,6 @@ export default class NewbieGift extends cc.Component { server: version === "开发版" || version === "体验版" ? Utils.testHttpip : Utils.httpip, }); const done = (data) => { - if (/^starter_pack_[2-5]$/.test(data?.data?.itemid || data?.data?.payment_name || "") && !data.data.starterPackOffer?.reward) { - this.polling = false; - this.paying = false; - this.openConfirmBox(); - this.refreshCountdown(); - return; - } if (data?.data && /^starter_pack(?:_[2-5])?$/.test(data.data.itemid || data.data.payment_name || "")) { this.orderContext = data.data; } diff --git a/server/laf-cloud/functions/limitedTimeEvent.ts b/server/laf-cloud/functions/limitedTimeEvent.ts index ed82a29..8e0c43e 100644 --- a/server/laf-cloud/functions/limitedTimeEvent.ts +++ b/server/laf-cloud/functions/limitedTimeEvent.ts @@ -70,11 +70,11 @@ const DAY = 24 * 60 * 60 * 1000; const BEIJING_OFFSET = 8 * 60 * 60 * 1000; const STARTER_REWARD_VERSION = PAYMENT_PROFILE_RULE.starterPackRewardVersion; const STARTER_TIERS = [ - { productId: 'starter_pack', priceFen: 300, coin: 3000, props: 1, minutes: 30 }, - { productId: 'starter_pack_2', priceFen: 600, coin: 3000, props: 1, minutes: 30 }, - { productId: 'starter_pack_3', priceFen: 1200, coin: 4500, props: 3, minutes: 45 }, - { productId: 'starter_pack_4', priceFen: 1800, coin: 5000, props: 5, minutes: 60 }, - { productId: 'starter_pack_5', priceFen: 3000, coin: 8000, props: 8, minutes: 180 }, + { productId: 'starter_pack', priceFen: 300 }, + { productId: 'starter_pack_2', priceFen: 600 }, + { productId: 'starter_pack_3', priceFen: 1200 }, + { productId: 'starter_pack_4', priceFen: 1800 }, + { productId: 'starter_pack_5', priceFen: 3000 }, ]; // A live offer is immutable, including when payment identity or daily factors change. @@ -82,12 +82,11 @@ export function starterPackPatch(user: any, now: number, trigger = false, option const expiry = Number(user.starter_pack) || 0; if (expiry > now) { if (user.starterPackOffer) return null; - // Preserve an existing legacy offer's original deadline and promised rewards. + // Preserve an existing legacy offer's original deadline and tier. return { starterPackVersion: 3, starterPackRound: starterPackRound(user), starterPackOffer: { version: 3, legacy: true, round: starterPackRound(user), productId: 'starter_pack', priceFen: 300, - reward: { coin: 3000, hammer: 3, freeze: 3, magic_wand: 3, infinite_health: 1800 }, startsAt: expiry - ((Number(user.starterPackVersion) || 1) < 2 ? DAY : 2 * DAY), expiresAt: expiry, rewardVersion: 'starter_pack_legacy', selectionBasis: 'legacy', }, @@ -137,7 +136,6 @@ export function starterPackPatch(user: any, now: number, trigger = false, option starter_pack: expiresAt, starter_packState: 0, starterPackVersion: 3, starterPackRound: round, starterPackOffer: { version: 3, round, productId: tier.productId, priceFen: tier.priceFen, - reward: { coin: tier.coin, hammer: tier.props, freeze: tier.props, magic_wand: tier.props, infinite_health: tier.minutes * 60 }, startsAt: now, expiresAt, rewardVersion: STARTER_REWARD_VERSION, selectionBasis, }, }; diff --git a/server/laf-cloud/starterPack.README.md b/server/laf-cloud/starterPack.README.md index 733720d..cedb796 100644 --- a/server/laf-cloud/starterPack.README.md +++ b/server/laf-cloud/starterPack.README.md @@ -22,17 +22,17 @@ - 等级至少 15。金币小于 500、普通体力为 0 且无限体力无效、三个道具各不超过 3、商城/局内购买意向、真实购买行为、当前关卡连续失败至少 4 次,任一满足即可。失败次数仍由客户端按账号和关卡计数,后端校验关卡和整数次数。 - 新轮只在满足触发条件且可安全展示的界面开始计时。普通读取不启动。购买意向恢复入口,不强制弹窗;资源提醒每轮最多自动展示一次,玩法、支付及领奖过程延后处理。 -- 本轮 `starterPackOffer` 固定商品、价格、奖励、起止时间和轮号;期间身份或画像改变不会重选。 +- 本轮 `starterPackOffer` 固定商品、价格、起止时间和轮号;期间身份或画像改变不会重选。奖励由客户端按商品 ID 固定配置。 - 下轮最早为原截止时间所属北京时间日期加 2 天的零点:9 月 23 日 02:00 到期,9 月 25 日 00:00 才能再触发。提前购买不改变这个截止时间。购买状态按轮次重置。 -- 旧有效礼包保留原截止时间和原奖励(3000 金币、三个道具各 3、无限体力 1800 秒);旧已购用户无可信截止时间时可在下一次合格触发进入新轮。 +- 旧有效礼包保留原截止时间并按 `starter_pack` 使用客户端固定的 3 元奖励;旧已购用户无可信截止时间时可在下一次合格触发进入新轮。 `limitedTimeEvent` 保留 read/save/reactivate/shown 动作;save/reactivate 需 reason。返回新增 `starterPackOffer` 和 `starterPackAutoShownRound`。shown 传本轮 expiry 和 automatic;支持客户端表单的字符串编码。`starterPackLastShownAt` 保留作为记录,不再决定冷却。 ## 支付及兼容 -现有下单接口仅对上述五个 SKU 校验当前有效轮次、未购买状态与商品匹配,并固定服务端报价和数量 1。订单保存 `starterPackOffer`、`starterPackBuyRound`、`starterPackExpiresAt`、`activityType`、`rewardVersion`,旧 iOS 转单继续携带这些字段。 +现有下单接口仅对上述五个 SKU 校验当前有效轮次、未购买状态与商品匹配,并固定服务端报价和数量 1。订单保存不含奖励内容的 `starterPackOffer`、`starterPackBuyRound`、`starterPackExpiresAt`、`activityType`、`rewardVersion`,旧 iOS 转单继续携带这些字段。 -新订单到期后不可创建;到期前的有效订单可继续原支付流程。回调只关闭所属轮次,不关闭后开的礼包。查单携带订单快照;`getOrderReward` 仍返回 `data="ok"`,礼包快照位于响应顶层。前端从该订单的快照发奖,缺快照旧单走原奖励,不使用当前轮报价替换旧单。登录和账号迁移保留轮次及报价。 +新订单到期后不可创建;到期前的有效订单可继续原支付流程。回调只关闭所属轮次,不关闭后开的礼包。查单携带商品和轮次快照;`getOrderReward` 仍返回 `data="ok"`,礼包快照位于响应顶层。前端只按订单商品 ID 读取本地固定奖励,不读取后端奖励字段。登录和账号迁移保留轮次及报价。 保留原跨设备并发下单、客户端发奖及旧 iOS 响应中断的边界,未新增订单占位锁或服务端发奖事务。礼包待支付订单不再按原通用 3 小时期限清理,以免删除仍可能支付的旧单;关闭但未成功的礼包订单也会保留,不因此发奖。退款不在本次范围内。 diff --git a/server/laf-cloud/tests/rookie-gift.test.mjs b/server/laf-cloud/tests/rookie-gift.test.mjs index d481a57..e9dde46 100644 --- a/server/laf-cloud/tests/rookie-gift.test.mjs +++ b/server/laf-cloud/tests/rookie-gift.test.mjs @@ -79,15 +79,15 @@ function order(user, extra = {}) { const ctx = body => ({ body, headers: {}, socket: { remoteAddress: '127.0.0.1' } }); const notification = () => ctx({ MiniGame: { Payload: JSON.stringify({ OutTradeNo: 'wcx_test' }) } }); -function setOffer(user, productId, priceFen, reward = { coin: 3000, hammer: 1, freeze: 1, magic_wand: 1, infinite_health: 1800 }) { +function setOffer(user, productId, priceFen) { user.starterPackVersion = 3; user.pay_user = productId !== 'starter_pack'; - user.starterPackOffer = { version: 3, round: user.starterPackRound, productId, priceFen, reward, + user.starterPackOffer = { version: 3, round: user.starterPackRound, productId, priceFen, startsAt: NOW - 10000, expiresAt: user.starter_pack, rewardVersion: 'starter_pack_v3_20260930', selectionBasis: 'profile' }; return user.starterPackOffer; } -test('each lucky tier uses the server offer price, quantity and reward snapshot on native payment', async () => { +test('each lucky tier uses the server offer price, quantity and tier snapshot on native payment', async () => { for (const create of [androidOrder, nativeIosOrder]) { for (const [productId, priceFen] of [['starter_pack', 300], ['starter_pack_2', 600], ['starter_pack_3', 1200], ['starter_pack_4', 1800], ['starter_pack_5', 3000]]) { const user = reset(), offer = setOffer(user, productId, priceFen); @@ -96,6 +96,7 @@ test('each lucky tier uses the server offer price, quantity and reward snapshot const saved = tables.order[0]; assert.equal(saved.goodsPrice, priceFen); assert.equal(saved.itemCount, 1); assert.deepEqual(saved.starterPackOffer, offer); + assert.equal(saved.starterPackOffer.reward, undefined); assert.equal(saved.activityType, 'lucky'); assert.equal(saved.rewardVersion, offer.rewardVersion); const signed = JSON.parse(result.data.signData); assert.equal(signed.productId, productId); assert.equal(signed.goodsPrice, priceFen); assert.equal(signed.buyQuantity, 1); diff --git a/server/laf-cloud/tests/starter-pack.test.mjs b/server/laf-cloud/tests/starter-pack.test.mjs index dc458f9..e2c4047 100644 --- a/server/laf-cloud/tests/starter-pack.test.mjs +++ b/server/laf-cloud/tests/starter-pack.test.mjs @@ -41,6 +41,7 @@ registerHooks({ }); const { default: activity, starterPackPatch, starterPackOrderSnapshot } = await import('../functions/limitedTimeEvent.ts'); const { PAYMENT_PROFILE_RULE } = await import('../functions/paymentProfile.ts'); +const { starterPackReward } = await import('../../../assets/Script/module/Config/StarterPack.ts'); function reset(patch = {}) { now = NOW; beforeUpdate = null; failProfile = false; for (const name of Object.keys(tables)) tables[name] = []; @@ -68,10 +69,11 @@ test('read or save without a reason cannot start; first unpaid offer lasts 12 ho assert.equal(user.starter_pack, NOW + DAY / 2); assert.equal(user.starterPackRound, 1); assert.equal(user.starterPackOffer.productId, 'starter_pack'); - assert.deepEqual(user.starterPackOffer.reward, { coin: 3000, hammer: 1, freeze: 1, magic_wand: 1, infinite_health: 1800 }); + assert.equal(user.starterPackOffer.reward, undefined); + assert.deepEqual(starterPackReward('starter_pack'), { coin: 3000, hammer: 1, freeze: 1, magic_wand: 1, infinite_health: 1800 }); }); -test('formula thresholds select four paid products and exact rewards for six hours', async () => { +test('formula thresholds select four paid products while the client fixes rewards by SKU', async () => { for (const [daily, id, price, coin, props, minutes] of [ [0, 'starter_pack_2', 600, 3000, 1, 30], [1199, 'starter_pack_2', 600, 3000, 1, 30], [1200, 'starter_pack_3', 1200, 4500, 3, 45], [1800, 'starter_pack_4', 1800, 5000, 5, 60], @@ -82,7 +84,8 @@ test('formula thresholds select four paid products and exact rewards for six hou assert.equal(user.starter_pack, NOW + DAY / 4); assert.equal(user.starterPackOffer.productId, id); assert.equal(user.starterPackOffer.priceFen, price); - assert.deepEqual(user.starterPackOffer.reward, { coin, hammer: props, freeze: props, magic_wand: props, infinite_health: minutes * 60 }); + assert.equal(user.starterPackOffer.reward, undefined); + assert.deepEqual(starterPackReward(id), { coin, hammer: props, freeze: props, magic_wand: props, infinite_health: minutes * 60 }); } }); @@ -134,15 +137,16 @@ test('a displayed offer stays fixed when payment identity and daily factors chan assert.equal(starterPackOrderSnapshot(user, 'starter_pack').starterPackOffer.priceFen, 300); }); -test('legacy active offers retain their original deadline and old promised reward', async () => { +test('legacy active offers retain their original deadline and use the client 3 yuan reward', async () => { for (const version of [undefined, 1, 2]) { const user = reset({ starter_pack: NOW + DAY, starterPackVersion: version }); await Promise.all([read(), read()]); assert.equal(user.starter_pack, NOW + DAY); assert.equal(user.starterPackRound, 1); assert.equal(user.starterPackOffer.legacy, true); - assert.equal(user.starterPackOffer.reward.hammer, 3); - assert.equal(user.starterPackOffer.reward.infinite_health, 1800); + assert.equal(user.starterPackOffer.reward, undefined); + assert.deepEqual(starterPackReward(user.starterPackOffer.productId), + { coin: 3000, hammer: 1, freeze: 1, magic_wand: 1, infinite_health: 1800 }); const locked = copy(user); await read(); assert.deepEqual(user, locked); } }); @@ -227,11 +231,11 @@ test('form-encoded failure counts and automatic flags match the client transport } }); -test('order snapshots bind a valid current SKU, price, reward and cycle', async () => { +test('order snapshots bind a valid current SKU, price and cycle without reward data', async () => { const user = reset({ pay_user: true }); profile(user, 3000); await trigger(); const snapshot = starterPackOrderSnapshot(user, 'starter_pack_5'); assert.equal(snapshot.starterPackOffer.priceFen, 3000); - assert.equal(snapshot.starterPackOffer.reward.coin, 8000); + assert.equal(snapshot.starterPackOffer.reward, undefined); assert.equal(snapshot.activityType, 'lucky'); assert.equal(snapshot.rewardVersion, PAYMENT_PROFILE_RULE.starterPackRewardVersion); assert.throws(() => starterPackOrderSnapshot(user, 'starter_pack'), /档位/); diff --git a/tools/test-starter-pack.cjs b/tools/test-starter-pack.cjs index 01e1484..a7017ec 100644 --- a/tools/test-starter-pack.cjs +++ b/tools/test-starter-pack.cjs @@ -185,7 +185,7 @@ test('pending order still completes after expiry while a fresh order is forbidde let grantedOrder; f.cc.fx.GameTool.shopBuy = (id, comp, data) => { grantedOrder = data.outTradeNo; }; f.gift.onShow(); assert.equal(grantedOrder, 'late'); assert.equal(f.gift.node.active, false); }); -for (const remaining of [0, -60, 600]) test(`starter pack grants 3000/3 and 30 minutes for purchases and compensation, with ${remaining}s existing health`, () => { +for (const remaining of [0, -60, 600]) test(`starter pack grants 3000/1 and 30 minutes for purchases and compensation, with ${remaining}s existing health`, () => { const f = fixture(); const ast = source('assets/Script/module/Tool/GameTool.ts'); const obj = ast.statements.find(n => ts.isVariableStatement(n) && n.declarationList.declarations.some(d => d.name.getText(ast) === 'GameTool')); @@ -203,7 +203,7 @@ for (const remaining of [0, -60, 600]) test(`starter pack grants 3000/3 and 30 m tool.shopBuy('starter_pack', true, { outTradeNo: 'new', starterPackVersion: 2 }); tool.shopBuy('starter_pack', false, { outTradeNo: 'new', starterPackVersion: 2 }); assert.deepEqual(coins, [3000,3000]); - assert.deepEqual(props.map(p => [p.hammer,p.freeze,p.magic_wand]), [[3,3,3],[3,3,3]]); + assert.deepEqual(props.map(p => [p.hammer,p.freeze,p.magic_wand]), [[1,1,1],[1,1,1]]); const base = nowSeconds + Math.max(0, remaining); assert.deepEqual(synced, [base + 1800, base + 3600]); assert.equal(f.storage.get('userPowerTime'), base + 3600); @@ -680,9 +680,9 @@ const tiers = [ ['starter_pack_3', 1200, 4500, 3, 2700], ['starter_pack_4', 1800, 5000, 5, 3600], ['starter_pack_5', 3000, 8000, 8, 10800], ]; -for (const [productId, priceFen, coin, props, seconds] of tiers) test(`${productId} uses order snapshot for UI, payment and legacy grant flow`, () => { +for (const [productId, priceFen, coin, props, seconds] of tiers) test(`${productId} uses server tier and client-fixed rewards`, () => { const f=fixture(), coins=[], grants=[], health=[], displayed=[]; - const offer={version:3,round:2,productId,priceFen,reward:{coin,hammer:props,freeze:props,magic_wand:props,infinite_health:seconds}}; + const offer={version:3,round:2,productId,priceFen}; Object.assign(f.info,{starterPackRound:2,starterPackOffer:offer}); f.gift.init(true); for(const value of [3,6,12,18,30]) assert.equal(f.cc.find('btnContainer/'+value,f.gift.node).active,value*100===priceFen); @@ -717,14 +717,14 @@ test('new round resets purchased status but stale round and same-round factor ch assert.equal(f.info.starterPackRound,2); assert.equal(f.info.starterPackPurchased,false); }); -test('late old order gives its original reward and leaves the current round purchasable', () => { - const f=fixture(); f.info.starterPackRound=4; f.info.starterPackOffer={round:4,reward:{coin:8000}}; +test('late old order uses its SKU reward and leaves the current round purchasable', () => { + const f=fixture(); f.info.starterPackRound=4; f.info.starterPackOffer={round:4,productId:'starter_pack_5',priceFen:3000}; const coins=[], props=[]; f.globals.MapConroler={_instance:null}; const tool=gameToolMethods(f,['shopBuy']); f.cc.fx.GameTool.changeCoin=n=>coins.push(n); tool.getShopProp=data=>props.push(data); tool.setUserPowerTime=()=>{}; tool.shopBuy('starter_pack',true,{outTradeNo:'legacy'}); - assert.equal(coins[0],3000); assert.equal(props[0].hammer,3); assert.equal(f.info.starterPackPurchased,false); - tool.shopBuy('starter_pack_3',true,{outTradeNo:'old3',starterPackBuyRound:3,starterPackOffer:{priceFen:1200,reward:{coin:4500,freeze:3,hammer:3,magic_wand:3,infinite_health:2700}}}); + assert.equal(coins[0],3000); assert.equal(props[0].hammer,1); assert.equal(f.info.starterPackPurchased,false); + tool.shopBuy('starter_pack_3',true,{outTradeNo:'old3',starterPackBuyRound:3,starterPackOffer:{priceFen:1200}}); assert.equal(coins[1],4500); assert.equal(f.info.starterPackPurchased,false); }); @@ -740,13 +740,19 @@ test('level purchase waits for a safe screen and queued purchase intent never fo }); -test('new SKU without its order snapshot is not acknowledged or granted legacy rewards', () => { - const f=fixture(); const tool=gameToolMethods(f,['shopBuy']); - assert.equal(tool.shopBuy('starter_pack_5',true,{outTradeNo:'missing'}),false); +test('new SKU is acknowledged and uses client rewards without a server reward snapshot', () => { + const f=fixture(), coins=[], props=[], health=[]; + f.globals.MapConroler={_instance:null}; + const tool=gameToolMethods(f,['shopBuy']); + f.cc.fx.GameTool.changeCoin=n=>coins.push(n); tool.getShopProp=data=>props.push(data); tool.setUserPowerTime=n=>health.push(n); + tool.shopBuy('starter_pack_5',true,{outTradeNo:'fixed-reward'}); + assert.deepEqual(coins,[8000]); assert.equal(props[0].hammer,8); assert.deepEqual(health,[10800]); f.gift.pendingOrder='missing'; f.api.getPayInfo=done=>done({code:1,data:{pay_state:2,itemid:'starter_pack_5'}}); - f.api.setPayInfo=()=>assert.fail('missing snapshot acknowledged'); - f.cc.fx.GameTool.shopBuy=()=>assert.fail('missing snapshot rewarded'); + let acknowledged=false, granted=''; + f.api.setPayInfo=done=>{acknowledged=true;done({code:1,data:'ok'});}; + f.cc.fx.GameTool.shopBuy=id=>{granted=id;}; f.gift.onShow(); - assert.equal(f.gift.pendingOrder,'missing'); assert.equal(f.gift.polling,false); assert.equal(f.gift.paying,false); + assert.equal(acknowledged,true); assert.equal(granted,'starter_pack_5'); + assert.equal(f.gift.pendingOrder,''); assert.equal(f.gift.polling,false); assert.equal(f.gift.paying,false); });