SaaSyBase
SaaSyBase

Adding Payment Providers

SaaSyBase already ships with Stripe, Paystack, Paddle, and Razorpay, but the payment layer is explicitly designed to support additional providers. This page explains the actual extension points you need to touch when adding a new gateway.

Understand the architecture first

Payment integrations follow the same provider pattern used elsewhere in the codebase: interface → provider implementation → registry → factory → payment service → webhook router.

Relevant files
lib/payment/
├── types.ts              # PaymentProvider interface and standardized types
├── registry.ts           # Provider registration and env-backed config
├── factory.ts            # Resolves the active provider
├── service.ts            # Shared payment orchestration
├── webhook-router.ts     # Centralized signature detection and dispatch
└── providers/
    ├── stripe.ts
    ├── paystack.ts
    ├── paddle.ts
    └── razorpay.ts

Warning

Do not import vendor SDKs directly into business logic or route handlers outside the payment provider implementation. The rest of the app should stay provider-agnostic.

1. Create the provider implementation

Add a new file under lib/payment/providers/ and implement the PaymentProvider interface.

ResponsibilityWhat your provider must do
Feature detectionReturn support flags from supportsFeature() so calling code can branch safely.
CheckoutCreate one-time and recurring checkout flows in the provider-native format.
SubscriptionsFetch, update, cancel, and normalize provider subscription state.
RefundsCreate refunds and normalize refund responses.
CatalogCreate or fetch products and prices when provider sync is enabled.
WebhooksVerify signatures and normalize provider events into standardized event types.

2. Register the provider

Wire the implementation into lib/payment/registry.ts so the factory can instantiate it fromPAYMENT_PROVIDER.

Environment and config surface

Add any new provider env vars to the same places the shipped providers use: the registry/config layer,.env.example, and the deployment docs or README if operators need to set them.

3. Hook it into the webhook router

The preferred payment ingress is /api/webhooks/payments. To participate, your provider needs a recognizable signature header and a registered config in the webhook router.

Current centralized provider list
createWebhookProviderConfigs(['razorpay', 'paddle', 'stripe', 'paystack'])

Add your provider to that routing list and, if you want a convenience alias route, create one underapp/api/webhooks/<provider>/route.ts or another explicit path that forwards into the same shared flow.

4. Add any client-side requirements

Some providers need a client script, publishable key, or callback page. When that happens, follow the same pattern used by the shipped providers.

NeedTypical destination
Checkout script or SDK bootstrappingcomponents/PaymentProviderScripts.tsx or the provider client components
Redirect callback handlingapp/checkout/... or provider-specific callback route
Hosted billing portal supportapp/api/billing/customer-portal/route.ts

5. Test the full lifecycle

Adding the provider class is not enough. You need regression coverage for checkout, webhooks, subscription transitions, and any provider-specific fallbacks.

  • Unit test webhook normalization and signature verification.
  • Test recurring and one-time checkout flows.
  • Test failure paths such as invalid signatures, unsupported features, and refund errors.
  • Run the shared suite with npm test, then run lint and typecheck.

Note

Treat webhook verification and lifecycle edge cases as first-class work, not cleanup. A provider that can create checkout sessions but cannot safely verify, normalize, and reconcile events is not production-ready in this architecture.

What the current admin UI does and does not do

The shipped admin plan UI supports core plan fields, recurring cadence, organization fields, and advanced external provider price ID overrides. It does not currently expose a full PlanPrice CRUD editor for per-provider localized pricing.

Assume the abstraction layer is richer than the current admin CRUD. If your provider depends on localized pricing, catalog synchronization, or extra checkout metadata, budget for either seed-time setup, a custom admin UI extension, or direct data-entry workflows during implementation.

Note

If your new provider needs a richer localized pricing workflow than the current admin modal exposes, plan for an additional admin UI pass or populate the provider catalog using seed, sync, Prisma Studio, or your own admin tooling.

Repository deep-dive guide

There is also a longer internal implementation guide in docs/adding-payment-providers.md. Use this docs page for the public overview, then read the repository markdown guide when you are actively implementing the provider.