Skip to content

Latest commit

 

History

History
410 lines (331 loc) · 9.64 KB

File metadata and controls

410 lines (331 loc) · 9.64 KB

订阅功能集成文档

概述

本文档介绍了 PitchMaster.ai 项目中新增的订阅检查功能,基于 Boxn API 的订阅状态检查接口。

新增功能

1. 订阅状态检查 API

src/api/payment.ts 中新增了订阅检查接口:

// 订阅检查响应接口
export interface SubscriptionCheckResponse {
  hasValidSubscription: boolean;  // 用户是否有有效订阅
  subscriptionType?: string;      // 订阅类型
  expiresAt?: string;            // 订阅过期日期
  features?: string[];           // 可用功能
}

// 订阅检查 API 函数
async checkHasValidSubscription(): Promise<SubscriptionCheckResponse> {
  const response = await paymentApiClient.get('/api/payments/subscription/check');
  return response.data;
}

2. 支付 Store 更新

src/stores/pay.ts 中新增了订阅相关的状态和方法:

新增状态

// 订阅状态
const subscriptionStatus = ref<SubscriptionCheckResponse | null>(null);
const isSubscriptionLoading = ref(false);

新增计算属性

// 检查用户是否有有效订阅
const hasValidSubscription = computed(() => {
  return subscriptionStatus.value?.hasValidSubscription ?? false;
});

// 获取订阅类型
const subscriptionType = computed(() => {
  return subscriptionStatus.value?.subscriptionType ?? null;
});

// 获取订阅过期日期
const subscriptionExpiresAt = computed(() => {
  return subscriptionStatus.value?.expiresAt ?? null;
});

// 获取可用订阅功能
const subscriptionFeatures = computed(() => {
  return subscriptionStatus.value?.features ?? [];
});

新增方法

// 检查用户订阅状态
const checkSubscription = async () => {
  try {
    isSubscriptionLoading.value = true;
    error.value = null;
    
    const response = await paymentApi.checkHasValidSubscription();
    subscriptionStatus.value = response;
    
    return response;
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Failed to check subscription status';
    throw err;
  } finally {
    isSubscriptionLoading.value = false;
  }
};

3. 用户 Store 集成

src/stores/user.ts 中更新了付费状态检查方法:

// 检查用户付费状态
async checkPaymentStatus() {
  try {
    // 使用新的订阅检查 API
    const paymentStore = usePaymentStore();
    const subscriptionStatus = await paymentStore.checkSubscription();
    
    // 更新付费状态
    this.hasPaid = subscriptionStatus.hasValidSubscription;
    
    // 如果用户有有效订阅,标记为已付费
    if (this.hasPaid) {
      localStorage.setItem('userPaymentStatus', 'paid');
    } else {
      localStorage.removeItem('userPaymentStatus');
    }
    
    return this.hasPaid;
  } catch (error) {
    console.error('检查付费状态失败:', error);
    // 如果 API 调用失败,回退到 localStorage
    const paymentStatus = localStorage.getItem('userPaymentStatus');
    this.hasPaid = paymentStatus === 'paid';
    return this.hasPaid;
  }
}

4. 订阅状态组件

新增了 src/components/SubscriptionStatus.vue 组件,用于显示用户的订阅状态:

功能特性

  • 实时显示订阅状态(有效/无效)
  • 显示订阅类型、到期时间和可用功能
  • 支持手动刷新订阅状态
  • 提供升级订阅的入口
  • 错误处理和加载状态

使用方法

<template>
  <SubscriptionStatus />
</template>

<script setup>
import SubscriptionStatus from '@/components/SubscriptionStatus.vue';
</script>

使用示例

1. 在组件中使用订阅状态

<template>
  <div>
    <div v-if="hasValidSubscription">
      <h2>欢迎,高级用户!</h2>
      <p>您的订阅类型: {{ subscriptionType }}</p>
      <p>到期时间: {{ subscriptionExpiresAt }}</p>
      <div>
        <h3>可用功能:</h3>
        <ul>
          <li v-for="feature in subscriptionFeatures" :key="feature">
            {{ feature }}
          </li>
        </ul>
      </div>
    </div>
    <div v-else>
      <h2>升级到高级版本</h2>
      <p>解锁更多功能</p>
      <button @click="upgrade">立即升级</button>
    </div>
  </div>
</template>

<script setup>
import { usePaymentStore } from '@/stores/pay';

const paymentStore = usePaymentStore();

const {
  hasValidSubscription,
  subscriptionType,
  subscriptionExpiresAt,
  subscriptionFeatures,
  checkSubscription
} = paymentStore;

const upgrade = () => {
  // 导航到支付页面
  router.push('/payment');
};

// 页面加载时检查订阅状态
onMounted(async () => {
  await checkSubscription();
});
</script>

2. 在路由守卫中使用

