Marketplace Payment Splits

Sellers onboarded, payments split automatically, payouts explained in plain language.

Built from scratch to demonstrate stripe connect marketplace

LaravelStripe ConnectSplit PaymentsGemini AINext.js
Marketplace Payment Splits interface
The problem

What this solves

A marketplace takes money from a buyer that mostly belongs to somebody else. The platform keeps a cut and the seller gets the rest, and getting that division wrong is not a rounding error, it is somebody's income.

The compliance side is worse than the maths. Paying a third party means identity verification, tax details and bank accounts, which is a regulated problem most teams should not be solving themselves.

I built this to demonstrate a marketplace where the split is handled by the payment processor in one operation, and seller verification is handled by Stripe rather than by me.

Approach

How I approached it

  • Sellers onboard through Stripe Connect Express, so identity verification, tax information and bank details are collected by Stripe without a single custom form on my side.
  • Express sits between Standard, where the seller owns the whole account, and Custom, where you rebuild compliance from the ground up. For a marketplace it is the sensible middle.
  • Permissions are enforced on the API, not hidden in the interface. A seller cannot list until Stripe reports charges_enabled, and a buyer cannot purchase from a disabled seller, because a hidden button is not an authorization model.
  • Gemini 2.5 Flash writes each seller a plain language weekly payout summary, with a template fallback so the feature degrades instead of disappearing when the model is unavailable.
The hard part

Splitting one payment between platform and seller without ever leaving the money in the wrong place.

Why the obvious solution fails

The obvious approach is to charge the buyer in full, then transfer the seller's share afterwards. That is two operations, and anything can happen between them. The charge succeeds and the transfer fails, and now the platform is holding money it does not own. Retry logic makes it worse, because a retried transfer can pay a seller twice. The same trap appears on refunds: refund the buyer and the platform has usually already taken its fee and paid the seller out, so it silently eats the difference on every refund.

What I did instead

  • The split happens inside a single Stripe Checkout Session rather than as a follow-up step. application_fee_amount takes the platform's 10 percent and transfer_data.destination routes the remainder to the seller's connected account.
  • Because it is one operation, there is no window where the charge has landed and the split has not. It either happens completely or not at all.
  • Refunds reverse both sides on purpose: refund_application_fee returns the platform's cut and reverse_transfer claws back the seller's share. Without those two flags the platform absorbs the entire refund while the seller keeps their portion, which is quietly unsustainable.
  • Webhook deliveries are guarded by a unique constraint on the Stripe event id, so a retried delivery cannot process the same payment twice.
Architecture

How the data flows

A Laravel 11 API with Sanctum handles onboarding, listings, checkout and payouts through the Stripe PHP SDK, with a Next.js 14 frontend. Webhook work runs on a database queue so the endpoint can answer immediately.

  1. A seller onboards through Stripe-hosted Connect Express and Stripe reports back when the account can accept charges.
  2. Only then can that seller publish a listing, checked on the API rather than in the UI.
  3. A buyer checks out through a Stripe Checkout Session carrying both the platform fee and the seller destination.
  4. Stripe splits the payment atomically and sends a webhook.
  5. The endpoint records the event id under a unique constraint and returns 200 in under a second.
  6. A queued job updates order and payout state, so a slow update never risks a webhook timeout.
  7. Gemini drafts each seller a weekly summary of what they earned and why, falling back to a template if it cannot.
At production scale

What I'd do differently

  • Payout summaries are written to the database rather than emailed. In production that needs a real delivery path and a record of what was sent, since a payout summary is close to a financial statement.
  • The demo runs on SQLite, which is fine for a walkthrough and wrong for a marketplace. Production means MySQL or Postgres with proper transaction isolation around anything that touches balances.
  • The platform fee is a fixed percentage. Real marketplaces need per-seller or per-category rates, which is a pricing model rather than a constant.
  • Disputes are the gap. A chargeback on a marketplace payment is a three-way argument between buyer, seller and platform, and deciding who absorbs it is a policy decision that has to exist before the first dispute arrives.
Screens
Marketplace Payment Splits screen 2
Marketplace Payment Splits screen 3
Marketplace Payment Splits screen 4

Want something like this built?

I build payment systems and AI features inside Laravel apps. Tell me what you need and I will tell you how I would approach it.

Get in touch