LogoMkSaaS Docs

Payment

Learn how to set up and use Stripe for handling payments and subscriptions in your application

MkSaaS uses Stripe for payment processing and subscription management. This integration handles both one-time payments and recurring subscriptions with a flexible, developer-friendly approach.

Setup

MkSaaS boilerplate uses three price plans by default: Free plan, Pro subscription plan (monthly/yearly), and Lifetime plan (one-time payment), follow these steps to set up Stripe integration:

  1. Create a Stripe account at stripe.com

  2. Get your API keys from the Stripe dashboard:

    • Go to the Stripe Dashboard > Developers > API keys
    • Copy your Secret key (starts with sk_test_ for test mode or sk_live_ for live mode)
    • Save it to your .env file as STRIPE_SECRET_KEY
  3. Set up webhook and get your Webhook Secret:

    • Go to the Stripe Dashboard > Developers > Webhooks
    • Click "Add endpoint"
    • Enter your webhook URL: https://your-domain.com/api/webhooks/stripe
    • Select the events to listen for:
      • checkout.session.completed
      • customer.subscription.created
      • customer.subscription.updated
      • customer.subscription.deleted
    • Click "Reveal" to view your Webhook Signing Secret (starts with whsec_)
    • Save it to your .env file as STRIPE_WEBHOOK_SECRET
  4. Create products and pricing plans in Stripe:

    • Go to the Stripe Dashboard > Products
    • Create Pro subscription product:
      • Click "Add product"
      • Name: "Pro Plan"
      • Description: "Advanced features with subscription pricing"
      • Add monthly price:
        • Click "Add price"
        • Price: $9.90 (currency select USD)
        • Recurring: Monthly
        • Save and copy the Price ID (starts with price_), this will be used for NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY
      • Add yearly price:
        • Click "Add price"
        • Price: $99.00 (currency select USD)
        • Recurring: Yearly
        • Save and copy the Price ID (starts with price_), this will be used for NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY
    • Create Lifetime product:
      • Click "Add product"
      • Name: "Lifetime Plan"
      • Description: "One-time payment for lifetime access"
      • Add a price:
        • Price: $199.00 (currency select USD)
        • Type: One-off
        • Save and copy the Price ID (starts with price_), this will be used for NEXT_PUBLIC_STRIPE_PRICE_LIFETIME
  5. Add the following environment variables:

.env
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=price_...
  1. Update the website.tsx file to use Stripe as the payment provider in the payment configuration, and configure your pricing plans in the price configuration:
src/config/website.tsx
import { PaymentTypes, PlanIntervals } from '@/payment/types';
 
export const websiteConfig = {
  // ...other config
  payment: {
    provider: 'stripe', // Payment provider to use
  },
  price: {
    plans: {
      free: {
        id: "free",
        prices: [],
        isFree: true,
        isLifetime: false,
      },
      pro: {
        id: "pro",
        prices: [
          {
            type: PaymentTypes.SUBSCRIPTION,
            priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY!,
            amount: 990,
            currency: "USD",
            interval: PlanIntervals.MONTH,
          },
          {
            type: PaymentTypes.SUBSCRIPTION,
            priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY!,
            amount: 9900,
            currency: "USD",
            interval: PlanIntervals.YEAR,
          },
        ],
        isFree: false,
        isLifetime: false,
        recommended: true,
      },
      lifetime: {
        id: "lifetime",
        prices: [
          {
            type: PaymentTypes.ONE_TIME,
            priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_LIFETIME!,
            amount: 19900,
            currency: "USD",
          },
        ],
        isFree: false,
        isLifetime: true,
      }
    }
  }
  // ...other config
}

If you are setting up the environment, now you can go back to the Environment Setup guide and continue. The rest of this guide can be read later.

Environment Setup

Set up environment variables


Payment System Structure

The payment system in MkSaaS is designed with the following components:

index.ts
types.ts
README.md

This modular structure makes it easy to extend the payment system with new providers, pricing plans, and UI components.

Core Features

  • One-time payments for lifetime access
  • Recurring subscription payments (monthly and yearly)
  • Customer portal integration for subscription management
  • Webhook handling for payment events
  • Subscription status tracking and verification
  • Pre-built pricing components (tables, cards, buttons)
  • Server actions for secure payment operations
  • React hooks for payment state management
  • Multiple pricing plans support (Free, Pro, Lifetime)
  • Metadata support for tracking payment sources

Usage

MkSaaS provides simple payment utilities for handling checkout sessions and customer portals:

import { createCheckout, createCustomerPortal } from '@/payment';
 
