expo-popcore-app/hooks/use-membership.ts

197 lines
6.3 KiB
TypeScript

import { useState, useEffect, useMemo } from 'react'
import { Linking, Alert } from 'react-native'
import { subscription, useSession } from '@/lib/auth'
import type { StripePricingTableResponse, SubscriptionListItem } from '@/lib/auth'
interface CreditPlan {
amountInCents: number
credits: number
popular?: boolean
name: string
currency: string
featureList: string[]
}
export function useMembership() {
const { data: session } = useSession()
const [selectedPlanIndex, setSelectedPlanIndex] = useState(0)
const [stripePricingData, setStripePricingData] = useState<StripePricingTableResponse>(null)
const [authSubscriptions, setAuthSubscriptions] = useState<any>(null)
const [isStripePricingLoading, setIsStripePricingLoading] = useState(false)
const [isLoadingSubscriptions, setIsLoadingSubscriptions] = useState(false)
const fetchStripePricing = async () => {
setIsStripePricingLoading(true)
const { data, error } = await subscription.plans({
query: {
id: process.env.EXPO_PUBLIC_STRIPE_PRICING_TABLE_ID || '',
key: process.env.EXPO_PUBLIC_STRIPE_PRICING_TABLE_KEY || '',
},
})
setIsStripePricingLoading(false)
if (!error) setStripePricingData(data)
}
const fetchAuthSubscriptions = async () => {
if (!session?.user?.id) return
setIsLoadingSubscriptions(true)
const { data, error } = await subscription.list({
query: { referenceId: session.user.id },
})
setIsLoadingSubscriptions(false)
if (!error) setAuthSubscriptions(data)
}
useEffect(() => {
fetchStripePricing()
}, [])
useEffect(() => {
if (session?.user?.id) fetchAuthSubscriptions()
}, [session?.user?.id])
const licensedSubscriptions = authSubscriptions?.filter((sub: SubscriptionListItem) => sub.type === 'licenced') || []
const meteredSubscriptions = authSubscriptions?.filter((sub: SubscriptionListItem) => 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 creditBalance = meteredSubscriptions?.[0]?.creditBalance?.remainingTokenBalance || 0
const creditPlans = useMemo((): CreditPlan[] => {
if (!stripePricingData?.pricing_table_items) return []
return stripePricingData.pricing_table_items
.filter((item) => item.recurring?.interval === 'month')
.map((item) => ({
amountInCents: parseInt(item.amount),
credits: parseInt(item.amount),
popular: item.is_highlight || item.highlight_text === 'most_popular',
name: item.name || '',
currency: item.currency || 'usd',
featureList: item.feature_list || [],
}))
}, [stripePricingData?.pricing_table_items])
const selectedPlan = creditPlans[selectedPlanIndex] || creditPlans[0]
const createSubscription = async ({ priceId, productId }: { priceId: string; productId: string }) => {
const { data, error } = await subscription.create({
priceId,
productId,
successUrl: `${process.env.EXPO_PUBLIC_APP_URL}/membership`,
cancelUrl: `${process.env.EXPO_PUBLIC_APP_URL}/membership?canceled=true`,
})
if (error) {
Alert.alert('错误', '订阅创建失败')
return
}
if (data?.url) await Linking.openURL(data.url)
}
const upgradeSubscription = async ({ credits }: { credits: number }) => {
if (!activeAuthSubscription?.stripeSubscriptionId) {
Alert.alert('错误', '未找到活跃订阅')
return
}
const planIndex = creditPlans.findIndex(plan => plan.credits === credits)
const planNames = stripePricingData?.pricing_table_items?.map((item: any) => item.product_name) || []
const planName = planNames[planIndex] || 'basic'
const { data, error } = await subscription.upgrade({
plan: planName,
subscriptionId: activeAuthSubscription.stripeSubscriptionId,
successUrl: `${process.env.EXPO_PUBLIC_APP_URL}/membership`,
cancelUrl: `${process.env.EXPO_PUBLIC_APP_URL}/membership?canceled=true`,
})
if (error) {
Alert.alert('错误', '订阅升级失败')
return
}
}
const restoreSubscription = async () => {
if (!activeAuthSubscription) {
Alert.alert('错误', '未找到可恢复的订阅')
return
}
const { data, error } = await subscription.restore({
subscriptionId: activeAuthSubscription.stripeSubscriptionId,
referenceId: activeAuthSubscription.referenceId,
})
if (error) {
Alert.alert('错误', '订阅恢复失败')
return
}
Alert.alert('成功', '订阅已恢复')
await fetchAuthSubscriptions()
}
const cancelSubscription = async () => {
if (!activeAuthSubscription) {
Alert.alert('错误', '未找到活跃订阅')
return
}
const { data, error } = await subscription.cancel({
subscriptionId: activeAuthSubscription.stripeSubscriptionId,
referenceId: activeAuthSubscription.referenceId,
})
if (error) {
Alert.alert('错误', '订阅取消失败')
return
}
Alert.alert('成功', '订阅已取消')
await fetchAuthSubscriptions()
}
const rechargeToken = async (amount: number) => {
if (!amount || amount <= 0) {
Alert.alert('错误', '充值金额无效')
return
}
if (!meteredSubscriptions?.[0]?.priceId) {
Alert.alert('错误', '定价数据不可用')
return
}
const { data, error } = await subscription.credit.topup({
amount,
priceId: meteredSubscriptions[0].priceId,
successUrl: `${process.env.EXPO_PUBLIC_APP_URL}/membership`,
cancelUrl: `${process.env.EXPO_PUBLIC_APP_URL}/membership?canceled=true`,
})
if (error) {
Alert.alert('错误', '充值失败')
return
}
if (data?.url) await Linking.openURL(data.url)
}
return {
selectedPlanIndex,
selectedPlan,
creditPlans,
stripePricingData,
session,
activeAuthSubscription,
hasActiveSubscription,
creditBalance,
isLoadingSubscriptions,
isStripePricingLoading,
setSelectedPlanIndex,
createSubscription,
upgradeSubscription,
restoreSubscription,
cancelSubscription,
rechargeToken,
refetchAuthSubscriptions: fetchAuthSubscriptions,
}
}