支付相关

This commit is contained in:
chen wei bo
2025-09-11 15:36:31 +08:00
parent 5accb0b507
commit a7e1724cc6
8 changed files with 118 additions and 34 deletions
+44 -10
View File
@@ -6,41 +6,75 @@ import { PaymentApi } from "./PaymentApi";
import proto from 'db://assets/Scripts/proto/proto.pb.js'; import proto from 'db://assets/Scripts/proto/proto.pb.js';
export interface PollOptions { export interface PollOptions {
maxDurationMs: number; // 轮询时长(例如 2分钟) maxDurationMs: number; // 最大轮询时长(超过就自动退出)(例如 2分钟)
startIntervalMs: number; // 初始间隔(例如 2s startIntervalMs: number; // 第一次轮询等待的间隔(例如 2s
maxIntervalMs: number; // 最大间隔(例如 10s maxIntervalMs: number; // 最大允许的间隔,防止指数增长过大(例如 10s
jitter?: boolean; // 抖动 jitter?: boolean; // 是否启用抖动(让等待时间带点随机性,避免所有客户端同一时间请求服务器)
} }
export class PaymentPoller { export class PaymentPoller {
private cancelled = false; private cancelled = false;
private currentPollCount = 0; // 轮询次数
// 取消轮询 // 取消轮询
cancel() { this.cancelled = true; } cancel() {
this.cancelled = true;
}
// 轮询订单 // 获取当前轮询次数
async poll(orderId: string, opt: PollOptions, onTick?: (d: proto.cs.ICSQueryOrderRes) => void): Promise<proto.cs.ICSQueryOrderRes | null> { public getCurrentPollCount(): number {
return this.currentPollCount;
}
/**
* 轮询订单
* @param orderId 要查询的订单 ID
* @param opt 轮询配置(间隔、最大时长、是否抖动)
* @param onTick 可选回调函数,每次轮询到结果时调用
* @returns 最终查询到的订单结果 ICSQueryOrderRes,或者 null(超时/被取消)
*/
async poll(orderId: string, opt: PollOptions, onTick?: (d: proto.cs.ICSQueryOrderRes) => void): Promise<proto.cs.ICSQueryOrderRes> {
const t0 = Date.now(); const t0 = Date.now();
let interval = opt.startIntervalMs; let interval = opt.startIntervalMs;
while (!this.cancelled) { while (!this.cancelled) {
// 增加轮询次数
this.currentPollCount++;
// 查询 // 查询
let reqData = {orderId: orderId}; let reqData = {orderId: orderId};
console.log("第 ", this.currentPollCount, " 次轮询订单的请求数据:", reqData);
const r = await PaymentApi.I.queryOrder(reqData); const r = await PaymentApi.I.queryOrder(reqData);
console.log("第 ", this.currentPollCount, " 次轮询订单的响应数据:", r);
if (r.code === proto.cs.EnmRetCode.SUCCESS && r.data) { if (r.code === proto.cs.EnmRetCode.SUCCESS && r.data) {
// 回调 // 回调
onTick?.(r.data); onTick?.(r.data);
// 订单状态,-1:失败,0:成功,1:处理中
if ([-1, 0].indexOf(r.data.status) !== -1) { if ([-1, 0].indexOf(r.data.status) !== -1) {
return r.data; return r.data;
} }
} }
// 退出条件 // 轮询超时
if (Date.now() - t0 > opt.maxDurationMs) return null; if (Date.now() - t0 > opt.maxDurationMs) {
let resultData: proto.cs.ICSQueryOrderRes = {
status: 92, // 自定义的状态,代表客户端轮询超时
retCode: 0,
balance: 0,
vipExpire: 0,
}
return resultData;
}
// 等待 // 等待
await this.sleep(this.jitter(interval, opt)); await this.sleep(this.jitter(interval, opt));
// 每次轮询后,interval 会按照 1.5 倍增长,直到达到最大间隔 maxIntervalMs,避免在短时间内重复请求 // 每次轮询后,interval 会按照 1.5 倍增长,直到达到最大间隔 maxIntervalMs,避免在短时间内重复请求
interval = Math.min(interval * 1.5, opt.maxIntervalMs); interval = Math.min(interval * 1.5, opt.maxIntervalMs);
} }
return null; // 至此,代表取消了轮询订单
let resultData: proto.cs.ICSQueryOrderRes = {
status: 90, // 自定义的状态,代表取消
retCode: 0,
balance: 0,
vipExpire: 0,
}
return resultData;
} }
// 用来暂停一定的时间(ms 毫秒),并让轮询进入等待状态 // 用来暂停一定的时间(ms 毫秒),并让轮询进入等待状态
@@ -32,12 +32,12 @@ export class PaymentService {
} }
/** 发起支付:创建订单→打开URL→回到App后开始轮询 */ /** 发起支付:创建订单→打开URL→回到App后开始轮询 */
async startPayment(req: proto.cs.ICSCreateOrderReq, open: OpenStrategy = "system"): Promise<proto.cs.ICSQueryOrderRes | null> { async startPayment(req: proto.cs.ICSCreateOrderReq, open: OpenStrategy = "system"): Promise<proto.cs.ICSQueryOrderRes> {
// 下单 // 下单
const create = await PaymentApi.I.createOrder(req); const create = await PaymentApi.I.createOrder(req);
console.log("创建订单的响应数据:", create); console.log("创建订单的响应数据:", create);
if (create.code !== proto.cs.EnmRetCode.SUCCESS || !create.data) { if (create.code !== proto.cs.EnmRetCode.SUCCESS || !create.data) {
return { status: -1, retCode: -1 }; return { status: -1, retCode: -1, balance: 0, vipExpire: 0};
} }
const { orderId, payUrl } = create.data; const { orderId, payUrl } = create.data;
this.bus.emit(PaymentEvents.Started, { orderId }); this.bus.emit(PaymentEvents.Started, { orderId });
@@ -108,6 +108,7 @@ export class PaymentService {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
let finished = false; let finished = false;
const finish = () => { const finish = () => {
console.log("应用回到 finish");
if (finished) return; if (finished) return;
finished = true; finished = true;
// 安全移除监听 // 安全移除监听
@@ -117,6 +118,7 @@ export class PaymentService {
const onShow = () => { const onShow = () => {
// 应用回到前台 // 应用回到前台
console.log("应用回到了前台");
finish(); finish();
}; };
@@ -22,13 +22,15 @@ export function mapQueryOrderStatus(
case 1: return "PENDING"; // 处理中 case 1: return "PENDING"; // 处理中
case 0: return "SUCCESS"; // 成功 case 0: return "SUCCESS"; // 成功
case -1: return "FAILED"; // 失败 case -1: return "FAILED"; // 失败
case 90: return "CANCELED"; // 取消
case 92: return "TIMEOUT"; // 轮询超时
default: return "CREATED"; // 未知/初始,按“已创建”兜底 default: return "CREATED"; // 未知/初始,按“已创建”兜底
} }
} }
/** 是否为终态(无需再轮询) */ /** 是否为终态(无需再轮询) */
export function isTerminalStatus(s: OrderStatus): boolean { export function isTerminalStatus(s: OrderStatus): boolean {
return s === "SUCCESS" || s === "FAILED" || s === "CANCELED" || s === "EXPIRED"; return s === "SUCCESS" || s === "FAILED" || s === "CANCELED" || s === "EXPIRED" || s === "TIMEOUT";
} }
/** 是否应继续轮询查询订单 */ /** 是否应继续轮询查询订单 */
+1
View File
@@ -9,6 +9,7 @@ export type OrderStatus =
| "SUCCESS" // 成功 | "SUCCESS" // 成功
| "FAILED" // 失败 | "FAILED" // 失败
| "CANCELED" // 取消 | "CANCELED" // 取消
| "TIMEOUT" // 轮询超时
| "EXPIRED"; // 过期 | "EXPIRED"; // 过期
// 订单数据的数据结构 // 订单数据的数据结构
@@ -124,7 +124,7 @@ export class PurchasePanel extends li_BaseView {
const isRechargeId = shopData.isRechargeId(id); const isRechargeId = shopData.isRechargeId(id);
if (isRechargeId) { if (isRechargeId) {
// 需要外部支付进行购买 // 需要外部支付进行购买
this.startPayment(id); // this.startPayment(id);
} }
} }
@@ -186,10 +186,19 @@ export class PurchasePanel extends li_BaseView {
const reqData = { const reqData = {
goodId goodId
}; };
// let res = await PaymentService.I.startPayment(reqData); let res = await PaymentService.I.startPayment(reqData);
// if (res) { console.log("支付完成的响应数据:", res);
if (res) {
// } let status = res.status;
if (status === -1) {
console.log("支付失败!!!,错误码:", res.retCode);
} else if (status === 0) {
console.log("支付成功!!!");
const walletData = DataManager.I.getDataById<WalletData>(DataId.Wallet);
walletData.balance = Number(res.balance);
walletData.vipExpire = Number(res.vipExpire);
}
}
} }
onDestroy() { onDestroy() {
+17 -5
View File
@@ -5802,11 +5802,23 @@ static getTypeUrl(typeUrlPrefix?: string): string;
/** EnmRetCode enum. */ /** EnmRetCode enum. */
enum EnmRetCode { enum EnmRetCode {
SUCCESS = 0, SUCCESS = 0,
GenJWTFailed = 5010, GenJWTFailed = 5000,
InvalidArg = 5013, Insufficient = 5001,
MySQL = 5027, InvalidArg = 5002,
TokenExpire = 5046, InvalidSign = 5003,
TokenInvalid = 5047 JsonFormat = 5004,
MySQL = 5005,
NotRegist = 5006,
NotResConfig = 5007,
Rate_Limited = 5008,
Redis = 5009,
Request_Too_More = 5010,
RMGOrder_Code = 5011,
RMGOrder_Http = 5012,
RMGOrder_MaxQuery = 5013,
RMGOrder_NotFound = 5014,
TokenExpire = 5015,
TokenInvalid = 5016
} }
} }
+34 -10
View File
@@ -13854,20 +13854,44 @@ $root.cs = (function() {
* @name cs.EnmRetCode * @name cs.EnmRetCode
* @enum {number} * @enum {number}
* @property {number} SUCCESS=0 SUCCESS value * @property {number} SUCCESS=0 SUCCESS value
* @property {number} GenJWTFailed=5010 GenJWTFailed value * @property {number} GenJWTFailed=5000 GenJWTFailed value
* @property {number} InvalidArg=5013 InvalidArg value * @property {number} Insufficient=5001 Insufficient value
* @property {number} MySQL=5027 MySQL value * @property {number} InvalidArg=5002 InvalidArg value
* @property {number} TokenExpire=5046 TokenExpire value * @property {number} InvalidSign=5003 InvalidSign value
* @property {number} TokenInvalid=5047 TokenInvalid value * @property {number} JsonFormat=5004 JsonFormat value
* @property {number} MySQL=5005 MySQL value
* @property {number} NotRegist=5006 NotRegist value
* @property {number} NotResConfig=5007 NotResConfig value
* @property {number} Rate_Limited=5008 Rate_Limited value
* @property {number} Redis=5009 Redis value
* @property {number} Request_Too_More=5010 Request_Too_More value
* @property {number} RMGOrder_Code=5011 RMGOrder_Code value
* @property {number} RMGOrder_Http=5012 RMGOrder_Http value
* @property {number} RMGOrder_MaxQuery=5013 RMGOrder_MaxQuery value
* @property {number} RMGOrder_NotFound=5014 RMGOrder_NotFound value
* @property {number} TokenExpire=5015 TokenExpire value
* @property {number} TokenInvalid=5016 TokenInvalid value
*/ */
cs.EnmRetCode = (function() { cs.EnmRetCode = (function() {
var valuesById = {}, values = Object.create(valuesById); var valuesById = {}, values = Object.create(valuesById);
values[valuesById[0] = "SUCCESS"] = 0; values[valuesById[0] = "SUCCESS"] = 0;
values[valuesById[5010] = "GenJWTFailed"] = 5010; values[valuesById[5000] = "GenJWTFailed"] = 5000;
values[valuesById[5013] = "InvalidArg"] = 5013; values[valuesById[5001] = "Insufficient"] = 5001;
values[valuesById[5027] = "MySQL"] = 5027; values[valuesById[5002] = "InvalidArg"] = 5002;
values[valuesById[5046] = "TokenExpire"] = 5046; values[valuesById[5003] = "InvalidSign"] = 5003;
values[valuesById[5047] = "TokenInvalid"] = 5047; values[valuesById[5004] = "JsonFormat"] = 5004;
values[valuesById[5005] = "MySQL"] = 5005;
values[valuesById[5006] = "NotRegist"] = 5006;
values[valuesById[5007] = "NotResConfig"] = 5007;
values[valuesById[5008] = "Rate_Limited"] = 5008;
values[valuesById[5009] = "Redis"] = 5009;
values[valuesById[5010] = "Request_Too_More"] = 5010;
values[valuesById[5011] = "RMGOrder_Code"] = 5011;
values[valuesById[5012] = "RMGOrder_Http"] = 5012;
values[valuesById[5013] = "RMGOrder_MaxQuery"] = 5013;
values[valuesById[5014] = "RMGOrder_NotFound"] = 5014;
values[valuesById[5015] = "TokenExpire"] = 5015;
values[valuesById[5016] = "TokenInvalid"] = 5016;
return values; return values;
})(); })();