import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { AuthUser } from '../../services/authService'; import { refreshStoredAccessToken } from '../../services/apiClient'; import { resolveProfileRechargeProductPaymentChannel, WECHAT_H5_PAYMENT_CHANNEL, WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL, WECHAT_NATIVE_PAYMENT_CHANNEL, } from '../../services/payment/paymentPlatform'; import { redirectToPaymentUrl } from '../../services/payment/paymentRedirect'; import { claimRpgProfileTaskReward, confirmWechatRpgProfileRechargeOrder, createRpgProfileRechargeOrder, getRpgProfileRechargeCenter, getRpgProfileReferralInviteCenter, getRpgProfileTasks, getRpgProfileWalletLedger, redeemRpgProfileReferralInviteCode, redeemRpgProfileRewardCode, watchWechatRpgProfileRechargeOrder, } from '../../services/rpg-entry/rpgProfileClient'; import { type ConfirmWechatProfileRechargeOrderResponse, type ProfileRechargeCenterResponse, type ProfileRechargeProduct, type ProfileReferralInviteCenterResponse, type ProfileTaskCenterResponse, type ProfileWalletLedgerResponse, type RedeemProfileRewardCodeResponse, type WechatMiniProgramPayParams, type WechatMiniProgramVirtualPayParams, type WechatNativePayment, } from '../../../packages/shared/src/contracts/runtime'; import { type CopyFeedbackState, useCopyFeedback, } from '../common/useCopyFeedback'; const PROFILE_TASK_DAY_MS = 24 * 60 * 60 * 1000; const PROFILE_TASK_BEIJING_OFFSET_MS = 8 * 60 * 60 * 1000; const PROFILE_TASK_MIN_RESET_DELAY_MS = 1000; const PROFILE_INVITE_QUERY_KEYS = ['inviteCode', 'invite_code'] as const; const WECHAT_JS_SDK_URL = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'; 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; export type ProfileReferralPanel = 'invite' | 'redeem' | 'community'; export type ProfilePopupPanel = ProfileReferralPanel | 'saveArchives'; export type RechargeTab = 'points' | 'membership'; type WechatPayResult = { requestId: string; orderId: string | null; status: 'success' | 'cancel' | 'fail'; errorMessage: string | null; }; type RechargePaymentResultKind = 'success' | 'pending' | 'cancel' | 'failed'; export type RechargePaymentResult = { kind: RechargePaymentResultKind; title: string; message: string; }; export type WechatRechargeOrderConfirmationState = { orderId: string; }; export type NativeWechatPaymentState = WechatNativePayment & { orderId: string; isConfirming: boolean; }; type UsePlatformProfileCenterControllerArgs = { activeTab: string; isAuthenticated: boolean; showRechargeEntry: boolean; profileTaskRefreshKey?: number; onRechargeSuccess?: () => void | Promise; requestLogin: () => void; currentUser: AuthUser | null | undefined; }; function getDelayUntilNextProfileTaskReset(nowMs = Date.now()) { const shiftedNow = nowMs + PROFILE_TASK_BEIJING_OFFSET_MS; const nextDayStart = Math.floor(shiftedNow / PROFILE_TASK_DAY_MS) * PROFILE_TASK_DAY_MS + PROFILE_TASK_DAY_MS; const nextResetAt = nextDayStart - PROFILE_TASK_BEIJING_OFFSET_MS; return Math.max(PROFILE_TASK_MIN_RESET_DELAY_MS, nextResetAt - nowMs); } 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 loadWechatJsSdk() { if (typeof window === 'undefined') { return Promise.reject(new Error('请在微信小程序内完成支付')); } if (window.wx?.miniProgram?.navigateTo) { return Promise.resolve(window.wx); } return new Promise>((resolve, reject) => { const existingScript = document.querySelector( `script[src="${WECHAT_JS_SDK_URL}"]`, ); const complete = () => { if (window.wx?.miniProgram?.navigateTo) { resolve(window.wx); } else { reject(new Error('请在微信小程序内完成支付')); } }; if (existingScript) { existingScript.addEventListener('load', complete, { once: true }); existingScript.addEventListener( 'error', () => reject(new Error('请在微信小程序内完成支付')), { once: true }, ); complete(); return; } const script = document.createElement('script'); script.src = WECHAT_JS_SDK_URL; script.async = true; script.onload = complete; script.onerror = () => reject(new Error('请在微信小程序内完成支付')); document.head.appendChild(script); }); } async function requestWechatMiniProgramPayment( payload: | WechatMiniProgramPayParams | WechatMiniProgramVirtualPayParams | null | undefined, orderId: string, ): Promise { if (!payload) { return Promise.reject(new Error('请在微信小程序内完成支付')); } const wxBridge = await loadWechatJsSdk(); const miniProgram = wxBridge.miniProgram; if (!miniProgram || typeof miniProgram.navigateTo !== 'function') { return Promise.reject(new Error('请在微信小程序内完成支付')); } const navigateTo = miniProgram.navigateTo; const requestId = `wechat_pay_${orderId}_${Date.now()}`; return new Promise((resolve, reject) => { navigateTo({ url: `/pages/wechat-pay/index?requestId=${encodeURIComponent(requestId)}&orderId=${encodeURIComponent(orderId)}&payParams=${encodeURIComponent(JSON.stringify(payload))}`, success() { resolve(); }, fail(error) { console.error('[wechat-pay] navigateTo failed', error); reject( error instanceof Error ? error : new Error('请在微信小程序内完成支付'), ); }, }); }); } function waitWechatPayConfirmDelay(delayMs: number) { return new Promise((resolve) => { window.setTimeout(resolve, delayMs); }); } async function confirmWechatRechargeOrderUntilSettled( orderId: string, ): Promise { let latestResponse = await confirmWechatRpgProfileRechargeOrder(orderId); if (latestResponse.order.status !== 'pending') { return latestResponse; } for (const delayMs of WECHAT_PAY_CONFIRM_RETRY_DELAYS_MS) { await waitWechatPayConfirmDelay(delayMs); latestResponse = await confirmWechatRpgProfileRechargeOrder(orderId); if (latestResponse.order.status !== 'pending') { return latestResponse; } } try { const streamedResponse = await watchWechatRpgProfileRechargeOrder(orderId); return streamedResponse; } catch { return latestResponse; } } export function usePlatformProfileCenterController({ activeTab, isAuthenticated, showRechargeEntry, profileTaskRefreshKey = 0, 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 [activeRechargeTab, setActiveRechargeTab] = useState('points'); 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 [isTaskCenterOpen, setIsTaskCenterOpen] = useState(false); const [taskCenter, setTaskCenter] = useState(null); const [taskCenterError, setTaskCenterError] = useState(null); const [isLoadingTaskCenter, setIsLoadingTaskCenter] = useState(false); const taskCenterRequestIdRef = useRef(0); const [claimingTaskId, setClaimingTaskId] = useState(null); const [taskClaimSuccess, setTaskClaimSuccess] = useState(null); 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 pendingWechatRechargeOrderIdRef = useRef(null); const confirmingWechatRechargeOrderIdRef = useRef(null); // 中文注释:支持带邀请码 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(() => { setWalletLedgerError(null); setIsLoadingWalletLedger(true); void getRpgProfileWalletLedger() .then(setWalletLedger) .catch((error: unknown) => { setWalletLedger(null); setWalletLedgerError( error instanceof Error ? error.message : '读取泥点账单失败', ); }) .finally(() => setIsLoadingWalletLedger(false)); }, []); const openWalletLedgerPanel = useCallback(() => { setIsWalletLedgerOpen(true); loadWalletLedger(); }, [loadWalletLedger]); const loadRechargeCenter = useCallback(() => { setRechargeError(null); setIsLoadingRechargeCenter(true); void getRpgProfileRechargeCenter() .then(setRechargeCenter) .catch((error: unknown) => { setRechargeCenter(null); setRechargeError( error instanceof Error ? error.message : '读取账户充值失败', ); }) .finally(() => setIsLoadingRechargeCenter(false)); }, []); const refreshRechargeState = useCallback(() => { loadRechargeCenter(); setSubmittingRechargeProductId(null); pendingWechatRechargeOrderIdRef.current = null; confirmingWechatRechargeOrderIdRef.current = null; setWechatRechargeOrderConfirmationState(null); setNativeWechatPayment(null); }, [loadRechargeCenter]); const handleWechatPayResult = useCallback(() => { const payResult = readWechatPayResultFromHash(); if (!payResult) { return false; } if ( pendingWechatRechargeOrderIdRef.current && payResult.orderId && payResult.orderId !== pendingWechatRechargeOrderIdRef.current ) { return false; } if (payResult.status === 'success') { const orderId = payResult.orderId || pendingWechatRechargeOrderIdRef.current; if (!orderId) { clearWechatPayResultHash(); return true; } if (confirmingWechatRechargeOrderIdRef.current === orderId) { clearWechatPayResultHash(); return true; } confirmingWechatRechargeOrderIdRef.current = orderId; setWechatRechargeOrderConfirmationState({ orderId }); setSubmittingRechargeProductId(null); setRechargePaymentResult(null); void confirmWechatRechargeOrderUntilSettled(orderId) .then((response) => { const isPaid = response.order.status === 'paid'; setRechargeCenter(response.center); pendingWechatRechargeOrderIdRef.current = null; confirmingWechatRechargeOrderIdRef.current = null; setWechatRechargeOrderConfirmationState(null); setRechargePaymentResult( isPaid ? { kind: 'success', title: '支付成功', message: '已到账,账户状态已刷新。', } : { kind: 'pending', title: '支付处理中', message: '正在等待到账状态确认,请稍后查看余额或会员状态。', }, ); if (isPaid) { void onRechargeSuccess?.(); } clearWechatPayResultHash(); }) .catch(() => { confirmingWechatRechargeOrderIdRef.current = null; setWechatRechargeOrderConfirmationState(null); setRechargePaymentResult({ kind: 'pending', title: '支付处理中', message: '暂时没能确认到账状态,请稍后查看余额或会员状态。', }); clearWechatPayResultHash(); }); } else if (payResult.status === 'cancel') { setRechargePaymentResult({ kind: 'cancel', title: '支付已取消', message: '本次没有扣款,账户状态未发生变化。', }); setWechatRechargeOrderConfirmationState(null); refreshRechargeState(); } else { const detail = payResult.errorMessage ? `微信返回:${payResult.errorMessage}` : '微信支付没有完成,本次不会入账。'; setRechargePaymentResult({ kind: 'failed', title: '支付未完成', message: detail, }); setWechatRechargeOrderConfirmationState(null); refreshRechargeState(); } clearWechatPayResultHash(); return true; }, [onRechargeSuccess, refreshRechargeState]); const pollWechatPayResultFromHash = useCallback( () => handleWechatPayResult(), [handleWechatPayResult], ); const confirmPendingWechatRechargeOrder = useCallback(() => { const orderId = pendingWechatRechargeOrderIdRef.current; if (!orderId || confirmingWechatRechargeOrderIdRef.current === orderId) { return false; } confirmingWechatRechargeOrderIdRef.current = orderId; setWechatRechargeOrderConfirmationState({ orderId }); setRechargePaymentResult(null); void confirmWechatRechargeOrderUntilSettled(orderId) .then((response) => { const isPaid = response.order.status === 'paid'; setRechargeCenter(response.center); pendingWechatRechargeOrderIdRef.current = null; confirmingWechatRechargeOrderIdRef.current = null; setWechatRechargeOrderConfirmationState(null); setSubmittingRechargeProductId(null); setRechargePaymentResult( isPaid ? { kind: 'success', title: '支付成功', message: '已到账,账户状态已刷新。', } : { kind: 'pending', title: '支付处理中', message: '正在等待到账状态确认,请稍后查看余额或会员状态。', }, ); if (isPaid) { void onRechargeSuccess?.(); } }) .catch(() => { confirmingWechatRechargeOrderIdRef.current = null; setWechatRechargeOrderConfirmationState(null); setRechargePaymentResult({ kind: 'pending', title: '支付处理中', message: '暂时没能确认到账状态,请稍后查看余额或会员状态。', }); }); return true; }, [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 buyRechargeProduct = useCallback( (product: ProfileRechargeProduct) => { if (submittingRechargeProductId) { return; } const paymentChannel = resolveProfileRechargeProductPaymentChannel( { kind: product.kind }, {}, ); setSubmittingRechargeProductId(product.productId); setRechargeError(null); setRechargePaymentResult(null); setWechatRechargeOrderConfirmationState(null); setNativeWechatPayment(null); void createRpgProfileRechargeOrder(product.productId, paymentChannel) .then(async (response) => { if (paymentChannel === WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL) { pendingWechatRechargeOrderIdRef.current = response.order.orderId; setRechargeCenter(response.center); await requestWechatMiniProgramPayment( response.wechatMiniProgramPayParams, response.order.orderId, ); return; } if (paymentChannel === WECHAT_H5_PAYMENT_CHANNEL) { const h5Url = response.wechatH5Payment?.h5Url?.trim(); if (!h5Url) { throw new Error('微信 H5 支付链接生成失败'); } pendingWechatRechargeOrderIdRef.current = response.order.orderId; setRechargeCenter(response.center); setRechargePaymentResult({ kind: 'pending', title: '正在打开微信支付', message: '完成支付后返回页面确认到账状态。', }); redirectToPaymentUrl(h5Url); return; } if (paymentChannel === WECHAT_NATIVE_PAYMENT_CHANNEL) { const codeUrl = response.wechatNativePayment?.codeUrl?.trim(); if (!codeUrl) { throw new Error('微信 Native 支付二维码生成失败'); } pendingWechatRechargeOrderIdRef.current = response.order.orderId; setRechargeCenter(response.center); setNativeWechatPayment({ orderId: response.order.orderId, codeUrl, isConfirming: false, }); setSubmittingRechargeProductId(null); return; } throw new Error('充值支付渠道无效'); }) .catch((error: unknown) => { pendingWechatRechargeOrderIdRef.current = null; setNativeWechatPayment(null); setRechargeError(error instanceof Error ? error.message : '充值失败'); setSubmittingRechargeProductId(null); }); }, [submittingRechargeProductId], ); const confirmNativeWechatPayment = useCallback(() => { if (!nativeWechatPayment || nativeWechatPayment.isConfirming) { return; } setNativeWechatPayment((current) => current && current.orderId === nativeWechatPayment.orderId ? { ...current, isConfirming: true } : current, ); setRechargePaymentResult({ kind: 'pending', title: '正在确认支付', message: '正在查询微信支付到账状态。', }); void confirmWechatRechargeOrderUntilSettled(nativeWechatPayment.orderId) .then((response) => { const isPaid = response.order.status === 'paid'; setRechargeCenter(response.center); setRechargePaymentResult( isPaid ? { kind: 'success', title: '支付成功', message: '已到账,账户状态已刷新。', } : { kind: 'pending', title: '等待微信确认', message: '暂时没能确认到账状态,请稍后再试。', }, ); if (isPaid) { setNativeWechatPayment(null); pendingWechatRechargeOrderIdRef.current = null; void onRechargeSuccess?.(); } else { setNativeWechatPayment((current) => current && current.orderId === nativeWechatPayment.orderId ? { ...current, isConfirming: false } : current, ); } }) .catch(() => { setRechargePaymentResult({ kind: 'pending', title: '等待微信确认', message: '暂时没能确认到账状态,请稍后再试。', }); setNativeWechatPayment((current) => current && current.orderId === nativeWechatPayment.orderId ? { ...current, isConfirming: false } : current, ); }) .finally(() => setSubmittingRechargeProductId(null)); }, [nativeWechatPayment, 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 loadTaskCenter = useCallback(() => { const requestId = ++taskCenterRequestIdRef.current; setTaskCenterError(null); setIsLoadingTaskCenter(true); void getRpgProfileTasks() .then((center) => { if (requestId === taskCenterRequestIdRef.current) { setTaskCenter(center); } }) .catch((error: unknown) => { if (requestId !== taskCenterRequestIdRef.current) { return; } setTaskCenter(null); setTaskCenterError( error instanceof Error ? error.message : '读取每日任务失败', ); }) .finally(() => { if (requestId === taskCenterRequestIdRef.current) { setIsLoadingTaskCenter(false); } }); }, []); useEffect(() => { if (activeTab !== 'profile' || !isAuthenticated) { taskCenterRequestIdRef.current += 1; setTaskCenter(null); setTaskCenterError(null); return; } loadTaskCenter(); }, [activeTab, isAuthenticated, loadTaskCenter, profileTaskRefreshKey]); useEffect(() => { if (activeTab !== 'profile' || !isAuthenticated) { return undefined; } // 中文注释:每日任务重置依赖北京时间跨天与 access token 刷新,继续留在 controller 里集中托管。 let cancelled = false; let timer: number | null = null; const scheduleNextReset = () => { if (cancelled) { return; } timer = window.setTimeout(() => { void refreshStoredAccessToken({ clearOnFailure: false }) .catch(() => undefined) .finally(() => { if (cancelled) { return; } loadTaskCenter(); scheduleNextReset(); }); }, getDelayUntilNextProfileTaskReset()); }; scheduleNextReset(); return () => { cancelled = true; if (timer !== null) { window.clearTimeout(timer); } }; }, [activeTab, isAuthenticated, loadTaskCenter]); const openTaskCenterPanel = useCallback(() => { setIsTaskCenterOpen(true); setTaskClaimSuccess(null); if (!taskCenter) { loadTaskCenter(); } }, [loadTaskCenter, taskCenter]); const loadReferralCenter = useCallback(() => { setIsLoadingReferral(true); setIsReferralCenterInitialized(false); void getRpgProfileReferralInviteCenter() .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); void redeemRpgProfileReferralInviteCode(inviteCode) .then((response) => { setReferralCenter(response.center); setReferralRedeemCode(''); setReferralSuccess('已填写'); void onRechargeSuccess?.(); }) .catch((error: unknown) => { setReferralError( error instanceof Error ? error.message : '填写邀请码失败', ); }) .finally(() => setIsSubmittingReferralRedeem(false)); }, [isSubmittingReferralRedeem, onRechargeSuccess, referralRedeemCode]); const submitRewardCode = useCallback(() => { if (isSubmittingRewardCode || !rewardCodeInput.trim()) { return; } setIsSubmittingRewardCode(true); setRewardCodeError(null); setRewardCodeSuccess(null); void redeemRpgProfileRewardCode(rewardCodeInput) .then((response: RedeemProfileRewardCodeResponse) => { setRewardCodeInput(''); setRewardCodeSuccess(`已到账 ${response.amountGranted} 泥点`); void onRechargeSuccess?.(); }) .catch((error: unknown) => { setRewardCodeError(error instanceof Error ? error.message : '兑换失败'); }) .finally(() => setIsSubmittingRewardCode(false)); }, [isSubmittingRewardCode, onRechargeSuccess, rewardCodeInput]); const claimTaskReward = useCallback( (taskId: string) => { if (claimingTaskId) { return; } setClaimingTaskId(taskId); setTaskCenterError(null); setTaskClaimSuccess(null); void claimRpgProfileTaskReward(taskId) .then((response) => { setTaskCenter(response.center); setTaskClaimSuccess(`已领取 ${response.rewardPoints} 泥点`); void onRechargeSuccess?.(); }) .catch((error: unknown) => { setTaskCenterError( error instanceof Error ? error.message : '领取任务奖励失败', ); }) .finally(() => setClaimingTaskId(null)); }, [claimingTaskId, onRechargeSuccess], ); return { activeRechargeTab, claimTaskReward, claimingTaskId, closeProfilePopupPanel, confirmNativeWechatPayment, inviteCopyState: inviteCopyState as CopyFeedbackState, isLoadingRechargeCenter, isLoadingReferral, isLoadingTaskCenter, isLoadingWalletLedger, isRechargeOpen, isRewardCodeOpen, isSubmittingReferralRedeem, isSubmittingRewardCode, isTaskCenterOpen, isWalletLedgerOpen, loadRechargeCenter, loadReferralCenter, loadTaskCenter, loadWalletLedger, nativeWechatPayment, openProfilePopupPanel, openRechargeOrRewardCodeModal, openRewardCodeModal, openTaskCenterPanel, openWalletLedgerPanel, profilePopupPanel, rechargeCenter, rechargeError, rechargePaymentResult, referralCenter, referralError, referralRedeemCode, referralSuccess, rewardCodeError, rewardCodeInput, rewardCodeSuccess, setActiveRechargeTab, setIsRechargeOpen, setIsRewardCodeOpen, setIsTaskCenterOpen, setIsWalletLedgerOpen, setRechargePaymentResult, setReferralRedeemCode, setRewardCodeInput, showRechargeEntry, submittingRechargeProductId, submitReferralRedeemCode, submitRewardCode, taskCenter, taskCenterError, taskClaimSuccess, walletLedger, walletLedgerError, wechatRechargeOrderConfirmationState, buyRechargeProduct, copyInviteInfo, }; }