Files
18xchat/assets/Scripts/chat18x/payment/PaymentService.ts
T

118 lines
4.4 KiB
TypeScript
Raw Normal View History

2025-08-19 18:32:09 +08:00
/**
* 支付对外的相关接口
*
*/
2025-08-20 10:41:58 +08:00
import { EventTarget, game, Game } from "cc";
2025-08-19 18:32:09 +08:00
import { PaymentApi } from "./PaymentApi";
import { PaymentOpenerFactory, type OpenStrategy } from "./PaymentOpener";
import PaymentPoller from "./PaymentPoller";
2025-09-18 18:28:14 +08:00
import { PaymentFlow } from "./PaymentFlow";
import { CashInrFlow } from "./CashInrFlow";
import { WebUsdFlow } from "./WebUsdFlow";
2025-09-28 14:15:54 +08:00
import { LuffaPayFlow } from "./LuffaPayFlow";
2025-08-26 16:41:28 +08:00
import proto from 'db://assets/Scripts/proto/proto.pb.js';
2025-08-27 14:29:37 +08:00
import { DataManager, DataId } from "db://assets/Scripts/chat18x/data/DataManager";
import { OrderData } from "db://assets/Scripts/chat18x/data/OrderData";
2025-09-18 17:12:10 +08:00
import Utils from "../../Main/Common/Utils";
import { InnerMsgCode } from "../../Main/Config/InnerMsgCode";
2025-09-21 20:18:24 +08:00
import { logger } from "db://assets/Scripts/Main/Common/Logger";
2025-09-28 14:15:54 +08:00
import { PaymentMethod } from './types';
2025-08-19 18:32:09 +08:00
export class PaymentService {
private static _I: PaymentService | null = null;
static get I() { return this._I ?? (this._I = new PaymentService()); }
2025-09-18 18:28:14 +08:00
/** 策略表 */
2025-09-28 14:15:54 +08:00
private flowMap: Map<PaymentMethod, PaymentFlow>;
2025-09-18 18:28:14 +08:00
private constructor() {
2025-09-28 14:15:54 +08:00
this.flowMap = new Map<PaymentMethod, PaymentFlow>();
this.flowMap.set(PaymentMethod.CashPay, new CashInrFlow(this.waitAppResumeIfNeeded.bind(this)));
this.flowMap.set(PaymentMethod.Web3, new WebUsdFlow(5000));
2025-09-28 23:04:16 +08:00
this.flowMap.set(PaymentMethod.Luffa, new LuffaPayFlow(10000));
2025-09-18 18:28:14 +08:00
}
2025-08-19 18:32:09 +08:00
/** 发起支付:创建订单→打开URL→回到App后开始轮询 */
2025-09-28 14:15:54 +08:00
async startPayment(payMethod: PaymentMethod, req: proto.cs.ICSCreateOrderReq, open: OpenStrategy = "system"): Promise<proto.cs.ICSQueryOrderRes> {
2025-08-19 18:32:09 +08:00
// 下单
2025-09-21 20:18:24 +08:00
logger.log("创建订单的请求数据:", req);
2025-08-19 18:32:09 +08:00
const create = await PaymentApi.I.createOrder(req);
2025-09-21 20:18:24 +08:00
logger.log("创建订单的响应数据:", create);
2025-08-26 16:41:28 +08:00
if (create.code !== proto.cs.EnmRetCode.SUCCESS || !create.data) {
2025-09-11 15:36:15 +08:00
return { status: -1, retCode: -1, balance: 0, vipExpire: 0};
2025-08-19 18:32:09 +08:00
}
2025-08-26 16:41:28 +08:00
const { orderId, payUrl } = create.data;
2025-08-19 18:32:09 +08:00
2025-08-27 14:29:37 +08:00
// 保存订单数据
const orderData = DataManager.I.getDataById<OrderData>(DataId.Order);
orderData.saveOrderData(create.data);
2025-08-19 18:32:09 +08:00
2025-09-18 17:12:10 +08:00
// 发出事件,支付订单创建成功
2025-09-18 22:27:02 +08:00
Utils.sendInnerMsg(InnerMsgCode.PayOrderCreateSucc, { orderId: orderId, payType: req.payType,goodId:req.goodId });
2025-09-18 17:12:10 +08:00
2025-09-18 18:28:14 +08:00
// 分发流程
logger.log("选择支付分发流程:", payMethod);
2025-09-28 14:15:54 +08:00
const flow = this.flowMap.get(payMethod);
2025-09-18 18:28:14 +08:00
return flow?.run(orderId, payUrl, open) ?? null;
2025-08-19 18:32:09 +08:00
}
/** App 回到前台后恢复未完成订单的轮询(在 onShow 时调用) */
async resumePending(): Promise<void> {
2025-08-27 14:29:37 +08:00
// 所有需要继续轮询查询的订单 id
const orderData = DataManager.I.getDataById<OrderData>(DataId.Order);
const idList = orderData.getAllShouldPollId();
for (const o of idList) {
2025-08-20 10:41:58 +08:00
// 轮询最新且未过期的订单
2025-08-27 14:29:37 +08:00
const r = await this.startPollingOnly(o);
2025-08-26 16:41:28 +08:00
if (r && r.status !== 1) {
2025-08-27 14:29:37 +08:00
orderData.applyQueryOrder(o, r);
2025-08-19 18:32:09 +08:00
}
2025-08-20 10:41:58 +08:00
// r === null(轮询超时)时保留在仓库,等待下一次 resume 再查
2025-08-19 18:32:09 +08:00
}
}
/** 仅轮询(用于 resumePending 或手动刷新) */
private async startPollingOnly(orderId: string) {
const poller = new PaymentPoller();
return poller.poll(orderId, {
maxDurationMs: 2 * 60 * 1000,
startIntervalMs: 2000,
maxIntervalMs: 8000,
jitter: true,
});
}
/** 等待“回到前台”的占位实现:实际请在 App 生命周期里调用 resumePending() */
2025-08-20 10:41:58 +08:00
private waitAppResumeIfNeeded(open: OpenStrategy): Promise<void> {
if (open !== "system") {
// 内嵌 WebView / 小游戏平台通常可以在回调里直接开始轮询
return Promise.resolve();
2025-08-19 18:32:09 +08:00
}
2025-08-20 10:41:58 +08:00
return new Promise<void>((resolve) => {
let finished = false;
const finish = () => {
2025-09-21 20:18:24 +08:00
logger.log("应用回到 finish");
2025-08-20 10:41:58 +08:00
if (finished) return;
finished = true;
// 安全移除监听
game.off(Game.EVENT_SHOW, onShow, this);
resolve();
};
const onShow = () => {
// 应用回到前台
2025-09-21 20:18:24 +08:00
logger.log("应用回到了前台");
2025-08-20 10:41:58 +08:00
finish();
};
// 监听一次“回到前台”
game.on(Game.EVENT_SHOW, onShow, this);
// 兜底:某些环境(桌面浏览器)可能不会触发隐藏/显示事件,避免一直卡住
2025-09-11 16:32:16 +08:00
const FALLBACK_MS = 25000; // 按需调整
// setTimeout(finish, FALLBACK_MS);
2025-08-20 10:41:58 +08:00
});
2025-08-19 18:32:09 +08:00
}
}
export default PaymentService;