中止跨账号微信充值确认链
为充值订单绑定 owner、账号 revision 与同一 AbortController。 在延迟确认、确认请求和 SSE watch 前后校验生命周期并透传 signal。 账号切换和卸载先中止旧链,再清理订单状态与支付回调。 补充 fake timer 回归并记录并发异步生命周期约束。
This commit is contained in:
@@ -61,6 +61,7 @@
|
||||
- 处理:旧 action 继续禁止重放或伪造 observation;旧 child 与父 Run 先真实终态。新 Supervisor continuation 仅扫描同 Session、同 source、同有效任务合同的历史根 Run,并要求对应 ready-task 同时存在 `failed / needs-reconciliation` 记录、最终 `cancelled` 记录和 durable cancel tombstone,才把当前 manifest 的同一 failed 节点恢复为 pending,让 scheduler 创建新 child Run。manifest 的读取、筛选、child 证据重验和写回放在同一项目写锁内;每个 task journal 只读取一次并按 parent Run 建索引。较新的无 child Run 默认阻断旧凭证,只有其 root journal 精确证明为旧 failed Graph 在进入 scheduler 前即失败时才允许向前查找;scheduler 自身失败不得被当成该兼容场景。
|
||||
- 验证:构造 reconciliation child、人工 cancel tombstone、failed manifest 和终态父 Run,证明同源 continuation 只重排该节点;并列普通 failed 节点保持 failed,完成合同继续继承原任务 SHA 与项目 baseline,旧 pending action 不恢复。追加覆盖“旧 failed Graph 未调度”的中间 Run 可以跨过,而较新的 scheduler failure 即使没有 child journal 也会阻断更老 tombstone。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs`。
|
||||
|
||||
## `timeout_at` 不能替代显式的预算耗尽预检
|
||||
|
||||
- 现象:给完美像素加端点级并发闸后,预算已经耗尽的请求仍然能拿到许可,白占一个名额继续去打几轮全账号 SpacetimeDB 扫描,直到下载那步才失败。
|
||||
@@ -76,6 +77,7 @@
|
||||
- 处理:把递增封进一个 guard 结构体,递减放在它的 `Drop` 实现里;递增本身用 `fetch_update` 的 CAS,不能用「先读后加」——两个线程同时读到 `max - 1` 各自加一就会越界。拿到资源后立即 `drop(guard)` 让出队列名额,不要让它跟着许可一起活到请求结束。
|
||||
- 验证:单测覆盖 CAS 边界(满了返回失败且计数不越界、上限为 0 时任何进入都失败),并由独立用例覆盖 guard 离开作用域后的计数归还。预算耗尽路径只断言 `504`,不得通过另一个测试也会修改的进程级 static before/after 来推断“未入队”,也不得用串行锁或 `--test-threads=1` 掩盖隔离问题。
|
||||
- 关联:`server-rs/crates/api-server/src/editor_project.rs`(`try_enter_bounded_queue`、`EditorPixelArtSnapQueueGuard`)。
|
||||
|
||||
## Linux 生产脚本门禁不能假设本地也是 GNU userland
|
||||
|
||||
- 现象:macOS 本地运行维护页、生产 API 部署和 Rust 产物门禁时,依次出现 `mv: illegal option -- T`、`mapfile: command not found`、`/usr/bin/cp` / `/usr/bin/chmod` 不存在,以及 `.rlib` 明明含有 `.o` 却报告“没有可扫描成员”;安全修复计划还会把 `/var/folders` 到 `/private/var/folders` 的系统别名误判为用户符号链接。
|
||||
@@ -4021,6 +4023,7 @@
|
||||
- 处理:灰度页只能以 `/admin/api/feature-gates` 为数据源,固定目标列表只登记现役功能;新增或退役业务 target 只修改固定目标注册,不得让通用页面依赖业务列表接口。旧 `creation-entry:*` 目标、接口和页面保持退役。
|
||||
- 验证:`adminRoutes` 必须包含 `gray-release`,admin-web TypeScript/ESLint/Vitest 不得排除灰度页;页面测试必须断言只请求 feature-gates,并继续覆盖现役固定 target、直接 Gate Key 保存与新 target 状态重置。
|
||||
- 关联:`apps/admin-web/src/pages/AdminGrayReleaseConfigPage.tsx`、`apps/admin-web/src/app/adminRoutes.ts`、`server-rs/crates/api-server/src/modules/admin.rs`、`docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md`。
|
||||
|
||||
## 历史钱包消费不能从最近流水或通用订单快照推算
|
||||
|
||||
- 现象:后台用户详情要展示累计花费时,直接复用只返回最近 50 条的 `list_profile_wallet_ledger`,或在充值订单每行使用的通用钱包快照里扫描该用户全部流水。
|
||||
@@ -4143,6 +4146,7 @@
|
||||
- 处理:从当前 root source 的 seed lane 动态解析全部零依赖首波任务,只对这些 child 容忍 hydration `Pending`,后续 code prototype / preview 仍严格要求 Running/Completed。`streaming / ready` 仍要求当前 revision,`committed` 回复改为依据 finalization 的稳定身份查询,不随后续项目 revision 失效。
|
||||
- 验证:覆盖 `design-director / art-director / code-director` 三个 Pending 首波 child 均可投影 Completed、`code-prototype` Pending 仍被拒绝;非流式专业 Agent 在 finalization 前无 stream,提交后形成 committed stream,再推进项目 revision 后仍可查询且正文不变。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/response_stream.rs`。
|
||||
|
||||
## 异步生成结果未知时不能换幂等键重提(2026-07-31)
|
||||
|
||||
- 现象:生成提交发生客户端超时、连接中断或响应丢失后,调用方创建新的 `Idempotency-Key` 再提交一次;原任务其实已经入队,最终造成重复生成、重复扣费和重复画布 / 素材库写入。
|
||||
@@ -4187,6 +4191,13 @@
|
||||
- 处理:调用方在未认证时不得启动受保护的钱包刷新;可取消的读取要为每轮分配 `AbortController`,新读取先失效并中止旧读取,组件卸载时同时推进 revision、abort 当前请求并清空句柄。所有 `then / catch / finally` 在更新状态前都要检查 signal 与 revision。
|
||||
- 验证:定向测试覆盖卸载后请求 signal 已中止;同时复跑触发钱包刷新回调的画布生成集成测试和完整前端测试,不能以单文件偶然快速收束代替全量验证。
|
||||
|
||||
## 账号级轮询和并发 bootstrap 必须中止整条旧生命周期(2026-08-06)
|
||||
|
||||
- 现象:任务列表 `Promise.all` 一侧失败后,另一侧请求可能跨过重试和卸载继续悬挂;微信充值第一次确认返回 pending 后切换账号,旧订单的延迟重试可能使用新账号 Token 再次请求,401 路径还会影响新账号登录态。
|
||||
- 原因:只用 React state 或最终回调里的 owner 判断,无法阻止已安排的 timer、下一次 HTTP 请求和同轮未完成分支继续执行;每轮重试覆盖单个 controller ref,也会遗失更早的悬挂请求。
|
||||
- 处理:并发 bootstrap 每次 attempt 使用独立 `AbortController`,任一分支失败时先中止同轮 controller 再安排有界重试,卸载时中止当前 attempt。充值订单从创建成功起持有同一个 owner、账号 revision 和 `AbortController`;每次 delay、confirm 和 SSE watch 前后都校验生命周期,并把同一 signal 传到请求层;账号切换和卸载先 abort,再清理 ref、state 与旧支付回调 hash。
|
||||
- 验证:bootstrap 用例覆盖“一侧 reject、另一侧 pending、重试后卸载”,并断言每轮 signal 都已中止;充值用 fake timer 证明首次确认 pending 后切换账号会中止 signal,推进全部退避时间也不会产生第二个确认请求或清理新账号 Token。
|
||||
|
||||
## 下游 manifest 回调测试不能冒充实时数据源(2026-08-05)
|
||||
|
||||
- 现象:工作台的资源、任务与版本重投影单测保持绿色,但后台 Agent 已更新 `.agent/manifest.json` 后,打开中的工作台仍长期显示旧快照,只有重开项目才更新。
|
||||
@@ -4247,6 +4258,7 @@
|
||||
- 现象:parent wake 的 200 次瞬态预算耗尽后 Runtime 仍长期显示 running,或 lane 忙、取消、child 前进、manifest 损坏时 reconciliation 被静默丢弃或覆盖新状态。
|
||||
- 处理:预算耗尽错误必须向上传递;lane 忙先持久化 deferred signal,再在 lane + 项目锁内重检最新事实。结构损坏路径使用不依赖 manifest hydration 的专用 journal/state 写入,CAS 失败转为继续对账,绝不覆写并发取消或 DAG 进展。可解析的空对象/空 runId 仍是损坏身份,只有完整有效的新 Run 才能阻止旧 marker;event/audit 的同键记录必须完整比对并拒绝冲突或重复。旧 task 已终态、Runtime 非 waiting 或新 Run 接管时,deferred signal 必须写 resolved/superseded,不能留给后续 wake 永久重复 settle。
|
||||
- 测试注意:autonomous child fixture 先 linked Pending、后正式 Running;终态 runId 必须拒绝复用。判断 Completed-only 诊断时按每个 seed task 的实际状态分析,不能因为 `code-prototype` Pending 就忽略已经 Completed 的 `art-asset-plan` 深验。
|
||||
|
||||
## macOS 安全路径测试必须使用规范化临时目录(2026-08-05)
|
||||
|
||||
- 现象:调用仓库上下文、Runtime context bundle 或 pending recovery 的 Rust 测试在 macOS 报“Repository root and its ancestors must not be symbolic links”,Linux CI 却可能通过;本地 HTTP 恢复夹具在完整串行测试中还可能偶发 `WouldBlock`。
|
||||
|
||||
@@ -178,6 +178,62 @@ describe('usePlatformProfileCenterController recharge fallback', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('aborts delayed confirmation retries when the account changes', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
profileClientMocks.createPlatformProfileRechargeOrder.mockResolvedValue(
|
||||
nativeRechargeResponse(),
|
||||
);
|
||||
profileClientMocks.confirmWechatPlatformProfileRechargeOrder.mockResolvedValue(
|
||||
{
|
||||
order: nativeRechargeResponse().order,
|
||||
center: nativeRechargeResponse().center,
|
||||
},
|
||||
);
|
||||
usePlatformWalletStore.getState().setWalletOwner('user-a');
|
||||
const { result, rerender } = renderController(userA);
|
||||
|
||||
await act(async () => {
|
||||
result.current.buyRechargeProduct(rechargeProduct);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(result.current.nativeWechatPayment?.orderId).toBe('order-a');
|
||||
|
||||
await act(async () => {
|
||||
result.current.confirmNativeWechatPayment();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(
|
||||
profileClientMocks.confirmWechatPlatformProfileRechargeOrder,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
const confirmationSignal = profileClientMocks
|
||||
.confirmWechatPlatformProfileRechargeOrder.mock.calls[0]?.[1]
|
||||
?.signal as AbortSignal;
|
||||
expect(confirmationSignal.aborted).toBe(false);
|
||||
expect(
|
||||
profileClientMocks.watchWechatPlatformProfileRechargeOrder,
|
||||
).toHaveBeenCalledWith('order-a', { signal: confirmationSignal });
|
||||
|
||||
act(() => {
|
||||
rerender({ user: userB });
|
||||
usePlatformWalletStore.getState().setWalletOwner('user-b');
|
||||
});
|
||||
expect(confirmationSignal.aborted).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
});
|
||||
expect(
|
||||
profileClientMocks.confirmWechatPlatformProfileRechargeOrder,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(apiClientMocks.clearStoredAccessToken).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
test('does not clear the new owner token from an old owner payment error', async () => {
|
||||
const pendingOrder = deferred<never>();
|
||||
paymentPlatformMocks.resolveProfileRechargeProductPaymentChannel.mockReturnValue(
|
||||
|
||||
@@ -84,6 +84,12 @@ type AccountLifecycle = Readonly<{
|
||||
type WechatRechargeOrderLifecycle = Readonly<{
|
||||
orderId: string;
|
||||
account: AccountLifecycle;
|
||||
abortController: AbortController;
|
||||
}>;
|
||||
|
||||
type WechatRechargeConfirmationOptions = Readonly<{
|
||||
signal: AbortSignal;
|
||||
isCurrent: () => boolean;
|
||||
}>;
|
||||
|
||||
function isWechatJsapiMissingIdentityError(error: unknown) {
|
||||
@@ -182,9 +188,36 @@ function readWechatPayResultFromHash(): WechatPayResult | null {
|
||||
};
|
||||
}
|
||||
|
||||
function waitWechatPayConfirmDelay(delayMs: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
window.setTimeout(resolve, delayMs);
|
||||
function createWechatRechargeAbortError(signal: AbortSignal) {
|
||||
return signal.reason instanceof Error
|
||||
? signal.reason
|
||||
: new DOMException('Wechat recharge confirmation aborted', 'AbortError');
|
||||
}
|
||||
|
||||
function assertWechatRechargeConfirmationActive(
|
||||
options: WechatRechargeConfirmationOptions,
|
||||
) {
|
||||
if (options.signal.aborted || !options.isCurrent()) {
|
||||
throw createWechatRechargeAbortError(options.signal);
|
||||
}
|
||||
}
|
||||
|
||||
function waitWechatPayConfirmDelay(delayMs: number, signal?: AbortSignal) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (signal?.aborted) {
|
||||
reject(createWechatRechargeAbortError(signal));
|
||||
return;
|
||||
}
|
||||
const timerId = window.setTimeout(() => {
|
||||
signal?.removeEventListener('abort', handleAbort);
|
||||
resolve();
|
||||
}, delayMs);
|
||||
const handleAbort = () => {
|
||||
window.clearTimeout(timerId);
|
||||
signal?.removeEventListener('abort', handleAbort);
|
||||
reject(createWechatRechargeAbortError(signal!));
|
||||
};
|
||||
signal?.addEventListener('abort', handleAbort, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,42 +237,71 @@ function isWechatRechargeOrderTerminalForConfirmation(
|
||||
|
||||
async function confirmWechatRechargeOrderUntilSettled(
|
||||
orderId: string,
|
||||
options: WechatRechargeConfirmationOptions,
|
||||
): Promise<ConfirmWechatProfileRechargeOrderResponse> {
|
||||
let latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
let latestResponse = await confirmWechatPlatformProfileRechargeOrder(
|
||||
orderId,
|
||||
{ signal: options.signal },
|
||||
);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
if (isWechatRechargeOrderTerminalForConfirmation(latestResponse.order)) {
|
||||
return latestResponse;
|
||||
}
|
||||
|
||||
for (const delayMs of WECHAT_PAY_CONFIRM_RETRY_DELAYS_MS) {
|
||||
await waitWechatPayConfirmDelay(delayMs);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
await waitWechatPayConfirmDelay(delayMs, options.signal);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
|
||||
latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId);
|
||||
latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId, {
|
||||
signal: options.signal,
|
||||
});
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
if (isWechatRechargeOrderTerminalForConfirmation(latestResponse.order)) {
|
||||
return latestResponse;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const streamedResponse =
|
||||
await watchWechatPlatformProfileRechargeOrder(orderId);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
const streamedResponse = await watchWechatPlatformProfileRechargeOrder(
|
||||
orderId,
|
||||
{
|
||||
signal: options.signal,
|
||||
},
|
||||
);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
return streamedResponse;
|
||||
} catch {
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
return latestResponse;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmWechatRechargeOrderQuickly(
|
||||
orderId: string,
|
||||
options: WechatRechargeConfirmationOptions,
|
||||
): Promise<ConfirmWechatProfileRechargeOrderResponse> {
|
||||
let latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
let latestResponse = await confirmWechatPlatformProfileRechargeOrder(
|
||||
orderId,
|
||||
{ signal: options.signal },
|
||||
);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
if (isWechatRechargeOrderTerminalForConfirmation(latestResponse.order)) {
|
||||
return latestResponse;
|
||||
}
|
||||
|
||||
for (const delayMs of WECHAT_NATIVE_CONFIRM_RETRY_DELAYS_MS) {
|
||||
await waitWechatPayConfirmDelay(delayMs);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
await waitWechatPayConfirmDelay(delayMs, options.signal);
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
|
||||
latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId);
|
||||
latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId, {
|
||||
signal: options.signal,
|
||||
});
|
||||
assertWechatRechargeConfirmationActive(options);
|
||||
if (isWechatRechargeOrderTerminalForConfirmation(latestResponse.order)) {
|
||||
return latestResponse;
|
||||
}
|
||||
@@ -404,6 +466,38 @@ export function usePlatformProfileCenterController({
|
||||
},
|
||||
[],
|
||||
);
|
||||
const abortRechargeOrders = useCallback(
|
||||
(...orders: Array<WechatRechargeOrderLifecycle | null | undefined>) => {
|
||||
const controllers = new Set(
|
||||
orders.flatMap((order) => (order ? [order.abortController] : [])),
|
||||
);
|
||||
controllers.forEach((controller) => controller.abort());
|
||||
},
|
||||
[],
|
||||
);
|
||||
const createRechargeOrderLifecycle = useCallback(
|
||||
(
|
||||
orderId: string,
|
||||
account: AccountLifecycle,
|
||||
): WechatRechargeOrderLifecycle => ({
|
||||
orderId,
|
||||
account,
|
||||
abortController: new AbortController(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const createRechargeConfirmationOptions = useCallback(
|
||||
(
|
||||
order: WechatRechargeOrderLifecycle,
|
||||
): WechatRechargeConfirmationOptions => ({
|
||||
signal: order.abortController.signal,
|
||||
isCurrent: () =>
|
||||
isRechargeOrderCurrent(order) &&
|
||||
(isSameRechargeOrder(pendingWechatRechargeOrderRef.current, order) ||
|
||||
isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order)),
|
||||
}),
|
||||
[isRechargeOrderCurrent, isSameRechargeOrder],
|
||||
);
|
||||
const walletOwnerUserId = usePlatformWalletStore(
|
||||
(state) => state.ownerUserId,
|
||||
);
|
||||
@@ -429,6 +523,15 @@ export function usePlatformProfileCenterController({
|
||||
rechargeCenterReadRevisionRef.current += 1;
|
||||
accountLifecycleRevisionRef.current += 1;
|
||||
walletLedgerReadRevisionRef.current += 1;
|
||||
const pendingOrder = pendingWechatRechargeOrderRef.current;
|
||||
const confirmingOrder = confirmingWechatRechargeOrderRef.current;
|
||||
abortRechargeOrders(pendingOrder, confirmingOrder);
|
||||
if (
|
||||
(pendingOrder && pendingOrder.account.ownerUserId !== currentUserId) ||
|
||||
(confirmingOrder && confirmingOrder.account.ownerUserId !== currentUserId)
|
||||
) {
|
||||
clearWechatPayResultHash();
|
||||
}
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
setRechargeCenter(null);
|
||||
@@ -448,7 +551,7 @@ export function usePlatformProfileCenterController({
|
||||
setRewardCodeError(null);
|
||||
setRewardCodeSuccess(null);
|
||||
setIsSubmittingReferralRedeem(false);
|
||||
}, [currentUserId]);
|
||||
}, [abortRechargeOrders, currentUserId]);
|
||||
const rechargeCenterReadAbortControllerRef = useRef<AbortController | null>(
|
||||
null,
|
||||
);
|
||||
@@ -457,10 +560,16 @@ export function usePlatformProfileCenterController({
|
||||
return () => {
|
||||
rechargeCenterReadRevisionRef.current += 1;
|
||||
accountLifecycleRevisionRef.current += 1;
|
||||
abortRechargeOrders(
|
||||
pendingWechatRechargeOrderRef.current,
|
||||
confirmingWechatRechargeOrderRef.current,
|
||||
);
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
rechargeCenterReadAbortControllerRef.current?.abort();
|
||||
rechargeCenterReadAbortControllerRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
}, [abortRechargeOrders]);
|
||||
|
||||
// 中文注释:支持带邀请码 query 的直达场景,登录成功后自动打开兑换面板并复用同一套输入状态。
|
||||
useEffect(() => {
|
||||
@@ -620,12 +729,16 @@ export function usePlatformProfileCenterController({
|
||||
}
|
||||
loadRechargeCenter();
|
||||
setSubmittingRechargeProductId(null);
|
||||
abortRechargeOrders(
|
||||
pendingWechatRechargeOrderRef.current,
|
||||
confirmingWechatRechargeOrderRef.current,
|
||||
);
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
setWechatRechargeOrderConfirmationState(null);
|
||||
setNativeWechatPayment(null);
|
||||
},
|
||||
[isAccountLifecycleCurrent, loadRechargeCenter],
|
||||
[abortRechargeOrders, isAccountLifecycleCurrent, loadRechargeCenter],
|
||||
);
|
||||
|
||||
const handleWechatPayResult = useCallback(() => {
|
||||
@@ -657,7 +770,8 @@ export function usePlatformProfileCenterController({
|
||||
clearWechatPayResultHash();
|
||||
return true;
|
||||
}
|
||||
const order = pendingOrder ?? { orderId, account };
|
||||
const order =
|
||||
pendingOrder ?? createRechargeOrderLifecycle(orderId, account);
|
||||
if (
|
||||
isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order)
|
||||
) {
|
||||
@@ -668,7 +782,10 @@ export function usePlatformProfileCenterController({
|
||||
setWechatRechargeOrderConfirmationState({ orderId });
|
||||
setSubmittingRechargeProductId(null);
|
||||
setRechargePaymentResult(null);
|
||||
void confirmWechatRechargeOrderUntilSettled(orderId)
|
||||
void confirmWechatRechargeOrderUntilSettled(
|
||||
orderId,
|
||||
createRechargeConfirmationOptions(order),
|
||||
)
|
||||
.then((response) => {
|
||||
if (
|
||||
!isRechargeOrderCurrent(order) ||
|
||||
@@ -690,6 +807,7 @@ export function usePlatformProfileCenterController({
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
}
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
order.abortController.abort();
|
||||
setWechatRechargeOrderConfirmationState(null);
|
||||
setRechargePaymentResult(result);
|
||||
if (isPaid) {
|
||||
@@ -742,6 +860,8 @@ export function usePlatformProfileCenterController({
|
||||
}, [
|
||||
applyRechargeCenter,
|
||||
captureAccountLifecycle,
|
||||
createRechargeConfirmationOptions,
|
||||
createRechargeOrderLifecycle,
|
||||
isAccountLifecycleCurrent,
|
||||
isRechargeOrderCurrent,
|
||||
isSameRechargeOrder,
|
||||
@@ -770,7 +890,10 @@ export function usePlatformProfileCenterController({
|
||||
confirmingWechatRechargeOrderRef.current = order;
|
||||
setWechatRechargeOrderConfirmationState({ orderId: order.orderId });
|
||||
setRechargePaymentResult(null);
|
||||
void confirmWechatRechargeOrderUntilSettled(order.orderId)
|
||||
void confirmWechatRechargeOrderUntilSettled(
|
||||
order.orderId,
|
||||
createRechargeConfirmationOptions(order),
|
||||
)
|
||||
.then((response) => {
|
||||
if (
|
||||
!isRechargeOrderCurrent(order) ||
|
||||
@@ -785,6 +908,7 @@ export function usePlatformProfileCenterController({
|
||||
}
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
order.abortController.abort();
|
||||
setWechatRechargeOrderConfirmationState(null);
|
||||
setSubmittingRechargeProductId(null);
|
||||
setRechargePaymentResult(result);
|
||||
@@ -810,6 +934,7 @@ export function usePlatformProfileCenterController({
|
||||
return true;
|
||||
}, [
|
||||
applyRechargeCenter,
|
||||
createRechargeConfirmationOptions,
|
||||
isRechargeOrderCurrent,
|
||||
isSameRechargeOrder,
|
||||
nativeWechatPayment,
|
||||
@@ -852,6 +977,7 @@ export function usePlatformProfileCenterController({
|
||||
pendingOrder?.orderId === current.orderId &&
|
||||
isRechargeOrderCurrent(pendingOrder)
|
||||
) {
|
||||
pendingOrder.abortController.abort();
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
}
|
||||
return null;
|
||||
@@ -885,12 +1011,17 @@ export function usePlatformProfileCenterController({
|
||||
if (!isAccountLifecycleCurrent(account)) {
|
||||
return;
|
||||
}
|
||||
const order: WechatRechargeOrderLifecycle = {
|
||||
orderId: response.order.orderId,
|
||||
const order = createRechargeOrderLifecycle(
|
||||
response.order.orderId,
|
||||
account,
|
||||
};
|
||||
);
|
||||
abortRechargeOrders(
|
||||
pendingWechatRechargeOrderRef.current,
|
||||
confirmingWechatRechargeOrderRef.current,
|
||||
);
|
||||
createdOrder = order;
|
||||
pendingWechatRechargeOrderRef.current = order;
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
|
||||
if (paymentChannel === WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL) {
|
||||
if (
|
||||
@@ -944,7 +1075,10 @@ export function usePlatformProfileCenterController({
|
||||
title: '支付处理中',
|
||||
message: '正在查询微信支付到账状态。',
|
||||
});
|
||||
void confirmWechatRechargeOrderUntilSettled(response.order.orderId)
|
||||
void confirmWechatRechargeOrderUntilSettled(
|
||||
response.order.orderId,
|
||||
createRechargeConfirmationOptions(order),
|
||||
)
|
||||
.then((confirmResponse) => {
|
||||
if (!isRechargeOrderCurrent(order)) {
|
||||
return;
|
||||
@@ -968,6 +1102,7 @@ export function usePlatformProfileCenterController({
|
||||
) {
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
}
|
||||
order.abortController.abort();
|
||||
}
|
||||
if (isPaid) {
|
||||
void onRechargeSuccess?.();
|
||||
@@ -1056,6 +1191,7 @@ export function usePlatformProfileCenterController({
|
||||
createdOrder,
|
||||
)
|
||||
) {
|
||||
createdOrder.abortController.abort();
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
}
|
||||
setNativeWechatPayment(null);
|
||||
@@ -1114,8 +1250,11 @@ export function usePlatformProfileCenterController({
|
||||
},
|
||||
[
|
||||
applyRechargeCenter,
|
||||
abortRechargeOrders,
|
||||
captureAccountLifecycle,
|
||||
captureWalletBalanceSnapshot,
|
||||
createRechargeConfirmationOptions,
|
||||
createRechargeOrderLifecycle,
|
||||
isAccountLifecycleCurrent,
|
||||
isRechargeOrderCurrent,
|
||||
isSameRechargeOrder,
|
||||
@@ -1141,7 +1280,10 @@ export function usePlatformProfileCenterController({
|
||||
? { ...current, isConfirming: true, confirmMessage: undefined }
|
||||
: current,
|
||||
);
|
||||
void confirmWechatRechargeOrderQuickly(nativeWechatPayment.orderId)
|
||||
void confirmWechatRechargeOrderQuickly(
|
||||
nativeWechatPayment.orderId,
|
||||
createRechargeConfirmationOptions(order),
|
||||
)
|
||||
.then((response) => {
|
||||
if (!isRechargeOrderCurrent(order)) {
|
||||
return;
|
||||
@@ -1158,6 +1300,7 @@ export function usePlatformProfileCenterController({
|
||||
) {
|
||||
pendingWechatRechargeOrderRef.current = null;
|
||||
}
|
||||
order.abortController.abort();
|
||||
setRechargePaymentResult(result);
|
||||
if (isPaid) {
|
||||
void onRechargeSuccess?.();
|
||||
@@ -1195,6 +1338,7 @@ export function usePlatformProfileCenterController({
|
||||
});
|
||||
}, [
|
||||
applyRechargeCenter,
|
||||
createRechargeConfirmationOptions,
|
||||
isRechargeOrderCurrent,
|
||||
isSameRechargeOrder,
|
||||
nativeWechatPayment,
|
||||
@@ -1215,20 +1359,22 @@ export function usePlatformProfileCenterController({
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const abortController = new AbortController();
|
||||
const confirmationOptions = createRechargeConfirmationOptions(order);
|
||||
const watchUntilSettled = async () => {
|
||||
while (
|
||||
!cancelled &&
|
||||
Date.now() < expiresAtMs &&
|
||||
isRechargeOrderCurrent(order)
|
||||
confirmationOptions.isCurrent()
|
||||
) {
|
||||
try {
|
||||
assertWechatRechargeConfirmationActive(confirmationOptions);
|
||||
const response = await watchWechatPlatformProfileRechargeOrder(
|
||||
orderId,
|
||||
{
|
||||
signal: abortController.signal,
|
||||
signal: confirmationOptions.signal,
|
||||
},
|
||||
);
|
||||
assertWechatRechargeConfirmationActive(confirmationOptions);
|
||||
if (
|
||||
cancelled ||
|
||||
!response ||
|
||||
@@ -1242,7 +1388,11 @@ export function usePlatformProfileCenterController({
|
||||
return;
|
||||
}
|
||||
if (result.kind === 'pending') {
|
||||
await waitWechatPayConfirmDelay(WECHAT_NATIVE_WATCH_RETRY_DELAY_MS);
|
||||
await waitWechatPayConfirmDelay(
|
||||
WECHAT_NATIVE_WATCH_RETRY_DELAY_MS,
|
||||
confirmationOptions.signal,
|
||||
);
|
||||
assertWechatRechargeConfirmationActive(confirmationOptions);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1252,6 +1402,7 @@ export function usePlatformProfileCenterController({
|
||||
) {
|
||||
confirmingWechatRechargeOrderRef.current = null;
|
||||
}
|
||||
order.abortController.abort();
|
||||
setNativeWechatPayment((current) =>
|
||||
current?.orderId === orderId ? null : current,
|
||||
);
|
||||
@@ -1264,26 +1415,34 @@ export function usePlatformProfileCenterController({
|
||||
} catch {
|
||||
if (
|
||||
cancelled ||
|
||||
abortController.signal.aborted ||
|
||||
!isRechargeOrderCurrent(order)
|
||||
confirmationOptions.signal.aborted ||
|
||||
!confirmationOptions.isCurrent()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await waitWechatPayConfirmDelay(WECHAT_NATIVE_WATCH_RETRY_DELAY_MS);
|
||||
try {
|
||||
await waitWechatPayConfirmDelay(
|
||||
WECHAT_NATIVE_WATCH_RETRY_DELAY_MS,
|
||||
confirmationOptions.signal,
|
||||
);
|
||||
assertWechatRechargeConfirmationActive(confirmationOptions);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void watchUntilSettled();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
abortController.abort();
|
||||
};
|
||||
}, [
|
||||
nativeWechatPayment?.expiresAt,
|
||||
nativeWechatPayment?.orderId,
|
||||
applyRechargeCenter,
|
||||
createRechargeConfirmationOptions,
|
||||
isRechargeOrderCurrent,
|
||||
isSameRechargeOrder,
|
||||
onRechargeSuccess,
|
||||
|
||||
Reference in New Issue
Block a user