8701f43f75
- 删除渲染层传输层: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 中无调用方的残留。
439 lines
15 KiB
TypeScript
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>;
|