// Create a checkout session
const checkoutResult = await createCheckout({
  planId: 'pro',
  priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY!,
  customerEmail: 'user@example.com',
  successUrl: 'https://example.com/payment/success',
  cancelUrl: 'https://example.com/payment/cancel',
  metadata: { userId: 'user_123' },
});
 
// Redirect to checkout URL
window.location.href = checkoutResult.url;
 
// Create a customer portal session
const portalResult = await createCustomerPortal({
  customerId: 'cus_123',
  returnUrl: 'https://example.com/account/billing',
});
 
// Redirect to portal URL
window.location.href = portalResult.url;

Webhooks

Stripe webhooks are essential for handling asynchronous events like successful payments and subscription changes.

Development

For local development, you can use the Stripe CLI to forward events to your local server:

pnpm install -g stripe/stripe-cli

Login to Stripe:

stripe login

Forward events to your local server:

stripe listen --forward-to localhost:3000/api/webhooks/stripe

The webhook secret is printed in the terminal. Copy it and add it to your .env file:

STRIPE_WEBHOOK_SECRET=whsec_...

You can trigger test events using the Stripe CLI, or test events on the website:

stripe trigger checkout.session.completed
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted

Production

  1. Go to the Stripe Dashboard > Developers > Webhooks
  2. Click "Add endpoint"
  3. Enter your webhook URL: https://your-domain.com/api/webhooks/stripe
  4. Select the events to listen for:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
  5. After creating, click "Reveal" to view your Webhook Signing Secret
  6. Copy the webhook secret (starts with whsec_) and add it to your environment variables

UI Components

MkSaaS includes pre-built React components for handling payments:

Server Actions

MkSaaS provides server actions for payment operations:

React Hooks

MkSaaS provides hooks for payment operations:

Webhook Events

The payment system handles these webhook events in handleWebhookEvent method:

src/payment/provider/stripe.ts
public async handleWebhookEvent(payload: string, signature: string): Promise<void> {
  // Implementation to handle various Stripe webhook events:
  // - checkout.session.completed
  // - customer.subscription.created
  // - customer.subscription.updated
  // - customer.subscription.deleted
}

Customization

Creating a New Payment Provider

MkSaaS makes it easy to extend the payment system with new providers:

  1. Create a new file in the src/payment/provider directory
  2. Implement the PaymentProvider interface from types.ts
  3. Update the provider selection logic in index.ts

Example implementation for a new provider:

src/payment/provider/my-provider.ts
import { 
  PaymentProvider,
  CreateCheckoutParams,
  CheckoutResult,
  CreatePortalParams,
  PortalResult,
  Subscription,
  getSubscriptionsParams
} from '@/payment/types';
 
export class MyProvider implements PaymentProvider {
  constructor() {
    // Initialize your provider
  }
 
  public async createCheckout(params: CreateCheckoutParams): Promise<CheckoutResult> {
    // Implementation for creating a checkout session
  }
 
  public async createCustomerPortal(params: CreatePortalParams): Promise<PortalResult> {
    // Implementation for creating a customer portal
  }
 
  public async getSubscriptions(params: getSubscriptionsParams): Promise<Subscription[]> {
    // Implementation for getting subscriptions
  }
 
  public async handleWebhookEvent(payload: string, signature: string): Promise<void> {
    // Implementation for handling webhook events
  }
}

Then update the provider selection in index.ts:

src/payment/index.ts
import { MyProvider } from './provider/my-provider';
 
export const initializePaymentProvider = (): PaymentProvider => {
  if (!paymentProvider) {
    if (websiteConfig.payment.provider === 'stripe') {
      paymentProvider = new StripeProvider();
    } else if (websiteConfig.payment.provider === 'my-provider') {
      paymentProvider = new MyProvider();
    } else {
      throw new Error(
        `Unsupported payment provider: ${websiteConfig.payment.provider}`
      );
    }
  }
 
  return paymentProvider;
};

Testing Cards

For testing Stripe integration, use Stripe's test mode and test credit cards:

  • 4242 4242 4242 4242 - Successful payment
  • 4000 0000 0000 3220 - 3D Secure authentication required
  • 4000 0000 0000 9995 - Insufficient funds failure

Best Practices

  1. Secure API Keys: Never expose your Stripe secret key in client-side code
  2. Validate Webhook Signatures: Always validate the signature of webhook events
  3. Handle Errors Gracefully: Provide user-friendly error messages when payments fail
  4. Use Idempotency Keys: Prevent duplicate charges with idempotency keys
  5. Test Webhooks Thoroughly: Ensure all webhook events are properly handled

Next Steps

Now that you understand how to work with payments in MkSaaS, explore these related integrations:

Table of Contents