LogoNEXTDEVKIT Docs

Payment Configuration

Learn how to configure pricing and payment plans for both Stripe and Creem in NEXTDEVKIT

💰 Payment Types

NEXTDEVKIT supports two payment types with both providers:

export enum PaymentType {
  SUBSCRIPTION = "subscription",
  ONE_TIME = "one-time",
}

export enum PlanInterval {
  MONTH = "monthly",
  YEAR = "yearly",
}

🎯 Provider Configuration

First, you need to set up the payment provider in src/config/index.ts:

payment: {
  provider: "stripe", // or "creem"
  currency: "USD",
  yearlyDiscount: 20,
  redirectAfterCheckout: "/app/dashboard",
  plans: {
    // ... your plan configuration
  }
}

Switching Between Providers

To switch from Stripe to Creem or vice versa:

  1. Update the provider:
// For Stripe
provider: "stripe"

// For Creem  
provider: "creem"
  1. Update environment variables:
# For Stripe
STRIPE_SECRET_KEY="sk_test_your_key"
STRIPE_WEBHOOK_SECRET="whsec_your_secret"

# For Creem
CREEM_API_KEY="ck_test_your_key"
CREEM_WEBHOOK_SECRET="your_webhook_secret"
  1. Update Price/Product IDs:
# The same environment variable names work for both providers
NEXT_PUBLIC_PRICE_ID_PRO_MONTHLY="your_monthly_id"
NEXT_PUBLIC_PRICE_ID_PRO_YEARLY="your_yearly_id"
NEXT_PUBLIC_PRICE_ID_LIFETIME="your_lifetime_id"

📋 Define Plans and Products

You can manage the plans and products in the configuration file of your NEXTDEVKIT project. There are different types of plans you can define:

🆓 Free Plan

The free plan is the default plan for users who have not purchased any plans or can be used to access a limited version of your product.

As this is no paid plan, you don't need to define any prices or attach a product id to it.

export const appConfig = {
  payment: {
    plans: {
      free: {
        id: "free",
        isFree: true,
      },
    }
  },
};

🏢 Enterprise Plan

The enterprise plan is not a real plan, but will show up in the pricing table with a link to a contact form, so customers can contact you to get access to your product.

As this is no paid plan, you don't need to define any prices or attach a product id to it.

export const appConfig = {
  payment: {
    plans: {
      enterprise: {
        id: "enterprise",
        isEnterprise: true,
        highlighted: true,
      },
    }
  },
};

🔄 Subscription Plans and 💎 One-time Purchase Plans

A plan represents a product or service of your application and each is a column in your pricing table. It has the following properties:

  • popular: if this plan should be highlighted as recommended ⭐
  • highlighted: highlight the plan from the pricing table ✨
  • prices: define the prices for this plan 💰

One plan can have multiple prices, for example a monthly and yearly price or/and for each currency you support.

A price has the following properties:

  • type: the type of the price, can be PaymentType.SUBSCRIPTION or PaymentType.ONE_TIME
  • priceId: the id of the product from your payment provider 🆔
  • interval: the interval of the price, can be PlanInterval.MONTH or PlanInterval.YEAR
  • amount: the amount of the price 💵
  • trialPeriodDays: the number of days of the trial period, leave out if you don't want to offer a trial period 🎁

Complete Plan Configuration Example

payment: {
  provider: "stripe", // or "creem"
  currency: "USD",
  yearlyDiscount: 20,
  redirectAfterCheckout: "/app/dashboard",
  plans: {
    free: {
      id: "free",
      isFree: true,
    },
    pro: {
      id: "pro",
      prices: [
        {
          type: PaymentType.SUBSCRIPTION,
          priceId: process.env.NEXT_PUBLIC_PRICE_ID_PRO_MONTHLY as string,
          amount: 9.9,
          interval: PlanInterval.MONTH,
          trialPeriodDays: 7,
        },
        {
          type: PaymentType.SUBSCRIPTION,
          priceId: process.env.NEXT_PUBLIC_PRICE_ID_PRO_YEARLY as string,
          amount: 99,
          interval: PlanInterval.YEAR,
          trialPeriodDays: 30,
        },
      ],
      popular: true,
    },
    lifetime: {
      id: "lifetime",
      prices: [
        {
          type: PaymentType.ONE_TIME,
          priceId: process.env.NEXT_PUBLIC_PRICE_ID_LIFETIME as string,
          amount: 399,
        },
      ],
      isLifetime: true,
    },
    enterprise: {
      id: "enterprise",
      isEnterprise: true,
      highlighted: true,
    },
  },
}

