LogoMkSaaS Docs
LogoMkSaaS Docs
HomepageIntroductionCodebaseVideo TutorialsGetting Started: Install and Run LocallyEnvironment Setup
Configuration

Integrations

DatabaseAuthenticationEmailNewsletterStorage
Payment
AIAnalyticsNotificationCaptchaChatboxAffiliates

Customization

MetadataFontsThemesImagesSaaS Internationalization with next-intlBlogDocsComponentsCustom PagesLanding PageUser ManagementAPI Key Management

Codebase

IDE SetupProject StructureFormatting & LintingUpdating the Codebase
X (Twitter)

Payment

Learn how to set up and use payment providers in MkSaaS

MkSaaS supports multiple payment providers, allowing you to choose the best payment solution for your needs.

All providers use the same application flow: a server action creates a hosted checkout, the provider redirects the customer back to /payment, and signed webhooks update the shared payment table. Access is granted from the database state written by webhooks, not from the browser redirect alone.

Payment Providers

Stripe

The most popular global payment platform, supporting one-time payments and subscriptions

Creem

Payment platform for indie developers with built-in tax compliance and subscription management

Waffo Pancake

Merchant of Record payment platform with hosted checkout, consumer portal, and global tax compliance

Custom Payment Provider

MkSaaS supports extending with new payment providers:

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

Example implementation:

src/payment/provider/my-provider.ts
import {
  type CheckoutResult,
  type CreateCheckoutParams,
  type CreatePortalParams,
  type PaymentProvider,
  type PortalResult,
} from '@/payment/types';

export class MyProvider implements PaymentProvider {
  readonly requiresCustomerId = true;
  readonly hostsPostCheckoutPage = false;

  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 handleWebhookEvent(payload: string, signature: string): Promise<void> {
    // Implementation for handling webhook events
  }

  public getProviderName(): string {
    return 'MyProvider';
  }
}

The optional flags describe provider-specific behavior:

  • requiresCustomerId: whether the customer portal needs a stored provider customer ID.
  • hostsPostCheckoutPage: whether the provider owns the post-checkout confirmation page.

Add the provider name to PaymentConfig['provider'] in src/types/index.d.ts, then register its lazy factory in src/payment/index.ts:

src/payment/index.ts
import { MyProvider } from './provider/my-provider';

const providerRegistry: Partial<
  Record<PaymentProviderName, PaymentProviderFactory>
> = {
  stripe: () => new StripeProvider(),
  creem: () => new CreemProvider(),
  waffo: () => new WaffoProvider(),
  'my-provider': () => new MyProvider(),
};

The webhook route returns 200 only after a supported event is handled or intentionally ignored. Signature errors and invalid payloads return 400, and processing failures return 500 so the provider can retry safely.

Storage

Learn how to set up and use cloud storage for file uploads and media handling

Stripe

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

Table of Contents

Payment ProvidersCustom Payment Provider