import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { PlatformProfileRechargeNativePaymentState } from '../../../packages/shared/src/components/PlatformProfileRechargeModal'; import { type ConfirmWechatProfileRechargeOrderResponse, type ProfileRechargeCenterResponse, type ProfileRechargeOrder, type ProfileRechargeProduct, type ProfileReferralInviteCenterResponse, type ProfileWalletLedgerResponse, type RedeemProfileRewardCodeResponse, } from '../../../packages/shared/src/contracts/runtime'; import { clearStoredAccessToken } from '../../services/apiClient'; import { type AuthUser, startWechatBind } from '../../services/authService'; import { getHostRuntime, requestHostLogin, requestHostPayment, } from '../../services/host-bridge/hostBridge'; import { resolveProfileRechargeProductPaymentChannel, WECHAT_H5_PAYMENT_CHANNEL, WECHAT_JSAPI_PAYMENT_CHANNEL, WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL, WECHAT_NATIVE_PAYMENT_CHANNEL, } from '../../services/payment/paymentPlatform'; import { redirectToPaymentUrl } from '../../services/payment/paymentRedirect'; import { requestWechatJsapiPayment } from '../../services/payment/wechatJsapiPayment'; import { confirmWechatPlatformProfileRechargeOrder, createPlatformProfileRechargeOrder, getPlatformProfileRechargeCenter, getPlatformProfileReferralInviteCenter, getPlatformProfileWalletLedger, redeemPlatformProfileReferralInviteCode, redeemPlatformProfileRewardCode, watchWechatPlatformProfileRechargeOrder, } from '../../services/platform-entry/platformProfileClient'; import { usePlatformWalletStore } from '../../stores/usePlatformWalletStore'; import { type CopyFeedbackState, useCopyFeedback, } from '../common/useCopyFeedback'; const PROFILE_INVITE_QUERY_KEYS = ['inviteCode', 'invite_code'] as const; const WECHAT_NATIVE_CONFIRM_RETRY_DELAYS_MS = [800, 1600] as const; const WECHAT_NATIVE_WATCH_RETRY_DELAY_MS = 1000; const WECHAT_PAY_CONFIRM_RETRY_DELAYS_MS = [800, 1600, 3000] as const; const WECHAT_PAY_RESULT_RECHECK_INTERVAL_MS = 250; const WECHAT_PAY_RESULT_RECHECK_TIMEOUT_MS = 10000; const WECHAT_RECHARGE_UNSUPPORTED_DEVICE_TEXT = '当前登录设备不支持充值'; const WECHAT_JSAPI_MISSING_IDENTITY_TEXT = '缺少微信'; export type ProfileReferralPanel = 'invite' | 'redeem' | 'community'; export type ProfilePopupPanel = ProfileReferralPanel; type WechatPayResult = { requestId: string; orderId: string | null; status: 'success' | 'cancel' | 'fail'; errorMessage: string | null; }; type RechargePaymentResultKind = | 'success' | 'pending' | 'cancel' | 'failed' | 'expired'; export type RechargePaymentResult = { kind: RechargePaymentResultKind; title: string; message: string; }; export type WechatRechargeOrderConfirmationState = { orderId: string; }; export type NativeWechatPaymentState = PlatformProfileRechargeNativePaymentState; type AccountLifecycle = Readonly<{ ownerUserId: string; revision: number; }>; type WechatRechargeOrderLifecycle = Readonly<{ orderId: string; account: AccountLifecycle; abortController: AbortController; }>; type WechatRechargeConfirmationOptions = Readonly<{ signal: AbortSignal; isCurrent: () => boolean; }>; function isWechatJsapiMissingIdentityError(error: unknown) { return ( error instanceof Error && error.message.includes(WECHAT_JSAPI_MISSING_IDENTITY_TEXT) ); } function isWechatRechargeUnsupportedDeviceError(error: unknown) { return ( error instanceof Error && error.message.includes(WECHAT_RECHARGE_UNSUPPORTED_DEVICE_TEXT) ); } type UsePlatformProfileCenterControllerArgs = { activeTab: string; isAuthenticated: boolean; showRechargeEntry: boolean; onRechargeSuccess?: () => void | Promise; requestLogin: () => void; currentUser: AuthUser | null | undefined; }; function readProfileInviteCodeFromLocationSearch(search: string) { const params = new URLSearchParams(search); for (const key of PROFILE_INVITE_QUERY_KEYS) { const value = (params.get(key) ?? '') .trim() .replace(/[^0-9a-z]/giu, '') .toUpperCase(); if (value) { return value; } } return ''; } function clearWechatPayResultHash() { if (typeof window === 'undefined') { return; } const rawHash = window.location.hash.replace(/^#/, ''); if (!rawHash.includes('wx_pay_result=')) { return; } const params = new URLSearchParams(rawHash); params.delete('wx_pay_result'); const nextHash = params.toString(); const nextUrl = `${window.location.pathname}${window.location.search}${nextHash ? `#${nextHash}` : ''}`; window.history.replaceState(null, '', nextUrl); } function readWechatPayResultFromHash(): WechatPayResult | null { if (typeof window === 'undefined') { return null; } const result = new URLSearchParams( window.location.hash.replace(/^#/, ''), ).get('wx_pay_result'); if (!result) { return null; } const [requestId = '', rawStatus = '', explicitOrderId = '', ...rawErrors] = result.split(':'); const inferredOrderId = requestId .replace(/^wechat_pay_/, '') .replace(/_\d+$/, '') .trim(); const orderId = explicitOrderId.trim() || inferredOrderId; const status = rawStatus === 'success' ? 'success' : rawStatus === 'cancel' ? 'cancel' : 'fail'; let errorMessage: string | null = null; const rawError = rawErrors.join(':'); if (rawError) { try { errorMessage = decodeURIComponent(rawError); } catch (_error) { errorMessage = rawError; } } return { requestId, orderId: orderId || null, status, errorMessage, }; } 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((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 }); }); } function isWechatRechargeOrderTerminalForConfirmation( order: Pick, ) { if (order.status === 'pending') { return false; } if (order.status === 'expired' && !order.expirationCheckedAt) { return false; } return true; } async function confirmWechatRechargeOrderUntilSettled( orderId: string, options: WechatRechargeConfirmationOptions, ): Promise { 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) { assertWechatRechargeConfirmationActive(options); await waitWechatPayConfirmDelay(delayMs, options.signal); assertWechatRechargeConfirmationActive(options); latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId, { signal: options.signal, }); assertWechatRechargeConfirmationActive(options); if (isWechatRechargeOrderTerminalForConfirmation(latestResponse.order)) { return latestResponse; } } try { 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 { 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) { assertWechatRechargeConfirmationActive(options); await waitWechatPayConfirmDelay(delayMs, options.signal); assertWechatRechargeConfirmationActive(options); latestResponse = await confirmWechatPlatformProfileRechargeOrder(orderId, { signal: options.signal, }); assertWechatRechargeConfirmationActive(options); if (isWechatRechargeOrderTerminalForConfirmation(latestResponse.order)) { return latestResponse; } } return latestResponse; } function buildRechargePaymentResultForOrder( order: Pick, ): RechargePaymentResult { switch (order.status) { case 'paid': return { kind: 'success', title: '支付成功', message: '已到账,泥点余额已刷新。', }; case 'expired': if (!order.expirationCheckedAt) { return { kind: 'pending', title: '支付处理中', message: '正在等待到账状态确认,请稍后查看泥点余额。', }; } return { kind: 'expired', title: '支付已过期', message: '订单已超过支付时限,本次没有入账。', }; case 'closed': return { kind: 'cancel', title: '支付未完成', message: '本次没有扣款,泥点余额未发生变化。', }; case 'failed': case 'refunded': return { kind: 'failed', title: '支付未完成', message: '微信支付没有完成,本次不会入账。', }; case 'pending': default: return { kind: 'pending', title: '支付处理中', message: '正在等待到账状态确认,请稍后查看泥点余额。', }; } } export function usePlatformProfileCenterController({ activeTab, isAuthenticated, showRechargeEntry, onRechargeSuccess, requestLogin, currentUser, }: UsePlatformProfileCenterControllerArgs) { // 中文注释:个人中心里的充值、任务、邀请码、账单等账户商业能力统一由这个 controller 托管, // 页面层只消费状态与回调,不再直接堆叠一大组本地 state / effect / callback。 const [isRewardCodeOpen, setIsRewardCodeOpen] = useState(false); const [rewardCodeInput, setRewardCodeInput] = useState(''); const [isSubmittingRewardCode, setIsSubmittingRewardCode] = useState(false); const [rewardCodeError, setRewardCodeError] = useState(null); const [rewardCodeSuccess, setRewardCodeSuccess] = useState( null, ); const [isRechargeOpen, setIsRechargeOpen] = useState(false); const [rechargeCenter, setRechargeCenter] = useState(null); const [isLoadingRechargeCenter, setIsLoadingRechargeCenter] = useState(false); const [rechargeError, setRechargeError] = useState(null); const [rechargePaymentResult, setRechargePaymentResult] = useState(null); const [ wechatRechargeOrderConfirmationState, setWechatRechargeOrderConfirmationState, ] = useState(null); const [nativeWechatPayment, setNativeWechatPayment] = useState(null); const [submittingRechargeProductId, setSubmittingRechargeProductId] = useState(null); const [isWalletLedgerOpen, setIsWalletLedgerOpen] = useState(false); const [walletLedger, setWalletLedger] = useState(null); const [walletLedgerError, setWalletLedgerError] = useState( null, ); const [isLoadingWalletLedger, setIsLoadingWalletLedger] = useState(false); const [profilePopupPanel, setProfilePopupPanel] = useState(null); const [referralCenter, setReferralCenter] = useState(null); const [isLoadingReferral, setIsLoadingReferral] = useState(false); const [isReferralCenterInitialized, setIsReferralCenterInitialized] = useState(false); const pendingProfileInviteCode = useMemo( () => typeof window === 'undefined' ? '' : readProfileInviteCodeFromLocationSearch(window.location.search), [], ); const promptedLoginForInviteQueryRef = useRef(false); const autoOpenedInviteQueryRef = useRef(false); const [referralRedeemCode, setReferralRedeemCode] = useState( pendingProfileInviteCode, ); const [isSubmittingReferralRedeem, setIsSubmittingReferralRedeem] = useState(false); const [referralError, setReferralError] = useState(null); const [referralSuccess, setReferralSuccess] = useState(null); const { copyState: inviteCopyState, copyText: copyInviteText } = useCopyFeedback(); const pendingWechatRechargeOrderRef = useRef(null); const confirmingWechatRechargeOrderRef = useRef(null); const rechargeCenterReadRevisionRef = useRef(0); const accountLifecycleRevisionRef = useRef(0); const walletLedgerReadRevisionRef = useRef(0); const currentUserId = currentUser?.id ?? ''; const currentUserIdRef = useRef(currentUserId); currentUserIdRef.current = currentUserId; const captureAccountLifecycle = useCallback( (): AccountLifecycle => ({ ownerUserId: currentUserId, revision: accountLifecycleRevisionRef.current, }), [currentUserId], ); const isAccountLifecycleCurrent = useCallback( (account: AccountLifecycle) => account.revision === accountLifecycleRevisionRef.current && account.ownerUserId === currentUserIdRef.current, [], ); const isRechargeOrderCurrent = useCallback( ( order: WechatRechargeOrderLifecycle | null | undefined, ): order is WechatRechargeOrderLifecycle => Boolean(order) && isAccountLifecycleCurrent(order!.account), [isAccountLifecycleCurrent], ); const isSameRechargeOrder = useCallback( ( left: WechatRechargeOrderLifecycle | null | undefined, right: WechatRechargeOrderLifecycle | null | undefined, ) => { if (!left || !right) { return false; } return ( left.orderId === right.orderId && left.account.ownerUserId === right.account.ownerUserId && left.account.revision === right.account.revision ); }, [], ); const abortRechargeOrders = useCallback( (...orders: Array) => { 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, ); const storedMudPointBalance = usePlatformWalletStore( (state) => state.mudPointBalance, ); const applyWalletBalanceSnapshot = usePlatformWalletStore( (state) => state.applyWalletBalanceSnapshot, ); const captureWalletBalanceSnapshot = usePlatformWalletStore( (state) => state.captureWalletBalanceSnapshot, ); const onWalletBalanceMayHaveChanged = usePlatformWalletStore( (state) => state.onWalletBalanceMayHaveChanged, ); const walletOwnerMatchesCurrentUser = Boolean(currentUserId) && walletOwnerUserId === currentUserId; const mudPointBalance = walletOwnerMatchesCurrentUser ? storedMudPointBalance : null; useEffect(() => { 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); setIsLoadingRechargeCenter(false); setRechargeError(null); setRechargePaymentResult(null); setWechatRechargeOrderConfirmationState(null); setNativeWechatPayment(null); setSubmittingRechargeProductId(null); setIsWalletLedgerOpen(false); setWalletLedger(null); setWalletLedgerError(null); setIsLoadingWalletLedger(false); setIsRewardCodeOpen(false); setRewardCodeInput(''); setIsSubmittingRewardCode(false); setRewardCodeError(null); setRewardCodeSuccess(null); setIsSubmittingReferralRedeem(false); }, [abortRechargeOrders, currentUserId]); const rechargeCenterReadAbortControllerRef = useRef( null, ); useEffect(() => { 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(() => { if (!pendingProfileInviteCode || autoOpenedInviteQueryRef.current) { return; } if (!currentUser) { if (!promptedLoginForInviteQueryRef.current) { promptedLoginForInviteQueryRef.current = true; requestLogin(); } return; } autoOpenedInviteQueryRef.current = true; setReferralRedeemCode(pendingProfileInviteCode); setReferralError(null); setReferralSuccess(null); setProfilePopupPanel('redeem'); }, [currentUser, pendingProfileInviteCode, requestLogin]); const loadWalletLedger = useCallback(() => { const snapshotOwnerUserId = currentUserId; const accountRevision = accountLifecycleRevisionRef.current; const revision = ++walletLedgerReadRevisionRef.current; setWalletLedgerError(null); setIsLoadingWalletLedger(true); void getPlatformProfileWalletLedger() .then((ledger) => { if ( revision === walletLedgerReadRevisionRef.current && accountRevision === accountLifecycleRevisionRef.current && currentUserIdRef.current === snapshotOwnerUserId ) { setWalletLedger(ledger); } }) .catch((error: unknown) => { if ( revision !== walletLedgerReadRevisionRef.current || accountRevision !== accountLifecycleRevisionRef.current || currentUserIdRef.current !== snapshotOwnerUserId ) { return; } setWalletLedger(null); setWalletLedgerError( error instanceof Error ? error.message : '读取泥点账单失败', ); }) .finally(() => { if ( revision === walletLedgerReadRevisionRef.current && accountRevision === accountLifecycleRevisionRef.current && currentUserIdRef.current === snapshotOwnerUserId ) { setIsLoadingWalletLedger(false); } }); }, [currentUserId]); const openWalletLedgerPanel = useCallback(() => { setIsWalletLedgerOpen(true); loadWalletLedger(); }, [loadWalletLedger]); const applyRechargeCenter = useCallback( ( center: ProfileRechargeCenterResponse, account: AccountLifecycle, refreshWallet = false, walletSnapshot = captureWalletBalanceSnapshot(account.ownerUserId), ) => { if ( !isAccountLifecycleCurrent(account) || usePlatformWalletStore.getState().ownerUserId !== account.ownerUserId ) { return false; } rechargeCenterReadRevisionRef.current += 1; rechargeCenterReadAbortControllerRef.current?.abort(); rechargeCenterReadAbortControllerRef.current = null; setIsLoadingRechargeCenter(false); setRechargeError(null); setRechargeCenter(center); if (center.mudPointBalance && walletSnapshot) { applyWalletBalanceSnapshot(walletSnapshot, center.mudPointBalance); } if (refreshWallet || !center.mudPointBalance) { void onWalletBalanceMayHaveChanged(); } return true; }, [ applyWalletBalanceSnapshot, captureWalletBalanceSnapshot, isAccountLifecycleCurrent, onWalletBalanceMayHaveChanged, ], ); const loadRechargeCenter = useCallback(() => { const account = captureAccountLifecycle(); const walletSnapshot = captureWalletBalanceSnapshot(account.ownerUserId); const revision = ++rechargeCenterReadRevisionRef.current; rechargeCenterReadAbortControllerRef.current?.abort(); const abortController = new AbortController(); rechargeCenterReadAbortControllerRef.current = abortController; setRechargeError(null); setIsLoadingRechargeCenter(true); void getPlatformProfileRechargeCenter({ signal: abortController.signal }) .then((center) => { if ( !abortController.signal.aborted && revision === rechargeCenterReadRevisionRef.current && isAccountLifecycleCurrent(account) ) { applyRechargeCenter(center, account, false, walletSnapshot); } }) .catch((error: unknown) => { if ( abortController.signal.aborted || revision !== rechargeCenterReadRevisionRef.current || !isAccountLifecycleCurrent(account) ) { return; } setRechargeCenter(null); setRechargeError( error instanceof Error ? error.message : '读取泥点购买信息失败', ); }) .finally(() => { if (rechargeCenterReadAbortControllerRef.current === abortController) { rechargeCenterReadAbortControllerRef.current = null; } if ( revision === rechargeCenterReadRevisionRef.current && isAccountLifecycleCurrent(account) ) { setIsLoadingRechargeCenter(false); } }); }, [ applyRechargeCenter, captureAccountLifecycle, captureWalletBalanceSnapshot, isAccountLifecycleCurrent, ]); const refreshRechargeState = useCallback( (account: AccountLifecycle) => { if (!isAccountLifecycleCurrent(account)) { return; } loadRechargeCenter(); setSubmittingRechargeProductId(null); abortRechargeOrders( pendingWechatRechargeOrderRef.current, confirmingWechatRechargeOrderRef.current, ); pendingWechatRechargeOrderRef.current = null; confirmingWechatRechargeOrderRef.current = null; setWechatRechargeOrderConfirmationState(null); setNativeWechatPayment(null); }, [abortRechargeOrders, isAccountLifecycleCurrent, loadRechargeCenter], ); const handleWechatPayResult = useCallback(() => { const payResult = readWechatPayResultFromHash(); if (!payResult) { return false; } const pendingOrder = pendingWechatRechargeOrderRef.current; if (pendingOrder && !isRechargeOrderCurrent(pendingOrder)) { return false; } if ( pendingOrder && payResult.orderId && payResult.orderId !== pendingOrder.orderId ) { return false; } const account = pendingOrder?.account ?? captureAccountLifecycle(); if (!account.ownerUserId || !isAccountLifecycleCurrent(account)) { return false; } if (payResult.status === 'success') { const orderId = payResult.orderId || pendingOrder?.orderId; if (!orderId) { clearWechatPayResultHash(); return true; } const order = pendingOrder ?? createRechargeOrderLifecycle(orderId, account); if ( isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order) ) { clearWechatPayResultHash(); return true; } confirmingWechatRechargeOrderRef.current = order; setWechatRechargeOrderConfirmationState({ orderId }); setSubmittingRechargeProductId(null); setRechargePaymentResult(null); void confirmWechatRechargeOrderUntilSettled( orderId, createRechargeConfirmationOptions(order), ) .then((response) => { if ( !isRechargeOrderCurrent(order) || !isSameRechargeOrder( confirmingWechatRechargeOrderRef.current, order, ) ) { return; } const result = buildRechargePaymentResultForOrder(response.order); const isPaid = result.kind === 'success'; if (!applyRechargeCenter(response.center, account, true)) { return; } if ( isSameRechargeOrder(pendingWechatRechargeOrderRef.current, order) ) { pendingWechatRechargeOrderRef.current = null; } confirmingWechatRechargeOrderRef.current = null; order.abortController.abort(); setWechatRechargeOrderConfirmationState(null); setRechargePaymentResult(result); if (isPaid) { void onRechargeSuccess?.(); } clearWechatPayResultHash(); }) .catch(() => { if ( !isRechargeOrderCurrent(order) || !isSameRechargeOrder( confirmingWechatRechargeOrderRef.current, order, ) ) { return; } confirmingWechatRechargeOrderRef.current = null; setWechatRechargeOrderConfirmationState(null); setRechargePaymentResult({ kind: 'pending', title: '支付处理中', message: '暂时没能确认到账状态,请稍后查看泥点余额。', }); clearWechatPayResultHash(); }); } else if (payResult.status === 'cancel') { setRechargePaymentResult({ kind: 'cancel', title: '支付已取消', message: '本次没有扣款,泥点余额未发生变化。', }); setWechatRechargeOrderConfirmationState(null); refreshRechargeState(account); } else { const detail = payResult.errorMessage ? `微信返回:${payResult.errorMessage}` : '微信支付没有完成,本次不会入账。'; setRechargePaymentResult({ kind: 'failed', title: '支付未完成', message: detail, }); setWechatRechargeOrderConfirmationState(null); refreshRechargeState(account); } clearWechatPayResultHash(); return true; }, [ applyRechargeCenter, captureAccountLifecycle, createRechargeConfirmationOptions, createRechargeOrderLifecycle, isAccountLifecycleCurrent, isRechargeOrderCurrent, isSameRechargeOrder, onRechargeSuccess, refreshRechargeState, ]); const pollWechatPayResultFromHash = useCallback( () => handleWechatPayResult(), [handleWechatPayResult], ); const confirmPendingWechatRechargeOrder = useCallback(() => { if (nativeWechatPayment) { return false; } const order = pendingWechatRechargeOrderRef.current; if ( !isRechargeOrderCurrent(order) || isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order) ) { return false; } confirmingWechatRechargeOrderRef.current = order; setWechatRechargeOrderConfirmationState({ orderId: order.orderId }); setRechargePaymentResult(null); void confirmWechatRechargeOrderUntilSettled( order.orderId, createRechargeConfirmationOptions(order), ) .then((response) => { if ( !isRechargeOrderCurrent(order) || !isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order) ) { return; } const result = buildRechargePaymentResultForOrder(response.order); const isPaid = result.kind === 'success'; if (!applyRechargeCenter(response.center, order.account, true)) { return; } pendingWechatRechargeOrderRef.current = null; confirmingWechatRechargeOrderRef.current = null; order.abortController.abort(); setWechatRechargeOrderConfirmationState(null); setSubmittingRechargeProductId(null); setRechargePaymentResult(result); if (isPaid) { void onRechargeSuccess?.(); } }) .catch(() => { if ( !isRechargeOrderCurrent(order) || !isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order) ) { return; } confirmingWechatRechargeOrderRef.current = null; setWechatRechargeOrderConfirmationState(null); setRechargePaymentResult({ kind: 'pending', title: '支付处理中', message: '暂时没能确认到账状态,请稍后查看泥点余额。', }); }); return true; }, [ applyRechargeCenter, createRechargeConfirmationOptions, isRechargeOrderCurrent, isSameRechargeOrder, nativeWechatPayment, onRechargeSuccess, ]); const openRechargeModal = useCallback(() => { if (!currentUser) { requestLogin(); return; } setIsRechargeOpen(true); loadRechargeCenter(); }, [currentUser, loadRechargeCenter, requestLogin]); const openRewardCodeModal = useCallback(() => { setIsRewardCodeOpen(true); setRewardCodeError(null); setRewardCodeSuccess(null); }, []); const openRechargeOrRewardCodeModal = useCallback(() => { if (showRechargeEntry) { openRechargeModal(); return; } openRewardCodeModal(); }, [openRechargeModal, openRewardCodeModal, showRechargeEntry]); const closeNativeWechatPayment = useCallback(() => { setNativeWechatPayment((current) => { if (current?.isConfirming) { return current; } const pendingOrder = pendingWechatRechargeOrderRef.current; if ( current && pendingOrder?.orderId === current.orderId && isRechargeOrderCurrent(pendingOrder) ) { pendingOrder.abortController.abort(); pendingWechatRechargeOrderRef.current = null; } return null; }); }, [isRechargeOrderCurrent]); const buyRechargeProduct = useCallback( (product: ProfileRechargeProduct) => { const account = captureAccountLifecycle(); if ( submittingRechargeProductId || !account.ownerUserId || !isAccountLifecycleCurrent(account) ) { return; } const walletSnapshot = captureWalletBalanceSnapshot(account.ownerUserId); let createdOrder: WechatRechargeOrderLifecycle | null = null; const paymentChannel = resolveProfileRechargeProductPaymentChannel( { kind: product.kind }, {}, ); setSubmittingRechargeProductId(product.productId); setRechargeError(null); setRechargePaymentResult(null); setWechatRechargeOrderConfirmationState(null); setNativeWechatPayment(null); void createPlatformProfileRechargeOrder(product.productId, paymentChannel) .then(async (response) => { if (!isAccountLifecycleCurrent(account)) { return; } 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 ( !applyRechargeCenter( response.center, account, true, walletSnapshot, ) || !isRechargeOrderCurrent(order) ) { return; } const paymentHandled = await requestHostPayment({ payload: response.wechatMiniProgramPayParams, orderId: response.order.orderId, }); if (!isRechargeOrderCurrent(order)) { return; } if (!paymentHandled) { throw new Error('请在微信小程序内完成支付'); } return; } if (paymentChannel === WECHAT_JSAPI_PAYMENT_CHANNEL) { if ( !applyRechargeCenter( response.center, account, true, walletSnapshot, ) || !isRechargeOrderCurrent(order) ) { return; } setRechargePaymentResult({ kind: 'pending', title: '正在打开微信支付', message: '请在微信支付面板中完成支付。', }); await requestWechatJsapiPayment( response.wechatMiniProgramPayParams, ); if (!isRechargeOrderCurrent(order)) { return; } setRechargePaymentResult({ kind: 'pending', title: '支付处理中', message: '正在查询微信支付到账状态。', }); void confirmWechatRechargeOrderUntilSettled( response.order.orderId, createRechargeConfirmationOptions(order), ) .then((confirmResponse) => { if (!isRechargeOrderCurrent(order)) { return; } const result = buildRechargePaymentResultForOrder( confirmResponse.order, ); const isPaid = result.kind === 'success'; if ( !applyRechargeCenter(confirmResponse.center, account, true) ) { return; } setRechargePaymentResult(result); if (result.kind !== 'pending') { if ( isSameRechargeOrder( pendingWechatRechargeOrderRef.current, order, ) ) { pendingWechatRechargeOrderRef.current = null; } order.abortController.abort(); } if (isPaid) { void onRechargeSuccess?.(); } }) .catch(() => { if (!isRechargeOrderCurrent(order)) { return; } setRechargePaymentResult({ kind: 'pending', title: '等待微信确认', message: '暂时没能确认到账状态,请稍后再试。', }); }); return; } if (paymentChannel === WECHAT_H5_PAYMENT_CHANNEL) { const h5Url = response.wechatH5Payment?.h5Url?.trim(); if (!h5Url) { throw new Error('微信 H5 支付链接生成失败'); } if ( !applyRechargeCenter( response.center, account, true, walletSnapshot, ) || !isRechargeOrderCurrent(order) ) { return; } setRechargePaymentResult({ kind: 'pending', title: '正在打开微信支付', message: '完成支付后返回页面确认到账状态。', }); if (!isRechargeOrderCurrent(order)) { return; } await redirectToPaymentUrl(h5Url); return; } if (paymentChannel === WECHAT_NATIVE_PAYMENT_CHANNEL) { const wechatNativePayment = response.wechatNativePayment; const codeUrl = wechatNativePayment?.codeUrl?.trim(); const expiresAt = wechatNativePayment?.expiresAt?.trim(); if (!wechatNativePayment || !codeUrl || !expiresAt) { throw new Error('微信 Native 支付链接生成失败'); } if ( !applyRechargeCenter( response.center, account, true, walletSnapshot, ) || !isRechargeOrderCurrent(order) ) { return; } setNativeWechatPayment({ ...wechatNativePayment, codeUrl, expiresAt, orderId: response.order.orderId, productTitle: response.order.productTitle, amountCents: response.order.amountCents, isConfirming: false, }); setSubmittingRechargeProductId(null); return; } throw new Error('充值支付渠道无效'); }) .catch((error: unknown) => { if (!isAccountLifecycleCurrent(account)) { return; } if ( createdOrder && isSameRechargeOrder( pendingWechatRechargeOrderRef.current, createdOrder, ) ) { createdOrder.abortController.abort(); pendingWechatRechargeOrderRef.current = null; } setNativeWechatPayment(null); if ( paymentChannel === WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL && getHostRuntime().kind === 'wechat_mini_program' && isWechatRechargeUnsupportedDeviceError(error) ) { clearStoredAccessToken(); setSubmittingRechargeProductId(null); setRechargeError(null); setRechargePaymentResult({ kind: 'pending', title: '需要重新登录', message: '正在打开微信小程序登录,请重新登录后再支付。', }); void requestHostLogin().catch((miniProgramLoginError: unknown) => { if (!isAccountLifecycleCurrent(account)) { return; } setRechargePaymentResult(null); setRechargeError( miniProgramLoginError instanceof Error ? miniProgramLoginError.message : '请在微信小程序内重新登录后再支付', ); }); return; } if ( paymentChannel === WECHAT_JSAPI_PAYMENT_CHANNEL && isWechatJsapiMissingIdentityError(error) ) { setSubmittingRechargeProductId(null); setRechargePaymentResult({ kind: 'pending', title: '需要微信授权', message: '正在跳转微信授权,授权后请重新发起支付。', }); void startWechatBind().catch((wechatLoginError: unknown) => { if (!isAccountLifecycleCurrent(account)) { return; } setRechargePaymentResult(null); setRechargeError( wechatLoginError instanceof Error ? wechatLoginError.message : '微信授权暂不可用,请稍后再试', ); }); return; } setRechargeError(error instanceof Error ? error.message : '充值失败'); setSubmittingRechargeProductId(null); }); }, [ applyRechargeCenter, abortRechargeOrders, captureAccountLifecycle, captureWalletBalanceSnapshot, createRechargeConfirmationOptions, createRechargeOrderLifecycle, isAccountLifecycleCurrent, isRechargeOrderCurrent, isSameRechargeOrder, onRechargeSuccess, submittingRechargeProductId, ], ); const confirmNativeWechatPayment = useCallback(() => { if (!nativeWechatPayment || nativeWechatPayment.isConfirming) { return; } const order = pendingWechatRechargeOrderRef.current; if ( !isRechargeOrderCurrent(order) || order.orderId !== nativeWechatPayment.orderId ) { return; } setNativeWechatPayment((current) => current && current.orderId === nativeWechatPayment.orderId ? { ...current, isConfirming: true, confirmMessage: undefined } : current, ); void confirmWechatRechargeOrderQuickly( nativeWechatPayment.orderId, createRechargeConfirmationOptions(order), ) .then((response) => { if (!isRechargeOrderCurrent(order)) { return; } const result = buildRechargePaymentResultForOrder(response.order); const isPaid = result.kind === 'success'; if (!applyRechargeCenter(response.center, order.account, true)) { return; } if (result.kind !== 'pending') { setNativeWechatPayment(null); if ( isSameRechargeOrder(pendingWechatRechargeOrderRef.current, order) ) { pendingWechatRechargeOrderRef.current = null; } order.abortController.abort(); setRechargePaymentResult(result); if (isPaid) { void onRechargeSuccess?.(); } } else { setNativeWechatPayment((current) => current && current.orderId === nativeWechatPayment.orderId ? { ...current, isConfirming: false, confirmMessage: '暂未确认到账,请确认付款完成后再点一次。', } : current, ); } }) .catch(() => { if (!isRechargeOrderCurrent(order)) { return; } setNativeWechatPayment((current) => current && current.orderId === nativeWechatPayment.orderId ? { ...current, isConfirming: false, confirmMessage: '暂时没能确认到账状态,请稍后再试。', } : current, ); }) .finally(() => { if (isRechargeOrderCurrent(order)) { setSubmittingRechargeProductId(null); } }); }, [ applyRechargeCenter, createRechargeConfirmationOptions, isRechargeOrderCurrent, isSameRechargeOrder, nativeWechatPayment, onRechargeSuccess, ]); useEffect(() => { const orderId = nativeWechatPayment?.orderId; const expiresAtMs = Date.parse(nativeWechatPayment?.expiresAt ?? ''); const order = pendingWechatRechargeOrderRef.current; if ( !orderId || !Number.isFinite(expiresAtMs) || !isRechargeOrderCurrent(order) || order.orderId !== orderId ) { return undefined; } let cancelled = false; const confirmationOptions = createRechargeConfirmationOptions(order); const watchUntilSettled = async () => { while ( !cancelled && Date.now() < expiresAtMs && confirmationOptions.isCurrent() ) { try { assertWechatRechargeConfirmationActive(confirmationOptions); const response = await watchWechatPlatformProfileRechargeOrder( orderId, { signal: confirmationOptions.signal, }, ); assertWechatRechargeConfirmationActive(confirmationOptions); if ( cancelled || !response || !isSameRechargeOrder(pendingWechatRechargeOrderRef.current, order) ) { return; } const result = buildRechargePaymentResultForOrder(response.order); if (!applyRechargeCenter(response.center, order.account, true)) { return; } if (result.kind === 'pending') { await waitWechatPayConfirmDelay( WECHAT_NATIVE_WATCH_RETRY_DELAY_MS, confirmationOptions.signal, ); assertWechatRechargeConfirmationActive(confirmationOptions); continue; } pendingWechatRechargeOrderRef.current = null; if ( isSameRechargeOrder(confirmingWechatRechargeOrderRef.current, order) ) { confirmingWechatRechargeOrderRef.current = null; } order.abortController.abort(); setNativeWechatPayment((current) => current?.orderId === orderId ? null : current, ); setSubmittingRechargeProductId(null); setRechargePaymentResult(result); if (result.kind === 'success') { void onRechargeSuccess?.(); } return; } catch { if ( cancelled || confirmationOptions.signal.aborted || !confirmationOptions.isCurrent() ) { return; } } try { await waitWechatPayConfirmDelay( WECHAT_NATIVE_WATCH_RETRY_DELAY_MS, confirmationOptions.signal, ); assertWechatRechargeConfirmationActive(confirmationOptions); } catch { return; } } }; void watchUntilSettled(); return () => { cancelled = true; }; }, [ nativeWechatPayment?.expiresAt, nativeWechatPayment?.orderId, applyRechargeCenter, createRechargeConfirmationOptions, isRechargeOrderCurrent, isSameRechargeOrder, onRechargeSuccess, ]); // 中文注释:H5 / 小程序支付返回页、页面恢复和 hash 轮询都统一走同一套到账确认逻辑, // 避免页面组件自己感知微信支付细节。 useEffect(() => { const handleHashChange = () => { handleWechatPayResult(); }; const handleResume = () => { if ( typeof document !== 'undefined' && document.visibilityState === 'hidden' ) { return; } if (!handleWechatPayResult()) { confirmPendingWechatRechargeOrder(); } }; window.addEventListener('hashchange', handleHashChange); window.addEventListener('focus', handleResume); window.addEventListener('pageshow', handleResume); document.addEventListener('visibilitychange', handleResume); handleWechatPayResult(); return () => { window.removeEventListener('hashchange', handleHashChange); window.removeEventListener('focus', handleResume); window.removeEventListener('pageshow', handleResume); document.removeEventListener('visibilitychange', handleResume); }; }, [confirmPendingWechatRechargeOrder, handleWechatPayResult]); useEffect(() => { if (!submittingRechargeProductId || wechatRechargeOrderConfirmationState) { return undefined; } const startedAt = Date.now(); let timer: number | null = null; const pollPayResult = () => { if (pollWechatPayResultFromHash()) { return; } if (Date.now() - startedAt >= WECHAT_PAY_RESULT_RECHECK_TIMEOUT_MS) { return; } timer = window.setTimeout( pollPayResult, WECHAT_PAY_RESULT_RECHECK_INTERVAL_MS, ); }; timer = window.setTimeout( pollPayResult, WECHAT_PAY_RESULT_RECHECK_INTERVAL_MS, ); return () => { if (timer !== null) { window.clearTimeout(timer); } }; }, [ pollWechatPayResultFromHash, submittingRechargeProductId, wechatRechargeOrderConfirmationState, ]); const loadReferralCenter = useCallback(() => { setIsLoadingReferral(true); setIsReferralCenterInitialized(false); void getPlatformProfileReferralInviteCenter() .then(setReferralCenter) .catch((error: unknown) => { setReferralCenter(null); setReferralError( error instanceof Error ? error.message : '读取邀请码失败', ); }) .finally(() => { setIsReferralCenterInitialized(true); setIsLoadingReferral(false); }); }, []); useEffect(() => { if (activeTab !== 'profile' || !isAuthenticated) { setIsReferralCenterInitialized(false); setReferralCenter(null); return; } loadReferralCenter(); }, [activeTab, isAuthenticated, loadReferralCenter]); const openProfilePopupPanel = useCallback( (panel: ProfileReferralPanel) => { setProfilePopupPanel(panel); setReferralError(null); setReferralSuccess(null); if (panel === 'redeem') { setReferralRedeemCode(pendingProfileInviteCode); } if (panel === 'community') { return; } if (!isReferralCenterInitialized && !isLoadingReferral) { loadReferralCenter(); } }, [ isLoadingReferral, isReferralCenterInitialized, loadReferralCenter, pendingProfileInviteCode, ], ); const closeProfilePopupPanel = useCallback(() => { setProfilePopupPanel(null); }, []); const copyInviteInfo = useCallback(() => { if (!referralCenter?.inviteCode) { return; } const inviteUrl = typeof window === 'undefined' ? referralCenter.inviteLinkPath : new URL(referralCenter.inviteLinkPath, window.location.origin).href; void copyInviteText(`${referralCenter.inviteCode} ${inviteUrl}`).then( (copied) => { setReferralSuccess(copied ? '已复制' : '复制失败'); }, ); }, [copyInviteText, referralCenter]); const submitReferralRedeemCode = useCallback(() => { const inviteCode = referralRedeemCode .trim() .replace(/[^0-9a-z]/gi, '') .toUpperCase(); if (isSubmittingReferralRedeem || !inviteCode) { return; } setIsSubmittingReferralRedeem(true); setReferralError(null); setReferralSuccess(null); const snapshotOwnerUserId = currentUserId; const accountRevision = accountLifecycleRevisionRef.current; void redeemPlatformProfileReferralInviteCode(inviteCode) .then((response) => { if ( accountRevision !== accountLifecycleRevisionRef.current || currentUserIdRef.current !== snapshotOwnerUserId ) { return; } setReferralCenter(response.center); setReferralRedeemCode(''); setReferralSuccess('已填写'); void onWalletBalanceMayHaveChanged(); void onRechargeSuccess?.(); }) .catch((error: unknown) => { if ( accountRevision !== accountLifecycleRevisionRef.current || currentUserIdRef.current !== snapshotOwnerUserId ) { return; } setReferralError( error instanceof Error ? error.message : '填写邀请码失败', ); }) .finally(() => { if ( accountRevision === accountLifecycleRevisionRef.current && currentUserIdRef.current === snapshotOwnerUserId ) { setIsSubmittingReferralRedeem(false); } }); }, [ currentUserId, isSubmittingReferralRedeem, onRechargeSuccess, onWalletBalanceMayHaveChanged, referralRedeemCode, ]); const submitRewardCode = useCallback(() => { if (isSubmittingRewardCode || !rewardCodeInput.trim()) { return; } setIsSubmittingRewardCode(true); setRewardCodeError(null); setRewardCodeSuccess(null); const snapshotOwnerUserId = currentUserId; const accountRevision = accountLifecycleRevisionRef.current; void redeemPlatformProfileRewardCode(rewardCodeInput) .then((response: RedeemProfileRewardCodeResponse) => { if ( accountRevision !== accountLifecycleRevisionRef.current || currentUserIdRef.current !== snapshotOwnerUserId ) { return; } setRewardCodeInput(''); setRewardCodeSuccess(`已到账 ${response.amountGranted} 泥点`); void onWalletBalanceMayHaveChanged(); void onRechargeSuccess?.(); }) .catch((error: unknown) => { if ( accountRevision !== accountLifecycleRevisionRef.current || currentUserIdRef.current !== snapshotOwnerUserId ) { return; } setRewardCodeError(error instanceof Error ? error.message : '兑换失败'); }) .finally(() => { if ( accountRevision === accountLifecycleRevisionRef.current && currentUserIdRef.current === snapshotOwnerUserId ) { setIsSubmittingRewardCode(false); } }); }, [ currentUserId, isSubmittingRewardCode, onRechargeSuccess, onWalletBalanceMayHaveChanged, rewardCodeInput, ]); const rechargeModalCenter = useMemo(() => { if (!walletOwnerMatchesCurrentUser || !rechargeCenter) { return null; } return { ...rechargeCenter, walletBalance: mudPointBalance?.totalPoints ?? rechargeCenter.walletBalance, mudPointBalance: mudPointBalance ?? rechargeCenter.mudPointBalance, }; }, [mudPointBalance, rechargeCenter, walletOwnerMatchesCurrentUser]); return { closeNativeWechatPayment, closeProfilePopupPanel, confirmNativeWechatPayment, inviteCopyState: inviteCopyState as CopyFeedbackState, isLoadingRechargeCenter, isLoadingReferral, isLoadingWalletLedger, isRechargeOpen, isRewardCodeOpen, isSubmittingReferralRedeem, isSubmittingRewardCode, isWalletLedgerOpen, loadRechargeCenter, loadReferralCenter, loadWalletLedger, nativeWechatPayment, openProfilePopupPanel, openRechargeOrRewardCodeModal, openRewardCodeModal, openWalletLedgerPanel, profilePopupPanel, rechargeCenter: rechargeModalCenter, rechargeError, rechargePaymentResult, referralCenter, referralError, referralRedeemCode, referralSuccess, rewardCodeError, rewardCodeInput, rewardCodeSuccess, setIsRechargeOpen, setIsRewardCodeOpen, setIsWalletLedgerOpen, setRechargePaymentResult, setReferralRedeemCode, setRewardCodeInput, showRechargeEntry, submittingRechargeProductId, submitReferralRedeemCode, submitRewardCode, walletLedger, walletLedgerError, wechatRechargeOrderConfirmationState, buyRechargeProduct, copyInviteInfo, }; }