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

  1. New Users Prioritize Creem: All new subscriptions default to Creem payment gateway
  2. Natural Transition for Existing Users: Stripe subscriptions don't renew after expiry, users are guided to resubscribe on Creem
  3. Benefit Protection: Preserve user credits and subscription benefits during migration
  4. 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 subscriptions
  • payment_customers - Stores user customer IDs for each gateway
  • subscription_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

  1. New user visits pricing page
  2. Selects subscription plan
  3. Creates Creem Checkout
  4. Completes payment
  5. Subscription activated

Existing User Migration Flow

  1. Stripe user logs in
  2. Check subscription status
    • Active: Show migration prompt
    • Expiring soon: Show urgent migration prompt
    • Expired: Show resubscription guidance
  3. User clicks migrate
  4. Set Stripe to not renew
  5. Guide to pricing page
  6. User selects plan
  7. Create Creem Checkout
  8. Complete payment
  9. New subscription activated
  10. 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:

  1. Change PAYMENT_GATEWAY back to stripe
  2. Restore Stripe subscription auto-renewal
  3. 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
Payment Gateway Migration - Hex2077 Starter