189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
import { useState, useEffect, useMemo } from 'react'
|
|
import { Linking, Alert } from 'react-native'
|
|
|
|
import { subscription, useSession } from '@/lib/auth'
|
|
import type { SubscriptionListItem } from '@/lib/auth'
|
|
|
|
interface CreditPlan {
|
|
amountInCents: number
|
|
credits: number
|
|
popular?: boolean
|
|
}
|
|
|
|
export function useMembership() {
|
|
const { data: session } = useSession()
|
|
const [selectedPlanIndex, setSelectedPlanIndex] = useState(0)
|
|
const [stripePricingData, setStripePricingData] = useState<any>(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: any) => item.recurring?.interval === 'month')
|
|
.map((item: any) => ({
|
|
amountInCents: parseInt(item.amount),
|
|
credits: parseInt(item.metadata?.grant_token || item.amount),
|
|
popular: item.is_highlight || item.highlight_text === 'most_popular',
|
|
}))
|
|
}, [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,
|
|
}
|
|
}
|