Menu
Back to Blog
1 min read
Technologie

Marketplace Platform Development

Multi-Vendor Marketplace bauen. Seller Management, Product Listings, Escrow Payments und Commission Handling.

MarketplaceMulti-VendorE-CommerceSeller ManagementCommissionEscrow
Marketplace Platform Development

Marketplace Platform Development

Meta-Description: Multi-Vendor Marketplace bauen. Seller Management, Product Listings, Escrow Payments und Commission Handling.

Keywords: Marketplace, Multi-Vendor, E-Commerce, Seller Management, Commission, Escrow, Platform Business


Einführung

Marketplace Platforms verbinden Käufer und Verkäufer. Von Seller Onboarding bis Commission Splitting – die technischen Herausforderungen sind komplex. Dieser Guide zeigt die Architektur und Implementation eines Multi-Vendor Marketplaces.


Marketplace Architecture

┌─────────────────────────────────────────────────────────────┐
│              MARKETPLACE PLATFORM ARCHITECTURE               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Stakeholders:                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Platform (You)                                     │   │
│  │  ├── Commission on sales                           │   │
│  │  ├── Subscription fees                             │   │
│  │  └── Value-added services                          │   │
│  │                                                     │   │
│  │  Sellers                                            │   │
│  │  ├── List products                                 │   │
│  │  ├── Receive payouts                               │   │
│  │  └── Manage inventory                              │   │
│  │                                                     │   │
│  │  Buyers                                             │   │
│  │  ├── Browse & purchase                             │   │
│  │  ├── Reviews & ratings                             │   │
│  │  └── Dispute resolution                            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Payment Flow:                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Buyer pays $100                                    │   │
│  │       ↓                                             │   │
│  │  Platform receives $100 (Stripe Connect)            │   │
│  │       ↓                                             │   │
│  │  Platform takes 15% commission ($15)                │   │
│  │       ↓                                             │   │
│  │  Seller receives $85 (after payout delay)          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Core Features:                                             │
│  ├── Seller Dashboard                                      │
│  ├── Product Catalog                                       │
│  ├── Shopping Cart                                         │
│  ├── Checkout (Split Payments)                             │
│  ├── Order Management                                      │
│  ├── Reviews & Ratings                                     │
│  ├── Dispute Resolution                                    │
│  └── Analytics & Reporting                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Database Schema

// prisma/schema.prisma
model Seller {
  id                String   @id @default(cuid())
  userId            String   @unique
  user              User     @relation(fields: [userId], references: [id])

  // Business Info
  businessName      String
  businessType      String   // individual, company
  taxId             String?
  description       String?
  logo              String?

  // Stripe Connect
  stripeAccountId   String?  @unique
  stripeOnboarded   Boolean  @default(false)
  payoutEnabled     Boolean  @default(false)

  // Settings
  commissionRate    Float    @default(0.15) // 15%
  payoutSchedule    String   @default("daily") // daily, weekly, monthly

  // Status
  status            SellerStatus @default(PENDING)
  verifiedAt        DateTime?

  // Relations
  products          Product[]
  orders            OrderItem[]
  payouts           Payout[]

  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
}

enum SellerStatus {
  PENDING
  ACTIVE
  SUSPENDED
  BANNED
}

model Product {
  id                String   @id @default(cuid())
  sellerId          String
  seller            Seller   @relation(fields: [sellerId], references: [id])

  // Product Info
  name              String
  slug              String
  description       String
  price             Int      // in cents
  compareAtPrice    Int?
  currency          String   @default("EUR")

  // Inventory
  sku               String?
  inventory         Int      @default(0)
  trackInventory    Boolean  @default(true)

  // Categorization
  categoryId        String?
  category          Category? @relation(fields: [categoryId], references: [id])
  tags              String[]

  // Media
  images            ProductImage[]

  // Status
  status            ProductStatus @default(DRAFT)
  publishedAt       DateTime?

  // Stats
  viewCount         Int      @default(0)
  salesCount        Int      @default(0)
  rating            Float?
  reviewCount       Int      @default(0)

  // Relations
  variants          ProductVariant[]
  orderItems        OrderItem[]
  reviews           Review[]

  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt

  @@unique([sellerId, slug])
  @@index([categoryId])
  @@index([status])
}

