{
diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx
index cc46041ba..8cf096b11 100644
--- a/src/components/image-editor/ImageCanvasEditorView.tsx
+++ b/src/components/image-editor/ImageCanvasEditorView.tsx
@@ -522,6 +522,10 @@ export function ImageCanvasEditorView({
requestLogin: () => authUiRef.current?.openLoginModal(),
currentUser: authUi?.user ?? null,
});
+ const refreshEditorWalletState = useCallback(() => {
+ refreshEditorWalletBalance();
+ loadRechargeCenter();
+ }, [loadRechargeCenter, refreshEditorWalletBalance]);
const isAccountPaymentModalOpen =
isRewardCodeOpen ||
isRechargeOpen ||
@@ -1239,7 +1243,7 @@ export function ImageCanvasEditorView({
assetFolderId: activeUploadFolderId,
upsertGeneratedAsset,
applyProjectSnapshot: applyGeneratedProjectSnapshot,
- onWalletBalanceMayHaveChanged: refreshEditorWalletBalance,
+ onWalletBalanceMayHaveChanged: refreshEditorWalletState,
});
const handleEditorAgentConfirmSent = useCallback(() => {
generationSurface.refreshTaskList();
@@ -1258,7 +1262,7 @@ export function ImageCanvasEditorView({
if (warning) {
showGenerationWarning(warning);
}
- refreshEditorWalletBalance();
+ refreshEditorWalletState();
void loadEditorProject(projectId)
.then(applyGeneratedProjectSnapshot)
.catch(() => undefined);
@@ -1266,7 +1270,7 @@ export function ImageCanvasEditorView({
[
applyGeneratedProjectSnapshot,
projectId,
- refreshEditorWalletBalance,
+ refreshEditorWalletState,
showGenerationWarning,
],
);
diff --git a/src/components/image-editor/ImageCanvasTopbarView.test.tsx b/src/components/image-editor/ImageCanvasTopbarView.test.tsx
index c9eb10d4b..2ff3eeaf5 100644
--- a/src/components/image-editor/ImageCanvasTopbarView.test.tsx
+++ b/src/components/image-editor/ImageCanvasTopbarView.test.tsx
@@ -120,7 +120,7 @@ describe('ImageCanvasTopbarView', () => {
await user.click(screen.getByRole('button', { name: '充值' }));
expect(props.onRecharge).toHaveBeenCalledTimes(1);
- expect(props.onRequestWalletDetails).not.toHaveBeenCalled();
+ expect(props.onRequestWalletDetails).toHaveBeenCalledTimes(1);
});
it('shows the current user avatar beside the mud point balance', () => {
diff --git a/src/components/platform-entry/usePlatformProfileCenterController.ts b/src/components/platform-entry/usePlatformProfileCenterController.ts
index dafbd71c9..5dc5e7a58 100644
--- a/src/components/platform-entry/usePlatformProfileCenterController.ts
+++ b/src/components/platform-entry/usePlatformProfileCenterController.ts
@@ -385,6 +385,7 @@ export function usePlatformProfileCenterController({
useCopyFeedback();
const pendingWechatRechargeOrderIdRef = useRef
(null);
const confirmingWechatRechargeOrderIdRef = useRef(null);
+ const rechargeCenterReadRevisionRef = useRef(0);
// 中文注释:支持带邀请码 query 的直达场景,登录成功后自动打开兑换面板并复用同一套输入状态。
useEffect(() => {
@@ -426,18 +427,40 @@ export function usePlatformProfileCenterController({
loadWalletLedger();
}, [loadWalletLedger]);
+ const applyRechargeCenter = useCallback(
+ (center: ProfileRechargeCenterResponse) => {
+ rechargeCenterReadRevisionRef.current += 1;
+ setIsLoadingRechargeCenter(false);
+ setRechargeError(null);
+ setRechargeCenter(center);
+ },
+ [],
+ );
+
const loadRechargeCenter = useCallback(() => {
+ const revision = ++rechargeCenterReadRevisionRef.current;
setRechargeError(null);
setIsLoadingRechargeCenter(true);
void getRpgProfileRechargeCenter()
- .then(setRechargeCenter)
+ .then((center) => {
+ if (revision === rechargeCenterReadRevisionRef.current) {
+ setRechargeCenter(center);
+ }
+ })
.catch((error: unknown) => {
+ if (revision !== rechargeCenterReadRevisionRef.current) {
+ return;
+ }
setRechargeCenter(null);
setRechargeError(
error instanceof Error ? error.message : '读取泥点购买信息失败',
);
})
- .finally(() => setIsLoadingRechargeCenter(false));
+ .finally(() => {
+ if (revision === rechargeCenterReadRevisionRef.current) {
+ setIsLoadingRechargeCenter(false);
+ }
+ });
}, []);
const refreshRechargeState = useCallback(() => {
@@ -482,7 +505,7 @@ export function usePlatformProfileCenterController({
.then((response) => {
const result = buildRechargePaymentResultForOrder(response.order);
const isPaid = result.kind === 'success';
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
pendingWechatRechargeOrderIdRef.current = null;
confirmingWechatRechargeOrderIdRef.current = null;
setWechatRechargeOrderConfirmationState(null);
@@ -525,7 +548,7 @@ export function usePlatformProfileCenterController({
clearWechatPayResultHash();
return true;
- }, [onRechargeSuccess, refreshRechargeState]);
+ }, [applyRechargeCenter, onRechargeSuccess, refreshRechargeState]);
const pollWechatPayResultFromHash = useCallback(
() => handleWechatPayResult(),
@@ -549,7 +572,7 @@ export function usePlatformProfileCenterController({
.then((response) => {
const result = buildRechargePaymentResultForOrder(response.order);
const isPaid = result.kind === 'success';
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
pendingWechatRechargeOrderIdRef.current = null;
confirmingWechatRechargeOrderIdRef.current = null;
setWechatRechargeOrderConfirmationState(null);
@@ -569,7 +592,7 @@ export function usePlatformProfileCenterController({
});
});
return true;
- }, [nativeWechatPayment, onRechargeSuccess]);
+ }, [applyRechargeCenter, nativeWechatPayment, onRechargeSuccess]);
const openRechargeModal = useCallback(() => {
if (!currentUser) {
@@ -625,7 +648,7 @@ export function usePlatformProfileCenterController({
.then(async (response) => {
if (paymentChannel === WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL) {
pendingWechatRechargeOrderIdRef.current = response.order.orderId;
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
const paymentHandled = await requestHostPayment({
payload: response.wechatMiniProgramPayParams,
orderId: response.order.orderId,
@@ -637,7 +660,7 @@ export function usePlatformProfileCenterController({
}
if (paymentChannel === WECHAT_JSAPI_PAYMENT_CHANNEL) {
pendingWechatRechargeOrderIdRef.current = response.order.orderId;
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
setRechargePaymentResult({
kind: 'pending',
title: '正在打开微信支付',
@@ -657,7 +680,7 @@ export function usePlatformProfileCenterController({
confirmResponse.order,
);
const isPaid = result.kind === 'success';
- setRechargeCenter(confirmResponse.center);
+ applyRechargeCenter(confirmResponse.center);
setRechargePaymentResult(result);
if (result.kind !== 'pending') {
pendingWechatRechargeOrderIdRef.current = null;
@@ -681,7 +704,7 @@ export function usePlatformProfileCenterController({
throw new Error('微信 H5 支付链接生成失败');
}
pendingWechatRechargeOrderIdRef.current = response.order.orderId;
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
setRechargePaymentResult({
kind: 'pending',
title: '正在打开微信支付',
@@ -698,7 +721,7 @@ export function usePlatformProfileCenterController({
throw new Error('微信 Native 支付链接生成失败');
}
pendingWechatRechargeOrderIdRef.current = response.order.orderId;
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
setNativeWechatPayment({
...wechatNativePayment,
codeUrl,
@@ -764,7 +787,7 @@ export function usePlatformProfileCenterController({
setSubmittingRechargeProductId(null);
});
},
- [onRechargeSuccess, submittingRechargeProductId],
+ [applyRechargeCenter, onRechargeSuccess, submittingRechargeProductId],
);
const confirmNativeWechatPayment = useCallback(() => {
@@ -787,7 +810,7 @@ export function usePlatformProfileCenterController({
}
const result = buildRechargePaymentResultForOrder(response.order);
const isPaid = result.kind === 'success';
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
if (result.kind !== 'pending') {
setNativeWechatPayment(null);
pendingWechatRechargeOrderIdRef.current = null;
@@ -819,7 +842,7 @@ export function usePlatformProfileCenterController({
);
})
.finally(() => setSubmittingRechargeProductId(null));
- }, [nativeWechatPayment, onRechargeSuccess]);
+ }, [applyRechargeCenter, nativeWechatPayment, onRechargeSuccess]);
useEffect(() => {
const orderId = nativeWechatPayment?.orderId;
@@ -845,7 +868,7 @@ export function usePlatformProfileCenterController({
}
const result = buildRechargePaymentResultForOrder(response.order);
- setRechargeCenter(response.center);
+ applyRechargeCenter(response.center);
if (result.kind === 'pending') {
await waitWechatPayConfirmDelay(WECHAT_NATIVE_WATCH_RETRY_DELAY_MS);
continue;
@@ -882,6 +905,7 @@ export function usePlatformProfileCenterController({
}, [
nativeWechatPayment?.expiresAt,
nativeWechatPayment?.orderId,
+ applyRechargeCenter,
onRechargeSuccess,
]);
diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
index d9873356c..699cdfe97 100644
--- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
+++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
@@ -4465,6 +4465,98 @@ test('logged in mobile recommend page exposes the shared wallet details and rech
expect(within(rechargeDialog).queryByText('会员月卡')).toBeNull();
});
+test('the latest recharge center read wins when wallet and recharge overlap', async () => {
+ const user = userEvent.setup();
+ mockNarrowMobileLayout();
+ let resolveWalletRead!: (
+ center: ProfileRechargeCenterResponse,
+ ) => void;
+ let resolveRechargeRead!: (
+ center: ProfileRechargeCenterResponse,
+ ) => void;
+ const walletReadPromise = new Promise(
+ (resolve) => {
+ resolveWalletRead = resolve;
+ },
+ );
+ const rechargeReadPromise = new Promise(
+ (resolve) => {
+ resolveRechargeRead = resolve;
+ },
+ );
+ mockGetRpgProfileRechargeCenter
+ .mockReturnValueOnce(walletReadPromise)
+ .mockReturnValueOnce(rechargeReadPromise);
+
+ const { container } = render(
+ ,
+ );
+ const walletLayer = container.querySelector(
+ '.platform-mobile-recommend-wallet-entry',
+ );
+ expect(walletLayer).toBeTruthy();
+
+ await user.click(
+ within(walletLayer as HTMLElement).getByRole('button', {
+ name: '泥点 207',
+ }),
+ );
+ const rechargeButtons = within(walletLayer as HTMLElement).getAllByRole(
+ 'button',
+ { name: '充值' },
+ );
+ await user.click(rechargeButtons[0]!);
+
+ expect(mockGetRpgProfileRechargeCenter).toHaveBeenCalledTimes(2);
+
+ resolveRechargeRead({
+ walletBalance: 207,
+ mudPointBalance: {
+ totalPoints: 207,
+ permanentPoints: 187,
+ limitedPoints: 0,
+ limitedExpiresAt: null,
+ dailyFreePoints: 20,
+ dailyFreeResetPoints: 20,
+ dailyFreeResetsAt: '2026-07-20T16:00:00Z',
+ },
+ membership: buildNormalMembership(),
+ pointProducts: [buildPointProduct()],
+ membershipProducts: [],
+ benefits: [],
+ latestOrder: null,
+ hasPointsRecharged: false,
+ });
+
+ expect(await screen.findByText('购买更多泥点')).toBeTruthy();
+ expect(screen.getByText('当前余额 207 泥点')).toBeTruthy();
+
+ resolveWalletRead({
+ walletBalance: 0,
+ mudPointBalance: {
+ totalPoints: 0,
+ permanentPoints: 0,
+ limitedPoints: 0,
+ limitedExpiresAt: null,
+ dailyFreePoints: 0,
+ dailyFreeResetPoints: 20,
+ dailyFreeResetsAt: '2026-07-20T16:00:00Z',
+ },
+ membership: buildNormalMembership(),
+ pointProducts: [],
+ membershipProducts: [],
+ benefits: [],
+ latestOrder: null,
+ hasPointsRecharged: false,
+ });
+
+ await act(async () => undefined);
+ expect(screen.getByText('当前余额 207 泥点')).toBeTruthy();
+});
+
test('mobile discover search submits public work code', async () => {
const user = userEvent.setup();
const onSearchPublicCode = vi.fn();