Building a Multi-Vendor Marketplace: Variants, Payments & Escrow

3/17/2026 • RiseGravity Team

Building a Multi-Vendor Marketplace: Variants, Payments & Escrow

A marketplace is not a bigger store

A single-vendor store sells your products. A multi-vendor marketplace coordinates other people's products, money, and reputations—at the same time, in the same checkout. That shift introduces problems a normal ecommerce build never faces: how one cart pays many sellers, who holds the money until delivery, how a vendor manages their own catalog without seeing anyone else's, and how buyers trust strangers enough to pay.

We built ShopWiz.mk, a production multi-vendor marketplace for the Macedonian market—bilingual (EN/MK), with cPay card payments, Stripe-based escrow, vendor dashboards, faceted search, and a full admin console. This guide is the architecture we'd hand a team starting one today.

Key takeaways

  • Model the vendor as a first-class tenant, not a tag on a product.
  • Variants are a matrix, not a list—get the data model right before the UI.
  • Split payments and escrow are the hard core; design money flow before features.
  • Search and trust are growth features, not nice-to-haves.
  • The admin console is product, not an afterthought.

Vendors as first-class tenants

Each vendor runs a little business inside your platform: their own products, orders, payouts, policies, and dashboard—and they must never see another vendor's data. That's multi-tenancy, and it pays to treat it as such from the start (we go deep in Multi-Tenant SaaS Architecture).

Concretely, scope every vendor-owned record by vendorId, and route vendor dashboard access through a layer that enforces it:

// A vendor can only ever touch their own catalog and orders.
async function vendorScope(req) {
  const vendorId = await resolveVendorForUser(req.userId);
  if (!vendorId) throw new ForbiddenError("Not a vendor account");
  return {
    products: (q) => db.products.find({ ...q, vendorId }),
    orders:   (q) => db.orderItems.find({ ...q, vendorId }),
  };
}

Vendor onboarding is its own flow: application, identity/business verification (KYC), payout account connection, and a review gate before products go live. Don't let an unverified vendor take real money.

The product data model: variants done right

Variants—size, color, material—are where ecommerce data models go to die. The mistake is treating a product as a flat list of options. The correct model separates the product (shared marketing content) from its variants (the actually purchasable SKUs, each with its own price, stock, and barcode).

// Product = shared content. Variant = the thing with price + stock.
const product = {
  id: "prd_100",
  vendorId: "ven_7",
  title: "Merino Crew Sweater",
  options: [
    { name: "Size",  values: ["S", "M", "L", "XL"] },
    { name: "Color", values: ["Charcoal", "Navy"] },
  ],
};

const variant = {
  id: "var_100_M_navy",
  productId: "prd_100",
  optionValues: { Size: "M", Color: "Navy" },
  sku: "MCS-M-NVY",
  priceCents: 5900,
  stock: 14,
};

This model makes the hard things possible: per-variant inventory, per-variant pricing, accurate "out of stock for Navy/XL only," and clean barcode/SKU handling for vendors importing catalogs in bulk (ShopWiz supported Excel import—a lifesaver for vendors with hundreds of SKUs). Build the variant matrix UI on top of this; never the other way around.

The hard core: payments, splits, and escrow

This is what separates a marketplace from a store, and it's worth designing before you write a single product page.

One cart, many sellers

A buyer's cart routinely contains items from several vendors. At checkout you collect one payment but must attribute it across vendors, each minus your platform commission. So an order is really a parent order containing per-vendor sub-orders:

Order #5012  (buyer pays once: 142.00)
 ├─ Sub-order  Vendor A  →  88.00   (commission 10% → vendor gets 79.20)
 └─ Sub-order  Vendor B  →  54.00   (commission 10% → vendor gets 48.60)

Each sub-order has its own fulfillment status, shipping, and payout. The buyer sees one order; each vendor sees only theirs.

Escrow: hold the money until delivery

Buyers trust a marketplace when they know money is only released to a vendor after the order is fulfilled. That's escrow, and it's a trust feature as much as a financial one. The platform captures payment, holds funds, and releases to the vendor when the delivery/return window clears (or refunds the buyer on dispute).

On ShopWiz we used cPay for local card acceptance and Stripe to manage escrow and payouts. The money flow:

  1. Capture — buyer pays; funds land with the platform.
  2. Hold — funds are held against each sub-order, not yet the vendor's.
  3. Release — on confirmed delivery (or after the return window), release each vendor's share minus commission.
  4. Refund / dispute — on a valid dispute within the window, refund the buyer from held funds.

Payouts and ledgers

Vendors need to see exactly what they're owed and when. Keep an append-only ledger of every money movement—capture, hold, commission, release, refund, payout—keyed by vendor and sub-order. Never compute a balance by mutating a number; compute it by summing immutable entries. This makes reconciliation, vendor statements, and audits tractable instead of terrifying.

// Append-only ledger entries; balance = sum of entries, never an edited field.
await ledger.insert({ vendorId, subOrderId, type: "hold",       cents:  4860 });
await ledger.insert({ vendorId, subOrderId, type: "commission", cents:  -540 });
await ledger.insert({ vendorId, subOrderId, type: "release",    cents:  4860 });