model Order {
  id                String   @id @default(cuid())
  orderNumber       String   @unique

  // Buyer
  userId            String
  user              User     @relation(fields: [userId], references: [id])

  // Totals
  subtotal          Int      // Sum of items
  platformFee       Int      // Platform commission
  shippingTotal     Int
  taxTotal          Int
  total             Int

  // Payment
  stripePaymentId   String?
  paymentStatus     PaymentStatus @default(PENDING)
  paidAt            DateTime?

  // Shipping
  shippingAddress   Json
  shippingMethod    String?

  // Status
  status            OrderStatus @default(PENDING)

  // Items (can be from multiple sellers)
  items             OrderItem[]

  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
}

model OrderItem {
  id                String   @id @default(cuid())
  orderId           String
  order             Order    @relation(fields: [orderId], references: [id])

  // Product
  productId         String
  product           Product  @relation(fields: [productId], references: [id])
  variantId         String?

  // Seller
  sellerId          String
  seller            Seller   @relation(fields: [sellerId], references: [id])

  // Pricing
  price             Int
  quantity          Int
  subtotal          Int      // price * quantity
  commission        Int      // Platform commission
  sellerPayout      Int      // subtotal - commission

  // Status (per-seller fulfillment)
  status            OrderItemStatus @default(PENDING)
  shippedAt         DateTime?
  trackingNumber    String?

  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
}

model Payout {
  id                String   @id @default(cuid())
  sellerId          String
  seller            Seller   @relation(fields: [sellerId], references: [id])

  amount            Int
  currency          String   @default("EUR")
  status            PayoutStatus @default(PENDING)

  stripePayoutId    String?
  paidAt            DateTime?
  failedAt          DateTime?
  failureReason     String?

  // Payout period
  periodStart       DateTime
  periodEnd         DateTime

  createdAt         DateTime @default(now())
}

Seller Onboarding

// lib/marketplace/seller-onboarding.ts
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/db';

export async function createSellerAccount(
  userId: string,
  data: {
    businessName: string;
    businessType: 'individual' | 'company';
    country: string;
    email: string;
  }
): Promise<string> {
  // Create Stripe Connect account
  const account = await stripe.accounts.create({
    type: 'express',
    country: data.country,
    email: data.email,
    business_type: data.businessType,
    business_profile: {
      name: data.businessName
    },
    capabilities: {
      card_payments: { requested: true },
      transfers: { requested: true }
    }
  });

  // Create seller record
  await db.seller.create({
    data: {
      userId,
      businessName: data.businessName,
      businessType: data.businessType,
      stripeAccountId: account.id,
      status: 'PENDING'
    }
  });

  // Generate onboarding link
  const accountLink = await stripe.accountLinks.create({
    account: account.id,
    refresh_url: `${process.env.NEXT_PUBLIC_URL}/seller/onboarding/refresh`,
    return_url: `${process.env.NEXT_PUBLIC_URL}/seller/onboarding/complete`,
    type: 'account_onboarding'
  });

  return accountLink.url;
}

export async function completeSellerOnboarding(
  stripeAccountId: string
): Promise<void> {
  // Check account status
  const account = await stripe.accounts.retrieve(stripeAccountId);

  if (account.charges_enabled && account.payouts_enabled) {
    await db.seller.update({
      where: { stripeAccountId },
      data: {
        stripeOnboarded: true,
        payoutEnabled: true,
        status: 'ACTIVE',
        verifiedAt: new Date()
      }
    });
  }
}

