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:
- Create a new file in the
src/payment/providerdirectory - Implement the
PaymentProviderinterface fromtypes.ts - Update the payment provider selection logic in
index.ts
Example implementation:
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:
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.
MkSaaS Docs