3 min readLast updated: 2025-12-31 12:12
Stripe to Creem Progressive Migration Guide
This document describes the complete solution for progressively migrating existing Stripe subscription users to Creem payment platform. Using a progressive migration strategy: new users use Creem, existing Stripe users are guided to resubscribe on Creem after their subscription expires, while preserving user benefits.
Migration Strategy
Core Principles
- New Users Prioritize Creem: All new subscriptions default to Creem payment gateway
- Natural Transition for Existing Users: Stripe subscriptions don't renew after expiry, users are guided to resubscribe on Creem
- Benefit Protection: Preserve user credits and subscription benefits during migration
- Smooth Experience: Keep users informed of migration progress through UI prompts and email notifications
Migration Timeline
Phase 1 (Immediate): Configuration Changes
├── Change default payment gateway to Creem
├── New users automatically use Creem
└── Existing Stripe subscriptions continue normally
Phase 2 (1-2 weeks): UI Updates
├── Display migration prompts on subscription management page
├── Add migration guidance components
└── Update multilingual copy
Phase 3 (Ongoing): Natural Migration
├── Stripe subscriptions don't renew after expiry
├── Show resubscription guidance when users visit
└── Users complete new subscription on Creem
Phase 4 (Optional): Active Migration
├── Send email notifications to remaining Stripe users
├── Provide migration incentives (e.g., bonus credits)
└── Set final deadline
Technical Implementation
Environment Variable Configuration
Modify .env file:
# Change default payment gateway to Creem
PAYMENT_GATEWAY=creem
# Creem configuration (ensure configured)
CREEM_API_KEY=your_creem_api_key
CREEM_WEBHOOK_SECRET=your_creem_webhook_secret
# Stripe configuration (keep for handling existing subscriptions)
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_WEBHOOK_SECRET=your_stripe_webhook_secret
Database Structure
Existing database structure already supports multiple gateways, no modifications needed:
user_subscriptions.paymentGateway- Distinguishes Stripe and Creem subscriptionspayment_customers- Stores user customer IDs for each gatewaysubscription_plans- Contains Stripe and Creem Price IDs
Core Code Modifications
Subscription Service - Add Migration Detection
// src/lib/services/subscription-service.ts
/**
* Check if user needs migration (Stripe subscription expiring soon)
*/
async needsMigration(userId: string): Promise<{
needsMigration: boolean;
currentSubscription?: UserSubscription;
daysUntilExpiry?: number;
}> {
const subscription = await this.getCurrentSubscription(userId);
if (!subscription) {
return { needsMigration: false };
}
// Only Stripe subscriptions need migration
if (subscription.paymentGateway !== 'stripe') {
return { needsMigration: false };
}
// Check if set to cancel at period end
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,
};
}
New API Endpoint
// 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 }
);
}
}
// Initiate migration (set Stripe subscription to not renew)
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 }
);
}
// Set Stripe subscription to cancel at period end
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 }
);
}
}
Multilingual Copy
English (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"
}
}
}
Chinese (public/locales/zh-CN/subscription.json)
{
"migration": {
"title": "支付系统升级",
"pendingMessage": "我们正在升级支付系统。您当前的订阅将继续正常运行,但我们建议您切换到新的支付方式以获得更好的体验。",
"inProgressMessage": "您当前的订阅将在 {{days}} 天后到期。请使用新的支付系统重新订阅以继续享受高级功能。",
"resubscribeButton": "立即重新订阅",
"startMigration": "开始迁移",
"migrationComplete": "迁移完成",
"benefits": {
"title": "新支付系统的优势:",
"faster": "更快的结账流程",
"morePayments": "支持更多支付方式",
"betterSupport": "更好的客户支持"
}
}
}
User Experience Flow
New User Flow
- New user visits pricing page
- Selects subscription plan
- Creates Creem Checkout
- Completes payment
- Subscription activated
Existing User Migration Flow
- Stripe user logs in
- Check subscription status
- Active: Show migration prompt
- Expiring soon: Show urgent migration prompt
- Expired: Show resubscription guidance
- User clicks migrate
- Set Stripe to not renew
- Guide to pricing page
- User selects plan
- Create Creem Checkout
- Complete payment
- New subscription activated
- Preserve existing credits
Important Notes
Benefit Protection
- User credit balance remains unchanged during migration
- If user completes Creem subscription before Stripe expires, both subscriptions can coexist
- Recommend activating Creem subscription credit distribution after Stripe subscription expires
Webhook Handling
- Keep Stripe Webhook endpoint running to handle existing subscription events
- Ensure Creem Webhook endpoint works properly
- Both Webhooks can run in parallel
Customer Support
- Prepare FAQ documentation to answer user questions
- Provide manual migration support channel
- Monitor errors and complaints during migration
Rollback Plan
If migration issues occur:
- Change
PAYMENT_GATEWAYback tostripe - Restore Stripe subscription auto-renewal
- Notify affected users
Implementation Checklist
- Confirm Creem API key and Webhook Secret configured
- Confirm Creem product/price IDs configured in subscription_plans table
- Test Creem Checkout flow
- Test Creem Webhook handling
- Update environment variable
PAYMENT_GATEWAY=creem - Deploy migration prompt components
- Add multilingual copy
- Run migration script (dry-run mode)
- Confirm migration script results
- Execute actual migration
- Monitor migration progress
- Handle user feedback