import { useState, useEffect, useRef, useMemo } from 'react'; import { Linking } from 'react-native'; import { useRouter } from 'expo-router'; import { authClient, useSession } from '@/lib/auth/client'; import { getStripePlans, getPlanNames } from '@/lib/api/pricing'; import { Alert } from '@/utils/alert'; import type { StripePricingTableResponse, CreditPlan, } from '@/lib/api/pricing'; export function usePricing() { const router = useRouter(); const { data: session, isPending: isAuthPending } = useSession(); const [selectedPlanIndex, setSelectedPlanIndex] = useState(0); // Stripe 套餐数据 const [stripePricingData, setStripePricingData] = useState(null); const [isStripePricingLoading, setIsStripePricingLoading] = useState(true); const [stripePricingError, setStripePricingError] = useState(null); // 订阅数据 const [authSubscriptions, setAuthSubscriptions] = useState([]); const [isLoadingSubscriptions, setIsLoadingSubscriptions] = useState(false); const [isFetchingSubscriptions, setIsFetchingSubscriptions] = useState(false); // Mutation 状态 const [createSubscriptionPending, setCreateSubscriptionPending] = useState(false); const [upgradeSubscriptionPending, setUpgradeSubscriptionPending] = useState(false); const [restoreSubscriptionPending, setRestoreSubscriptionPending] = useState(false); const [rechargeTokenPending, setRechargeTokenPending] = useState(false); const isInitialized = useRef(false); // 获取 Stripe 套餐数据 useEffect(() => { const fetchStripePlans = async () => { setIsStripePricingLoading(true); const result = await getStripePlans(); if (result.success && result.data) { setStripePricingData(result.data); setStripePricingError(null); } else { setStripePricingError(result.message || 'Failed to load pricing'); } setIsStripePricingLoading(false); }; fetchStripePlans(); }, []); // 获取订阅数据 const fetchAuthSubscriptions = async () => { if (!session?.user?.id) return; setIsFetchingSubscriptions(true); try { const { data, error } = await authClient.subscription.list({ query: { referenceId: session?.user?.id || '', }, }); if (!error && data) { setAuthSubscriptions(data); } } catch (error) { console.error('Failed to fetch subscriptions:', error); } finally { setIsFetchingSubscriptions(false); } }; useEffect(() => { if (session?.user?.id) { setIsLoadingSubscriptions(true); fetchAuthSubscriptions().finally(() => { setIsLoadingSubscriptions(false); }); } }, [session?.user?.id]); // 订阅类型分类 const licensedSubscriptions = authSubscriptions?.filter((sub: any) => sub.type === 'licenced') || []; const meteredSubscriptions = authSubscriptions?.filter((sub: any) => sub.type === 'metered') || []; // 订阅状态计算 const activeAuthSubscription = licensedSubscriptions?.find( (sub: any) => sub.status === 'active' || sub.status === 'trialing' ); const hasActiveSubscription = !!( activeAuthSubscription && activeAuthSubscription.cancelAtPeriodEnd === false && activeAuthSubscription.status === 'active' ); const hasCanceledButActiveSubscription = !!( activeAuthSubscription && activeAuthSubscription.status === 'active' && activeAuthSubscription.cancelAtPeriodEnd === true ); // 获取价格计划 const creditPlans = useMemo((): CreditPlan[] => { if (stripePricingData?.pricing_table_items) { return stripePricingData.pricing_table_items .filter((item) => item.recurring?.interval === 'month') .map((item) => { const amountInCents = parseInt(item.amount); const credits = item.metadata?.grant_token || amountInCents; return { amountInCents, credits, popular: item.is_highlight || item.highlight_text === 'most_popular', }; }); } return []; }, [stripePricingData?.pricing_table_items]); // 当前订阅对应的积分 const currentSubscriptionCredits = useMemo((): number | null => { if (!activeAuthSubscription?.plan) return null; const planNameLower = activeAuthSubscription.plan.toLowerCase(); const planNames = getPlanNames(stripePricingData); const matchingPlan = creditPlans.find((_, index) => { return planNames[index]?.toLowerCase() === planNameLower; }); return matchingPlan?.credits || null; }, [activeAuthSubscription?.plan, creditPlans, stripePricingData]); // 积分余额 const creditBalanceData = meteredSubscriptions?.[0]?.creditBalance; const creditBalance = creditBalanceData?.remainingTokenBalance || 0; const selectedPlan = creditPlans[selectedPlanIndex] || creditPlans[0]; // 初始化选中的计划 useEffect(() => { const hasRequiredData = creditPlans.length > 0 && !isStripePricingLoading && !isLoadingSubscriptions; if (!isInitialized.current && hasRequiredData) { let targetIndex = Math.floor(creditPlans.length / 2); if (currentSubscriptionCredits) { const foundIndex = creditPlans.findIndex(plan => plan.credits.toString() === currentSubscriptionCredits.toString()); if (foundIndex !== -1) { targetIndex = foundIndex; } } setSelectedPlanIndex(targetIndex); isInitialized.current = true; } }, [currentSubscriptionCredits, creditPlans, isStripePricingLoading, isLoadingSubscriptions]); // 创建订阅 const createSubscription = async (priceId: string, productId: string) => { setCreateSubscriptionPending(true); try { const { data, error } = await authClient.subscription.create({ priceId, productId, successUrl: 'bestaibest://exchange', cancelUrl: 'bestaibest://exchange?canceled=true', }); if (error) { Alert.alert('Error', error.message || 'Failed to create subscription'); return null; } if (data?.url) { await Linking.openURL(data.url); } return data; } catch (error) { Alert.alert('Error', 'Failed to create subscription'); return null; } finally { setCreateSubscriptionPending(false); } }; // 升级订阅 const upgradeSubscription = async (credits: number) => { if (!activeAuthSubscription?.stripeSubscriptionId) { Alert.alert('Error', 'No active subscription found'); return null; } setUpgradeSubscriptionPending(true); try { const planIndex = creditPlans.findIndex(plan => plan.credits === credits); const planNames = getPlanNames(stripePricingData); const planName = planNames[planIndex] || 'basic'; const { data, error } = await authClient.subscription.upgrade({ plan: planName, subscriptionId: activeAuthSubscription.stripeSubscriptionId, successUrl: 'bestaibest://exchange', cancelUrl: 'bestaibest://exchange?canceled=true', }); if (error) { Alert.alert('Error', error.message || 'Failed to upgrade subscription'); return null; } if (data?.url) { await Linking.openURL(data.url); } return data; } catch (error) { Alert.alert('Error', 'Failed to upgrade subscription'); return null; } finally { setUpgradeSubscriptionPending(false); } }; // 恢复订阅 const restoreSubscription = async () => { if (!activeAuthSubscription) { Alert.alert('Error', 'No subscription to restore'); return null; } setRestoreSubscriptionPending(true); try { const { data, error } = await authClient.subscription.restore({ subscriptionId: activeAuthSubscription.stripeSubscriptionId, referenceId: activeAuthSubscription.referenceId, }); if (error) { Alert.alert('Error', error.message || 'Failed to restore subscription'); return null; } Alert.alert('Success', 'Subscription restored successfully'); await fetchAuthSubscriptions(); return data; } catch (error) { Alert.alert('Error', 'Failed to restore subscription'); return null; } finally { setRestoreSubscriptionPending(false); } }; // 充值 Token const rechargeToken = async (amount: number) => { if (!amount || amount <= 0) { Alert.alert('Error', 'Invalid recharge amount'); return null; } // 检查 pricing data 是否加载完成 if (!stripePricingData?.pricing_table_items?.[0]?.metered_price_id) { Alert.alert('Error', 'Pricing data not available. Please try again later.'); return null; } // 检查用户是否有 metered subscription if (!meteredSubscriptions || meteredSubscriptions.length === 0 || !meteredSubscriptions[0]?.priceId) { Alert.alert('Error', 'No active metered subscription found. Please contact support.'); return null; } setRechargeTokenPending(true); try { const { data, error } = await authClient.subscription.credit.topup({ amount: amount, priceId: meteredSubscriptions[0].priceId, successUrl: 'bestaibest://exchange', cancelUrl: 'bestaibest://exchange?canceled=true', }); if (error) { Alert.alert('Error', error.message || 'Failed to recharge'); return null; } if (data?.url) { await Linking.openURL(data.url); } return data; } catch (error) { Alert.alert('Error', 'Failed to recharge'); return null; } finally { setRechargeTokenPending(false); } }; // 处理订阅操作 const handleSubscriptionAction = async (planIndexOverride?: number) => { const targetPlanIndex = planIndexOverride !== undefined ? planIndexOverride : selectedPlanIndex; const targetPlan = creditPlans[targetPlanIndex]; if (!targetPlan) { Alert.alert('Error', 'Invalid plan selected'); return; } if (isStripePricingLoading) { Alert.alert('Please wait', 'Loading pricing data...'); return; } // 如果选择的是当前订阅 if (hasActiveSubscription && currentSubscriptionCredits === targetPlan.credits) { return; } // 如果是已取消但仍活跃的订阅 if (hasCanceledButActiveSubscription && currentSubscriptionCredits === targetPlan.credits) { await restoreSubscription(); return; } const pricingItem = stripePricingData?.pricing_table_items?.[targetPlanIndex]; if (!pricingItem) { Alert.alert('Error', 'Plan not found'); return; } // 判断创建还是升级 if (hasActiveSubscription || hasCanceledButActiveSubscription) { await upgradeSubscription(targetPlan.credits); } else { await createSubscription(pricingItem.price_id, pricingItem.product_id); } }; // 格式化积分 const formatCredits = (credits: number) => { return credits.toLocaleString(); }; // 综合加载状态 const isDataFullyLoaded = useMemo(() => { if (isAuthPending) return false; if (!session) return !isStripePricingLoading; return !isStripePricingLoading && !isLoadingSubscriptions; }, [session, isAuthPending, isStripePricingLoading, isLoadingSubscriptions]); return { // 状态 selectedPlanIndex, selectedPlan, creditPlans, // 认证状态 session, isAuthPending, isDataFullyLoaded, // 订阅状态 activeAuthSubscription, hasActiveSubscription, hasCanceledButActiveSubscription, currentSubscriptionCredits, isLoadingSubscriptions, isFetchingSubscriptions, meteredSubscriptions, // Credits 余额 creditBalance, creditBalanceData, // Stripe 数据 stripePricingData, isStripePricingLoading, stripePricingError, // 操作方法 setSelectedPlanIndex, handleSubscriptionAction, fetchAuthSubscriptions, rechargeToken, // 工具方法 formatCredits, // Mutation 状态 createSubscriptionPending, upgradeSubscriptionPending, restoreSubscriptionPending, rechargeTokenPending, }; }