🔧 Provider-Specific Notes

Stripe Configuration

  • priceId: Use Stripe Price IDs (starts with price_)
  • Products: Create products and prices in Stripe Dashboard
  • Webhooks: Set up webhook endpoint at /api/webhooks/stripe

Creem Configuration

  • priceId: Use Creem Product IDs (starts with prod_)
  • Products: Create products in Creem Dashboard
  • Webhooks: Set up webhook endpoint at /api/webhooks/creem

📝 Plan Information for Pricing Table

You can define the information for the pricing table for each plan in src/config/marketing/pricing.ts.

You can define a title, a description and a features array for each plan.

We recommend you to use the t() function to get the translations for the plan information and then define the translations in the /messages/ folder.

export async function getPricingConfig(): Promise<PricingConfig> {
  const t = await getTranslations("pricing");
  const priceConfig = appConfig.payment;
  const plans: PricePlan[] = [];

  if (priceConfig.plans.free) {
    plans.push({
      ...priceConfig.plans.free,
      name: t("products.free.title"),
      description: t("products.free.description"),
      features: [
        t("products.free.features.feature1"),
        t("products.free.features.feature2"),
        t("products.free.features.feature3"),
      ],
    });
  }

  if (priceConfig.plans.pro) {
    plans.push({
      ...priceConfig.plans.pro,
      name: t("products.pro.title"),
      description: t("products.pro.description"),
      features: [
        t("products.pro.features.feature1"),
        t("products.pro.features.feature2"),
        t("products.pro.features.feature3"),
        t("products.pro.features.feature4"),
      ],
    });
  }

  if (priceConfig.plans.lifetime) {
    plans.push({
      ...priceConfig.plans.lifetime,
      name: t("products.lifetime.title"),
      description: t("products.lifetime.description"),
      features: [
        t("products.lifetime.features.feature1"),
        t("products.lifetime.features.feature2"),
        t("products.lifetime.features.feature3"),
      ],
    });
  }

  if (priceConfig.plans.enterprise) {
    plans.push({
      ...priceConfig.plans.enterprise,
      name: t("products.enterprise.title"),
      description: t("products.enterprise.description"),
      features: [
        t("products.enterprise.features.feature1"),
        t("products.enterprise.features.feature2"),
        t("products.enterprise.features.feature3"),
        t("products.enterprise.features.feature4"),
        t("products.enterprise.features.feature5"),
      ],
    });
  }

  return {
    title: t("title"),
    subtitle: t("subtitle"),
    frequencies: [t("frequencies.monthly"), t("frequencies.yearly")],
    yearlyDiscount: priceConfig.yearlyDiscount,
    plans,
  };
}

🧪 Testing Configuration

Environment Variables for Testing

# Stripe Test Mode
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_test_..."

# Creem Test Mode  
CREEM_API_KEY="ck_test_..."
CREEM_WEBHOOK_SECRET="test_webhook_secret"

# Test Price/Product IDs
NEXT_PUBLIC_PRICE_ID_PRO_MONTHLY="test_price_monthly"
NEXT_PUBLIC_PRICE_ID_PRO_YEARLY="test_price_yearly"
NEXT_PUBLIC_PRICE_ID_LIFETIME="test_price_lifetime"

🔧 Troubleshooting Configuration

Common Configuration Issues

Provider Not Found:

  • Check that provider is set to either "stripe" or "creem"
  • Verify the correct provider implementation exists

Missing Environment Variables:

  • Ensure all required environment variables are set
  • Check for typos in variable names
  • Verify test vs production keys

Invalid Price/Product IDs:

  • Confirm IDs exist in your payment provider dashboard
  • Check ID format matches provider requirements
  • Verify test vs production IDs

Plan Configuration Errors:

  • Ensure plan structure matches expected format
  • Check that required fields are present
  • Verify data types match interface definitions

🎯 Next Steps

Now that your payment configuration is set up: