
Marketplace Building
- 65 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Launch a multi-vendor marketplace with seller onboarding, commission rules, automated Stripe Connect payouts, and vendor dashboards.
About
Covers building a multi-vendor marketplace including seller onboarding, commission configuration, automated payouts via Stripe Connect, and vendor dashboards. A developer uses it to stand up a marketplace platform rather than a single-seller store.
- Seller onboarding and commission-rule configuration
- Automated Stripe Connect payouts and vendor dashboards
Marketplace Building by the numbers
- 65 all-time installs (skills.sh)
- Ranked #3,110 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill marketplace-buildingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Launch a multi-vendor marketplace with seller onboarding, commission rules, automated Stripe Connect payouts, and vendor dashboards.
Files
Marketplace Building
Overview
A multi-vendor marketplace lets independent sellers list products on your platform, collects payment from buyers, deducts your commission, and pays out the remainder to sellers. The key components are: seller onboarding with KYC verification, product listing management per seller, commission calculation, and automated payouts. For Shopify and WooCommerce merchants, purpose-built marketplace apps handle most of this — custom development is needed primarily for highly specific commission structures or white-label marketplace platforms.
When to Use This Skill
- When building a platform where third-party sellers list and sell their own products (not your inventory)
- When you need the platform to collect payment from buyers and distribute funds to sellers minus a commission
- When sellers need their own dashboard to manage listings, view orders, and track earnings
- When complying with KYC (Know Your Customer) requirements for seller identity verification
- When designing the commission structure (percentage, tiered, category-based) and payout schedule
Core Instructions
Step 1: Determine your platform and choose the right marketplace tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Multi Vendor Marketplace by Webkul or BOLD Multi-Vendor | Webkul's app adds seller accounts, product management, commission rules, and a seller dashboard to Shopify without replacing the storefront |
| WooCommerce | Dokan Multi-Vendor (most popular, 60K+ installs) or WC Vendors | Dokan is purpose-built for WooCommerce marketplaces with seller onboarding, commission management, payout requests, and a seller dashboard |
| BigCommerce | Multi Vendor Marketplace by Webkul (BigCommerce version) | Webkul has a BigCommerce version of their marketplace app |
| Custom / Headless | Build seller accounts + Stripe Connect for KYC and payouts | Stripe Connect handles KYC, bank account collection, and 1099-K tax forms — use it for any custom marketplace |
Step 2: Set up seller onboarding and KYC
KYC (Know Your Customer) is required to verify seller identity before you can legally send them payments. Using Stripe Connect for this is strongly recommended — building it yourself is expensive and legally complex.
Shopify — Multi Vendor Marketplace by Webkul
1. Install Multi Vendor Marketplace by Webkul from the Shopify App Store 2. Sellers register via a seller signup form (customizable URL, e.g., yourstore.com/seller/register) 3. You approve seller applications manually in the Webkul admin — review the seller profile before approval 4. Connect Webkul to Stripe Connect for payouts: go to Webkul → Settings → Payment → Stripe Connect and enter your Stripe credentials 5. When you approve a seller, Webkul sends them an onboarding email with a link to connect their Stripe account (Stripe Express account — Stripe handles KYC and bank details) 6. Seller is live once their Stripe Express account is verified (Stripe notifies you via webhook)
WooCommerce — Dokan Multi-Vendor
1. Install Dokan Multi-Vendor Plugin from WordPress.org (free) or Dokan.com (pro) 2. Enable seller registration: go to Dokan → Settings → General → Allow Registration 3. Customize the seller registration form with required fields (business name, tax ID, bank info) 4. For KYC: Dokan Pro integrates with Stripe Connect — go to Dokan → Settings → Withdrawal → Stripe Connect and enter your Stripe platform credentials 5. Sellers connect their bank accounts via the Stripe Connect onboarding flow built into Dokan 6. In Dokan → Vendors, approve or reject seller applications manually
Custom / Headless — Stripe Connect for KYC
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Create a Stripe Express account for a new seller and return the onboarding URL
async function onboardSeller(sellerId: string, sellerEmail: string): Promise<string> {
// Create the Stripe Express account
const account = await stripe.accounts.create({
type: 'express',
email: sellerEmail,
capabilities: {
transfers: { requested: true },
},
});
// Store the Stripe account ID on the seller record
await db.sellers.update(sellerId, { stripe_account_id: account.id });
// Generate the onboarding link (valid for 24 hours)
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: `${process.env.APP_URL}/seller/onboarding/refresh`,
return_url: `${process.env.APP_URL}/seller/onboarding/complete`,
type: 'account_onboarding',
});
return accountLink.url; // send this URL to the seller
}
// Webhook handler: activate seller when Stripe confirms KYC is complete
async function handleStripeAccountUpdated(account: Stripe.Account): Promise<void> {
const seller = await db.sellers.findByStripeAccountId(account.id);
if (!seller) return;
if (account.charges_enabled && account.payouts_enabled && seller.status !== 'active') {
await db.sellers.update(seller.id, { status: 'active' });
// Send welcome email to seller
}
}Step 3: Set up product listings per seller
Shopify (Webkul)
1. Approved sellers log in to their Seller Dashboard at yourstore.com/seller/dashboard 2. Sellers add products from their dashboard — products are submitted to you for approval before going live (configurable in Webkul settings) 3. You control whether sellers can set their own prices or if all prices need your approval 4. Product images, descriptions, and inventory are all managed by the seller from their dashboard
WooCommerce (Dokan)
1. Sellers access their store dashboard at yourstore.com/dashboard 2. Sellers add products from Dokan → Products → Add New 3. Enable "Product Review" in Dokan settings to require your approval before new products go live 4. Sellers manage their own inventory counts, product variations, and prices 5. In Dokan Pro, you can set commission rates at the product level, category level, or globally
Step 4: Configure commission rules and payouts
Shopify (Webkul)
1. Go to Webkul → Commission to set commission rates:
- Global commission: e.g., 15% on all sales
- Seller-specific: override for specific sellers (e.g., 10% for VIP sellers)
- Category-specific: different rates by product category
2. Commissions are deducted automatically from each order when paid 3. Seller earnings are tracked in Webkul → Payments → Seller Transactions 4. To pay out sellers: go to Webkul → Payments → Process Payout. Select sellers and click Process via Stripe Connect — funds transfer from your Stripe balance to the seller's connected account
WooCommerce (Dokan)
1. Go to Dokan → Settings → Selling → Commission to set the global commission rate 2. Override per-seller in Dokan → Vendors → [Seller] → Commission 3. Sellers request withdrawals from their dashboard (Dokan → Withdraw Requests) 4. You approve withdrawal requests in Dokan → Withdraw Requests → Pending 5. For automated payouts via Stripe: Dokan Pro's Stripe Connect module automatically processes approved withdrawal requests
Custom / Headless — commission and payout logic
// Calculate commission and record seller earnings when an order is paid
async function recordSellerEarning(params: {
orderId: string;
sellerId: string;
grossAmountCents: number; // what buyer paid for this seller's items
commissionRate: number; // e.g., 0.15 for 15%
}): Promise<void> {
const commissionCents = Math.round(params.grossAmountCents * params.commissionRate);
const netAmountCents = params.grossAmountCents - commissionCents;
// Funds held until return window closes (e.g., 30 days)
const availableAt = new Date();
availableAt.setDate(availableAt.getDate() + 30);
await db.sellerEarnings.insert({
seller_id: params.sellerId,
order_id: params.orderId,
gross_amount_cents: params.grossAmountCents,
commission_cents: commissionCents,
net_amount_cents: netAmountCents,
status: 'held', // becomes 'available' after return window
available_at: availableAt,
});
}
// Transfer available earnings to seller's Stripe account
async function payoutSeller(sellerId: string): Promise<void> {
const seller = await db.sellers.findById(sellerId);
const availableEarnings = await db.sellerEarnings.findAll({
seller_id: sellerId,
status: 'available',
available_at: { lte: new Date() },
});
const totalCents = availableEarnings.reduce((s, e) => s + e.net_amount_cents, 0);
if (totalCents < 100) return; // minimum payout $1.00
// Transfer from your Stripe balance to seller's connected account
const transfer = await stripe.transfers.create({
amount: totalCents,
currency: 'usd',
destination: seller.stripe_account_id,
metadata: { seller_id: sellerId },
});
// Mark earnings as paid out
await db.sellerEarnings.updateMany(
availableEarnings.map(e => e.id),
{ status: 'paid_out' }
);
}Step 5: Set up seller dashboards
Shopify (Webkul)
- Sellers access a Webkul-provided dashboard at
yourstore.com/seller/dashboardshowing: orders, products, earnings summary, and payout history - Customize the dashboard appearance (colors, logo) in Webkul → Settings → Dashboard
WooCommerce (Dokan)
- Dokan provides a full frontend dashboard at
yourstore.com/dashboardwith: sales analytics, product management, withdrawal requests, and order management - The dashboard is highly customizable via Dokan's template overrides
Best Practices
- Use Stripe Connect Express for all seller payouts — Express handles KYC, bank account verification, and IRS 1099-K reporting; building this yourself is expensive and legally risky
- Hold funds for the return window — don't release earnings to sellers until the buyer's return window closes; releasing early means the platform absorbs refund losses
- Record commission in the same transaction as order confirmation — never compute commission asynchronously from a queue that might fail; the earning record must be atomic with the order
- Send payout summaries to sellers by email — weekly earnings summaries with order-level detail build seller trust and reduce support contacts
- Block seller publishing until Stripe onboarding is complete — check
charges_enabled && payouts_enabledon the Stripe account before allowing a seller to publish listings
Common Pitfalls
| Problem | Solution |
|---|---|
| Payout fails but earnings marked as paid | In Stripe Connect, use the transfer.created webhook to confirm success before marking earnings as paid_out; never mark paid-out in the same call that initiates the transfer |
| Platform pays out before buyer payment clears | Only trigger payout eligibility from the payment_intent.succeeded webhook, not from checkout session creation |
| Seller lists products before KYC is verified | Check Stripe account status (charges_enabled && payouts_enabled) before allowing product publication; Webkul and Dokan do this automatically |
| Seller disputes commission deduction | Store the commission rate and gross amount on every seller_earning record; show sellers their commission calculation history in the seller dashboard |
Related Skills
- @multi-channel-selling
- @vendor-management
- @b2b-commerce
- @order-management-system
{
"context": "Tests whether the agent implements all three commission types correctly, enforces the 30-day return window hold, triggers earning recording from the right Stripe event, caps commission on shipping, and logs calculation inputs alongside results.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Percentage commission formula",
"max_score": 8,
"description": "Percentage commission is calculated as Math.round(grossAmountCents * (rate / 100)) — uses Math.round and divides rate by 100"
},
{
"name": "Fixed commission in cents",
"max_score": 8,
"description": "Fixed commission is calculated as commission_rate * 100 (converting dollars to cents), not just commission_rate"
},
{
"name": "Tiered commission boundaries",
"max_score": 10,
"description": "Tiered commission uses three tiers: 20% on first $1,000 (100000 cents), 15% on $1,000-$10,000, 10% above $10,000"
},
{
"name": "Tiered cumulative calculation",
"max_score": 8,
"description": "Tiered commission applies each rate only to the portion of gross amount within that tier (cumulative/bracket-style), not a flat rate on the whole amount"
},
{
"name": "Shipping excluded from gross",
"max_score": 10,
"description": "The gross_amount passed to commission calculation is the item amount only (itemAmountCents), NOT including shippingAmountCents"
},
{
"name": "30-day availability hold",
"max_score": 10,
"description": "available_at is set to 30 days in the future from the current date (using setDate(getDate() + 30) or equivalent)"
},
{
"name": "Initial status is held",
"max_score": 8,
"description": "New seller_earnings records are inserted with status 'held' (not 'pending' or 'available')"
},
{
"name": "payment_intent.succeeded trigger",
"max_score": 12,
"description": "The webhook handler calls recordSellerEarning only for 'payment_intent.succeeded' events, NOT for checkout.session.completed or other earlier events"
},
{
"name": "Commission inputs logged",
"max_score": 10,
"description": "The earning record stores the commission_type and commission_rate (the inputs) alongside the computed commission amount"
},
{
"name": "Atomic transaction",
"max_score": 8,
"description": "recordSellerEarning wraps the earning insert inside a database transaction (using db.transaction or equivalent)"
},
{
"name": "Net amount derived correctly",
"max_score": 8,
"description": "net_amount is stored as gross_amount minus commission (not gross including shipping)"
}
]
}
Commission Engine and Earning Records
Problem/Feature Description
ShopBridge is a B2B marketplace that connects wholesale suppliers with retail buyers. Different suppliers have negotiated different commission arrangements with the platform: some pay a percentage of each sale, others have agreed to a flat fee per transaction, and the platform's highest-volume suppliers are on a tiered structure where the commission rate decreases as their monthly volume grows.
The finance team has been receiving disputes from suppliers who claim the platform charged them the wrong commission amount. The root cause is that the current system recalculates commission at payout time rather than recording it at the moment of sale — meaning rate changes between sale and payout lead to discrepancies. They need a reliable earning-recording system that captures the commission calculation at the exact moment of the sale.
Additionally, suppliers are frustrated because funds are occasionally released to them before buyers have had a chance to return items, leaving the platform exposed to losses when refunds come in. The system needs to enforce a holding period before earnings become available for payout.
Output Specification
Produce a TypeScript file commission-engine.ts containing:
1. calculateCommission(seller: Seller, grossAmountCents: number): number — returns the commission in cents for each commission type. 2. recordSellerEarning(orderId: string, sellerId: string, itemAmountCents: number, shippingAmountCents: number): Promise<void> — records the earning, applying the appropriate hold period. 3. handlePaymentWebhook(event: StripeEvent): Promise<void> — a Stripe webhook handler that calls recordSellerEarning at the correct point in the payment lifecycle.
Also produce a commission-test-cases.md file showing worked examples of commission calculation for:
- A percentage-commission seller with 15% rate on a $200 sale
- A fixed-commission seller with $5 fee on a $200 sale
- A tiered-commission seller on a $1,500 sale (showing the tier boundary calculation)
Input Files
The following stub types are provided. Extract them before beginning.
=============== FILE: inputs/types.ts =============== export interface Seller { id: string; name: string; commission_type: 'percentage' | 'fixed' | 'tiered'; commission_rate: number; payout_schedule: string; status: string; }
export interface SellerEarningInsert { seller_id: string; order_id: string; gross_amount: number; commission: number; net_amount: number; commission_type: string; commission_rate: number; status: string; available_at: Date; }
export interface StripeEvent { type: string; data: { object: { id: string; metadata: { order_id?: string; seller_id?: string }; }; }; }
export interface DB { sellers: { findById(id: string): Promise<Seller>; }; sellerEarnings: { insert(data: SellerEarningInsert): Promise<void>; }; transaction<T>(fn: (tx: DB) => Promise<T>): Promise<T>; }
{
"context": "Tests whether the agent implements bulletproof payout processing with correct status transitions, minimum threshold, atomic transactions, multi-seller transfer groups, suspended seller scoping, email summaries, and the correct earnings dashboard query.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Minimum payout threshold",
"max_score": 8,
"description": "Payouts below 100 cents ($1.00) are skipped — the code checks totalAmount < 100 and continues to the next seller"
},
{
"name": "stripe.transfers.create used",
"max_score": 8,
"description": "Funds are sent to sellers using stripe.transfers.create (not stripe.payouts.create or a different method)"
},
{
"name": "Transfer metadata included",
"max_score": 6,
"description": "stripe.transfers.create includes a metadata object containing at minimum payout_id and seller_id"
},
{
"name": "Atomic status update on success",
"max_score": 12,
"description": "Payout status ('paid') and earning status ('paid_out') are updated together inside a single database transaction — not in two separate calls"
},
{
"name": "paid_out only after transfer confirms",
"max_score": 10,
"description": "Earning status is set to 'paid_out' only after the stripe.transfers.create call returns successfully — NOT before the transfer"
},
{
"name": "Failed payout status set",
"max_score": 8,
"description": "On Stripe transfer error, the payout record status is updated to 'failed' (not left as 'pending')"
},
{
"name": "Active-only sellers for new payouts",
"max_score": 8,
"description": "processWeeklyPayouts queries for sellers with status: 'active' only — suspended sellers are excluded from new payouts"
},
{
"name": "transfer_group for multi-seller orders",
"max_score": 8,
"description": "processMultiSellerOrder creates a PaymentIntent with transfer_group set to the orderId, linking all transfers to the same order"
},
{
"name": "Separate transfer per seller",
"max_score": 8,
"description": "processMultiSellerOrder creates individual stripe.transfers.create calls for each seller, each with transfer_group: orderId"
},
{
"name": "Earnings summary query structure",
"max_score": 8,
"description": "The earnings API uses SUM(CASE WHEN status = 'available' ...) and SUM(CASE WHEN status = 'held' ...) to compute available_balance and held_balance separately"
},
{
"name": "Payout summary email sent",
"max_score": 8,
"description": "After a successful payout, an email is sent to the seller summarizing the payout (referencing orders, commission, and/or payout amount)"
},
{
"name": "Payout period tracked",
"max_score": 8,
"description": "Each payout record includes period_start and period_end dates (covering the 7-day window for weekly payouts)"
}
]
}
Payout Processing and Earnings Dashboard
Problem/Feature Description
ArtisanHub is an online marketplace for independent craftspeople. Every week, the platform processes payouts to sellers whose earnings have cleared the return window. The team recently had a painful incident: a payout transfer to a seller's bank failed partway through, but the system had already marked the associated earnings as disbursed — meaning those earnings were never retried and the seller was left short-changed. The team needs to make the payout flow bulletproof, with correct status transitions that can only happen once a transfer is confirmed.
A separate issue has come up with suspended sellers: when a seller account is suspended for policy violations, the ops team found that the suspension was also inadvertently blocking payouts for orders that had already been completed before the suspension. Sellers were owed money for sales they had legitimately made, but weren't receiving it. The payout logic needs to correctly scope which sellers are eligible for new payouts.
Finally, seller retention has been suffering because sellers have no visibility into their earnings. A finance dashboard endpoint is needed that gives each seller a real-time snapshot of their balance, broken down by funds that are available now, funds still in the holding period, and historical totals.
Output Specification
Produce two TypeScript files:
`payout-processor.ts` containing: 1. processWeeklyPayouts(): Promise<void> — runs the weekly payout job for all eligible sellers. 2. processMultiSellerOrder(orderId: string, sellerAmounts: Map<string, number>): Promise<void> — handles a payment for an order with items from multiple sellers, creating a grouped payment intent and individual transfers.
`earnings-api.ts` containing: 1. An Express route handler for GET /api/seller/earnings that returns an earnings summary for the authenticated seller.
Also produce a payout-design.md file documenting:
- How the system handles a payout failure (what states are set and when)
- What determines whether a suspended seller receives a payout
Input Files
The following stub types are provided. Extract them before beginning.
=============== FILE: inputs/types.ts =============== export interface Seller { id: string; name: string; email: string; stripe_account_id: string; status: 'pending' | 'active' | 'suspended' | 'deactivated'; payout_schedule: 'daily' | 'weekly' | 'monthly' | 'manual'; }
export interface SellerEarning { id: string; seller_id: string; order_id: string; gross_amount: number; commission: number; net_amount: number; status: 'pending' | 'held' | 'available' | 'paid_out'; available_at: Date; }
export interface Payout { id: string; seller_id: string; amount: number; stripe_payout_id: string | null; status: 'pending' | 'processing' | 'paid' | 'failed'; period_start: string; period_end: string; }
export interface DB { sellers: { findAll(filter: any): Promise<Seller[]>; findById(id: string): Promise<Seller>; }; sellerEarnings: { findAll(filter: any): Promise<SellerEarning[]>; updateMany(ids: string[], data: Partial<SellerEarning>): Promise<void>; raw(query: string, params: any[]): Promise<{ rows: any[] }>; }; payouts: { insert(data: Omit<Payout, 'id'>): Promise<Payout>; update(id: string, data: Partial<Payout>): Promise<void>; findAll(filter: any): Promise<Payout[]>; }; transaction<T>(fn: (tx: DB) => Promise<T>): Promise<T>; }
export interface EmailService { send(opts: { to: string; template: string; data: Record<string, any> }): Promise<void>; }
{
"context": "Tests whether the agent correctly implements seller onboarding using Stripe Connect Express accounts, generates the proper onboarding link type, and checks the right conditions before activating a seller.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Express account type",
"max_score": 12,
"description": "stripe.accounts.create is called with type: 'express' (not 'standard' or 'custom')"
},
{
"name": "Transfers capability requested",
"max_score": 10,
"description": "The accounts.create call includes capabilities: { transfers: { requested: true } }"
},
{
"name": "Payout schedule propagated",
"max_score": 8,
"description": "The accounts.create call includes settings.payouts.schedule.interval set from the seller's payout_schedule field"
},
{
"name": "account_onboarding link type",
"max_score": 12,
"description": "stripe.accountLinks.create is called with type: 'account_onboarding' (not 'account_update' or another type)"
},
{
"name": "refresh_url included",
"max_score": 6,
"description": "The accountLinks.create call includes a refresh_url parameter"
},
{
"name": "return_url included",
"max_score": 6,
"description": "The accountLinks.create call includes a return_url parameter"
},
{
"name": "Reuses existing account",
"max_score": 8,
"description": "If stripe_account_id is already set on the seller, the code skips account creation and reuses the existing ID"
},
{
"name": "Saves stripe_account_id",
"max_score": 8,
"description": "After creating a new Stripe account, stripe_account_id is persisted back to the seller record in the database"
},
{
"name": "Both conditions checked for activation",
"max_score": 14,
"description": "The webhook handler checks BOTH charges_enabled AND payouts_enabled before setting seller status to 'active' — not just one of the two"
},
{
"name": "Activation email sent",
"max_score": 8,
"description": "An email is sent to the seller after their status is set to 'active' (e.g. using a 'seller-account-approved' or equivalent template)"
},
{
"name": "No double activation",
"max_score": 8,
"description": "The webhook handler does NOT update the seller's status if they are already 'active' (guards against repeated webhook delivery)"
}
]
}
Seller Onboarding Module
Problem/Feature Description
CraftHive is a handmade goods marketplace launching later this year. The platform lets independent artisans ("sellers") list and sell their own products while CraftHive collects payments on their behalf and pays them out on a schedule. Before a seller can publish listings, they must complete identity verification and link a bank account — this is a legal requirement in most jurisdictions.
The engineering team needs to implement the seller onboarding flow. When a new seller signs up, the system should set up the necessary third-party account infrastructure and return a link the seller can follow to complete identity verification and payment setup. Once the seller finishes this external process, a webhook from the payment provider should update their status in the platform so they can start selling.
A seller's onboarding state needs to be clearly tracked: newly registered sellers start in a pending state and can only be activated after the payment provider confirms they are fully capable of both receiving charges and having funds transferred to them.
Output Specification
Implement this as a TypeScript module. Produce a single file seller-onboarding.ts containing:
1. A function createSellerOnboardingLink(sellerId: string): Promise<string> that provisions the third-party account (if not already created) and returns the onboarding URL. 2. A webhook handler function handleAccountUpdated(account: any): Promise<void> that processes the event fired when a seller's external account details change, updating the seller's status in the database appropriately. 3. A short IMPLEMENTATION_NOTES.md file explaining:
- Which account type was chosen and why (1-2 sentences)
- What condition is checked to determine a seller is ready to go live (1 sentence)
- What happens after a seller is activated (1 sentence)
Input Files
The following stub types and mock database are provided as inputs. Extract them before beginning.
=============== FILE: inputs/types.ts =============== export interface Seller { id: string; name: string; email: string; user_id: string; stripe_account_id: string | null; status: string; commission_type: string; commission_rate: number; payout_schedule: string; }
export interface DB { sellers: { findById(id: string): Promise<Seller>; findByStripeAccountId(stripeAccountId: string): Promise<Seller | null>; update(id: string, data: Partial<Seller>): Promise<void>; }; }
export interface EmailService { send(opts: { to: string; template: string; data: Record<string, any> }): Promise<void>; }
{
"name": "finsi/marketplace-building",
"version": "0.1.0",
"summary": "Multi-vendor marketplace architecture — seller onboarding, commissions, payouts",
"skills": {
"marketplace-building": {
"path": "SKILL.md"
}
}
}