diff --git a/assets/Scripts/chat18x/payment/PaymentService.ts b/assets/Scripts/chat18x/payment/PaymentService.ts index 00f4898d..58c185a3 100644 --- a/assets/Scripts/chat18x/payment/PaymentService.ts +++ b/assets/Scripts/chat18x/payment/PaymentService.ts @@ -2,7 +2,7 @@ * 支付对外的相关接口 * */ -import { EventTarget } from "cc"; +import { EventTarget, game, Game } from "cc"; import { PaymentApi } from "./PaymentApi"; import { PaymentOpenerFactory, type OpenStrategy } from "./PaymentOpener"; import { PendingOrderRepo } from "./PendingOrderRepo"; @@ -79,21 +79,49 @@ export class PaymentService { /** App 回到前台后恢复未完成订单的轮询(在 onShow 时调用) */ async resumePending(): Promise { - const all = PendingOrderRepo.instance.all(); - for (const o of all) { - // 可以做去重/只处理未过期的 + const now = Date.now(); + const list = PendingOrderRepo.instance.all(); + + // 去重:同 orderId 只保留 createdAt 最新的一条 + const uniq = new Map(); + for (const o of list) { + const ex = uniq.get(o.orderId); + if (!ex || (o.createdAt ?? 0) > (ex.createdAt ?? 0)) { + uniq.set(o.orderId, o); + } + } + + for (const o of uniq.values()) { + // 只处理未过期 + if (this.isOrderExpired(o, now)) { + PendingOrderRepo.instance.remove(o.orderId); + this.bus.emit(PaymentEvents.Finished, { + orderId: o.orderId, + status: "EXPIRED" as OrderStatus, + message: "order expired", + } as PaymentResult); + continue; + } + + // 轮询最新且未过期的订单 const r = await this.startPollingOnly(o.orderId); if (r && r.status !== "PENDING" && r.status !== "CREATED") { PendingOrderRepo.instance.remove(o.orderId); this.bus.emit(PaymentEvents.Finished, { orderId: o.orderId, status: r.status as OrderStatus, - message: r.failureReason + message: r.failureReason, } as PaymentResult); } + // r === null(轮询超时)时保留在仓库,等待下一次 resume 再查 } } + /** 判断订单是否过期:有 expireAt 且 now >= expireAt 才认为过期 */ + private isOrderExpired(o: PendingOrder, now = Date.now()): boolean { + return typeof o.expireAt === "number" && now >= o.expireAt; + } + /** 仅轮询(用于 resumePending 或手动刷新) */ private async startPollingOnly(orderId: string) { const poller = new PaymentPoller(); @@ -106,12 +134,34 @@ export class PaymentService { } /** 等待“回到前台”的占位实现:实际请在 App 生命周期里调用 resumePending() */ - private async waitAppResumeIfNeeded(open: OpenStrategy) { - // 如果是系统浏览器,我们通常等“App 回到前台”再开始轮询; - // 这里简单 sleep 1s,实际项目请监听 Cocos 的 onShow/onHide 或平台回调。 - if (open === "system") { - await new Promise(res => setTimeout(res, 1000)); + private waitAppResumeIfNeeded(open: OpenStrategy): Promise { + if (open !== "system") { + // 内嵌 WebView / 小游戏平台通常可以在回调里直接开始轮询 + return Promise.resolve(); } + + return new Promise((resolve) => { + let finished = false; + const finish = () => { + if (finished) return; + finished = true; + // 安全移除监听 + game.off(Game.EVENT_SHOW, onShow, this); + resolve(); + }; + + const onShow = () => { + // 应用回到前台 + finish(); + }; + + // 监听一次“回到前台” + game.on(Game.EVENT_SHOW, onShow, this); + + // 兜底:某些环境(桌面浏览器)可能不会触发隐藏/显示事件,避免一直卡住 + const FALLBACK_MS = 15000; // 按需调整 + setTimeout(finish, FALLBACK_MS); + }); } } export default PaymentService;