Multi-Vendor Marketplace
Two audiences on one dataset: sellers who list and shoppers who buy, both modelled by extending customer:base_customer, with one catalog type that references its seller. What the platform provides, and where the two-sided economics remain yours.
The Project
- •Third parties create and price their own listings, and you review them before they go live.
- •A shopper's cart can legitimately contain items from more than one seller.
- •Reviews attach to a listing, and you care whether the reviewer actually bought it.
- •Sellers need a profile with public branding — a store name, a logo, a banner, contact details.
- •You need to answer "what did this seller sell last month" as a query, not a report you assemble by hand.
ℹ️ The exemplar behind this page
Architecture & Design
{
"name": "seller",
"namespace": "voltmart",
"schema_type": "object",
"schema_content": {
"name": "seller",
"parent_type": "tenant",
"extends": "customer:base_customer",
"attributes": [
{"name": "store_name", "type": "string"},
{"name": "store_logo", "type": "Image"},
{"name": "store_banner", "type": "Image"},
{"name": "description", "type": "string"},
{"name": "contact_email", "type": "string"},
{"name": "contact_phone", "type": "string"},
{"name": "is_approved", "type": "boolean"}
]
}
}| Type in voltmart | Extends | Notes |
|---|---|---|
| seller | customer:base_customer | store branding plus is_approved |
| shopper | customer:base_customer | the buying side of the same base type |
| product | product:base_product | references Seller as actor; adds brand, model, condition, stock_quantity, a photo list and is_visible |
| cart | cart:base_cart | status of open, checked_out, abandoned or expired |
| cart_item | cart:base_cart_item | parented to cart; references Product as primary_item |
| order | order:base_order | references Shopper as actor; adds order_number and a shipping address |
| order_item | order:base_order_item | parented to order |
| payment | payment:base_payment | references Order as primary_item; adds transaction_id and paid_at |
| review | feedback:base_feedback | references Product as primary_item and Shopper as actor; adds is_verified_purchase |
| attachment | attachment:base_attachment | file records for listing media |
⚠️ Seller is a record, not a role
The End-to-End Flow
- 1
Onboard a seller
A seller record is created with is_approved false. Nothing in the platform enforces that flag — see the next section — so this step is where your own gate belongs, whether that is an admin action, an approval workflow, or an access policy that only permits listing creation once the flag is set.
- 2
List a product
The seller creates product records referencing themselves as actor. price, currency, sku and the active/inactive/discontinued status all come from base_product; brand, model, condition, stock_quantity and the photo gallery are voltmart's own additions.
- 3
Browse
Shoppers read the catalog through the ordinary CRUD surface with cursor-based pagination — a limit and a cursor, never page and page_size, which are silently ignored. Filtering to one seller is a query on the reference.
- 4
Add to cart
cart / add_to_cart. The first call materialises the cart and ensures the shopper has a customer profile, which means the shopper's access policy has to permit that create. Items from different sellers land in the same cart with no complaint.
- 5
Check out
cart / checkout_cart converts the cart into one order and copies the cart_item rows into order_item rows. One cart produces exactly one order, regardless of how many sellers contributed items to it.
- 6
Take payment
stripe_checkout / create_checkout_session, then the payment service tracks it: authorize_payment, capture_payment, and refund_payment if needed. The payment references the order, and carries a single amount.
- 7
Fulfil
order / place_order, fulfill_order and cancel_order run the order state machine — pending, confirmed, fulfilled, cancelled, refunded — stamping confirmed_at, fulfilled_at and cancelled_at as they go. An illegal transition returns 409 with INVALID_STATE_TRANSITION rather than being applied.
- 8
Review
The shopper writes a review referencing the product and themselves. base_feedback provides the draft, published, hidden, flagged and deleted states, so moderation is a transition rather than a delete.
What You Get Without Building It
- •The entire buy-side transaction — cart materialisation, totals, cart-to-order conversion, the payment lifecycle — with idempotent operations and guarded transitions.
- •Two distinct customer types over one base type, so both parties inherit the same profile machinery without a shared table you have to discriminate on.
- •Stripe Checkout wired as an integration: stripe_api_key and stripe_webhook_secret held in the keystore and masked on read, rather than an SDK in your code.
- •SendGrid email with campaign, template, sender, message and suppression modelled, plus the notification service for in-app messages.
- •An attachment type for listing media, with file storage on a bucket you control.
- •An admin panel generated per type — separate seller and shopper sections, plus product, order, payment, cart and review — from the base admin UI schemas, and a public landing page built from hero, catalog, product-detail, cart and order-detail blocks.
- •Metric cards and charts as records: VoltMart ships total revenue, active orders, active products, a revenue trend and orders by status.
- •Tenancy, ownership and the platform roles underneath all of it, with fq_name as the record address rather than a tenant column in your schema.
How to Implement in Supero
Model it
- 1
Build the schema
Describe the marketplace in App Studio — it generates the schemas and a reviewable UI — or author it over MCP from your editor. Aim at the object model above: a type opts into a service by extending its base type (cart:base_cart, order:base_order, payment:base_payment, and customer:base_customer twice, for buyer and seller). Schemas are live on the CRUD API the moment they upload — there is no separate build step.
- 2
Configure the services
Imported services arrive uncredentialed. Put each provider’s keys in the keystore — here that is Stripe for checkout, plus email/SMS for notifications — see Email & Notifications. Until a service is credentialed, its calls fail with a clear error rather than silently doing nothing.
What you write yourself
⚠️ There are no payouts, no commission and no split settlement
| Decision | The state of it in the exemplar |
|---|---|
| Seller approval | is_approved is a boolean nobody checks. Nothing stops an unapproved seller from creating listings. The gate is an access policy or a workflow you write. |
| One order, many sellers | A cart spanning three sellers checks out into one order with one status. Per-seller sub-orders, independent fulfilment and per-seller cancellation are a modelling decision — most often a child type under order — not a service feature. |
| Stock | stock_quantity is mandatory with a minimum of zero, and that is all it is. Nothing decrements it at checkout and nothing refuses a purchase at zero. The platform takes no lock on inventory; overselling is prevented by logic you write. |
| Verified purchase | is_verified_purchase on a review is a flag you set. Correlating the reviewer with an order_item is your query. |
| Seller data isolation | Preventing one seller from reading another's orders is an access-policy question, and it is the highest-consequence one in this shape. Model it explicitly and test it explicitly. |
| Shipping and tax | The order carries shipping_address, shipping_city, shipping_state, shipping_zip, and inherits tax_amount, shipping_amount and discount_amount. Rates, carriers and jurisdictions are yours. |
| Dispute and refund policy | refund_payment is an operation, and it is cumulative rather than idempotent — call it twice and it refunds twice, so a retried or double-submitted refund needs its own guard (a dedup key, a status check). capture_payment, by contrast, is a state transition: capturing an already-captured payment changes nothing. Who may refund, within what window, and whether a seller is debited, are all yours. |
💡 Run the order lifecycle as a saga
Deployment
- 1
Preview while you iterate
A preview deploy answers at
https://preview-{domain}-{project}.supero.livefor a short lease while you build and test the order flow. Publish Your App covers how preview and live differ. - 2
Go Live
Go Live publishes to a permanent, Supero-hosted URL —
https://live-{domain}-{project}.supero.live— with no cloud account to connect and nothing to provision. It is asynchronous: you get a202and a poll URL, then the live address. The full contract is in Publish Your App. - 3
Watch the logs
When a deploy fails or the live app misbehaves, the **Cloud Deploy Logs** page in the dashboard shows the published service’s container logs — pick the Live lane to read the published app rather than the preview build (up to ~24 hours back, ~500 lines per request). For platform activity — requests, auth, errors across your domain —
GET /api/v1/logsis a filtered read, and a single request traces across services at/api/v1/logs/trace/{correlation_id}when you set your ownX-Correlation-ID. See Operations & Observability.
Final Thoughts
Where to Go Next
The isolation question
Access Control
The decision that matters most here: what a seller may read, and what a shopper may not. Read it
The mechanism
Transactional Services
services/execute, the state machines behind cart, order and payment, and idempotency. Read it
The gaps
Workflows & Automation
Where seller approval, stock checks and payout logic actually live. Read it
The boundary
Building Multi-Tenant Apps
Whether each seller should be a tenant, and what that costs you. Read it
On this page