How we extended Medusa for a legal document marketplace
JuraDocs needed more than products, carts, and orders. We used Medusa as the commerce foundation, then added professional verification, document safety, seller-aware payouts, invoices, and admin recovery around it.

Boumakrane Hamza
Technical lead, QuadiByte
JuraDocs sells legal documents, but the product is really trust. A buyer has to know the seller is a real lawyer, the file is safe to release, and the payout can be explained if a payment, webhook, or transfer retries later. A normal commerce backend does not make those guarantees on its own.
Medusa gave us products, carts, orders, users, stores, workflows, and an admin foundation. JuraDocs needed the legal marketplace layer around that foundation: profession permissions, registry-backed seller verification, role-aware file access, fee splits, payout records, invoices, and operational tools.
So we used Medusa as the commerce system underneath JuraDocs, then made the legal-marketplace concepts explicit instead of hiding them in product metadata.
We kept Medusa's model instead of fighting it
Medusa's own marketplace guidance points teams toward custom seller models, module links, protected vendor routes, and workflow customization. That matched what JuraDocs needed. Rather than fork Medusa, we attached the JuraDocs domain to Medusa records through linkable models, keeping the commerce records clean while still making seller ownership, permissions, and payout state queryable.
An account starts as a Medusa user, because authentication and identity still belong there. If the chosen profession is allowed to sell, the registration workflow creates a vendor profile, creates a Medusa store, and links user to vendor to store. Products stay Medusa products, linked back to the seller's store. Orders stay Medusa orders, with payout accounting linked alongside through a read-only link so the order itself stays clean.
- User to Vendor: one legal account owns one seller profile.
- Vendor to Store: the professional profile is connected to the Medusa store that owns products.
- Store to Product: products remain normal Medusa catalog objects, with seller ownership attached through links.
- Profession to Vendor: professions define who can sell, who can only buy, and which marketplace fee rules can apply.
- VendorShare to Order and Vendor: payouts are tracked outside the order, but tied back to the commerce event that created them.
Registration became a workflow, not a form
A normal registration flow creates a user, maybe a customer profile, and stops. JuraDocs makes a bigger decision at signup, and the profession decides it, not the person: is this an account that can only buy, or one that should also become a seller with a vendor profile and a store?
The registration workflow always creates the user and a customer profile, because even sellers can buy documents and checkout needs a customer identity. Then it looks at the chosen profession. If the profession has seller permission, the workflow creates the vendor profile, links the user to it, creates a store, and links the store to the vendor.
The vendor profile carries the seller's bar registration number and an uploaded proof document alongside the ordinary company fields. Verification then checks that number against a bar-association registry. Because real registry names are messy, each association is stored with a normalized name and a set of known aliases, so a seller who types their bar's name a little differently still matches.
That is the difference between a seller table and a trust gate. JuraDocs has to know whether someone is allowed to publish paid legal material before any product workflow runs.
A legal document has a lifecycle
Medusa can handle digital commerce patterns, but JuraDocs needed a stricter document lifecycle. The source file, the processed file, and the buyer-facing file are not always the same thing.
- Upload routes only presign expected file areas, such as original and anonymized document paths.
- An anonymization callback validates the job and token before accepting external processing results.
- Document processing can move through completed, failed, and still-processing states.
- The owner can access the owner-facing file, while a buyer gets the published or download-safe version.
- A product file route checks whether the requester is the owner, a purchaser, or accessing a free product before issuing a short-lived URL.
That last point carries real weight. A buyer must never receive the private source document by accident, and the seller must still be able to inspect what they uploaded and what the platform prepared from it. File access in JuraDocs resolves against the requester's role before any URL is issued, and the URL it returns is short-lived.
Checkout became seller-aware settlement
Checkout is where JuraDocs' rules come together, since that is when money moves and the buyer gets the document. The price can be the listed one, or a price the buyer and seller agreed on. Before charging, the platform checks that the buyer is allowed that price, does not already own the document, and is not a seller buying their own work. Those are JuraDocs rules, so the checkout flow accounts for them directly.
Once those checks pass, a single click has to fan out into three things that all have to agree: the order itself, the Stripe payment, and a per-seller record of who is owed what. This is the part most multi-vendor write-ups skip. Putting several sellers on one page is easy. Making one purchase produce an order, a charge, and seller payouts that line up exactly, and that can recover if something fails halfway through, is the actual work.
Vendor shares became the payout ledger
We added a vendor share model because the order itself is not enough. An order can tell you what the buyer purchased. It does not, by itself, give the platform a durable seller-payout ledger with retries, fee snapshots, Stripe Connect transfer IDs, and failure state.
A vendor share groups the accounting data the business needs after checkout: who earned the money, which order created the obligation, what the gross and net amounts were, which marketplace and Stripe fees applied, which transfer or charge IDs belong to the payment, and what state the payout is in if it has to be retried or investigated.
The checkout helpers aggregate seller amounts by vendor and currency. They also snapshot the fee configuration so an old sale does not silently change because someone edits marketplace fees later. That is not glamorous code. It is the code that keeps payouts explainable.
Idempotency became part of the design
Payment code gets retried. Browsers retry. Users double-click. Webhooks arrive more than once. Workers crash halfway through. If the system cannot survive that, it is not ready to handle marketplace money.
The checkout design centers on idempotency records: a key, the actor behind the request, a way to compare the request body, the processing state, the response to replay, and enough references to recover the order or payment session. If the same request comes back, the backend can return the same outcome. If the same key is reused for a different request, it can reject the conflict. If processing gets stale, the system has a recovery path instead of pretending nothing happened.
Webhook events are recorded too. Transfer retries are designed around fresh reads, processing locks, succeeded-state checks, and deterministic Stripe idempotency keys, so a repeated job should not pay a seller twice. When retries are exhausted, the failure needs to become visible to operators instead of disappearing into logs.
- Idempotency protects checkout from duplicate orders and duplicate sessions.
- Webhook records protect event handlers from doing the same work twice.
- Processing locks protect seller transfers from concurrent workers.
- Retry state and failure queues make payout problems operationally visible.
The admin work was part of the product
For JuraDocs, admin screens are where the platform handles trust and exceptions. That includes managed users, vendor/customer creation, profession permissions, bar registry lookups, warning states, invoice configuration, reviews, statistics, document processing settings, and payout visibility.
This is one reason the project should not be described as "we built a storefront." The storefront is only one surface. The admin and operational flows are what let the business keep running when a seller needs verification, a document needs review, a payout needs investigation, or a fee rule needs to change.
What Medusa gave us, and what we added
Medusa provided the commerce backbone: catalog objects, carts, orders, customers, stores, workflows, modules, links, and a backend architecture we could extend without starting from zero.
We built the JuraDocs layer: professional registration, vendor/store linking, profession permissions, legal-document processing, protected file access, buyer purchase checks, seller dashboards, Stripe Connect onboarding, vendor shares, retryable transfers, invoices, admin controls, and localized buyer/seller experience.
The important detail is that these are not separate features sprinkled around the app. They touch the same flow. A seller's profession affects whether they can sell. Their vendor profile affects store ownership. Store ownership affects product ownership. Product ownership affects checkout validation. Checkout creates vendor shares. Vendor shares drive payouts, seller dashboards, invoices, and admin recovery.
What I would tell another team
If you are using Medusa for a marketplace, decide early which parts are commerce and which parts are your domain. Do not wait until checkout to discover that seller ownership, fees, permissions, invoices, and fulfillment all need the same missing concept.
- Make seller ownership explicit early. It will show up in products, orders, permissions, dashboards, search, fees, and invoices.
- Use links and domain models for durable concepts. Do not hide everything in product metadata because it feels faster.
- Treat payment success as the beginning of accounting, not the end of checkout.
- Store fee snapshots when money moves. Current settings are not enough to explain old payouts.
- Build admin tools around real failure modes: verification mismatches, document processing failures, duplicate events, payout retries, and invoice corrections.
- For sensitive documents, separate owner access from buyer access. The private source file should not casually become the purchased file.
The takeaway
Medusa worked for JuraDocs because it was flexible enough to stay underneath the business. We used its commerce primitives, then added the domain models a legal marketplace needs: professional trust, document safety, seller accounting, and operational recovery.
The lesson is simple: Medusa gave us the commerce foundation. JuraDocs became viable when trust, document safety, and seller accounting became first-class domain concerns around that foundation.

Full case study
Turning legal work that usually disappears into a trusted marketplace.
