Back to Articles
December 7, 20248 min

How I Built a Multi-Tenant SaaS With Stripe Connect

StripeNext.jsSaaSArchitecture
How I Built a Multi-Tenant SaaS With Stripe Connect

Building a marketplace infrastructure is one of the most complex challenges in SaaS. You're not just taking payments; you're routing them, splitting them, and managing compliance for thousands of sub-merchants.

In this deep dive, I'll show you how I architected a payment system handling $50K+ monthly volume using Stripe Connect.

The Challenge

We needed a platform where:

  1. Customers pay for services.
  2. Service Providers (our users) get paid instantly.
  3. The Platform (us) takes a commission fee.
  4. Everything happens transparently and compliantly.

Handling money for others requires strict compliance (KYC/KYB). Never hold funds in your own bank account if you can avoid it. Stripe Connect solves this.

Architecture Overview

I chose Stripe Connect Custom Accounts for complete control over the onboarding UI.

// lib/stripe.ts
import Stripe from 'stripe';
 
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
  appInfo: {
    name: 'MySaaS Platform',
    version: '1.0.0'
  }
});

The Split Payment Flow

When a payment occurs, we don't just charge the card. We create a PaymentIntent that automatically routes funds to the connected account.

const paymentIntent = await stripe.paymentIntents.create({
  amount: 5000, // $50.00
  currency: 'usd',
  automatic_payment_methods: { enabled: true },
  application_fee_amount: 500, // We keep $5.00 (10%)
  transfer_data: {
    destination: connectedAccountId, // The Service Provider's ID
  },
});

Secure Onboarding with Webhooks

The most critical part is keeping account status in sync. Users might start onboarding, drop off, or get flagged for fraud.

We use webhooks to listen for account.updated:

export async function handleWebhook(event) {
  switch (event.type) {
    case 'account.updated':
      const account = event.data.object;
      await db.user.update({
        where: { stripeAccountId: account.id },
        data: {
          payoutsEnabled: account.payouts_enabled,
          chargesEnabled: account.charges_enabled,
        }
      });
      break;
    // Handle other events
  }
}

Handling Payouts

Funds accumulate in the connected account's Stripe balance. We configured automatic daily payouts to their bank accounts, reducing our liability.

FeatureCustom AccountsExpress AccountsStandard Accounts
Whitelabel UI✅ Yes❌ No❌ No
Global Reach✅ Yes✅ Yes✅ Yes
Dev Effort🔥 High⚡ Medium🟢 Low

Results

After deploying this architecture:

  • Onboarding Time: Reduced from 3 days to 5 minutes.
  • Support Tickets: Dropped by 60% due to automated status transparency.
  • Volume: Successfully scaled to $50K+ MRR without code changes.

Need help architecting your Stripe integration? Get in touch for a consultation.