修复钱包旧快照吞掉终态刷新
为钱包快照绑定请求发起时的 owner 生命周期与 invalidation 版本 拒绝过期普通 GET 快照覆盖余额或中止更新刷新 同步主站与 AI Game Creator 调用方及竞态回归测试 更新账户与充值基线约束
This commit is contained in:
@@ -22,6 +22,7 @@ export function useAccountWallet(currentUserId: string) {
|
||||
mudPointBalanceStatus,
|
||||
mudPointBalanceError,
|
||||
setWalletOwner,
|
||||
captureWalletBalanceSnapshot,
|
||||
applyWalletBalanceSnapshot,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
resetWalletBalance,
|
||||
@@ -136,7 +137,7 @@ export function useAccountWallet(currentUserId: string) {
|
||||
}
|
||||
|
||||
function applyRechargeContent(
|
||||
snapshotOwnerUserId: string,
|
||||
walletSnapshot: ReturnType<typeof captureWalletBalanceSnapshot>,
|
||||
center: ProfileRechargeCenterResponse,
|
||||
) {
|
||||
const {
|
||||
@@ -145,8 +146,8 @@ export function useAccountWallet(currentUserId: string) {
|
||||
...content
|
||||
} = center;
|
||||
void walletBalance;
|
||||
if (nextMudPointBalance) {
|
||||
applyWalletBalanceSnapshot(snapshotOwnerUserId, nextMudPointBalance);
|
||||
if (nextMudPointBalance && walletSnapshot) {
|
||||
applyWalletBalanceSnapshot(walletSnapshot, nextMudPointBalance);
|
||||
} else {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
}
|
||||
@@ -155,6 +156,7 @@ export function useAccountWallet(currentUserId: string) {
|
||||
|
||||
async function loadRechargeCenter() {
|
||||
const snapshotOwnerUserId = currentUserId;
|
||||
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
|
||||
const rechargeLifecycle = rechargeLifecycleRef.current;
|
||||
setRechargeLoading(true);
|
||||
setRechargeError(null);
|
||||
@@ -163,7 +165,7 @@ export function useAccountWallet(currentUserId: string) {
|
||||
if (rechargeLifecycleRef.current !== rechargeLifecycle) {
|
||||
return;
|
||||
}
|
||||
applyRechargeContent(snapshotOwnerUserId, center);
|
||||
applyRechargeContent(walletSnapshot, center);
|
||||
} catch (error) {
|
||||
if (rechargeLifecycleRef.current === rechargeLifecycle) {
|
||||
setRechargeError(
|
||||
@@ -307,9 +309,7 @@ export function useAccountWallet(currentUserId: string) {
|
||||
submittingRechargeProductId: walletUiIsVisible
|
||||
? submittingRechargeProductId
|
||||
: null,
|
||||
nativeRechargePayment: walletUiIsVisible
|
||||
? nativeRechargePayment
|
||||
: null,
|
||||
nativeRechargePayment: walletUiIsVisible ? nativeRechargePayment : null,
|
||||
setNativeRechargePayment,
|
||||
loadWalletLedger,
|
||||
openWalletLedger,
|
||||
|
||||
@@ -53,6 +53,7 @@ describe('useWalletStore', () => {
|
||||
expect(result.current.mudPointBalanceError).toBe('');
|
||||
expect(Object.keys(result.current).sort()).toEqual([
|
||||
'applyWalletBalanceSnapshot',
|
||||
'captureWalletBalanceSnapshot',
|
||||
'mudPointBalance',
|
||||
'mudPointBalanceError',
|
||||
'mudPointBalanceStatus',
|
||||
@@ -70,9 +71,12 @@ describe('useWalletStore', () => {
|
||||
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
|
||||
const mudPointBalance = detailedBalance(120);
|
||||
const snapshot = useWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
useWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot('user-a', mudPointBalance);
|
||||
.applyWalletBalanceSnapshot(snapshot!, mudPointBalance);
|
||||
|
||||
expect(useWalletStore.getState().mudPointBalance).toEqual(mudPointBalance);
|
||||
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('ready');
|
||||
@@ -90,9 +94,12 @@ describe('useWalletStore', () => {
|
||||
|
||||
const refresh = useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
const mudPointBalance = detailedBalance(160);
|
||||
const snapshot = useWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
useWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot('user-a', mudPointBalance);
|
||||
.applyWalletBalanceSnapshot(snapshot!, mudPointBalance);
|
||||
rejectRequest?.(new Error('过期请求失败'));
|
||||
await refresh;
|
||||
|
||||
@@ -141,10 +148,12 @@ describe('useWalletStore', () => {
|
||||
mudPointBalance: detailedBalance(80),
|
||||
});
|
||||
|
||||
const firstRefresh =
|
||||
useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
const trailingRefresh =
|
||||
useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
const firstRefresh = useWalletStore
|
||||
.getState()
|
||||
.onWalletBalanceMayHaveChanged();
|
||||
const trailingRefresh = useWalletStore
|
||||
.getState()
|
||||
.onWalletBalanceMayHaveChanged();
|
||||
resolveFirst?.({
|
||||
walletBalance: 120,
|
||||
mudPointBalance: detailedBalance(120),
|
||||
@@ -194,9 +203,12 @@ describe('useWalletStore', () => {
|
||||
});
|
||||
|
||||
test('clears immediately when the adapter switches wallet owners', () => {
|
||||
const snapshot = useWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
useWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot('user-a', detailedBalance(88));
|
||||
.applyWalletBalanceSnapshot(snapshot!, detailedBalance(88));
|
||||
|
||||
useWalletStore.getState().setWalletOwner('user-b');
|
||||
|
||||
@@ -208,9 +220,12 @@ describe('useWalletStore', () => {
|
||||
});
|
||||
|
||||
test('masks another owner snapshot before the owner-binding effect runs', () => {
|
||||
const snapshot = useWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
useWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot('user-a', detailedBalance(88));
|
||||
.applyWalletBalanceSnapshot(snapshot!, detailedBalance(88));
|
||||
|
||||
function WalletProbe() {
|
||||
const wallet = useAccountWallet('user-b');
|
||||
|
||||
@@ -59,7 +59,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台。当
|
||||
|
||||
## 账户与充值
|
||||
|
||||
1. 主站、图片画板和 AI Game Creator 统一使用 `packages/shared` 的依赖注入式钱包 Zustand Store;主站与 Tauri 客户端只保留各自的 URL、认证和重试 transport adapter。主站顶部、图片画板顶部和“我的”统计必须消费同一份 `ProfileMudPointBalance` 快照,泥点总额固定取 `totalPoints`,不得混用 dashboard 的 `walletBalance`。切换或退出账号必须立即清空快照并拒绝旧账号在途响应;消费端在 owner 绑定 effect 生效前也必须按当前用户 ID 同步屏蔽 owner 不匹配的快照,旧账号请求不得阻塞新账号首次读取。生成、退款、充值、兑换码等余额可能变化事件只通知 Store 合并刷新。external generation 在 worker 领取后才预扣泥点,因此主站必须在任一项目的 active task 轮询期间持续推动钱包合并刷新,并在 `completed / failed` 任一终态再刷新以覆盖成功结算或失败退款;公共泥点资产入口收起态展示“泥点图标 + 泥点总额 | 充值”;桌面端通过 hover / focus 展开,移动端通过点击展开。展开态只展示不限时泥点、每日免费泥点及后端返回的每日重置额度,并提供“使用详情”入口;余额都以后端充值中心 read model 为准,前端不得自行相减推算。
|
||||
1. 主站、图片画板和 AI Game Creator 统一使用 `packages/shared` 的依赖注入式钱包 Zustand Store;主站与 Tauri 客户端只保留各自的 URL、认证和重试 transport adapter。主站顶部、图片画板顶部和“我的”统计必须消费同一份 `ProfileMudPointBalance` 快照,泥点总额固定取 `totalPoints`,不得混用 dashboard 的 `walletBalance`。切换或退出账号必须立即清空快照并拒绝旧账号在途响应;消费端在 owner 绑定 effect 生效前也必须按当前用户 ID 同步屏蔽 owner 不匹配的快照,旧账号请求不得阻塞新账号首次读取。普通充值中心 GET 必须在发起时捕获钱包 owner 生命周期与 invalidation 版本;回包只能结算不晚于该版本的刷新,过期快照不得覆盖余额或中止更新的终态刷新。生成、退款、充值、兑换码等余额可能变化事件只通知 Store 合并刷新。external generation 在 worker 领取后才预扣泥点,因此主站必须在任一项目的 active task 轮询期间持续推动钱包合并刷新,并在 `completed / failed` 任一终态再刷新以覆盖成功结算或失败退款;公共泥点资产入口收起态展示“泥点图标 + 泥点总额 | 充值”;桌面端通过 hover / focus 展开,移动端通过点击展开。展开态只展示不限时泥点、每日免费泥点及后端返回的每日重置额度,并提供“使用详情”入口;余额都以后端充值中心 read model 为准,前端不得自行相减推算。
|
||||
2. 账户充值弹窗标题统一为“购买更多泥点”,当前版本只展示泥点商品,不展示会员页签、会员商品、购买会员或升级会员入口。底层会员数据与周期刷新能力继续保留用于存量兼容和结算,会员周期限时泥点不在当前版本前台展示。
|
||||
3. 泥点默认商品固定为四档:`60 泥点 / ¥6`、`180 + 90 泥点 / ¥18`、`300 + 150 泥点 / ¥30`、`680 + 340 泥点 / ¥68`。`60` 档不加赠,后三档首次购买各加赠基础泥点的 `50%`;实际展示、下单校验和支付确认仍以后端返回的充值商品配置为准。
|
||||
4. 首充加赠资格按泥点商品档位独立计算。用户买过 `points_180` 后,只影响 `points_180` 的首充展示和结算,其它未购买档位仍保留各自首充加赠资格。
|
||||
|
||||
@@ -19,7 +19,9 @@ function balance(totalPoints: number): ProfileMudPointBalance {
|
||||
}
|
||||
|
||||
function center(totalPoints: number): ProfileRechargeCenterResponse {
|
||||
return { mudPointBalance: balance(totalPoints) } as ProfileRechargeCenterResponse;
|
||||
return {
|
||||
mudPointBalance: balance(totalPoints),
|
||||
} as ProfileRechargeCenterResponse;
|
||||
}
|
||||
|
||||
function deferred<T>() {
|
||||
@@ -91,7 +93,9 @@ describe('createProfileWalletStore', () => {
|
||||
store.getState().setWalletOwner('user-a');
|
||||
|
||||
const refresh = store.getState().onWalletBalanceMayHaveChanged();
|
||||
store.getState().applyWalletBalanceSnapshot('user-a', balance(80));
|
||||
const snapshot = store.getState().captureWalletBalanceSnapshot('user-a');
|
||||
expect(snapshot).not.toBeNull();
|
||||
store.getState().applyWalletBalanceSnapshot(snapshot!, balance(80));
|
||||
pending.resolve(center(20));
|
||||
await refresh;
|
||||
|
||||
@@ -114,7 +118,8 @@ describe('createProfileWalletStore', () => {
|
||||
});
|
||||
const store = createProfileWalletStore({ getRechargeCenter });
|
||||
store.getState().setWalletOwner('user-a');
|
||||
store.getState().applyWalletBalanceSnapshot('user-a', balance(50));
|
||||
const snapshot = store.getState().captureWalletBalanceSnapshot('user-a');
|
||||
store.getState().applyWalletBalanceSnapshot(snapshot!, balance(50));
|
||||
|
||||
const oldRefresh = store.getState().onWalletBalanceMayHaveChanged();
|
||||
store.getState().setWalletOwner('user-b');
|
||||
@@ -143,7 +148,8 @@ describe('createProfileWalletStore', () => {
|
||||
getRechargeCenter: vi.fn().mockRejectedValue(new Error('network down')),
|
||||
});
|
||||
store.getState().setWalletOwner('user-a');
|
||||
store.getState().applyWalletBalanceSnapshot('user-a', balance(55));
|
||||
const snapshot = store.getState().captureWalletBalanceSnapshot('user-a');
|
||||
store.getState().applyWalletBalanceSnapshot(snapshot!, balance(55));
|
||||
|
||||
await store.getState().onWalletBalanceMayHaveChanged();
|
||||
|
||||
@@ -171,14 +177,15 @@ describe('createProfileWalletStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('ignores a snapshot captured for a different owner', () => {
|
||||
test('does not capture a snapshot for a different owner', () => {
|
||||
const store = createProfileWalletStore({
|
||||
getRechargeCenter: vi.fn(),
|
||||
});
|
||||
store.getState().setWalletOwner('user-b');
|
||||
|
||||
store.getState().applyWalletBalanceSnapshot('user-a', balance(100));
|
||||
const snapshot = store.getState().captureWalletBalanceSnapshot('user-a');
|
||||
|
||||
expect(snapshot).toBeNull();
|
||||
expect(store.getState().mudPointBalance).toBeNull();
|
||||
expect(store.getState().mudPointBalanceStatus).toBe('idle');
|
||||
});
|
||||
@@ -189,13 +196,60 @@ describe('createProfileWalletStore', () => {
|
||||
});
|
||||
store.getState().setWalletOwner('user-a');
|
||||
|
||||
store
|
||||
const snapshot = store
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(' user-a ', balance(100));
|
||||
.captureWalletBalanceSnapshot(' user-a ');
|
||||
store.getState().applyWalletBalanceSnapshot(snapshot!, balance(100));
|
||||
|
||||
expect(store.getState()).toMatchObject({
|
||||
mudPointBalance: balance(100),
|
||||
mudPointBalanceStatus: 'ready',
|
||||
});
|
||||
});
|
||||
|
||||
test('does not let an older ordinary snapshot swallow a terminal refresh', async () => {
|
||||
const terminalRefresh = deferred<ProfileRechargeCenterResponse>();
|
||||
const requestSignals: Array<AbortSignal | undefined> = [];
|
||||
const store = createProfileWalletStore({
|
||||
getRechargeCenter: vi.fn((signal?: AbortSignal) => {
|
||||
requestSignals.push(signal);
|
||||
return terminalRefresh.promise;
|
||||
}),
|
||||
});
|
||||
store.getState().setWalletOwner('user-a');
|
||||
const ordinarySnapshot = store
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
|
||||
const refresh = store.getState().onWalletBalanceMayHaveChanged();
|
||||
expect(
|
||||
store
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(ordinarySnapshot!, balance(80)),
|
||||
).toBe(false);
|
||||
expect(requestSignals[0]?.aborted).toBe(false);
|
||||
|
||||
terminalRefresh.resolve(center(100));
|
||||
await refresh;
|
||||
|
||||
expect(store.getState().mudPointBalance?.totalPoints).toBe(100);
|
||||
});
|
||||
|
||||
test('rejects a snapshot after switching away from and back to the same owner', () => {
|
||||
const store = createProfileWalletStore({ getRechargeCenter: vi.fn() });
|
||||
store.getState().setWalletOwner('user-a');
|
||||
const oldLifecycleSnapshot = store
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
|
||||
store.getState().setWalletOwner('user-b');
|
||||
store.getState().setWalletOwner('user-a');
|
||||
|
||||
expect(
|
||||
store
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(oldLifecycleSnapshot!, balance(100)),
|
||||
).toBe(false);
|
||||
expect(store.getState().mudPointBalance).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,19 +8,30 @@ import type {
|
||||
export type MudPointBalanceStatus = 'idle' | 'loading' | 'ready' | 'error';
|
||||
|
||||
export type ProfileWalletApi = {
|
||||
getRechargeCenter(signal?: AbortSignal): Promise<ProfileRechargeCenterResponse>;
|
||||
getRechargeCenter(
|
||||
signal?: AbortSignal,
|
||||
): Promise<ProfileRechargeCenterResponse>;
|
||||
};
|
||||
|
||||
export type ProfileWalletBalanceSnapshot = Readonly<{
|
||||
ownerUserId: string;
|
||||
ownerVersion: number;
|
||||
invalidationVersion: number;
|
||||
}>;
|
||||
|
||||
export type ProfileWalletStore = {
|
||||
ownerUserId: string | null;
|
||||
mudPointBalance: ProfileMudPointBalance | null;
|
||||
mudPointBalanceStatus: MudPointBalanceStatus;
|
||||
mudPointBalanceError: string;
|
||||
setWalletOwner: (userId: string | null) => void;
|
||||
applyWalletBalanceSnapshot: (
|
||||
captureWalletBalanceSnapshot: (
|
||||
ownerUserId: string,
|
||||
) => ProfileWalletBalanceSnapshot | null;
|
||||
applyWalletBalanceSnapshot: (
|
||||
snapshot: ProfileWalletBalanceSnapshot,
|
||||
balance: ProfileMudPointBalance,
|
||||
) => void;
|
||||
) => boolean;
|
||||
onWalletBalanceMayHaveChanged: () => Promise<void>;
|
||||
resetWalletBalance: () => void;
|
||||
};
|
||||
@@ -36,6 +47,7 @@ export function createProfileWalletStore(
|
||||
): UseBoundStore<StoreApi<ProfileWalletStore>> {
|
||||
let refreshVersion = 0;
|
||||
let settledRefreshVersion = 0;
|
||||
let ownerVersion = 0;
|
||||
let requestGeneration = 0;
|
||||
let activeRefresh: Promise<void> | null = null;
|
||||
let activeAbortController: AbortController | null = null;
|
||||
@@ -56,6 +68,7 @@ export function createProfileWalletStore(
|
||||
return;
|
||||
}
|
||||
|
||||
ownerVersion += 1;
|
||||
invalidateActiveRefresh();
|
||||
settledRefreshVersion = refreshVersion;
|
||||
set({
|
||||
@@ -63,22 +76,39 @@ export function createProfileWalletStore(
|
||||
...EMPTY_WALLET_STATE,
|
||||
});
|
||||
},
|
||||
applyWalletBalanceSnapshot: (ownerUserId, balance) => {
|
||||
captureWalletBalanceSnapshot: (ownerUserId) => {
|
||||
const normalizedOwnerUserId = ownerUserId.trim();
|
||||
if (
|
||||
!normalizedOwnerUserId ||
|
||||
get().ownerUserId !== normalizedOwnerUserId
|
||||
) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
ownerUserId: normalizedOwnerUserId,
|
||||
ownerVersion,
|
||||
invalidationVersion: refreshVersion,
|
||||
};
|
||||
},
|
||||
applyWalletBalanceSnapshot: (snapshot, balance) => {
|
||||
if (
|
||||
get().ownerUserId !== snapshot.ownerUserId ||
|
||||
ownerVersion !== snapshot.ownerVersion ||
|
||||
snapshot.invalidationVersion < refreshVersion ||
|
||||
snapshot.invalidationVersion < settledRefreshVersion
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
invalidateActiveRefresh();
|
||||
settledRefreshVersion = refreshVersion;
|
||||
settledRefreshVersion = snapshot.invalidationVersion;
|
||||
set({
|
||||
mudPointBalance: balance,
|
||||
mudPointBalanceStatus: 'ready',
|
||||
mudPointBalanceError: '',
|
||||
});
|
||||
return true;
|
||||
},
|
||||
onWalletBalanceMayHaveChanged: () => {
|
||||
refreshVersion += 1;
|
||||
@@ -149,6 +179,7 @@ export function createProfileWalletStore(
|
||||
return refresh;
|
||||
},
|
||||
resetWalletBalance: () => {
|
||||
ownerVersion += 1;
|
||||
invalidateActiveRefresh();
|
||||
settledRefreshVersion = refreshVersion;
|
||||
set({ ownerUserId: null, ...EMPTY_WALLET_STATE });
|
||||
|
||||
@@ -596,15 +596,20 @@ describe('ImageCanvasEditorView', () => {
|
||||
|
||||
it('shows the live mud point balance in the canvas topbar when logged in', async () => {
|
||||
usePlatformWalletStore.getState().setWalletOwner('user-1');
|
||||
usePlatformWalletStore.getState().applyWalletBalanceSnapshot('user-1', {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 214,
|
||||
limitedExpiresAt: '2026-07-31T16:00:00Z',
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
});
|
||||
const walletSnapshot = usePlatformWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-1');
|
||||
usePlatformWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(walletSnapshot!, {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 214,
|
||||
limitedExpiresAt: '2026-07-31T16:00:00Z',
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
});
|
||||
render(
|
||||
<AuthUiContext.Provider
|
||||
value={createAuthValue({
|
||||
@@ -668,15 +673,20 @@ describe('ImageCanvasEditorView', () => {
|
||||
it('opens the shared wallet breakdown and ledger from the canvas topbar', async () => {
|
||||
const user = userEvent.setup();
|
||||
usePlatformWalletStore.getState().setWalletOwner('user-1');
|
||||
usePlatformWalletStore.getState().applyWalletBalanceSnapshot('user-1', {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
dailyFreePoints: 234,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
});
|
||||
const walletSnapshot = usePlatformWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-1');
|
||||
usePlatformWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(walletSnapshot!, {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
dailyFreePoints: 234,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
});
|
||||
render(
|
||||
<AuthUiContext.Provider
|
||||
value={createAuthValue({
|
||||
|
||||
@@ -61,11 +61,7 @@ type WechatPayResult = {
|
||||
};
|
||||
|
||||
type RechargePaymentResultKind =
|
||||
| 'success'
|
||||
| 'pending'
|
||||
| 'cancel'
|
||||
| 'failed'
|
||||
| 'expired';
|
||||
'success' | 'pending' | 'cancel' | 'failed' | 'expired';
|
||||
|
||||
export type RechargePaymentResult = {
|
||||
kind: RechargePaymentResultKind;
|
||||
@@ -369,6 +365,9 @@ export function usePlatformProfileCenterController({
|
||||
const applyWalletBalanceSnapshot = usePlatformWalletStore(
|
||||
(state) => state.applyWalletBalanceSnapshot,
|
||||
);
|
||||
const captureWalletBalanceSnapshot = usePlatformWalletStore(
|
||||
(state) => state.captureWalletBalanceSnapshot,
|
||||
);
|
||||
const onWalletBalanceMayHaveChanged = usePlatformWalletStore(
|
||||
(state) => state.onWalletBalanceMayHaveChanged,
|
||||
);
|
||||
@@ -402,8 +401,9 @@ export function usePlatformProfileCenterController({
|
||||
setRewardCodeSuccess(null);
|
||||
setIsSubmittingReferralRedeem(false);
|
||||
}, [currentUserId]);
|
||||
const rechargeCenterReadAbortControllerRef =
|
||||
useRef<AbortController | null>(null);
|
||||
const rechargeCenterReadAbortControllerRef = useRef<AbortController | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -480,7 +480,11 @@ export function usePlatformProfileCenterController({
|
||||
}, [loadWalletLedger]);
|
||||
|
||||
const applyRechargeCenter = useCallback(
|
||||
(center: ProfileRechargeCenterResponse, refreshWallet = false) => {
|
||||
(
|
||||
center: ProfileRechargeCenterResponse,
|
||||
refreshWallet = false,
|
||||
walletSnapshot = captureWalletBalanceSnapshot(currentUserId),
|
||||
) => {
|
||||
if (
|
||||
!currentUserId ||
|
||||
usePlatformWalletStore.getState().ownerUserId !== currentUserId
|
||||
@@ -493,8 +497,8 @@ export function usePlatformProfileCenterController({
|
||||
setIsLoadingRechargeCenter(false);
|
||||
setRechargeError(null);
|
||||
setRechargeCenter(center);
|
||||
if (center.mudPointBalance) {
|
||||
applyWalletBalanceSnapshot(currentUserId, center.mudPointBalance);
|
||||
if (center.mudPointBalance && walletSnapshot) {
|
||||
applyWalletBalanceSnapshot(walletSnapshot, center.mudPointBalance);
|
||||
}
|
||||
if (refreshWallet || !center.mudPointBalance) {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
@@ -503,12 +507,14 @@ export function usePlatformProfileCenterController({
|
||||
},
|
||||
[
|
||||
applyWalletBalanceSnapshot,
|
||||
captureWalletBalanceSnapshot,
|
||||
currentUserId,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
],
|
||||
);
|
||||
|
||||
const loadRechargeCenter = useCallback(() => {
|
||||
const walletSnapshot = captureWalletBalanceSnapshot(currentUserId);
|
||||
const revision = ++rechargeCenterReadRevisionRef.current;
|
||||
rechargeCenterReadAbortControllerRef.current?.abort();
|
||||
const abortController = new AbortController();
|
||||
@@ -521,7 +527,7 @@ export function usePlatformProfileCenterController({
|
||||
!abortController.signal.aborted &&
|
||||
revision === rechargeCenterReadRevisionRef.current
|
||||
) {
|
||||
applyRechargeCenter(center);
|
||||
applyRechargeCenter(center, false, walletSnapshot);
|
||||
}
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
@@ -544,7 +550,7 @@ export function usePlatformProfileCenterController({
|
||||
setIsLoadingRechargeCenter(false);
|
||||
}
|
||||
});
|
||||
}, [applyRechargeCenter]);
|
||||
}, [applyRechargeCenter, captureWalletBalanceSnapshot, currentUserId]);
|
||||
|
||||
const refreshRechargeState = useCallback(() => {
|
||||
loadRechargeCenter();
|
||||
|
||||
Reference in New Issue
Block a user