+ {plans.map((plan) => {
+ const limits = getPlanLimits(plan)
+ const features = getPlanFeatures(plan)
+ const isFree = isPlanFree(plan)
+ const isPro = isPlanPro(plan)
+ const isCurrent = isCurrentPlan(plan.id)
+ const theme = getPlanTheme(plan)
- {/* Pro Plan */}
-
-
- {t('popular')}
-
-
- {isCurrentPlan('pro') && (
-
- {t('currentPlan')}
-
- )}
-
-
-
-
-
+ return (
+
+ {isCurrent && (
+
+ {t('currentPlan')}
+
+ )}
+
+ {isPro && (
+
+ {t('popular')}
+
+ )}
+
+
+
+
+ {isPro ? (
+
+ ) : (
+
+ )}
+
+
+
+ {plan.displayName}
+
+
+ {formatPlanPrice(plan, t)}
+
+
+ {plan.description}
+
+
+
+ -
+
+
+ {limits.promptLimit} Prompt Limit
+
+
+ -
+
+
+ {limits.maxVersionLimit} Versions per Prompt
+
+
+ {features.includes('prioritySupport') && (
+ -
+
+ Priority Support
+
+ )}
+ {features.includes('advancedAnalytics') && (
+ -
+
+ Advanced Analytics
+
+ )}
+ {features.includes('apiAccess') && (
+ -
+
+ API Access
+
+ )}
+
+
+ {isFree ? (
+
+ ) : (
+
+ {isCurrent ? t('currentPlan') : t('upgradePlan')}
+
+ )}
-
- {t('pro.title')}
-
-
- {t('pro.price')}
-
-
- {t('perMonth')}
-
-
- {t('pro.description')}
-
-
-
-
- -
-
-
- {SUBSCRIPTION_PLANS.pro.promptLimit} Prompt Limit
-
-
- -
-
-
- {SUBSCRIPTION_PLANS.pro.maxVersionLimit} Versions per Prompt
-
-
- -
-
- Priority Support
-
-
-
- {isCurrentPlan('pro') ? (
-
- ) : (
-
- {t('upgradeToPro')}
-
- )}
-
+ )
+ })}
{/* Additional Info */}
diff --git a/src/app/subscription/page.tsx b/src/app/subscription/page.tsx
index 1cff21b..745e926 100644
--- a/src/app/subscription/page.tsx
+++ b/src/app/subscription/page.tsx
@@ -11,7 +11,7 @@ import { LoadingSpinner } from '@/components/ui/loading-spinner'
import { Crown, Star, CreditCard, AlertTriangle } from 'lucide-react'
import { SubscriptionStatus } from '@/components/subscription/SubscriptionStatus'
-import { SubscribeButton } from '@/components/subscription/SubscribeButton'
+import { QuickUpgradeButton } from '@/components/subscription/SubscribeButton'
interface SubscriptionData {
plan: string
@@ -239,14 +239,10 @@ export default function SubscriptionPage() {
Quick Actions
{currentPlan === 'free' ? (
-
+
{t('upgradePlan')}
-
+
) : (
<>
{/* 简单的账单信息显示 */}
diff --git a/src/components/subscription/SubscribeButton.tsx b/src/components/subscription/SubscribeButton.tsx
index efa4d3c..e0ec288 100644
--- a/src/components/subscription/SubscribeButton.tsx
+++ b/src/components/subscription/SubscribeButton.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import { useAuth } from '@/hooks/useAuth'
import { Button } from '@/components/ui/button'
import { LoadingSpinner } from '@/components/ui/loading-spinner'
@@ -94,17 +94,63 @@ export function SubscribeButton({
// 简化版本,用于快速升级
interface QuickUpgradeButtonProps {
className?: string
+ children?: React.ReactNode
}
-export function QuickUpgradeButton({ className }: QuickUpgradeButtonProps) {
+export function QuickUpgradeButton({ className, children }: QuickUpgradeButtonProps) {
+ const [proPriceId, setProPriceId] = useState
('')
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState('')
+
+ useEffect(() => {
+ const fetchProPriceId = async () => {
+ try {
+ const response = await fetch('/api/subscription/pro-price-id')
+ if (response.ok) {
+ const data = await response.json()
+ if (data.priceId) {
+ setProPriceId(data.priceId)
+ } else {
+ setError('Pro plan not available')
+ }
+ } else {
+ setError('Failed to load Pro plan')
+ }
+ } catch (err) {
+ setError('Failed to load Pro plan')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchProPriceId()
+ }, [])
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ if (error || !proPriceId) {
+ return (
+
+ )
+ }
+
return (
- Upgrade to Pro
+ {children || 'Upgrade to Pro'}
)
}
diff --git a/src/components/subscription/SubscriptionStatus.tsx b/src/components/subscription/SubscriptionStatus.tsx
index 6bffe22..b061c59 100644
--- a/src/components/subscription/SubscriptionStatus.tsx
+++ b/src/components/subscription/SubscriptionStatus.tsx
@@ -5,16 +5,30 @@ import { useUser } from '@/hooks/useUser'
import { useTranslations } from 'next-intl'
import { Crown, Star, AlertTriangle, CheckCircle } from 'lucide-react'
import { cn } from '@/lib/utils'
+import { getPlanTheme } from '@/lib/subscription-utils'
interface SubscriptionData {
- plan: string
- status: string
- subscriptions: Array<{
+ subscription: {
+ isActive: boolean
+ planId: string
+ stripeSubscriptionId?: string
+ currentPeriodStart?: Date
+ currentPeriodEnd?: Date
+ cancelAtPeriodEnd?: boolean
+ status?: string
+ }
+ permissions: {
+ maxVersionLimit: number
+ promptLimit: number
+ features: string[]
+ canCreatePrompt: boolean
+ canCreateVersion: boolean
+ }
+ plan?: {
id: string
- status: string
- currentPeriodEnd: number
- cancelAtPeriodEnd: boolean
- }>
+ displayName: string
+ price: number
+ }
}
interface SubscriptionStatusProps {
@@ -58,22 +72,18 @@ export function SubscriptionStatus({ className, showDetails = true }: Subscripti
)
}
- const currentPlan = userData?.subscribePlan || 'free'
- const isPro = currentPlan === 'pro'
- const activeSubscription = subscriptionData?.subscriptions?.find(sub => sub.status === 'active')
+ const planData = subscriptionData?.plan
+ const isPro = planData ? planData.price > 19 : false
+ const theme = getPlanTheme(planData)
+ const subscription = subscriptionData?.subscription
+ const permissions = subscriptionData?.permissions
return (
-
+
-
+
{isPro ? (
) : (
@@ -84,10 +94,7 @@ export function SubscriptionStatus({ className, showDetails = true }: Subscripti
{t('currentPlan')}
-
+
{isPro ? t('proPlan') : t('freePlan')}
@@ -95,7 +102,7 @@ export function SubscriptionStatus({ className, showDetails = true }: Subscripti
{/* Status indicator */}
- {activeSubscription?.cancelAtPeriodEnd ? (
+ {subscription?.cancelAtPeriodEnd ? (
Canceling
@@ -116,26 +123,26 @@ export function SubscriptionStatus({ className, showDetails = true }: Subscripti
Prompts:
- {userData?.promptLimit || (isPro ? '5000' : '500')}
+ {permissions?.promptLimit || (isPro ? '5000' : '500')}
Versions:
- {userData?.maxVersionLimit || (isPro ? '10' : '3')} per prompt
+ {permissions?.maxVersionLimit || (isPro ? '10' : '3')} per prompt
- {activeSubscription && (
+ {subscription?.currentPeriodEnd && (
{t('nextBilling')}:
- {new Date(activeSubscription.currentPeriodEnd * 1000).toLocaleDateString()}
+ {new Date(subscription.currentPeriodEnd).toLocaleDateString()}
- {activeSubscription.cancelAtPeriodEnd && (
+ {subscription.cancelAtPeriodEnd && (
Subscription will end on the next billing date
diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts
index f8714fc..dbd566b 100644
--- a/src/hooks/useUser.ts
+++ b/src/hooks/useUser.ts
@@ -12,9 +12,10 @@ interface UserData {
language: string
isAdmin: boolean
versionLimit: number
+ subscriptionPlanId: string
subscribePlan: string
maxVersionLimit: number
- promptLimit: number
+ promptLimit?: number // 已弃用,忽略此字段
creditBalance: number
createdAt: Date
updatedAt: Date
diff --git a/src/lib/credits.ts b/src/lib/credits.ts
index 9bd3676..c05dfb8 100644
--- a/src/lib/credits.ts
+++ b/src/lib/credits.ts
@@ -213,19 +213,21 @@ export async function consumeCredit(userId: string, amount: number): Promise
{
return stripePromise
}
-// Stripe 产品和价格配置
+// Stripe 配置
export const STRIPE_CONFIG = {
- products: {
- pro: {
- priceId: process.env.STRIPE_PRO_PRICE_ID || 'price_dummy', // 需要在 Stripe Dashboard 中创建
- name: 'Pro Plan',
- amount: 1990, // $19.90 in cents
- currency: 'usd',
- interval: 'month' as const,
- }
- },
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || 'whsec_dummy',
}
diff --git a/src/lib/subscription-service.ts b/src/lib/subscription-service.ts
new file mode 100644
index 0000000..8cc0174
--- /dev/null
+++ b/src/lib/subscription-service.ts
@@ -0,0 +1,280 @@
+import { prisma } from '@/lib/prisma'
+import { getCustomerSubscriptions } from '@/lib/stripe'
+import type { SubscriptionPlan } from '@prisma/client'
+import { isPlanPro } from '@/lib/subscription-utils'
+
+// 订阅状态类型
+export interface SubscriptionStatus {
+ isActive: boolean
+ planId: string
+ stripeSubscriptionId?: string
+ currentPeriodStart?: Date
+ currentPeriodEnd?: Date
+ cancelAtPeriodEnd?: boolean
+ status?: string
+}
+
+// 用户权益类型
+export interface UserPermissions {
+ maxVersionLimit: number
+ promptLimit: number
+ features: string[]
+ canCreatePrompt: boolean
+ canCreateVersion: boolean
+}
+
+// 套餐配置类型
+export interface PlanLimits {
+ maxVersionLimit: number
+ promptLimit: number
+ creditMonthly: number
+}
+
+export interface PlanFeatures {
+ [key: string]: boolean | string | number
+}
+
+export class SubscriptionService {
+ /**
+ * 判断套餐是否为 Pro 级别(价格超过 19)
+ */
+ static isPlanPro(plan: SubscriptionPlan | null): boolean {
+ return isPlanPro(plan)
+ }
+
+ /**
+ * 根据套餐 ID 判断是否为 Pro 级别
+ */
+ static async isPlanIdPro(planId: string): Promise {
+ const plan = await this.getPlanById(planId)
+ return this.isPlanPro(plan)
+ }
+
+ /**
+ * 判断用户是否为 Pro 用户
+ */
+ static async isUserPro(userId: string): Promise {
+ try {
+ const subscriptionStatus = await this.getUserSubscriptionStatus(userId)
+ return await this.isPlanIdPro(subscriptionStatus.planId)
+ } catch (error) {
+ console.error('Error checking if user is pro:', error)
+ return false
+ }
+ }
+ /**
+ * 获取所有可用的订阅套餐
+ */
+ static async getAvailablePlans(): Promise {
+ return await prisma.subscriptionPlan.findMany({
+ where: { isActive: true },
+ orderBy: { sortOrder: 'asc' }
+ })
+ }
+
+ /**
+ * 根据ID获取套餐
+ */
+ static async getPlanById(planId: string): Promise {
+ return await prisma.subscriptionPlan.findUnique({
+ where: { id: planId }
+ })
+ }
+
+ /**
+ * 获取 Pro 套餐(名称为 "pro")
+ */
+ static async getProPlan(): Promise {
+ return await prisma.subscriptionPlan.findFirst({
+ where: {
+ name: 'pro',
+ isActive: true
+ }
+ })
+ }
+
+ /**
+ * 获取 Pro 套餐的 Stripe 价格 ID
+ */
+ static async getProPriceId(): Promise {
+ const proPlan = await this.getProPlan()
+ return proPlan?.stripePriceId || null
+ }
+
+ /**
+ * 获取用户的当前订阅状态(从 Stripe 实时获取)
+ */
+ static async getUserSubscriptionStatus(userId: string): Promise {
+ const user = await prisma.user.findUnique({
+ where: { id: userId },
+ select: {
+ stripeCustomerId: true,
+ subscriptionPlanId: true,
+ subscriptionPlan: true
+ }
+ })
+
+ if (!user) {
+ throw new Error('User not found')
+ }
+
+ // 如果 plan 没有 stripePriceId,则直接返回当前套餐有效
+ // 无需从 Stripe 实时获取
+ if (!user.subscriptionPlan?.stripePriceId) {
+ return {
+ isActive: true,
+ planId: user.subscriptionPlanId || 'free'
+ }
+ }
+
+ // 如果没有匹配到上述规则,则需要从 Stripe 获取实时状态
+ // 如果此时没有 Stripe 客户 ID,则返免费套餐状态
+ if (!user.stripeCustomerId) {
+ return {
+ isActive: true,
+ planId: 'free'
+ }
+ }
+
+ try {
+ // 从 Stripe 获取实时订阅状态
+ const subscriptions = await getCustomerSubscriptions(user.stripeCustomerId)
+ const activeSubscription = subscriptions.find(sub =>
+ sub.status === 'active' || sub.status === 'trialing'
+ )
+
+ if (activeSubscription) {
+ // 根据 Stripe 价格 ID 确定套餐
+ const items = activeSubscription.items?.data || []
+ const priceId = items[0]?.price?.id
+
+ // 查找对应的套餐
+ const plan = await prisma.subscriptionPlan.findFirst({
+ where: { stripePriceId: priceId }
+ })
+
+ const subData = activeSubscription as unknown as Record
+
+ return {
+ isActive: true,
+ planId: plan?.id || 'free',
+ stripeSubscriptionId: activeSubscription.id,
+ currentPeriodStart: new Date((subData.current_period_start as number) * 1000),
+ currentPeriodEnd: new Date((subData.current_period_end as number) * 1000),
+ cancelAtPeriodEnd: subData.cancel_at_period_end as boolean,
+ status: activeSubscription.status
+ }
+ }
+
+ // 没有活跃订阅,返回免费套餐
+ return {
+ isActive: true,
+ planId: 'free'
+ }
+ } catch (error) {
+ console.error('Error fetching subscription status from Stripe:', error)
+ // Stripe 错误时,返回用户当前套餐状态
+ return {
+ isActive: true,
+ planId: user.subscriptionPlanId || 'free'
+ }
+ }
+ }
+
+ /**
+ * 获取用户的权益和限制
+ */
+ static async getUserPermissions(userId: string): Promise {
+ const user = await prisma.user.findUnique({
+ where: { id: userId },
+ include: { subscriptionPlan: true }
+ })
+
+ if (!user) {
+ throw new Error('User not found')
+ }
+
+ // 获取实时订阅状态
+ const subscriptionStatus = await this.getUserSubscriptionStatus(userId)
+
+ // 获取对应的套餐配置
+ const plan = await this.getPlanById(subscriptionStatus.planId)
+ if (!plan) {
+ throw new Error('Subscription plan not found')
+ }
+
+ const limits = plan.limits as unknown as PlanLimits
+ const features = plan.features as unknown as PlanFeatures
+
+ // 用户自定义的版本限制不能超过套餐限制
+ const maxVersionLimit = Math.min(user.versionLimit, limits.maxVersionLimit)
+
+ // 检查当前使用情况(完全基于套餐限制,忽略用户表中的 promptLimit)
+ const currentPromptCount = await prisma.prompt.count({
+ where: { userId }
+ })
+
+ return {
+ maxVersionLimit,
+ promptLimit: limits.promptLimit, // 完全从套餐配置获取
+ features: Object.keys(features).filter(key => features[key] === true),
+ canCreatePrompt: currentPromptCount < limits.promptLimit, // 使用套餐限制
+ canCreateVersion: true // 这个需要在具体创建时检查
+ }
+ }
+
+ /**
+ * 检查用户是否可以创建新的提示词
+ */
+ static async canCreatePrompt(userId: string): Promise {
+ const permissions = await this.getUserPermissions(userId)
+ return permissions.canCreatePrompt
+ }
+
+ /**
+ * 检查用户是否可以创建新版本
+ */
+ static async canCreateVersion(userId: string, promptId: string): Promise {
+ const permissions = await this.getUserPermissions(userId)
+
+ // 获取当前版本数量
+ const currentVersionCount = await prisma.promptVersion.count({
+ where: { promptId }
+ })
+
+ return currentVersionCount < permissions.maxVersionLimit
+ }
+
+ /**
+ * 计算需要删除的版本数量(当超出限制时)
+ */
+ static async getVersionsToDelete(userId: string, promptId: string): Promise {
+ const permissions = await this.getUserPermissions(userId)
+
+ const currentVersionCount = await prisma.promptVersion.count({
+ where: { promptId }
+ })
+
+ return Math.max(0, currentVersionCount - permissions.maxVersionLimit + 1)
+ }
+
+ /**
+ * 更新用户的订阅套餐(通常在 webhook 中调用)
+ */
+ static async updateUserSubscriptionPlan(userId: string, planId: string): Promise {
+ await prisma.user.update({
+ where: { id: userId },
+ data: { subscriptionPlanId: planId }
+ })
+ }
+
+ /**
+ * 根据 Stripe 价格 ID 获取套餐 ID
+ */
+ static async getPlanIdByStripePriceId(stripePriceId: string): Promise {
+ const plan = await prisma.subscriptionPlan.findFirst({
+ where: { stripePriceId }
+ })
+ return plan?.id || 'free'
+ }
+}
diff --git a/src/lib/subscription-utils.ts b/src/lib/subscription-utils.ts
new file mode 100644
index 0000000..f63cb53
--- /dev/null
+++ b/src/lib/subscription-utils.ts
@@ -0,0 +1,144 @@
+import type { SubscriptionPlan } from '@prisma/client'
+
+/**
+ * 订阅相关的工具函数
+ */
+
+// 支持部分套餐数据的类型
+type PlanLike = { price: number } | SubscriptionPlan | null | undefined
+
+/**
+ * 判断套餐是否为 Pro 级别(价格超过 19)
+ * 这是一个纯函数,可以在客户端和服务端使用
+ */
+export function isPlanPro(plan: PlanLike): boolean {
+ if (!plan) return false
+ return plan.price > 19
+}
+
+/**
+ * 判断套餐是否为免费套餐(价格为 0)
+ */
+export function isPlanFree(plan: PlanLike): boolean {
+ if (!plan) return true
+ return plan.price === 0
+}
+
+/**
+ * 获取套餐的显示级别
+ */
+export function getPlanTier(plan: PlanLike): 'free' | 'pro' | 'enterprise' {
+ if (!plan || plan.price === 0) return 'free'
+ if (plan.price > 50) return 'enterprise' // 为未来的企业版预留
+ if (plan.price > 19) return 'pro'
+ return 'free'
+}
+
+/**
+ * 获取套餐的图标组件名称
+ */
+export function getPlanIcon(plan: PlanLike): 'Star' | 'Crown' | 'Building' {
+ const tier = getPlanTier(plan)
+ switch (tier) {
+ case 'enterprise':
+ return 'Building'
+ case 'pro':
+ return 'Crown'
+ case 'free':
+ default:
+ return 'Star'
+ }
+}
+
+/**
+ * 获取套餐的颜色主题
+ */
+export function getPlanTheme(plan: PlanLike): {
+ gradient: string
+ iconGradient: string
+ textColor: string
+ borderColor?: string
+} {
+ const tier = getPlanTier(plan)
+
+ switch (tier) {
+ case 'enterprise':
+ return {
+ gradient: 'bg-gradient-to-r from-purple-50/60 to-indigo-50/60 dark:from-purple-950/10 dark:to-indigo-950/10',
+ iconGradient: 'bg-gradient-to-br from-purple-500 to-indigo-500 dark:from-purple-400 dark:to-indigo-400',
+ textColor: 'text-purple-700 dark:text-purple-300',
+ borderColor: 'border-purple-200 dark:border-purple-800'
+ }
+ case 'pro':
+ return {
+ gradient: 'bg-gradient-to-r from-amber-50/60 to-orange-50/60 dark:from-amber-950/10 dark:to-orange-950/10',
+ iconGradient: 'bg-gradient-to-br from-amber-500 to-orange-500 dark:from-amber-400 dark:to-orange-400',
+ textColor: 'text-orange-700 dark:text-orange-300',
+ borderColor: 'border-primary'
+ }
+ case 'free':
+ default:
+ return {
+ gradient: 'bg-gradient-to-r from-slate-50/60 to-gray-50/60 dark:from-slate-950/5 dark:to-gray-950/5',
+ iconGradient: 'bg-gradient-to-br from-slate-400 to-gray-500 dark:from-slate-500 dark:to-gray-400',
+ textColor: 'text-slate-600 dark:text-slate-400'
+ }
+ }
+}
+
+/**
+ * 格式化套餐价格显示
+ */
+export function formatPlanPrice(plan: PlanLike & { interval?: string }, t?: (key: string) => string): string {
+ if (!plan || plan.price === 0) {
+ return t?.('free.price') || 'Free'
+ }
+
+ const price = `$${plan.price}`
+ const planWithInterval = plan as { interval?: string }
+ const interval = planWithInterval.interval === 'year' ? '/year' : '/month'
+
+ return `${price}${interval}`
+}
+
+/**
+ * 获取套餐限制的类型安全访问器
+ */
+export function getPlanLimits(plan: SubscriptionPlan | null | undefined): {
+ promptLimit: number
+ maxVersionLimit: number
+ creditMonthly: number
+} {
+ if (!plan) {
+ return {
+ promptLimit: 500,
+ maxVersionLimit: 3,
+ creditMonthly: 0
+ }
+ }
+
+ const limits = plan.limits as Record
+ return {
+ promptLimit: (limits?.promptLimit as number) || 500,
+ maxVersionLimit: (limits?.maxVersionLimit as number) || 3,
+ creditMonthly: (limits?.creditMonthly as number) || 0
+ }
+}
+
+/**
+ * 获取套餐功能的类型安全访问器
+ */
+export function getPlanFeatures(plan: SubscriptionPlan | null | undefined): string[] {
+ if (!plan) return []
+
+ const features = plan.features as Record
+ return Object.keys(features).filter(key => features[key] === true)
+}
+
+/**
+ * 检查套餐是否包含特定功能
+ */
+export function planHasFeature(plan: SubscriptionPlan | null | undefined, feature: string): boolean {
+ const features = getPlanFeatures(plan)
+ return features.includes(feature)
+}
diff --git a/src/lib/subscription.ts b/src/lib/subscription.ts
index 48116f4..c0d20ff 100644
--- a/src/lib/subscription.ts
+++ b/src/lib/subscription.ts
@@ -1,31 +1,37 @@
-// 订阅计划配置
+// 兼容性:重新导出新的订阅服务
+export { SubscriptionService } from './subscription-service'
+
+// 订阅计划配置(已弃用,保留用于向后兼容)
+// @deprecated 请使用 SubscriptionService.getAvailablePlans() 替代
export const SUBSCRIPTION_PLANS = {
free: {
name: 'Free',
maxVersionLimit: 3,
- promptLimit: 500, // 放宽限制到500
+ promptLimit: 500,
price: 0,
features: [
- 'promptLimit', // 500 Prompt Limit
- 'versionsPerPrompt' // 3 Versions per Prompt
+ 'promptLimit',
+ 'versionsPerPrompt'
]
},
pro: {
name: 'Pro',
maxVersionLimit: 10,
- promptLimit: 5000, // Pro版本5000个提示词限制
+ promptLimit: 5000,
price: 19.9,
features: [
- 'promptLimit', // 5000 Prompt Limit
- 'versionsPerPrompt', // 10 Versions per Prompt
- 'prioritySupport' // Priority Support
+ 'promptLimit',
+ 'versionsPerPrompt',
+ 'prioritySupport'
]
}
} as const
export type SubscriptionPlan = keyof typeof SUBSCRIPTION_PLANS
-// 获取用户的版本限制
+// 兼容性函数(已弃用,保留用于向后兼容)
+
+// @deprecated 请使用 SubscriptionService.getUserPermissions() 替代
export function getUserVersionLimit(user: {
versionLimit: number
subscribePlan: string
@@ -33,25 +39,23 @@ export function getUserVersionLimit(user: {
}): number {
const plan = user.subscribePlan as SubscriptionPlan
const planConfig = SUBSCRIPTION_PLANS[plan]
-
+
if (!planConfig) {
- // 如果订阅计划无效,使用免费计划的限制
return Math.min(user.versionLimit, SUBSCRIPTION_PLANS.free.maxVersionLimit)
}
-
- // 用户设置的限制不能超过订阅计划的最大限制
+
return Math.min(user.versionLimit, planConfig.maxVersionLimit)
}
-// 获取用户的最大版本限制(基于订阅)
+// @deprecated 请使用 SubscriptionService.getPlanById() 替代
export function getMaxVersionLimit(subscribePlan: string): number {
const plan = subscribePlan as SubscriptionPlan
const planConfig = SUBSCRIPTION_PLANS[plan]
-
+
return planConfig?.maxVersionLimit || SUBSCRIPTION_PLANS.free.maxVersionLimit
}
-// 检查用户是否可以创建新版本
+// @deprecated 请使用 SubscriptionService.canCreateVersion() 替代
export function canCreateNewVersion(
currentVersionCount: number,
user: {
@@ -64,7 +68,7 @@ export function canCreateNewVersion(
return currentVersionCount < versionLimit
}
-// 计算需要删除的版本数量
+// @deprecated 请使用 SubscriptionService.getVersionsToDelete() 替代
export function getVersionsToDelete(
currentVersionCount: number,
user: {
@@ -74,32 +78,19 @@ export function getVersionsToDelete(
}
): number {
const versionLimit = getUserVersionLimit(user)
- return Math.max(0, currentVersionCount - versionLimit + 1) // +1 因为要创建新版本
+ return Math.max(0, currentVersionCount - versionLimit + 1)
}
-// 获取用户的提示词限制
+// @deprecated 请使用 SubscriptionService.getUserPermissions() 替代
export function getPromptLimit(subscribePlan: string): number {
const plan = subscribePlan as SubscriptionPlan
const planConfig = SUBSCRIPTION_PLANS[plan]
-
+
return planConfig?.promptLimit || SUBSCRIPTION_PLANS.free.promptLimit
}
-// 检查用户是否可以创建新提示词
+// @deprecated 请使用 SubscriptionService.canCreatePrompt() 替代
export async function canCreateNewPrompt(userId: string): Promise {
- const { prisma } = await import('@/lib/prisma')
-
- const user = await prisma.user.findUnique({
- where: { id: userId },
- select: { subscribePlan: true }
- })
-
- if (!user) return false
-
- const promptLimit = getPromptLimit(user.subscribePlan)
- const currentPromptCount = await prisma.prompt.count({
- where: { userId }
- })
-
- return currentPromptCount < promptLimit
+ const { SubscriptionService } = await import('./subscription-service')
+ return await SubscriptionService.canCreatePrompt(userId)
}
\ No newline at end of file