// router/index.ts
import { usePaymentStore } from '@/stores/pay';

router.beforeEach(async (to, from, next) => {
  const paymentStore = usePaymentStore();
  
  // 检查需要订阅的页面
  if (to.meta.requiresSubscription) {
    try {
      await paymentStore.checkSubscription();
      
      if (!paymentStore.hasValidSubscription) {
        // 重定向到支付页面
        next('/payment');
        return;
      }
    } catch (error) {
      console.error('Failed to check subscription:', error);
      // 如果检查失败,允许访问(可以根据需要调整)
      next();
      return;
    }
  }
  
  next();
});

3. 在 API 拦截器中使用

// 在 API 请求失败时检查订阅状态
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 403) {
      // 可能是订阅过期,重新检查订阅状态
      const paymentStore = usePaymentStore();
      await paymentStore.checkSubscription();
      
      if (!paymentStore.hasValidSubscription) {
        // 重定向到支付页面
        router.push('/payment');
      }
    }
    return Promise.reject(error);
  }
);

API 接口说明

订阅检查接口

请求:

GET /api/payments/subscription/check
Authorization: Bearer {access_token}

响应:

{
  "hasValidSubscription": true,
  "subscriptionType": "premium",
  "expiresAt": "2024-12-31T23:59:59Z",
  "features": [
    "unlimited_pitches",
    "advanced_analytics",
    "priority_support"
  ]
}

字段说明:

  • hasValidSubscription: 布尔值,表示用户是否有有效订阅
  • subscriptionType: 字符串,订阅类型(如 "basic", "premium", "enterprise")
  • expiresAt: 字符串,订阅过期时间(ISO 8601 格式)
  • features: 字符串数组,用户可用的功能列表

错误处理

常见错误场景

  1. 网络错误

    try {
      await paymentStore.checkSubscription();
    } catch (error) {
      if (error.code === 'NETWORK_ERROR') {
        // 显示网络错误提示
        showNetworkError();
      }
    }
  2. 认证失败

    // 在 API 拦截器中处理 401 错误
    if (error.response?.status === 401) {
      // 清除用户会话,重定向到登录页面
      userStore.logout();
      router.push('/login');
    }
  3. 服务器错误

    if (error.response?.status >= 500) {
      // 显示服务器错误提示
      showServerError();
    }

最佳实践

1. 缓存策略

// 缓存订阅状态,避免频繁请求
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟

const checkSubscriptionWithCache = async () => {
  const cached = localStorage.getItem('subscriptionCache');
  if (cached) {
    const { data, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp < CACHE_DURATION) {
      return data;
    }
  }
  
  const result = await paymentStore.checkSubscription();
  
  // 缓存结果
  localStorage.setItem('subscriptionCache', JSON.stringify({
    data: result,
    timestamp: Date.now()
  }));
  
  return result;
};

2. 定期检查

// 定期检查订阅状态
const startSubscriptionCheck = () => {
  setInterval(async () => {
    try {
      await paymentStore.checkSubscription();
    } catch (error) {
      console.error('Periodic subscription check failed:', error);
    }
  }, 10 * 60 * 1000); // 每10分钟检查一次
};

3. 用户体验优化

// 在关键操作前检查订阅状态
const performPremiumAction = async () => {
  if (!paymentStore.hasValidSubscription) {
    // 显示升级提示
    showUpgradeModal();
    return;
  }
  
  // 执行高级功能
  await executePremiumFeature();
};

测试

单元测试示例

import { describe, it, expect, vi } from 'vitest';
import { usePaymentStore } from '@/stores/pay';

describe('Subscription Check', () => {
  it('should check subscription status successfully', async () => {
    const store = usePaymentStore();
    
    const mockResponse = {
      hasValidSubscription: true,
      subscriptionType: 'premium',
      expiresAt: '2024-12-31T23:59:59Z',
      features: ['feature1', 'feature2']
    };
    
    vi.spyOn(paymentApi, 'checkHasValidSubscription').mockResolvedValue(mockResponse);
    
    const result = await store.checkSubscription();
    
    expect(result).toEqual(mockResponse);
    expect(store.hasValidSubscription).toBe(true);
    expect(store.subscriptionType).toBe('premium');
  });
});

部署注意事项

  1. 环境变量: 确保生产环境中正确配置了 Boxn API 的基础 URL 和认证信息
  2. CORS: 确保 API 服务器允许前端域名的跨域请求
  3. 缓存: 在生产环境中考虑使用 Redis 等缓存系统来缓存订阅状态
  4. 监控: 添加订阅检查 API 的监控和告警机制

更新日志

  • v1.0.0: 初始版本,支持基本的订阅状态检查功能
  • v1.1.0: 添加了订阅状态组件和用户 Store 集成
  • v1.2.0: 优化了错误处理和缓存策略