Claro
CompletedMulti-tenant marketplace for digital products.

Click image to enlarge
1 / 4
Claro is a platform where vendors sell digital products — courses, files, content — each from their own store. The platform takes a cut. I built it to understand what it actually takes to run a marketplace with real financial complexity underneath.
Each vendor gets their own subdomain, their own Stripe Connect account, and their own isolated data. When a buyer purchases something, Stripe splits the payment automatically. Vendor gets their share, platform takes 10%. Stripe handles the math.
Content is gated behind purchase. You buy a course, you get access. You don't buy it, you get a 404 — not a 403. That's intentional. A 403 tells you the resource exists. A 404 tells you nothing. On a marketplace with multiple vendors and sensitive content, that difference matters.
How I built it
Payload CMS 3 handles the backend — collections, auth, admin panel, access control. It generates types from collections and those types flow through tRPC all the way to the component. The whole stack is type-safe end-to-end, which means the compiler catches a lot of things before they become runtime problems.
Cart state lives in Zustand with localStorage, but not one cart — separate carts per tenant. If you're shopping across two vendors, your items stay separated. At checkout, the server re-verifies every product before creating the Stripe session. What's in localStorage is convenient, not trusted.
Stripe webhooks are idempotent. Stripe sometimes sends the same event twice. I check for an existing order before writing anything, so the second one does nothing.
Vendor onboarding is blocked until Stripe Connect is complete. You can register but you can't list products until Stripe confirms your account details. A webhook flips that flag when it's done.
Reviews are one per buyer per product, enforced by a unique index in MongoDB on the product and user pair. The app can't create a duplicate because the database won't allow it. The AI piece generates a draft review using Claude's tool use with Zod-validated output. Either the schema validates or it fails. I didn't want to parse strings and hope.


