Files
Portfolio/blog-posts/96-marketplace-platform.md
T
damjan_savicandClaude Opus 4.5 43484c5023 Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:42:11 +01:00

666 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```tsx
// 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 Stream | Example |
|---------------|---------|
| **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
- [Stripe Connect](https://stripe.com/docs/connect)
- [Marketplace Business Models](https://a16z.com/marketplace-100/)
- [Sharetribe Marketplace Guide](https://www.sharetribe.com/academy/)
- [Building Marketplaces](https://www.nfx.com/post/marketplace-handbook)