阅读时间 3 分钟最后更新: 2025-12-31 12:12
Stripe 到 Creem 渐进迁移方案
本文档描述了将现有 Stripe 订阅用户渐进迁移到 Creem 支付平台的完整方案。采用渐进迁移策略:新用户使用 Creem,Stripe 老用户订阅到期后自动引导到 Creem 重新订阅,保留用户权益。
迁移策略
核心原则
- 新用户优先 Creem:所有新订阅默认使用 Creem 支付网关
- 老用户自然过渡:Stripe 订阅到期后不续费,引导用户在 Creem 重新订阅
- 权益保障:迁移期间保留用户的积分和订阅权益
- 平滑体验:通过 UI 提示和邮件通知,让用户了解迁移进度
迁移时间线
阶段 1 (立即): 配置变更
├── 将默认支付网关改为 Creem
├── 新用户自动使用 Creem
└── 现有 Stripe 订阅继续正常运行
阶段 2 (1-2周): UI 更新
├── 在订阅管理页面显示迁移提示
├── 添加迁移引导组件
└── 更新多语言文案
阶段 3 (持续): 自然迁移
├── Stripe 订阅到期后不续费
├── 用户访问时显示重新订阅引导
└── 用户在 Creem 完成新订阅
阶段 4 (可选): 主动迁移
├── 发送邮件通知剩余 Stripe 用户
├── 提供迁移激励(如额外积分)
└── 设置最终截止日期
技术实现
环境变量配置
修改 .env 文件:
# 将默认支付网关改为 Creem
PAYMENT_GATEWAY=creem
# Creem 配置(确保已配置)
CREEM_API_KEY=your_creem_api_key
CREEM_WEBHOOK_SECRET=your_creem_webhook_secret
# Stripe 配置(保留,用于处理现有订阅)
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_WEBHOOK_SECRET=your_stripe_webhook_secret
数据库结构
现有数据库结构已支持多网关,无需修改:
user_subscriptions.paymentGateway- 区分 Stripe 和 Creem 订阅payment_customers- 存储用户在各网关的客户 IDsubscription_plans- 包含 Stripe 和 Creem 的 Price ID
核心代码修改
订阅服务 - 添加迁移检测
// src/lib/services/subscription-service.ts
/**
* 检查用户是否需要迁移(Stripe 订阅即将到期)
*/
async needsMigration(userId: string): Promise<{
needsMigration: boolean;
currentSubscription?: UserSubscription;
daysUntilExpiry?: number;
}> {
const subscription = await this.getCurrentSubscription(userId);
if (!subscription) {
return { needsMigration: false };
}
// 只有 Stripe 订阅需要迁移
if (subscription.paymentGateway !== 'stripe') {
return { needsMigration: false };
}
// 检查是否已设置周期结束取消
if (!subscription.cancelAtPeriodEnd) {
return { needsMigration: false };
}
const now = new Date();
const periodEnd = subscription.currentPeriodEnd;
const daysUntilExpiry = Math.ceil(
(periodEnd.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
);
return {
needsMigration: true,
currentSubscription: subscription,
daysUntilExpiry,
};
}
新增 API 端点
// src/app/api/subscription/migration/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { subscriptionService } from '@/lib/services/subscription-service';
export async function GET(request: NextRequest) {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const migrationStatus = await subscriptionService.getMigrationStatus(
session.user.id
);
const needsMigration = await subscriptionService.needsMigration(
session.user.id
);
return NextResponse.json({
...migrationStatus,
...needsMigration,
});
} catch (error) {
console.error('Error getting migration status:', error);
return NextResponse.json(
{ error: 'Failed to get migration status' },
{ status: 500 }
);
}
}
// 启动迁移(设置 Stripe 订阅不续费)
export async function POST(request: NextRequest) {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const subscription = await subscriptionService.getCurrentSubscription(
session.user.id
);
if (!subscription || subscription.paymentGateway !== 'stripe') {
return NextResponse.json(
{ error: 'No Stripe subscription found' },
{ status: 400 }
);
}
// 设置 Stripe 订阅在周期结束时取消
await subscriptionService.cancelSubscription(session.user.id, true);
return NextResponse.json({
success: true,
message: 'Migration initiated.',
periodEnd: subscription.currentPeriodEnd,
});
} catch (error) {
console.error('Error initiating migration:', error);
return NextResponse.json(
{ error: 'Failed to initiate migration' },
{ status: 500 }
);
}
}
多语言文案
中文 (public/locales/zh-CN/subscription.json)
{
"migration": {
"title": "支付系统升级",
"pendingMessage": "我们正在升级支付系统。您当前的订阅将继续正常运行,但我们建议您切换到新的支付方式以获得更好的体验。",
"inProgressMessage": "您当前的订阅将在 {{days}} 天后到期。请使用新的支付系统重新订阅以继续享受高级功能。",
"resubscribeButton": "立即重新订阅",
"startMigration": "开始迁移",
"migrationComplete": "迁移完成",
"benefits": {
"title": "新支付系统的优势:",
"faster": "更快的结账流程",
"morePayments": "支持更多支付方式",
"betterSupport": "更好的客户支持"
}
}
}
英文 (public/locales/en/subscription.json)
{
"migration": {
"title": "Payment System Migration",
"pendingMessage": "We're upgrading our payment system. Your current subscription will continue normally, but we recommend switching to our new payment provider for a better experience.",
"inProgressMessage": "Your current subscription will expire in {{days}} days. Please resubscribe using our new payment system to continue enjoying premium features.",
"resubscribeButton": "Resubscribe Now",
"startMigration": "Start Migration",
"migrationComplete": "Migration Complete",
"benefits": {
"title": "Benefits of the new payment system:",
"faster": "Faster checkout process",
"morePayments": "More payment methods supported",
"betterSupport": "Better customer support"
}
}
}
用户体验流程
新用户流程
- 新用户访问定价页
- 选择订阅计划
- 创建 Creem Checkout
- 完成支付
- 订阅激活
老用户迁移流程
- Stripe 用户登录
- 检查订阅状态
- 活跃:显示迁移提示
- 即将到期:显示紧急迁移提示
- 已过期:显示重新订阅引导
- 用户点击迁移
- 设置 Stripe 不续费
- 引导到定价页
- 用户选择计划
- 创建 Creem Checkout
- 完成支付
- 新订阅激活
- 保留原有积分
注意事项
权益保障
- 用户的积分余额在迁移过程中保持不变
- 如果用户在 Stripe 订阅到期前完成 Creem 订阅,两个订阅可以并存
- 建议在 Stripe 订阅到期后再激活 Creem 订阅的积分发放
Webhook 处理
- 保持 Stripe Webhook 端点运行,处理现有订阅的事件
- 确保 Creem Webhook 端点正常工作
- 两个 Webhook 可以并行运行
客户支持
- 准备 FAQ 文档解答用户疑问
- 提供手动迁移支持渠道
- 监控迁移过程中的错误和投诉
回滚计划
如果迁移出现问题:
- 将
PAYMENT_GATEWAY改回stripe - 恢复 Stripe 订阅的自动续费
- 通知受影响的用户
实施检查清单
- 确认 Creem API 密钥和 Webhook Secret 已配置
- 确认 Creem 产品/价格 ID 已在 subscription_plans 表中配置
- 测试 Creem Checkout 流程
- 测试 Creem Webhook 处理
- 更新环境变量
PAYMENT_GATEWAY=creem - 部署迁移提示组件
- 添加多语言文案
- 运行迁移脚本(dry-run 模式)
- 确认迁移脚本结果
- 执行实际迁移
- 监控迁移进度
- 处理用户反馈