// Webhook handler for account updates
export async function handleAccountUpdated(
  account: Stripe.Account
): Promise<void> {
  await db.seller.update({
    where: { stripeAccountId: account.id },
    data: {
      payoutEnabled: account.payouts_enabled || false,
      stripeOnboarded: account.details_submitted || false
    }
  });
}

Split Payments

// lib/marketplace/checkout.ts
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/db';
import { nanoid } from 'nanoid';

interface CartItem {
  productId: string;
  quantity: number;
  variantId?: string;
}

export async function createMarketplaceCheckout(
  userId: string,
  items: CartItem[],
  shippingAddress: any
): Promise<string> {
  // Group items by seller
  const itemsBySeller = await groupItemsBySeller(items);

  // Calculate totals
  const { subtotal, platformFee, lineItems, transfers } =
    await calculateOrderTotals(itemsBySeller);

  // Create order
  const order = await db.order.create({
    data: {
      orderNumber: `ORD-${nanoid(10).toUpperCase()}`,
      userId,
      subtotal,
      platformFee,
      shippingTotal: 0, // Calculate based on sellers
      taxTotal: 0,
      total: subtotal,
      shippingAddress,
      status: 'PENDING',
      items: {
        create: lineItems.map(item => ({
          productId: item.productId,
          sellerId: item.sellerId,
          variantId: item.variantId,
          price: item.price,
          quantity: item.quantity,
          subtotal: item.subtotal,
          commission: item.commission,
          sellerPayout: item.sellerPayout,
          status: 'PENDING'
        }))
      }
    }
  });

  // Create Stripe Checkout with transfers
  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    customer_email: (await db.user.findUnique({ where: { id: userId } }))?.email,
    line_items: lineItems.map(item => ({
      price_data: {
        currency: 'eur',
        unit_amount: item.price,
        product_data: {
          name: item.productName,
          images: item.images
        }
      },
      quantity: item.quantity
    })),
    payment_intent_data: {
      // Split payment to sellers
      transfer_group: order.id,
      metadata: {
        orderId: order.id
      }
    },
    success_url: `${process.env.NEXT_PUBLIC_URL}/orders/${order.id}/success`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cart`,
    metadata: {
      orderId: order.id
    }
  });

  return session.url!;
}

async function calculateOrderTotals(itemsBySeller: Map<string, CartItem[]>) {
  const lineItems: any[] = [];
  let subtotal = 0;
  let platformFee = 0;
  const transfers: any[] = [];

  for (const [sellerId, items] of itemsBySeller) {
    const seller = await db.seller.findUnique({
      where: { id: sellerId }
    });

    for (const item of items) {
      const product = await db.product.findUnique({
        where: { id: item.productId }
      });

      if (!product) continue;

      const itemSubtotal = product.price * item.quantity;
      const commission = Math.round(itemSubtotal * (seller?.commissionRate || 0.15));
      const sellerPayout = itemSubtotal - commission;

      subtotal += itemSubtotal;
      platformFee += commission;

      lineItems.push({
        productId: product.id,
        productName: product.name,
        sellerId,
        variantId: item.variantId,
        price: product.price,
        quantity: item.quantity,
        subtotal: itemSubtotal,
        commission,
        sellerPayout,
        images: [] // Add product images
      });

      // Prepare transfer for seller
      transfers.push({
        sellerId,
        stripeAccountId: seller?.stripeAccountId,
        amount: sellerPayout
      });
    }
  }

  return { subtotal, platformFee, lineItems, transfers };
}

// After successful payment, create transfers
export async function createSellerTransfers(orderId: string): Promise<void> {
  const order = await db.order.findUnique({
    where: { id: orderId },
    include: {
      items: {
        include: { seller: true }
      }
    }
  });

  if (!order) return;

  // Group payouts by seller
  const payoutsBySeller = new Map<string, number>();

  for (const item of order.items) {
    const current = payoutsBySeller.get(item.sellerId) || 0;
    payoutsBySeller.set(item.sellerId, current + item.sellerPayout);
  }

  // Create transfers
  for (const [sellerId, amount] of payoutsBySeller) {
    const seller = order.items.find(i => i.sellerId === sellerId)?.seller;

    if (!seller?.stripeAccountId) continue;

    await stripe.transfers.create({
      amount,
      currency: 'eur',
      destination: seller.stripeAccountId,
      transfer_group: orderId,
      metadata: {
        orderId,
        sellerId
      }
    });
  }

  // Update order status
  await db.order.update({
    where: { id: orderId },
    data: {
      paymentStatus: 'PAID',
      paidAt: new Date()
    }
  });
}

Seller Dashboard

// app/seller/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { db } from '@/lib/db';

export default async function SellerDashboard() {
  const session = await getServerSession();
  const seller = await db.seller.findUnique({
    where: { userId: session!.user.id }
  });

  const [stats, recentOrders, topProducts] = await Promise.all([
    getSellerStats(seller!.id),
    getRecentOrders(seller!.id),
    getTopProducts(seller!.id)
  ]);

  return (
    <div className="p-6 space-y-6">
      <h1 className="text-2xl font-bold">Seller Dashboard</h1>

      {/* Stats Cards */}
      <div className="grid grid-cols-4 gap-4">
        <StatCard
          title="Revenue (30d)"
          value={`€${(stats.revenue / 100).toFixed(2)}`}
          change={stats.revenueChange}
        />
        <StatCard
          title="Orders"
          value={stats.orderCount}
          change={stats.orderChange}
        />
        <StatCard
          title="Products"
          value={stats.productCount}
        />
        <StatCard
          title="Pending Payout"
          value={`€${(stats.pendingPayout / 100).toFixed(2)}`}
        />
      </div>

      <div className="grid grid-cols-2 gap-6">
        {/* Recent Orders */}
        <div className="bg-white rounded-lg shadow p-6">
          <h2 className="text-lg font-semibold mb-4">Recent Orders</h2>
          <OrderList orders={recentOrders} />
        </div>

        {/* Top Products */}
        <div className="bg-white rounded-lg shadow p-6">
          <h2 className="text-lg font-semibold mb-4">Top Products</h2>
          <ProductList products={topProducts} />
        </div>
      </div>
    </div>
  );
}

async function getSellerStats(sellerId: string) {
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const [revenue, orders, products, pendingPayout] = await Promise.all([
    db.orderItem.aggregate({
      where: {
        sellerId,
        order: { paidAt: { gte: thirtyDaysAgo } }
      },
      _sum: { sellerPayout: true }
    }),
    db.orderItem.count({
      where: {
        sellerId,
        createdAt: { gte: thirtyDaysAgo }
      }
    }),
    db.product.count({ where: { sellerId } }),
    db.orderItem.aggregate({
      where: {
        sellerId,
        order: { paymentStatus: 'PAID' },
        // Not yet paid out
      },
      _sum: { sellerPayout: true }
    })
  ]);

  return {
    revenue: revenue._sum.sellerPayout || 0,
    revenueChange: 0, // Calculate vs previous period
    orderCount: orders,
    orderChange: 0,
    productCount: products,
    pendingPayout: pendingPayout._sum.sellerPayout || 0
  };
}

Platform Economics

Revenue StreamExample
**Commission**10-20% per sale
**Listing Fee**$0.20 per listing
**Subscription**$29/mo for premium
**Promoted Listings**$0.10 per click
**Transaction Fee**2.9% + $0.30

Fazit

Marketplace Platform Development erfordert:

  1. Multi-Party Payments: Stripe Connect für Split Payments
  2. Seller Management: Onboarding und Verification
  3. Trust & Safety: Reviews, Disputes, Moderation
  4. Scalability: Handling vieler Verkäufer und Produkte

Marketplaces sind komplex, aber hochprofitabel.


Bildprompts

  1. "Marketplace platform architecture diagram, buyers sellers and platform"
  2. "Seller dashboard interface, revenue charts and order list"
  3. "Payment flow visualization, split payment to multiple sellers"

Quellen