diff --git a/docs/pricing-page-improvements.md b/docs/pricing-page-improvements.md
new file mode 100644
index 0000000..e02f833
--- /dev/null
+++ b/docs/pricing-page-improvements.md
@@ -0,0 +1,185 @@
+# 定价页面改进说明
+
+## 概述
+
+定价页面现在实现了智能的套餐过滤和按钮显示逻辑,确保只显示有效的可订阅套餐,并提供清晰的用户体验。
+
+## 主要改进
+
+### 1. 智能套餐过滤
+
+定价页面现在只显示以下套餐:
+
+1. **免费套餐** - 总是显示
+2. **用户当前套餐** - 总是显示(即使没有 stripePriceId)
+3. **有效的付费套餐** - 必须有 `stripePriceId` 才显示
+
+#### 过滤逻辑
+```typescript
+const filteredPlans = plans.filter((plan: SubscriptionPlan) => {
+ // 免费套餐总是显示
+ if (isPlanFree(plan)) return true
+
+ // 用户当前套餐总是显示
+ if (userData && isCurrentPlan(plan.id)) return true
+
+ // 其他套餐必须有 stripePriceId 才能显示(可订阅)
+ return plan.stripePriceId && plan.stripePriceId.trim() !== ''
+})
+```
+
+### 2. 智能按钮显示
+
+按钮显示遵循以下逻辑:
+
+#### 免费套餐
+- **当前套餐**: 显示 "当前方案" 按钮(禁用)
+- **非当前套餐**: 不显示任何按钮
+
+#### 付费套餐
+- **当前套餐**: 显示 "当前方案" 按钮(禁用)
+- **可订阅套餐**: 显示 "立即订阅" 按钮
+- **无价格ID套餐**: 不显示按钮(理论上不会出现,因为已过滤)
+
+#### 按钮逻辑代码
+```typescript
+{(() => {
+ // 免费套餐逻辑
+ if (isFree) {
+ if (isCurrent) {
+ return
+ }
+ return null // 免费套餐且非当前套餐,不显示按钮
+ }
+
+ // 付费套餐逻辑
+ if (isCurrent) {
+ return
+ }
+
+ // 可订阅的付费套餐
+ if (plan.stripePriceId && plan.stripePriceId.trim() !== '') {
+ return (
+
+ {t('subscribeNow')}
+
+ )
+ }
+
+ return null
+})()}
+```
+
+### 3. 多语言支持
+
+添加了新的翻译键:
+
+#### 英文 (en.json)
+```json
+{
+ "pricing": {
+ "subscribeNow": "Subscribe Now",
+ "currentPlan": "Current Plan"
+ }
+}
+```
+
+#### 中文 (zh.json)
+```json
+{
+ "pricing": {
+ "subscribeNow": "立即订阅",
+ "currentPlan": "当前方案"
+ }
+}
+```
+
+## 用户体验场景
+
+### 1. 匿名用户
+- 看到:免费套餐(无按钮)+ 有价格ID的付费套餐(立即订阅按钮)
+- 不看到:没有价格ID的付费套餐
+
+### 2. 免费用户
+- 看到:免费套餐(当前方案按钮)+ 有价格ID的付费套餐(立即订阅按钮)
+- 不看到:没有价格ID的付费套餐
+
+### 3. Pro 用户
+- 看到:免费套餐(无按钮)+ 当前Pro套餐(当前方案按钮)+ 其他有价格ID的套餐(立即订阅按钮)
+- 不看到:没有价格ID的付费套餐
+
+## 技术实现
+
+### 1. 动态数据获取
+```typescript
+useEffect(() => {
+ fetchPlans()
+}, [userData]) // 依赖 userData,确保用户数据加载后再过滤套餐
+```
+
+### 2. 套餐判定工具
+使用统一的工具函数:
+- `isPlanFree(plan)` - 判断是否为免费套餐
+- `isPlanPro(plan)` - 判断是否为Pro套餐
+- `isCurrentPlan(planId)` - 判断是否为用户当前套餐
+
+### 3. 类型安全
+```typescript
+const filteredPlans = (data.plans || []).filter((plan: SubscriptionPlan) => {
+ // 过滤逻辑
+})
+```
+
+## 配置要求
+
+### 1. 套餐配置
+确保付费套餐有有效的 `stripePriceId`:
+
+```sql
+-- 检查套餐配置
+SELECT id, name, "displayName", price, "stripePriceId", "isActive"
+FROM subscription_plans
+WHERE "isActive" = true;
+
+-- 设置价格ID
+UPDATE subscription_plans
+SET "stripePriceId" = 'price_your_stripe_price_id'
+WHERE name = 'pro';
+```
+
+### 2. 验证工具
+使用测试脚本验证配置:
+
+```bash
+npx tsx scripts/test-pricing-page-filtering.ts
+```
+
+## 优势
+
+1. **用户友好**: 只显示用户可以实际订阅的套餐
+2. **防止错误**: 避免显示无法订阅的套餐
+3. **清晰导航**: 明确的按钮状态和操作
+4. **灵活配置**: 支持动态套餐管理
+5. **多语言**: 完整的国际化支持
+
+## 故障排除
+
+### 问题:套餐不显示
+**原因**: 付费套餐没有设置 `stripePriceId`
+**解决**: 在数据库中设置正确的 Stripe 价格 ID
+
+### 问题:按钮不显示
+**原因**: 套餐被过滤掉或逻辑判断问题
+**解决**: 检查套餐配置和用户状态
+
+### 问题:翻译缺失
+**原因**: 缺少 `subscribeNow` 翻译键
+**解决**: 在对应语言文件中添加翻译
+
+## 测试建议
+
+1. 测试不同用户状态下的页面显示
+2. 验证按钮功能和状态
+3. 检查多语言显示
+4. 确认套餐过滤逻辑
+5. 测试订阅流程
diff --git a/messages/en.json b/messages/en.json
index 072624d..d48b136 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -220,6 +220,7 @@
"startProTrial": "Upgrade to Pro",
"currentPlan": "Current Plan",
"upgradeToPro": "Upgrade to Pro",
+ "subscribeNow": "Subscribe Now",
"manageSubscription": "Manage Subscription"
},
"subscription": {
diff --git a/messages/zh.json b/messages/zh.json
index f0392e3..fcd880f 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -220,6 +220,7 @@
"startProTrial": "升级到专业版",
"currentPlan": "当前方案",
"upgradeToPro": "升级到专业版",
+ "subscribeNow": "立即订阅",
"manageSubscription": "管理订阅"
},
"subscription": {
diff --git a/scripts/test-pricing-page-filtering.ts b/scripts/test-pricing-page-filtering.ts
new file mode 100644
index 0000000..c7419db
--- /dev/null
+++ b/scripts/test-pricing-page-filtering.ts
@@ -0,0 +1,122 @@
+import { PrismaClient } from '@prisma/client'
+import { SubscriptionService } from '../src/lib/subscription-service'
+import { isPlanFree } from '../src/lib/subscription-utils'
+
+const prisma = new PrismaClient()
+
+async function testPricingPageFiltering() {
+ console.log('🧪 Testing pricing page filtering logic...')
+
+ try {
+ // 1. 获取所有套餐
+ console.log('\n1. Getting all available plans...')
+ const allPlans = await SubscriptionService.getAvailablePlans()
+ console.log(`📊 Found ${allPlans.length} active plans:`)
+
+ allPlans.forEach(plan => {
+ console.log(` - ${plan.displayName} (${plan.id}):`)
+ console.log(` * Price: $${plan.price}`)
+ console.log(` * Stripe Price ID: ${plan.stripePriceId || 'Not set'}`)
+ console.log(` * Is Free: ${isPlanFree(plan)}`)
+ })
+
+ // 2. 模拟定价页面的过滤逻辑
+ console.log('\n2. Testing pricing page filtering logic...')
+
+ // 模拟不同用户场景
+ const testScenarios = [
+ { name: 'Anonymous User', userData: null },
+ { name: 'Free User', userData: { subscriptionPlanId: 'free' } },
+ { name: 'Pro User', userData: { subscriptionPlanId: 'pro' } }
+ ]
+
+ for (const scenario of testScenarios) {
+ console.log(`\n📋 Scenario: ${scenario.name}`)
+
+ const filteredPlans = allPlans.filter(plan => {
+ // 免费套餐总是显示
+ if (isPlanFree(plan)) return true
+
+ // 用户当前套餐总是显示
+ if (scenario.userData && scenario.userData.subscriptionPlanId === plan.id) return true
+
+ // 其他套餐必须有 stripePriceId 才能显示(可订阅)
+ return plan.stripePriceId && plan.stripePriceId.trim() !== ''
+ })
+
+ console.log(` Visible plans: ${filteredPlans.length}`)
+ filteredPlans.forEach(plan => {
+ const isCurrent = scenario.userData?.subscriptionPlanId === plan.id
+ const canSubscribe = !isPlanFree(plan) && plan.stripePriceId && !isCurrent
+
+ console.log(` - ${plan.displayName}:`)
+ console.log(` * Current plan: ${isCurrent}`)
+ console.log(` * Can subscribe: ${canSubscribe}`)
+ console.log(` * Button action: ${getButtonAction(plan, isCurrent, canSubscribe)}`)
+ })
+ }
+
+ // 3. 检查套餐配置问题
+ console.log('\n3. Checking for potential issues...')
+
+ const plansWithoutPriceId = allPlans.filter(plan =>
+ !isPlanFree(plan) && (!plan.stripePriceId || plan.stripePriceId.trim() === '')
+ )
+
+ if (plansWithoutPriceId.length > 0) {
+ console.log(`⚠️ Found ${plansWithoutPriceId.length} paid plans without Stripe Price ID:`)
+ plansWithoutPriceId.forEach(plan => {
+ console.log(` - ${plan.displayName} ($${plan.price}) - will not be visible to non-subscribers`)
+ })
+ } else {
+ console.log('✅ All paid plans have valid Stripe Price IDs')
+ }
+
+ // 4. 验证免费套餐
+ const freePlans = allPlans.filter(isPlanFree)
+ console.log(`\n4. Free plans validation:`)
+ console.log(` Found ${freePlans.length} free plans`)
+ freePlans.forEach(plan => {
+ console.log(` - ${plan.displayName}: Always visible, no subscription button`)
+ })
+
+ console.log('\n🎉 Pricing page filtering test completed!')
+
+ } catch (error) {
+ console.error('❌ Test failed:', error)
+ throw error
+ } finally {
+ await prisma.$disconnect()
+ }
+}
+
+function getButtonAction(plan: any, isCurrent: boolean, canSubscribe: boolean): string {
+ if (isPlanFree(plan)) {
+ return isCurrent ? 'Current Plan (disabled)' : 'No button'
+ }
+
+ if (isCurrent) {
+ return 'Current Plan (disabled)'
+ }
+
+ if (canSubscribe) {
+ return 'Subscribe Now'
+ }
+
+ return 'No button (no price ID)'
+}
+
+// 运行测试
+if (require.main === module) {
+ testPricingPageFiltering()
+ .then(() => {
+ console.log('✅ All tests completed!')
+ process.exit(0)
+ })
+ .catch((error) => {
+ console.error('❌ Tests failed:', error)
+ process.exit(1)
+ })
+}
+
+export { testPricingPageFiltering }
diff --git a/src/app/pricing/page.tsx b/src/app/pricing/page.tsx
index f6acc95..e7916c2 100644
--- a/src/app/pricing/page.tsx
+++ b/src/app/pricing/page.tsx
@@ -25,16 +25,23 @@ export default function PricingPage() {
const [plans, setPlans] = useState([])
const [loading, setLoading] = useState(true)
- useEffect(() => {
- fetchPlans()
- }, [])
-
const fetchPlans = async () => {
try {
const response = await fetch('/api/subscription-plans')
if (response.ok) {
const data = await response.json()
- setPlans(data.plans || [])
+ // 过滤套餐:只显示免费套餐、用户当前套餐,以及有 stripePriceId 的套餐
+ const filteredPlans = (data.plans || []).filter((plan: SubscriptionPlan) => {
+ // 免费套餐总是显示
+ if (isPlanFree(plan)) return true
+
+ // 用户当前套餐总是显示
+ if (userData && isCurrentPlan(plan.id)) return true
+
+ // 其他套餐必须有 stripePriceId 才能显示(可订阅)
+ return plan.stripePriceId && plan.stripePriceId.trim() !== ''
+ })
+ setPlans(filteredPlans)
}
} catch (error) {
console.error('Error fetching plans:', error)
@@ -43,13 +50,11 @@ export default function PricingPage() {
}
}
- const handleGetStarted = () => {
- if (user) {
- window.location.href = '/studio'
- } else {
- window.location.href = '/signup'
- }
- }
+ useEffect(() => {
+ fetchPlans()
+ }, [userData]) // 依赖 userData,确保用户数据加载后再过滤套餐
+
+
const isCurrentPlan = (planId: string) => {
if (!userData) return false
@@ -170,25 +175,55 @@ export default function PricingPage() {
)}
- {isFree ? (
-
- ) : (
-
- {isCurrent ? t('currentPlan') : t('upgradePlan')}
-
- )}
+ {(() => {
+ // 免费套餐逻辑
+ if (isFree) {
+ // 如果是当前套餐,显示"当前套餐"按钮
+ if (isCurrent) {
+ return (
+
+ )
+ }
+ // 免费套餐且非当前套餐,不显示按钮
+ return null
+ }
+
+ // 付费套餐逻辑
+ if (isCurrent) {
+ // 当前套餐,显示"当前套餐"按钮
+ return (
+
+ )
+ }
+
+ // 可订阅的付费套餐,显示"立即订阅"按钮
+ if (plan.stripePriceId && plan.stripePriceId.trim() !== '') {
+ return (
+
+ {t('subscribeNow')}
+
+ )
+ }
+
+ // 没有 stripePriceId 的套餐不显示按钮(理论上不会到这里,因为已经过滤了)
+ return null
+ })()}
)
})}