Reconcile this ledger against your payment provider via idempotent webhooks—every payment event must be safe to receive twice.

Search, discovery, and faceted filtering

In a marketplace, catalog size is the whole point—and it makes search the primary navigation. Buyers need faceted filters (category, price, brand, vendor, rating, in-stock) that compose, plus relevant full-text search across many vendors' inconsistent product copy.

  • Index at the variant level so "in stock" and price filters are accurate.
  • Make facets composable and fast—a category + price + rating filter should be one query, not five.
  • Surface vendor reputation in results; it's a ranking signal buyers care about.

ShopWiz layered wishlists, reviews with vendor replies, and loyalty tiers on top—each one a reason to come back, and each one feeding the trust signals that make a marketplace work.

Trust: reviews, ratings, and dispute resolution

A marketplace lives or dies on trust between strangers. The mechanisms that build it:

  • Verified-purchase reviews, with the vendor able to reply publicly.
  • Vendor ratings aggregated and shown on storefronts and search.
  • A clear dispute flow tied to escrow—buyers know funds are held, vendors know the rules, and the platform adjudicates within a defined window.
  • Policies surfaced at checkout—shipping times, returns, who pays for what.

Trust features aren't decoration; they directly move conversion and repeat purchase.

The admin console is product

Marketplaces generate operational work the moment they go live: vendor approvals, dispute resolution, refunds, catalog moderation, commission configuration, payout oversight. If your team does that work in the database, you don't have a product—you have a liability. Build the admin console as a real surface: vendor management, order and sub-order views, refund/payout controls, content moderation, and dashboards for GMV, take rate, and dispute rates. Every ShopWiz operational task had a screen; that's what let a small team run a real marketplace.

A build sequence that de-risks the hard parts

  1. Vendor model + onboarding/KYC + tenant-scoped data access.
  2. Product/variant data model (matrix), then catalog UI and bulk import.
  3. Cart spanning multiple vendors → parent order + per-vendor sub-orders.
  4. Payment capture, escrow hold, commission, release, refund—plus the append-only ledger.
  5. Webhook reconciliation with the payment provider (idempotent).
  6. Faceted search and discovery at the variant level.
  7. Reviews, ratings, and the dispute flow tied to escrow.
  8. Admin console for approvals, disputes, payouts, and moderation.

Going local: language, currency, and payment methods

Marketplaces are inherently regional—buyers, vendors, and money all live somewhere specific—so localization is core architecture, not a translation layer you sprinkle on at the end. ShopWiz served the Macedonian market bilingually (English and Macedonian), and that shaped decisions throughout the stack.

Language. Bake internationalization in from the start: externalize every user-facing string, store vendor-supplied content (product titles, descriptions) in a way that can carry multiple locales, and make the URL and SEO strategy locale-aware so each language can rank. Retrofitting i18n after launch means touching every component—far more expensive than designing for it on day one.

Currency and pricing. Store money as integer minor units (cents/denari) with an explicit currency code—never floats—and decide early whether vendors price in one platform currency or their own. Display formatting, rounding rules, and tax handling all flow from getting this model right.

Local payment methods matter more than you think. The "obvious" global processor is often not how a given market actually pays. In Macedonia, local card acceptance via cPay was essential for conversion—buyers trust and use what they recognize. So we paired a local acquirer for card acceptance with Stripe for the escrow and payout machinery behind the scenes. The lesson generalizes: meet buyers with the payment methods they already trust, and don't assume one provider covers every region. Architect your payment layer so the acquirer is swappable per market, and keep your ledger and escrow logic independent of any single processor.

Frequently asked questions

How does one checkout pay multiple vendors? Collect a single payment, then attribute it across per-vendor sub-orders, each minus your commission. Modern payment providers support split/destination payments; pair that with an append-only ledger so every vendor's balance is the sum of immutable entries.

Do I really need escrow? For most marketplaces, yes. Holding funds until delivery is the core trust mechanism that makes buyers comfortable paying strangers and gives you a clean path for refunds and disputes. It also keeps you compliant about whose money you're holding and when.

What's the right way to model product variants? Separate the product (shared content and the list of options) from its variants (the purchasable SKUs, each with its own price, stock, and barcode). This makes per-variant inventory, pricing, and accurate stock states possible, and it's essential for bulk catalog import.

How do I keep one vendor from seeing another's data? Treat each vendor as a tenant: scope every vendor-owned record by vendorId and enforce it in a data-access layer (and ideally the database), so an un-scoped query is the visible exception, not an easy mistake.

Build a marketplace that vendors and buyers trust

Marketplaces are among the most demanding things to build well—money, multi-tenancy, search, and trust all at once—and the payoff is a platform that compounds as vendors and buyers join. If you're planning one, we've shipped it end to end. See ShopWiz.mk and other work on our Projects page, read our multi-tenant architecture guide, or reach out at contact@risegravity.com.