Files
Genarrative/apps/ai-game-creator-shell/src/features/app-shell/useAccountWallet.ts
T
kdletters 8701f43f75 AGC 渲染层网络与状态下沉 Rust,并修复画布交互与失败文案
- 删除渲染层传输层:services/clientApi.ts、clientHttp 的 fetchClientHttp 与超时封装整体退场,账户、认证、素材直传、错误上报与游戏分发发布改走 Rust typed command。
- 新增宿主封装:accountHost、editorAssetHost、platformSessionRetry、agentRuntimeErrorDetail 与 features/app-shell/workspacePreferences;渲染层不再读写 access token / refresh cookie,localStorage 只剩测试态旧键迁移 shim。
- 修复 DirectProject 失败文案 v2 映射与「查看详情」按需读取,画布标签面板高度夹紧、框选每帧重算、文档预览入口与场景标题栏 key、资源筛选面板样式。
- 补齐与调整回归:accountHost、agentRuntimeErrorDetail、clientAuthHost、clientHttp、platformSession、platformSessionRetry、resourceFilterPanelStyle、directThreadChatSubscription 等定向用例,并清理测试 harness 中无调用方的残留。
2026-09-28 14:15:02 +08:00

439 lines
15 KiB
TypeScript

import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import type { PlatformProfileRechargeNativePaymentState } from '../../../../../packages/shared/src/components/PlatformProfileRechargeModal';
import type {
ProfileRechargeCenterResponse,
ProfileRechargeProduct,
ProfileWalletLedgerResponse,
} from '../../../../../packages/shared/src/contracts/runtime';
import type { RechargeContent } from '../../app/types';
import {
confirmClientWechatProfileRechargeOrder,
createClientProfileRechargeOrder,
getClientProfileRechargeCenter,
getClientProfileWalletLedger,
redeemClientProfileRewardCode,
} from '../../services/accountHost';
import { useWalletStore } from '../../stores/useWalletStore';
export function useAccountWallet(currentUserId: string) {
const {
ownerUserId,
mudPointBalance,
legacyWalletBalance,
mudPointBalanceStatus,
mudPointBalanceError,
setWalletOwner,
captureWalletBalanceSnapshot,
applyWalletBalanceSnapshot,
applyLegacyWalletBalanceSnapshot,
onWalletBalanceMayHaveChanged,
resetWalletBalance,
} = useWalletStore();
const [walletLedgerOpen, setWalletLedgerOpen] = useState(false);
const [walletLedger, setWalletLedger] =
useState<ProfileWalletLedgerResponse | null>(null);
const [walletLedgerLoading, setWalletLedgerLoading] = useState(false);
const [walletLedgerError, setWalletLedgerError] = useState<string | null>(
null,
);
const [rechargeOpen, setRechargeOpen] = useState(false);
const [rechargeContent, setRechargeContent] =
useState<RechargeContent | null>(null);
const [rechargeLoading, setRechargeLoading] = useState(false);
const [rechargeError, setRechargeError] = useState<string | null>(null);
const [submittingRechargeProductId, setSubmittingRechargeProductId] =
useState<string | null>(null);
const [nativeRechargePayment, setNativeRechargePayment] =
useState<PlatformProfileRechargeNativePaymentState | null>(null);
const [redeemCodeOpen, setRedeemCodeOpen] = useState(false);
const [redeemCodeInput, setRedeemCodeInput] = useState('');
const [redeemCodeLoading, setRedeemCodeLoading] = useState(false);
const [redeemCodeError, setRedeemCodeError] = useState<string | null>(null);
const [redeemCodeSuccess, setRedeemCodeSuccess] = useState<string | null>(
null,
);
const rechargeLifecycleRef = useRef(0);
const walletLedgerLifecycleRef = useRef(0);
const redeemLifecycleRef = useRef(0);
const [walletUiOwnerUserId, setWalletUiOwnerUserId] = useState(currentUserId);
const currentUserIdRef = useRef(currentUserId);
const walletOwnerMatchesCurrentUser =
Boolean(currentUserId) && ownerUserId === currentUserId;
const walletUiOwnerMatchesCurrentUser =
Boolean(currentUserId) && walletUiOwnerUserId === currentUserId;
const walletUiIsVisible =
walletOwnerMatchesCurrentUser && walletUiOwnerMatchesCurrentUser;
const visibleMudPointBalance = walletOwnerMatchesCurrentUser
? mudPointBalance
: null;
const visibleLegacyWalletBalance = walletOwnerMatchesCurrentUser
? legacyWalletBalance
: null;
const visibleMudPointBalanceStatus = walletOwnerMatchesCurrentUser
? mudPointBalanceStatus
: 'idle';
const visibleMudPointBalanceError = walletOwnerMatchesCurrentUser
? mudPointBalanceError
: '';
const visibleWalletBalance =
visibleMudPointBalance?.totalPoints ?? visibleLegacyWalletBalance;
useLayoutEffect(() => {
currentUserIdRef.current = currentUserId;
rechargeLifecycleRef.current += 1;
walletLedgerLifecycleRef.current += 1;
}, [currentUserId]);
useEffect(() => {
setWalletOwner(currentUserId || null);
if (currentUserId) {
void onWalletBalanceMayHaveChanged();
}
const refreshWalletBalance = () => {
if (currentUserId) {
void onWalletBalanceMayHaveChanged();
}
};
window.addEventListener('focus', refreshWalletBalance);
return () => {
window.removeEventListener('focus', refreshWalletBalance);
};
}, [currentUserId, onWalletBalanceMayHaveChanged, setWalletOwner]);
useEffect(() => {
setWalletUiOwnerUserId(currentUserId);
setWalletLedgerOpen(false);
setWalletLedger(null);
setWalletLedgerLoading(false);
setWalletLedgerError(null);
setRechargeOpen(false);
setRechargeContent(null);
setRechargeLoading(false);
setRechargeError(null);
setSubmittingRechargeProductId(null);
setNativeRechargePayment(null);
redeemLifecycleRef.current += 1;
setRedeemCodeOpen(false);
setRedeemCodeInput('');
setRedeemCodeLoading(false);
setRedeemCodeError(null);
setRedeemCodeSuccess(null);
}, [currentUserId]);
async function loadWalletLedger() {
const walletLedgerLifecycle = ++walletLedgerLifecycleRef.current;
const snapshotOwnerUserId = currentUserId;
setWalletLedgerLoading(true);
setWalletLedgerError(null);
try {
const ledger = await getClientProfileWalletLedger();
if (
walletLedgerLifecycleRef.current === walletLedgerLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setWalletLedger(ledger);
}
} catch (error) {
if (
walletLedgerLifecycleRef.current !== walletLedgerLifecycle ||
currentUserIdRef.current !== snapshotOwnerUserId
) {
return;
}
setWalletLedger(null);
setWalletLedgerError(
error instanceof Error ? error.message : '读取泥点账单失败',
);
} finally {
if (
walletLedgerLifecycleRef.current === walletLedgerLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setWalletLedgerLoading(false);
}
}
}
function openWalletLedger() {
setWalletLedgerOpen(true);
void loadWalletLedger();
}
function applyRechargeContent(
walletSnapshot: ReturnType<typeof captureWalletBalanceSnapshot>,
center: ProfileRechargeCenterResponse,
) {
const {
walletBalance,
mudPointBalance: nextMudPointBalance,
...content
} = center;
if (nextMudPointBalance && walletSnapshot) {
applyWalletBalanceSnapshot(walletSnapshot, nextMudPointBalance);
} else if (walletSnapshot && Number.isFinite(walletBalance)) {
applyLegacyWalletBalanceSnapshot(walletSnapshot, walletBalance);
void onWalletBalanceMayHaveChanged();
} else {
void onWalletBalanceMayHaveChanged();
}
setRechargeContent(content);
}
async function loadRechargeCenter() {
const snapshotOwnerUserId = currentUserId;
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
const rechargeLifecycle = rechargeLifecycleRef.current;
setRechargeLoading(true);
setRechargeError(null);
try {
const center = await getClientProfileRechargeCenter();
if (
rechargeLifecycleRef.current !== rechargeLifecycle ||
currentUserIdRef.current !== snapshotOwnerUserId
) {
return;
}
applyRechargeContent(walletSnapshot, center);
} catch (error) {
if (
rechargeLifecycleRef.current === rechargeLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setRechargeError(
error instanceof Error ? error.message : '读取泥点购买信息失败',
);
}
} finally {
if (
rechargeLifecycleRef.current === rechargeLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setRechargeLoading(false);
}
}
}
function openRecharge() {
rechargeLifecycleRef.current += 1;
setRechargeOpen(true);
setRechargeError(null);
void loadRechargeCenter();
}
function closeRecharge() {
rechargeLifecycleRef.current += 1;
setRechargeOpen(false);
setNativeRechargePayment(null);
setSubmittingRechargeProductId(null);
}
function openRedeemCode() {
redeemLifecycleRef.current += 1;
setRedeemCodeOpen(true);
setRedeemCodeInput('');
setRedeemCodeError(null);
setRedeemCodeSuccess(null);
}
function closeRedeemCode() {
redeemLifecycleRef.current += 1;
setRedeemCodeOpen(false);
setRedeemCodeLoading(false);
}
async function redeemCode() {
const code = redeemCodeInput.trim();
if (!code || redeemCodeLoading) return;
const lifecycle = redeemLifecycleRef.current;
const owner = currentUserId;
setRedeemCodeLoading(true);
setRedeemCodeError(null);
setRedeemCodeSuccess(null);
try {
const response = await redeemClientProfileRewardCode(code);
if (
redeemLifecycleRef.current !== lifecycle ||
currentUserIdRef.current !== owner
)
return;
setRedeemCodeSuccess(`兑换成功,已到账 ${response.amountGranted} 泥点`);
setRedeemCodeInput('');
void onWalletBalanceMayHaveChanged();
} catch (error) {
if (
redeemLifecycleRef.current === lifecycle &&
currentUserIdRef.current === owner
) {
setRedeemCodeError(error instanceof Error ? error.message : '兑换失败');
}
} finally {
if (
redeemLifecycleRef.current === lifecycle &&
currentUserIdRef.current === owner
) {
setRedeemCodeLoading(false);
}
}
}
async function buyRechargeProduct(product: ProfileRechargeProduct) {
if (submittingRechargeProductId) {
return;
}
const rechargeLifecycle = rechargeLifecycleRef.current;
const snapshotOwnerUserId = currentUserId;
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
setSubmittingRechargeProductId(product.productId);
setRechargeError(null);
try {
const response = await createClientProfileRechargeOrder(
product.productId,
);
if (
rechargeLifecycleRef.current !== rechargeLifecycle ||
currentUserIdRef.current !== snapshotOwnerUserId
) {
return;
}
applyRechargeContent(walletSnapshot, response.center);
const nativePayment = response.wechatNativePayment;
const codeUrl = nativePayment?.codeUrl?.trim();
const expiresAt = nativePayment?.expiresAt?.trim();
if (!nativePayment || !codeUrl || !expiresAt) {
throw new Error('微信 Native 支付链接生成失败');
}
setNativeRechargePayment({
orderId: response.order.orderId,
productTitle: response.order.productTitle,
amountCents: response.order.amountCents,
codeUrl,
expiresAt,
isConfirming: false,
});
} catch (error) {
if (
rechargeLifecycleRef.current === rechargeLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setRechargeError(error instanceof Error ? error.message : '充值失败');
}
} finally {
if (
rechargeLifecycleRef.current === rechargeLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setSubmittingRechargeProductId(null);
}
}
}
async function confirmNativeRechargePayment() {
if (!nativeRechargePayment || nativeRechargePayment.isConfirming) {
return;
}
const rechargeLifecycle = rechargeLifecycleRef.current;
const snapshotOwnerUserId = currentUserId;
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
const orderId = nativeRechargePayment.orderId;
setNativeRechargePayment((current) =>
current?.orderId === orderId
? { ...current, isConfirming: true, confirmMessage: undefined }
: current,
);
try {
const response = await confirmClientWechatProfileRechargeOrder(orderId);
if (
rechargeLifecycleRef.current !== rechargeLifecycle ||
currentUserIdRef.current !== snapshotOwnerUserId
) {
return;
}
applyRechargeContent(walletSnapshot, response.center);
if (response.order.status === 'paid') {
setNativeRechargePayment(null);
void onWalletBalanceMayHaveChanged();
return;
}
const confirmMessage =
response.order.status === 'pending' ||
(response.order.status === 'expired' &&
!response.order.expirationCheckedAt)
? '暂未确认到账,请确认付款完成后再点一次。'
: '订单未支付成功,请关闭二维码后重新下单。';
setNativeRechargePayment((current) =>
current?.orderId === orderId
? { ...current, isConfirming: false, confirmMessage }
: current,
);
} catch {
if (
rechargeLifecycleRef.current === rechargeLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setNativeRechargePayment((current) =>
current?.orderId === orderId
? {
...current,
isConfirming: false,
confirmMessage: '暂时没能确认到账状态,请稍后再试。',
}
: current,
);
}
}
}
return {
ownerUserId,
mudPointBalance: visibleMudPointBalance,
legacyWalletBalance: visibleLegacyWalletBalance,
mudPointBalanceStatus: visibleMudPointBalanceStatus,
mudPointBalanceError: visibleMudPointBalanceError,
onWalletBalanceMayHaveChanged,
resetWalletBalance,
walletLedgerOpen: walletUiIsVisible && walletLedgerOpen,
setWalletLedgerOpen,
walletLedger: walletUiIsVisible ? walletLedger : null,
walletLedgerLoading: walletUiIsVisible && walletLedgerLoading,
walletLedgerError: walletUiIsVisible ? walletLedgerError : null,
rechargeOpen: walletUiIsVisible && rechargeOpen,
rechargeModalCenter:
rechargeContent && visibleWalletBalance !== null
? {
...rechargeContent,
walletBalance: visibleWalletBalance,
...(visibleMudPointBalance
? { mudPointBalance: visibleMudPointBalance }
: {}),
}
: null,
rechargeLoading: walletUiIsVisible && rechargeLoading,
rechargeError: walletUiIsVisible ? rechargeError : null,
submittingRechargeProductId: walletUiIsVisible
? submittingRechargeProductId
: null,
nativeRechargePayment: walletUiIsVisible ? nativeRechargePayment : null,
setNativeRechargePayment,
loadWalletLedger,
openWalletLedger,
loadRechargeCenter,
openRecharge,
closeRecharge,
buyRechargeProduct,
confirmNativeRechargePayment,
redeemCodeOpen: walletUiIsVisible && redeemCodeOpen,
redeemCodeInput,
redeemCodeLoading: walletUiIsVisible && redeemCodeLoading,
redeemCodeError: walletUiIsVisible ? redeemCodeError : null,
redeemCodeSuccess: walletUiIsVisible ? redeemCodeSuccess : null,
setRedeemCodeInput,
openRedeemCode,
closeRedeemCode,
redeemCode,
};
}
export type AccountWalletController = ReturnType<typeof useAccountWallet>;