支付协议
This commit is contained in:
@@ -5,7 +5,6 @@
|
|||||||
import { ApiClient } from "../network/client/ApiClient";
|
import { ApiClient } from "../network/client/ApiClient";
|
||||||
import type { Endpoint } from "../network/client/endpoints";
|
import type { Endpoint } from "../network/client/endpoints";
|
||||||
import type { ApiResponse } from "../network/client/types";
|
import type { ApiResponse } from "../network/client/types";
|
||||||
import { OrderStatus, CreateOrderReq, CreateOrderData, QueryOrderData } from "./types";
|
|
||||||
import proto from 'db://assets/Scripts/proto/proto.pb.js';
|
import proto from 'db://assets/Scripts/proto/proto.pb.js';
|
||||||
|
|
||||||
export class PaymentApi {
|
export class PaymentApi {
|
||||||
@@ -14,9 +13,9 @@ export class PaymentApi {
|
|||||||
private constructor(private api = ApiClient.I) {}
|
private constructor(private api = ApiClient.I) {}
|
||||||
|
|
||||||
// 下单
|
// 下单
|
||||||
createOrder(req: CreateOrderReq): Promise<ApiResponse<CreateOrderData>> {
|
createOrder(req: proto.cs.ICSCreateOrderReq): Promise<ApiResponse<proto.cs.ICSCreateOrderRes>> {
|
||||||
let epData: Endpoint<CreateOrderReq, { orderId: string; payUrl: string; expireAt?: number; }> = {
|
let epData: Endpoint<proto.cs.ICSCreateOrderReq, proto.cs.ICSCreateOrderRes> = {
|
||||||
path: "pay/create",
|
path: "api/logic/createOrder",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
codec: "json",
|
codec: "json",
|
||||||
needsAuth: true,
|
needsAuth: true,
|
||||||
@@ -25,24 +24,13 @@ export class PaymentApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询订单
|
// 查询订单
|
||||||
queryOrder(orderId: string): Promise<ApiResponse<QueryOrderData>> {
|
queryOrder(req: proto.cs.ICSQueryOrderReq): Promise<ApiResponse<proto.cs.ICSQueryOrderRes>> {
|
||||||
let epData: Endpoint<{ orderId: string }, { orderId: string; status: OrderStatus; paidAt?: number; failureReason?: string; }> = {
|
let epData: Endpoint<proto.cs.ICSQueryOrderReq, proto.cs.ICSQueryOrderRes> = {
|
||||||
path: "pay/query",
|
path: "api/logic/queryOrder",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
codec: "json",
|
codec: "json",
|
||||||
needsAuth: true,
|
needsAuth: true,
|
||||||
};
|
};
|
||||||
return this.api.call(epData, { orderId });
|
return this.api.call(epData, req);
|
||||||
}
|
|
||||||
|
|
||||||
// 取消订单
|
|
||||||
cancelOrder(orderId: string): Promise<ApiResponse<{ ok: boolean }>> {
|
|
||||||
let epData: Endpoint<{ orderId: string }, { ok: boolean; }> = {
|
|
||||||
path: "pay/cancel",
|
|
||||||
method: "POST",
|
|
||||||
codec: "json",
|
|
||||||
needsAuth: true,
|
|
||||||
};
|
|
||||||
return this.api.call(epData, { orderId });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { PaymentApi } from "./PaymentApi";
|
import { PaymentApi } from "./PaymentApi";
|
||||||
import type { QueryOrderData } from "./types";
|
import proto from 'db://assets/Scripts/proto/proto.pb.js';
|
||||||
import { ApiCode } from "../network/client/types";
|
|
||||||
|
|
||||||
export interface PollOptions {
|
export interface PollOptions {
|
||||||
maxDurationMs: number; // 轮询总时长(例如 2分钟)
|
maxDurationMs: number; // 轮询总时长(例如 2分钟)
|
||||||
@@ -21,16 +20,17 @@ export class PaymentPoller {
|
|||||||
cancel() { this.cancelled = true; }
|
cancel() { this.cancelled = true; }
|
||||||
|
|
||||||
// 轮询订单
|
// 轮询订单
|
||||||
async poll(orderId: string, opt: PollOptions, onTick?: (d: QueryOrderData) => void): Promise<QueryOrderData | null> {
|
async poll(orderId: string, opt: PollOptions, onTick?: (d: proto.cs.ICSQueryOrderRes) => void): Promise<proto.cs.ICSQueryOrderRes | null> {
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
let interval = opt.startIntervalMs;
|
let interval = opt.startIntervalMs;
|
||||||
while (!this.cancelled) {
|
while (!this.cancelled) {
|
||||||
// 查询
|
// 查询
|
||||||
const r = await PaymentApi.I.queryOrder(orderId);
|
let reqData = {orderId: orderId};
|
||||||
if (r.code === ApiCode.OK && r.data) {
|
const r = await PaymentApi.I.queryOrder(reqData);
|
||||||
|
if (r.code === proto.cs.EnmRetCode.SUCCESS && r.data) {
|
||||||
// 回调
|
// 回调
|
||||||
onTick?.(r.data);
|
onTick?.(r.data);
|
||||||
if (["SUCCESS", "FAILED", "CANCELED", "EXPIRED"].indexOf(r.data.status) !== -1) {
|
if ([-1, 0].indexOf(r.data.status) !== -1) {
|
||||||
return r.data;
|
return r.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { PaymentApi } from "./PaymentApi";
|
|||||||
import { PaymentOpenerFactory, type OpenStrategy } from "./PaymentOpener";
|
import { PaymentOpenerFactory, type OpenStrategy } from "./PaymentOpener";
|
||||||
import { PendingOrderRepo } from "./PendingOrderRepo";
|
import { PendingOrderRepo } from "./PendingOrderRepo";
|
||||||
import PaymentPoller from "./PaymentPoller";
|
import PaymentPoller from "./PaymentPoller";
|
||||||
import type { CreateOrderReq, PaymentResult, PendingOrder, OrderStatus } from "./types";
|
import type { PaymentResult, PendingOrder, OrderStatus } from "./types";
|
||||||
import { ApiCode } from "../network/client/types";
|
import proto from 'db://assets/Scripts/proto/proto.pb.js';
|
||||||
|
|
||||||
export const PaymentEvents = {
|
export const PaymentEvents = {
|
||||||
Started: "pay-started", // { orderId }
|
Started: "pay-started", // { orderId }
|
||||||
@@ -32,13 +32,13 @@ export class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 发起支付:创建订单→打开URL→回到App后开始轮询 */
|
/** 发起支付:创建订单→打开URL→回到App后开始轮询 */
|
||||||
async startPayment(req: CreateOrderReq, open: OpenStrategy = "system"): Promise<PaymentResult> {
|
async startPayment(req: proto.cs.ICSCreateOrderReq, open: OpenStrategy = "system"): Promise<proto.cs.ICSQueryOrderRes | null> {
|
||||||
// 下单
|
// 下单
|
||||||
const create = await PaymentApi.I.createOrder(req);
|
const create = await PaymentApi.I.createOrder(req);
|
||||||
if (create.code !== ApiCode.OK || !create.data) {
|
if (create.code !== proto.cs.EnmRetCode.SUCCESS || !create.data) {
|
||||||
return { orderId: "", status: "FAILED", message: create.msg || "create order failed" };
|
return { status: -1, retCode: -1 };
|
||||||
}
|
}
|
||||||
const { orderId, payUrl, expireAt } = create.data;
|
const { orderId, payUrl } = create.data;
|
||||||
this.bus.emit(PaymentEvents.Started, { orderId });
|
this.bus.emit(PaymentEvents.Started, { orderId });
|
||||||
|
|
||||||
// 存为未决订单(用于异常恢复)
|
// 存为未决订单(用于异常恢复)
|
||||||
@@ -62,19 +62,10 @@ export class PaymentService {
|
|||||||
jitter: true,
|
jitter: true,
|
||||||
}, (d) => this.bus.emit(PaymentEvents.PollTick, { orderId, status: d.status }));
|
}, (d) => this.bus.emit(PaymentEvents.PollTick, { orderId, status: d.status }));
|
||||||
|
|
||||||
// 产出结果
|
|
||||||
let res: PaymentResult;
|
|
||||||
if (!data) {
|
|
||||||
res = { orderId, status: "EXPIRED", message: "poll timeout" };
|
|
||||||
} else if (data.status === "SUCCESS") {
|
|
||||||
res = { orderId, status: "SUCCESS" };
|
|
||||||
} else {
|
|
||||||
res = { orderId, status: data.status as OrderStatus, message: data.failureReason };
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingOrderRepo.instance.remove(orderId);
|
PendingOrderRepo.instance.remove(orderId);
|
||||||
this.bus.emit(PaymentEvents.Finished, res);
|
// 返回结果
|
||||||
return res;
|
this.bus.emit(PaymentEvents.Finished, data);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** App 回到前台后恢复未完成订单的轮询(在 onShow 时调用) */
|
/** App 回到前台后恢复未完成订单的轮询(在 onShow 时调用) */
|
||||||
@@ -105,13 +96,9 @@ export class PaymentService {
|
|||||||
|
|
||||||
// 轮询最新且未过期的订单
|
// 轮询最新且未过期的订单
|
||||||
const r = await this.startPollingOnly(o.orderId);
|
const r = await this.startPollingOnly(o.orderId);
|
||||||
if (r && r.status !== "PENDING" && r.status !== "CREATED") {
|
if (r && r.status !== 1) {
|
||||||
PendingOrderRepo.instance.remove(o.orderId);
|
PendingOrderRepo.instance.remove(o.orderId);
|
||||||
this.bus.emit(PaymentEvents.Finished, {
|
this.bus.emit(PaymentEvents.Finished, r);
|
||||||
orderId: o.orderId,
|
|
||||||
status: r.status as OrderStatus,
|
|
||||||
message: r.failureReason,
|
|
||||||
} as PaymentResult);
|
|
||||||
}
|
}
|
||||||
// r === null(轮询超时)时保留在仓库,等待下一次 resume 再查
|
// r === null(轮询超时)时保留在仓库,等待下一次 resume 再查
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user