Multi-Tenant Stripe: Bring-Your-Own (BYO) Payments
This is the playbook for letting each tenant run payments under their own Stripe account while our platform handles the booking experience, fees, and reporting. Conversational, opinionated, and ready to implement.
Why BYO at all?
- Trust & ownership – Funds settle to the tenant’s Stripe, not ours. They can reconcile, refund, and export on their own.
- Compliance & risk – Disputes, KYC, tax settings, and chargeback liability live with the merchant who delivered the service.
- Scalability – We avoid “all money flows through us” constraints, payout ops, and 1099 headaches (unless we choose to).
- Business flexibility – We can still take a platform fee per booking without being the Merchant of Record.
The right way: Stripe Connect (recommended)
Tenants bring a Stripe account, but we do not store their raw API keys. Instead we onboard them to our platform via Connect OAuth:
- Account type: Start with Standard (lowest lift, tenant manages their dashboard). Upgrade to Express if we want a guided dashboard and unified reporting. Avoid Custom for now (max compliance burden).
- Charge flow: Prefer Direct charges (charge occurs on the connected account; application fee taken by the platform).
Alternative: Destination charges (we charge on our platform account and transfer funds to tenant); useful if we need more control but increases liability.
What we get
- Our platform fee via
application_fee_amount
. - Disputes and refunds handled by the tenant’s account.
- Clear separation for taxes (tenant can enable Stripe Tax on their own).
The “raw keys” pattern (not recommended, but documented)
Tenants generate restricted API keys and give them to us. We store and use those keys to create charges directly on their account (no Connect).
Trade-offs:
- ✅ Works without Connect.
- ❌ Secret handling, rotation, and audit burden on us.
- ❌ Harder to take platform fees cleanly.
- ❌ We lose Connect’s onboarding/compliance rails.
If ever needed, limit to read-only/reporting or very narrow charge scopes and require KMS-backed storage + rotation.
Config model (per tenant)
type PaymentMode = "platform_managed" | "connect_standard" | "connect_express" | "byo_restricted_keys";
interface TenantPaymentsConfig {
tenantId: string;
mode: PaymentMode;
currency: "usd";
testMode: boolean;
// Connect
connectAccountId?: string; // acct_*
payoutSchedule?: "daily" | "weekly" | "manual";
feeBasisPoints?: number; // e.g., 500 = 5% platform fee
statementDescriptor?: string;
// BYO keys (discouraged)
restrictedKeyRef?: string; // encrypted ref in KMS/Secrets
// Booking rules
depositPercent?: number; // deposit now, remainder later
savePaymentMethod?: boolean; // use SetupIntent for re-billing
}