231 lines
12 KiB
TypeScript
231 lines
12 KiB
TypeScript
import Utils from './module/Pay/Utils';
|
|
import Art from './BattlePassArt';
|
|
import NumberToImage from './NumberToImage';
|
|
import { settlementRewards, settlementSteps } from './module/Config/BattlePassSettlementModel';
|
|
import { MiniGameSdk } from './Sdk/MiniGameSdk';
|
|
|
|
const { ccclass } = cc._decorator;
|
|
@ccclass
|
|
export default class BattlePassSettlement extends cc.Component {
|
|
private root: cc.Node;
|
|
private panel: cc.Node;
|
|
private confirm: cc.Node;
|
|
private rows: any[] = [];
|
|
private row: any;
|
|
private latest = 0;
|
|
private serverTime = 0;
|
|
private mode = '';
|
|
private busy = false;
|
|
private offerToken = '';
|
|
private finished: () => void;
|
|
private iosOrder = '';
|
|
private recoveredRewards: any[] = [];
|
|
private back = (e: cc.Event.EventKeyboard) => {
|
|
if (e.keyCode === cc.macro.KEY.escape || e.keyCode === 6) this.close();
|
|
};
|
|
private resume = () => { if (this.iosOrder) this.checkIos(); };
|
|
|
|
async run(done: () => void) {
|
|
this.finished = done;
|
|
try {
|
|
// Resolve paid entitlements before selecting frozen rewards. Other login
|
|
// orders remain in the home queue until this entire sequence is closed.
|
|
const gm = cc.fx.GameConfig.GM_INFO;
|
|
for (const order of (gm.allOutTradeNo || []).slice()) {
|
|
if (!/^battlepass(?:_30|_12)?$/.test(order.itemid)) continue;
|
|
try {
|
|
const paid = await Utils.completePassOrder(order.outTradeNo);
|
|
if (!paid.data.alreadyGranted) {
|
|
if (paid.data.postSeason) this.recoveredRewards.push(...(paid.data.rewards || []));
|
|
else if (paid.data.bonus) this.recoveredRewards.push({ type: 'coin', count: 3000 }, { type: 'pass_key', count: 20 });
|
|
}
|
|
gm.allOutTradeNo = gm.allOutTradeNo.filter(o => o.outTradeNo !== order.outTradeNo);
|
|
} catch (e) {
|
|
// if (e.code !== 410) throw e;
|
|
// Paid duplicates/obsolete orders remain available for manual review.
|
|
cc.warn('战令订单需要人工处理', order.outTradeNo, e.msg);
|
|
}
|
|
}
|
|
const result = await Utils.settlementRequest('read');
|
|
if (!cc.isValid(this.node)) return;
|
|
this.serverTime = result.data.serverTime;
|
|
this.latest = Math.max(0, ...result.data.rows.map(r => r.end));
|
|
this.rows = settlementSteps(result.data.rows, this.latest, this.serverTime);
|
|
if (!this.rows.length && !this.recoveredRewards.length) { done(); this.destroy(); return; }
|
|
await Art.load();
|
|
if (!cc.isValid(this.node)) return;
|
|
this.root = cc.instantiate(Art.settlementPrefab); this.root.active = false; this.root.parent = this.node; this.root.zIndex = 1000;
|
|
this.panel = this.root.getChildByName('panel');
|
|
const fit = Math.min(this.node.width / this.root.width, this.node.height / this.root.height);
|
|
this.panel.scaleX *= fit; this.panel.scaleY *= fit;
|
|
for (const mode of ['offer', 'empty', 'claim']) this.bindButton(mode + '/closeButton', () => this.close());
|
|
this.bindButton('claim/claimButton', () => this.claim());
|
|
this.bindButton('offer/buyButton', () => this.purchase());
|
|
this.bindButton('confirmation/thinkAgainButton', () => this.hideConfirmation());
|
|
this.bindButton('confirmation/abandonButton', () => this.decline());
|
|
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.back, this);
|
|
// @ts-ignore
|
|
if (typeof wx !== 'undefined') wx.onShow(this.resume);
|
|
this.next();
|
|
} catch (e) {
|
|
this.error(e);
|
|
if (this.root && cc.isValid(this.root)) this.root.destroy();
|
|
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.back, this);
|
|
// @ts-ignore
|
|
if (typeof wx !== 'undefined') wx.offShow(this.resume);
|
|
// Keep the queue reserved until the settlement check can complete.
|
|
this.scheduleOnce(() => this.run(done), 5);
|
|
}
|
|
}
|
|
private error(e: any) {
|
|
cc.log(e.msg || e.message || '网络异常,请重试');
|
|
// MiniGameSdk.API.showToast(e.msg || e.message || '网络异常,请重试');
|
|
}
|
|
private next() {
|
|
this.row = this.rows.shift();
|
|
if (!this.row) {
|
|
if (this.recoveredRewards.length) {
|
|
const rewards = this.recoveredRewards.splice(0);
|
|
this.playRewards(rewards, () => this.next());
|
|
return;
|
|
}
|
|
this.root.destroy(); this.finished(); this.destroy(); return;
|
|
}
|
|
if (!this.row.claimed && !this.row.emptySeen) {
|
|
const rewards = settlementRewards(this.row, cc.fx.GameConfig.PASS_CHECK);
|
|
if (rewards.length) this.show('claim', rewards);
|
|
else {
|
|
this.show('empty');
|
|
this.busy = true;
|
|
// Mark only after the successfully constructed view has reached a rendered frame.
|
|
this.scheduleOnce(() => this.markEmpty(), 0);
|
|
}
|
|
} else this.offerOrNext();
|
|
}
|
|
private async markEmpty() {
|
|
try { const r = await Utils.settlementRequest('empty_seen', { end: this.row.end }); this.row = r.data.row; this.busy = false; }
|
|
catch (e) { this.busy = false; this.error(e); }
|
|
}
|
|
private offerOrNext() {
|
|
if (this.row.end !== this.latest || this.row.end <= this.serverTime - 30 * 86400000 || this.row.stage.activate || this.row.stage.tier || this.row.offer !== 'unused' || this.row.purchase) { this.next(); return; }
|
|
this.offerToken = Date.now().toString(36) + '_' + Math.random().toString(36).slice(2) + '_' + Math.random().toString(36).slice(2);
|
|
// A stale snapshot may pass the local check but be rejected by the server.
|
|
// Keep the prefab (including its default offer and shade) hidden until accepted.
|
|
this.root.active = false;
|
|
this.busy = true;
|
|
this.markOffer();
|
|
}
|
|
private async markOffer() {
|
|
try {
|
|
const r = await Utils.settlementRequest('offer_shown', { end: this.row.end, offerToken: this.offerToken });
|
|
if (!cc.isValid(this.node) || !cc.isValid(this.root)) return;
|
|
this.row = r.data.row;
|
|
this.show('offer', settlementRewards(this.row, cc.fx.GameConfig.PASS_CHECK, true));
|
|
this.busy = false;
|
|
}
|
|
catch (e) {
|
|
if (!cc.isValid(this.node) || !cc.isValid(this.root)) return;
|
|
this.busy = false;
|
|
if (e.code === 410) { this.next(); return; }
|
|
this.error(e);
|
|
// No button is visible yet. Retry with the same token so a lost response
|
|
// does not consume the opportunity twice or leave the home queue stuck.
|
|
this.busy = true;
|
|
this.scheduleOnce(() => this.markOffer(), 5);
|
|
}
|
|
}
|
|
private bindButton(path: string, click: () => void) {
|
|
cc.find(path, this.panel).on('click', () => { if (!this.busy) click(); }, this);
|
|
}
|
|
private show(mode: string, rewards: any[] = []) {
|
|
this.mode = mode; this.hideConfirmation();
|
|
for (const name of ['offer', 'empty', 'claim']) this.panel.getChildByName(name).active = name === mode;
|
|
if (mode !== 'empty') this.preview(rewards);
|
|
this.root.active = true;
|
|
}
|
|
private preview(rewards: any[]) {
|
|
const group = cc.find(this.mode + '/rewards', this.panel);
|
|
const slots = group.getChildByName('topRow').children.concat(group.getChildByName('bottomRow').children);
|
|
slots.forEach((cell, i) => {
|
|
const r = rewards[i]; cell.active = !!r; if (!r) return;
|
|
const item = cell.getChildByName('item');
|
|
const icon = item.getChildByName('icon'); for (const child of icon.children) child.active = child.name === r.type;
|
|
const label = item.getChildByName('amount');
|
|
if (r.type === 'infinite_health') NumberToImage.durationToImageNodes(r.count, label);
|
|
else NumberToImage.numberToImageNodesNumber(r.count, 45, 5, 'big_', label, true);
|
|
});
|
|
}
|
|
private hideConfirmation() {
|
|
const confirmation = this.confirm || this.panel.getChildByName('confirmation');
|
|
if (confirmation) confirmation.active = false;
|
|
this.confirm = null;
|
|
}
|
|
private close() {
|
|
if (this.busy) return;
|
|
if (this.confirm) { this.hideConfirmation(); return; }
|
|
if (this.mode === 'claim') { this.claim(); return; }
|
|
if (this.mode === 'empty') { if (!this.row.emptySeen) { this.busy = true; this.markEmpty(); } else this.offerOrNext(); return; }
|
|
if (this.mode !== 'offer') return;
|
|
this.confirm = this.panel.getChildByName('confirmation');
|
|
this.confirm.active = true;
|
|
}
|
|
private async decline() {
|
|
this.busy = true;
|
|
try { await Utils.settlementRequest('decline', { end: this.row.end }); this.busy = false; this.next(); }
|
|
catch (e) { this.busy = false; this.error(e); }
|
|
}
|
|
private async claim() {
|
|
if (this.busy) return; this.busy = true;
|
|
try {
|
|
const r = await Utils.commitPassSettlement('claim', this.row);
|
|
if (!cc.isValid(this.root)) return;
|
|
this.row = r.data.row;
|
|
this.playRewards(r.data.rewards, () => { this.busy = false; this.offerOrNext(); });
|
|
} catch (e) { this.busy = false; this.error(e); }
|
|
}
|
|
private playRewards(rewards: any[], done: () => void) {
|
|
this.root.active = true;
|
|
this.mode = 'animation'; this.panel.active = false;
|
|
const home = this.node.getComponent('JiaZai'); home.updateCoin(); home.updatePower();
|
|
if (rewards?.length) home.openRewardWindow(rewards, undefined, () => { this.panel.active = true; done(); });
|
|
else { this.panel.active = true; done(); }
|
|
}
|
|
private async purchase() {
|
|
if (this.busy) return;
|
|
if (this.row.offer !== 'shown') { this.busy = true; await this.markOffer(); return; }
|
|
this.busy = true;
|
|
let system = 'Android';
|
|
// @ts-ignore
|
|
if (typeof wx !== 'undefined' && wx.getSystemInfoSync().platform === 'ios') system = 'ios';
|
|
if (system === 'ios' && !cc.fx.GameConfig.GM_INFO.iosCanPay) { this.oldIos(); return; }
|
|
Utils.buyProp('battlepass', 1, 1800, system, 'battlepass', res => {
|
|
if (!res || res.err) { if (res?.errCode === 16) { this.oldIos(); return; } this.busy = false; this.error(res || {}); return; }
|
|
Utils.getPayInfo(data => {
|
|
if (data?.data?.pay_state === 2) this.fulfill(res.outTradeNo);
|
|
else { this.busy = false; this.error({ msg: '支付未完成,请重试' }); }
|
|
}, res.outTradeNo);
|
|
}, String(this.row.end), undefined, this.offerToken);
|
|
}
|
|
private oldIos() {
|
|
Utils.GoKEFu({ payment_name: 'battlepass', payment_count: 1, price: 1800, passEnd: String(this.row.end), passOfferToken: this.offerToken },
|
|
() => { this.busy = false; }, order => { this.iosOrder = order; });
|
|
}
|
|
private checkIos() {
|
|
this.busy = true;
|
|
Utils.getIosPayInfo(this.iosOrder, res => { if (res.code === 1) this.fulfill(this.iosOrder); else this.busy = false; });
|
|
}
|
|
private async fulfill(order: string) {
|
|
try {
|
|
const result = await Utils.completePassOrder(order); this.iosOrder = ''; cc.fx.GameConfig.GM_INFO.iosPassCheckOrder = null;
|
|
if (!cc.isValid(this.root)) return;
|
|
this.playRewards(result.data.rewards, () => { this.busy = false; this.next(); });
|
|
} catch (e) { this.busy = false; this.error(e); }
|
|
}
|
|
onDestroy() {
|
|
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.back, this);
|
|
// @ts-ignore
|
|
if (typeof wx !== 'undefined') wx.offShow(this.resume);
|
|
if (this.root && cc.isValid(this.root)) this.root.destroy();
|
|
}
|
|
}
|