SeriesPart 0 of Commerce architecture in practiceView the cluster →
Medusa & ArchitecturePillar

How we extended Medusa to solve thirteen real e-commerce problems

The client needed capabilities that no standard commerce installation could provide. This is how we used Medusa as an extensible foundation and built the missing business solutions.

We Are Souk case-study cover: extending Medusa to solve thirteen real e-commerce problems
Olivier and Souk EngineeringCommerce architectureAug 2026·28 min read
Key takeaways
  • Choosing Medusa was an architecture decision: its primitives gave us a stable commerce core we could extend.
  • The project added thirteen concrete capabilities around markets, pricing, payments, ERP, catalogue and operations.
  • Every extension started with a client problem, not with a technology demonstration.
  • Operator screens, recovery paths and audit trails were part of the feature—not technical aftercare.
  • The result shows what composable commerce is useful for when standard behaviour stops at the client’s real requirement.

The project and the evidence boundary

The client—an anonymised European industrial group—did not come to us asking for abstract architecture. It needed an international storefront connected to Odoo and Mollie, with prices refreshed from exchange rates, promotions that made sense in each currency, an employee purchase programme, safer catalogue imports, useful stock alerts, reviewable transactional emails and catalogue filters that did not lie about what customers could buy.

Medusa v2 already provided the hard foundation of a commerce engine: products, variants, carts, regions, prices, promotions, payments, orders, workflows and Admin extension points. It did not—and should not—contain this organisation’s rules. That gap is exactly why choosing Medusa was an architecture decision. We could keep the standard commerce core and extend the places where the client’s business was genuinely different.

We built the storefront with TanStack Start and treated each requirement as a concrete client problem. How should a paid Mollie checkout recover when no order exists? Which product data belongs to Medusa and which stock data belongs to Odoo? How can an operator see an integration that has gone silent? What should a £ promotion threshold mean when the campaign was conceived in euros? How can a guest save products without being forced into an account?

Those questions produced thirteen focused capabilities. Some extended Medusa’s backend domain model. Some used its workflow and event boundaries. Some added Medusa Admin screens so the client could operate what had been built. Others lived in the TanStack storefront while continuing to use Medusa as the commercial source. The common thread was not ‘we wrote clean code’. It was that the chosen platform let us solve the client’s actual problems without disguising them as generic plugins.

The architecture method still matters, but it appears here as evidence inside each solution. Payment recovery needed durable identity because the client could receive money without an order. The Odoo integration needed explicit ownership because two systems could overwrite the same fact. Currency automation needed an audit trail because finance had to explain a selling price. We separated concerns because those business outcomes required it, not because separation itself was the story.

This case study brings the thirteen solutions together. Each linked guide begins with the client problem, states what Medusa supplied and what it did not solve automatically, then walks through the extension we found in the repository. Readers already using Medusa can reuse the approach; commerce leaders can see what an extensible platform makes possible before they know the implementation details.

The evidence boundary matters. We reviewed implementation sources, tests, interfaces and private project material authorised for anonymised editorial use. That supports statements about mechanisms found in the project. It does not support claims about production volume, conversion uplift, revenue impact, deployment coverage or customer satisfaction. We therefore make none of those claims. Where the inspected evidence did not establish atomicity, concurrency safety, provider delivery or live deployment, we describe the limitation rather than upgrading an implementation into a guarantee.

The executive summary

The simplest summary is that the client wanted one commerce platform to behave like its business rather than forcing its business into a standard storefront template.

For international selling, we separated language from commercial country, connected countries to Medusa regions, generated market-aware SEO and added controlled FX pricing plus deliberate promotion thresholds per currency. For catalogue work, we created domain-specific CSV contracts, improved variant-aware filtering and kept a guest wishlist deliberately local until the business promise required more.

For operations, we gave Medusa and Odoo explicit responsibilities, translated between their models and built integration health and reconciliation surfaces in Admin. For customer journeys, we added Mollie payment recovery, variant-specific stock-alert lifecycles and a way for the e-commerce team to review every transactional email without manufacturing a test incident.

For the employee programme, we did not disguise a long-lived entitlement as a coupon. We added lifecycle, allowance, usage history, cart behaviour and operator visibility as a dedicated Medusa domain. Across the thirteen capabilities, Medusa remained the commerce engine while project-specific modules, workflows, routes and Admin extensions expressed the client’s rules.

This is the practical case for composable commerce. The value is not that anything can be customised in theory. The value is that a team can start from proven commerce primitives, identify the exact point where standard behaviour ends and build the missing capability without rebuilding the entire platform.

The client needed one storefront to behave correctly in every market

The project’s international dimension could easily have been treated as a localisation exercise: translate the interface, add a country selector and show another currency. That framing would have hidden the hardest part. International commerce is a state-management problem before it is a translation problem.

Consider an English-speaking visitor in Belgium following a link shared from another country while travelling. Browser language may suggest English. Network location may suggest the country they are currently visiting. The URL may intentionally describe another market. Their future shipping address may add a fourth signal. If the storefront chooses one of these values silently and lets it determine price, delivery and payment, it has converted weak evidence into a strong commercial decision.

We therefore separated content language from commercial country. Language controls which words the visitor reads. Country participates in resolving the commercial context supported by the backend. A Medusa region then scopes currency and other commerce configuration. The important point is not the exact chain of functions. It is that every value has one job and an explicit precedence.

An explicit market in the current URL takes priority because a shared link should remain stable. A remembered customer choice can be useful when no explicit route exists. Geolocation can help with an unresolved first visit, but it is a suggestion, not consent. A two-letter country code must also be checked against markets actually supported by commerce configuration. Syntactic validity is not business availability.

This model changed the country selector. Switching country is not merely changing a flag. It can invalidate calculated prices, shipping methods, payment sessions, taxes, promotions and address assumptions attached to a cart. A safe transition must either migrate or clear the dependent state according to a known policy. If the transition fails, navigation and commerce state must not quietly disagree.

The decision also created the foundation for international SEO. Search engines see URLs and metadata, not the intentions in a React state store. If the router resolves one market while canonical tags or a sitemap describe another, the site publishes contradictory versions of reality. One shared market contract must feed both the customer journey and the discovery layer.

This was our first major lesson: solve the domain vocabulary before optimising the interaction. A polished selector cannot compensate for a model that confuses country and language.

For the detailed resolution order, cart-transition risks and validation model, read How we made one Medusa storefront work across countries, languages and markets.

Catalogue discovery exposed the same modelling problem

Once a visitor reaches the correct market, the catalogue must show products that can actually be bought in that context. Faceted filtering seems unrelated to market resolution, yet it raised the same issue: the interface was tempted to merge facts that belonged to different identities.

In a variant-based catalogue, a parent product may have one red variant, another in the requested size and a third inside the selected price range. Three separate database existence checks can all return true even though no single variant satisfies colour, size and price together. The customer sees a product that appears to match and then discovers that the promised combination does not exist.

The correct question is not “Does this product have red, the chosen size and the chosen price somewhere?” It is “Does one concrete variant satisfy the complete variant-level predicate in the active market?” Product-level facts such as category can remain at product level. Variant options and market-specific price must remain correlated through the same variant identity.

The display unit matters too. If the storefront renders one card per colour but the backend counts parent products, the result number and the visible grid describe different things. Pagination can become sparse when filtering happens after a page has been retrieved. Analytics may count items that the interface discards. The architecture must name the unit at every stage: product, variant or presentation card.

We also made filter state shareable through the URL. That means query parameters are a public contract. Refreshing, sharing, sorting and using browser history should reconstruct the same selection. Unknown or historical parameters must be normalised rather than silently simulated. A filter panel is therefore both an interface and a small versioned query language.

The focused guide explains the correlated-predicate trap, URL contract and test fixtures: How we fixed Medusa catalogue filters that showed combinations customers could not buy.

Simplicity was allowed when the promise was simple

Not every feature required a distributed system. The wishlist gave us a useful counterexample. The initial product promise was narrow: a guest could remember products in the same browser without creating an account. It was not a promise of cross-device recovery, customer-service access or shared lists.

For that promise, storing stable product identifiers in browser storage was a deliberate boundary. We avoided persisting prices, titles, stock or other volatile commerce data. When the list is displayed, current information must be resolved from the active market. The browser remembers interest; Medusa remains responsible for commerce truth.

The implementation coordinated components in the same page and other tabs in the same browser profile. That still does not make it account synchronisation. Storage can be unavailable, cleared or malformed. The interface must tolerate those conditions and must not imply permanence it cannot provide. If the business later promises continuity across devices, the architecture should move to a server-side list with an explicit merge rule rather than quietly changing the meaning of the original feature.

This decision demonstrates an important aspect of composable architecture: maturity is not measured by the number of services. The right design is the smallest system that fulfils the stated promise honestly and has a clear path when the promise expands.

Read the storage contract and migration boundaries in How we added a Medusa wishlist without forcing customers to create an account.

The client needed an employee programme, not another coupon

The employee purchasing programme looked, at first glance, like a discount-code feature. A member of staff enters a code and receives a benefit. If we had implemented only that visible behaviour, the platform would have been unable to answer the questions that matter after the first order.

Who is eligible now? Can one code be active on more than one account? What is the allowance period? Does usage mean the amount spent, the eligible base or the amount saved? What happens to orders that have started but are not final? Can a public promotion be combined with the employee benefit? If a person leaves the programme, do past transactions disappear?

These are entitlement and accounting questions. A percentage formula is only one small part of the answer.

We separated identity, eligibility, usage history and price adjustment. The code has a lifecycle and can be associated according to the programme’s rules. The customer’s current eligibility can change without rewriting historical orders. Usage belongs to a period and customer, not merely to a global promotion budget. Employee-related adjustments must be recognisable among other cart adjustments so that the system can apply an explicit stacking policy.

The ledger is what makes the programme explainable. Support should be able to understand how a remaining allowance was derived from relevant order history rather than trust a mutable number with no provenance. Revoking a current right must not erase the financial past. Recalculation after cart changes matters because a decision made when checkout opens may become stale as quantities, products or promotions change.

The inspected work also showed why implementation language must remain precise. A complete programme needs a policy for amounts that are pending before finalisation, for releases after failure or cancellation, for returns, and for concurrent checkout attempts. The evidence reviewed did not justify claiming that every reservation and concurrency boundary was fully guaranteed. We therefore describe the required model and the implemented entitlement layer without pretending that an unresolved race has disappeared.

For a director, the transferable lesson is straightforward: when a commercial benefit accumulates over time or belongs to a person, do not model it as a clever coupon. Ask for a lifecycle, a ledger, an audit view and an explicit policy for pending and reversed transactions.

The full programme model is covered in How we built an employee purchase programme on Medusa v2.

Promotion thresholds revealed another hidden policy

A promotion described as “available from 100” also sounds trivial. Yet the number is meaningless until the business states what it measures. Is it the product subtotal before tax? The total with tax? Does shipping count? Which amount applies in another currency?

Medusa provides native cart totals and promotion-rule primitives. The custom work should translate the business choice into those primitives, not rebuild a separate promotion engine inside the storefront. We mapped the selected tax-and-shipping basis to the appropriate cart attribute and used a greater-than-or-equal rule at the boundary.

For multiple currencies, we kept promotional policy separate from foreign-exchange calculation. A merchandising team may deliberately choose thresholds that are easy to communicate in each market. Those values do not have to equal a live conversion at the moment the customer opens the cart. Treating the threshold as an exchange-rate output would make a campaign drift without a conscious commercial decision.

This is a recurring theme in the project: a number is not a fact until its unit, owner, context and effective time are known. The guide How we added market-specific minimum-spend promotions to Medusa v2 explains the four cart-total bases and the per-currency configuration model.

The client needed Medusa and Odoo to cooperate without fighting

“Synchronise Medusa and the ERP” is not a sufficient architecture requirement. The phrase sounds decisive while avoiding every conflict question. Which system owns the commercial product description? Which one owns physical stock? Can both change price? What happens when a record exists in one system but not the other? Is the middleware allowed to repair data automatically?

We replaced the idea of a universal source of truth with an ownership matrix by domain. In the inspected project state, Medusa carried the commercial catalogue structure, storefront content, translations, metadata and commerce prices. The ERP carried physical inventory. Orders moved from commerce towards the ERP; fulfilment, tracking and invoice information could return towards commerce through defined integration paths.

That allocation is not a universal recommendation for every company. It is an example of the decision every integration needs. Another organisation may choose a product-information system or ERP as the owner of different fields. What matters is that only one authority can make each business fact true, mirrors are named and conflict behaviour is deliberate.

The boundary between systems should translate concepts rather than simply copy tables. Commerce and ERP models exist for different purposes. A storefront product is an object for merchandising, discovery and purchase. An ERP item is part of physical and financial operations. A stable relation can connect them without forcing one model to impersonate the other.

We also distinguished record counts from linkage quality. Equal totals in two systems do not prove that the records are correctly paired. One missing relation and one duplicate may preserve the count while corrupting the meaning. Useful reconciliation needs states such as linked, missing, present only on one side or duplicated. Once a difference has a name, it can be filtered, investigated and assigned.

Observation and repair were separated. A dashboard that exposes discrepancies is valuable precisely because it does not silently mutate the systems while an operator is trying to understand them. Reconciliation should be an explicit, permissioned action with a known direction and an audit trail.

This approach turned the integration from an opaque connector into an operating contract. It allowed the team to ask the right question when data diverged: not “Which database is correct?” but “Who owns this field, what was the last accepted transition, and which bounded action restores the contract?”

The complete ownership and reconciliation framework appears in How we connected Medusa to Odoo without losing control of product and stock data.

A transport log was not enough

Even with clear ownership, automated exchanges can fail or do nothing. Traditional server logs are rarely a usable operating interface. A support or integration team needs a timeline expressed in business language.

A webhook can be received successfully and still produce no stock change. That may be correct if the event repeats an already-applied state, or it may indicate missing data. A batch can update some records and reject others. An endpoint can stop receiving events entirely without generating an error. None of those outcomes is adequately described by the HTTP code alone.

We separated transport status from business outcome. The useful record includes direction, event kind, time, duration, business result and a safe explanation. Incoming ERP events, outgoing commerce actions and scheduled tasks should be recognisable as different directions in the same operating history. A noop is a meaningful outcome, but it is not automatically proof of idempotence. Replaying an event safely requires a stable operation identity and business-level protection, not merely a label in the log.

Freshness matters as much as failure. If a normally active flow becomes silent, there may be no red event to display. Operators need to understand the time of the last relevant exchange and whether that is expected. Payloads must be cleaned or truncated to avoid exposing personal data, secrets or large internal messages while retaining enough correlation to investigate.

The guide How we made Medusa–ERP integration failures visible to the e-commerce team covers business outcomes, partial results, silence and safe replay in more detail.

The client needed exchange rates without surrendering price control

Multi-currency catalogue pricing is often described as multiplication: take a reference price, retrieve a rate and write the converted values. The arithmetic is easy. The business problem is being able to explain later which raw market rate was used, which commercial adjustment was applied, when the value became effective and which products were actually updated.

We treated the external rate, commercial policy and catalogue mutation as separate stages. A provider supplies market data. The business may apply a buffer or rounding rule. The resulting rate is a versioned decision with dates and provenance. Catalogue prices are then derived from that decision.

This separation prevents a single unexplained number from representing several concepts. Finance can distinguish what came from outside from what the company deliberately changed. Engineering can reproduce a calculation. Operators can inspect the current policy without confusing it with the last fetch response. A later provider change does not have to rewrite the meaning of historical prices.

Validation before mutation reduces risk. If a required currency has no usable rate, the process should stop before starting the affected update. But pre-validation is not the same as global atomicity. Once a workflow updates many products or price records, an error can still occur after some mutations unless the storage and workflow provide a genuine transaction or a well-tested compensation strategy. The inspected evidence did not justify claiming full catalogue-wide atomicity, so the public lesson is framed accurately: validate first, record progress, make retries deterministic and treat partial application as a state to detect and repair.

The same care applies to timing. There may be a date when a raw rate was observed and another when a policy or derived price became active. Storing one timestamp loses an important audit distinction. If finance, support or merchandising will ever ask “Why was this the price?”, the architecture needs enough history to answer without reconstructing intent from source code.

Read the full pricing pipeline in How we kept Medusa prices aligned with exchange rates without losing commercial control.

The client needed powerful back-office tools that were safe to operate

An international catalogue produces operational work: importing products, editing taxonomies, reviewing translations and inspecting outbound communications. These surfaces may have fewer users than the storefront, but each action can affect a large part of the customer experience. We designed them with the same attention to contracts and failure states.

CSV safety begins with the meaning of absence

A spreadsheet cell can be empty for several reasons. The operator may want to preserve the current value, deliberately clear it or have omitted a required field. If the importer guesses, a blank becomes a destructive operation by accident.

We worked with separate CSV contracts for different catalogue concepts rather than a universal sheet that tries to represent every relation. Headers, identifiers, locale handling and reference rules must be validated before mutation. Destructive behaviour such as deleting records that are missing from a file should remain explicit and opt-in. A preview should show what would be created, updated, skipped or rejected before the operator commits the change.

Export–import round trips are a useful test, but their meaning must be precise. Equal row counts do not prove semantic equality. Ordering may change, translated fields may be missing and relations can be wrong while totals remain stable. A strong round-trip check compares canonical meaning: identifiers, field values, relationships and intended absence semantics.

Reversibility should not be overstated either. A preview helps an operator avoid mistakes; it does not automatically create rollback. Genuine recovery requires a prior snapshot, event history, compensating import or another explicit mechanism. In the absence of that evidence, we say the importer is designed for safer review rather than calling it reversible.

The detailed contract and test approach are in How we made large Medusa catalogue imports safer for an e-commerce team.

Email preview solved reviewability, not delivery

Transactional emails are part of the product, but they are often reviewed only after someone triggers the corresponding workflow. Rare messages are neglected, locale variants drift and content teams depend on developers to produce examples.

The project included a Medusa Admin surface for browsing available templates and language variants. Components were rendered with deterministic fictional data and displayed for review. The visible set came from a registry rather than a marketing claim about a fixed number of templates. This makes missing combinations and visual regressions easier to find.

The evidence boundary is crucial: a rendered preview shows what a component produces under the supplied data. It does not prove that the real business event invokes it, that the sender is configured, that the provider accepts the message or that an inbox receives it. Trigger, rendering, provider submission and delivery are separate contracts and should be tested separately.

That distinction makes the tool more useful, not less. Each part has a clear purpose. Editorial and localisation teams gain a deterministic review surface; engineering retains responsibility for event and transport verification.

Read How we let the e-commerce team review every Medusa email before it is sent for the registry and release-gate model.

The client needed customer promises to survive a closed browser

Distributed commerce is most vulnerable between systems. The browser waits while a provider acts. The ERP answers later. Inventory changes after a visitor leaves. These gaps are not unusual exceptions. They are normal states that deserve names.

A payment redirect is evidence to investigate

The payment journey made this principle concrete. A customer can approve a payment with the provider while the browser disappears before commerce creates or confirms the order. A return page can load twice. A webhook can arrive before, after or without the customer returning. If the application treats the return URL as proof of success, it risks telling the customer something the commerce backend cannot yet support.

We treated the return route as a resolver rather than a success page. The resolver starts from a server-side commerce identity and determines whether an order already exists, whether the current payment state allows completion, whether recovery is needed or whether the customer must wait or retry. The browser supplies context; it does not become the source of payment truth.

The same completion command should be usable from legitimate recovery paths so that the business rules do not drift between a live checkout and a repair route. Repeated attempts must converge on an existing outcome or a safe no-op rather than create another order. In-memory process locks may reduce duplicate work inside one runtime, but they do not establish exactly-once behaviour across processes or restarts. Durable idempotency requires database-level or provider-level identities and tests that cross those boundaries.

The customer experience should reflect uncertainty honestly. “We are confirming your payment” is better than a false success or an unexplained error. Support needs a correlation trail that connects the commerce session, provider state and resulting order without exposing sensitive payment data.

The payment state and recovery paths are explained in What happens when Mollie takes payment but Medusa has no order?.

Back-in-stock was also a durable promise

A stock-alert form looks much smaller than payment, but its lifecycle has the same shape. The customer chooses a specific product option, supplies a contact address and expects one useful notification when that choice becomes available. Duplicate subscriptions, variant ambiguity, repeat sends and unsubscribe all need policy.

We modelled the alert around the exact variant identity rather than a product title or visual option that might later resolve differently. The subscription needs states for waiting, sent, cancelled and error handling. Duplicate intent should converge instead of filling the queue with copies. An operator needs visibility into what is waiting and what happened without being encouraged to send messages blindly.

Stock is volatile, so the send decision must validate current availability at execution time. Queueing a notification and actually delivering it are separate steps. A scheduler or workflow can select eligible records; the email provider and inbox remain external evidence boundaries. We therefore do not claim that a queued or rendered notification proves delivery.

The full lifecycle appears in How we added back-in-stock alerts to Medusa, from product page to operator queue.

Together, payment recovery and stock alerts taught us that customer promises outlive pages. Anything that must remain true after the browser closes belongs in durable server-side state with a visible lifecycle.

The client needed search engines to understand the same market model

International SEO is frequently implemented at the end of a project as a collection of tags. That approach fails when canonical URLs, alternate-language links, market routing and sitemaps do not share the same model.

We treated SEO output as another projection of the commerce contract. A public URL represents a particular market-language combination. Canonical rules must agree with routing. hreflang alternates must point to real, reciprocal versions. Sitemap generation must enumerate indexable pages supported by the same market configuration. Products unavailable in a market should not be advertised there merely because a translation route can be formed.

This also means not every useful interface should be indexed. Faceted result pages may help customers while producing enormous, overlapping search spaces. The business must decide which page types are intended landing pages and which remain navigational tools. Robots settings, canonical policy and sitemap inclusion should follow that decision rather than emerge accidentally from component defaults.

Drift is the central risk. One helper can generate a canonical while another independently assembles alternates and a third enumerates sitemap routes. Each function may look correct in isolation while the combined output is contradictory. Contract tests should select representative products and markets, then compare rendered metadata, route resolution and sitemap entries as one system.

This work also matters for AI discovery. Search and answer systems can cite only content they can crawl, understand and connect to a clear entity and topic structure. The answer is not to manufacture dozens of near-duplicate pages. It is to publish a strong evidence-backed case study, focused guides with distinct intents, explicit authorship, consistent internal links and machine-readable metadata that agrees with the human page.

Our detailed SEO model is in How we made a multi-country Medusa storefront understandable to search engines.

Thirteen extensions, one reason for choosing Medusa

Viewed separately, the project contains a country selector, filters, a wishlist, an employee benefit, an ERP integration, webhook history, currency processing, CSV tools, payment recovery, stock alerts, email previews, SEO rules and promotion thresholds. Together, they show why we selected an extensible commerce engine: the client could keep one coherent Medusa core while each real business problem received a purpose-built solution.

Every fact has an owner

The model asks who may author a value and who may only mirror it. The ERP owns physical stock in this project; Medusa owns the commercial product experience. A rate provider supplies market data; the company owns its pricing policy. The browser may remember wishlist intent; it does not own price or availability. The payment provider supplies payment status; the return page does not invent it.

Every consequential value has context

A country value has a source and precedence. A price has currency, policy and effective time. A promotion threshold has a cart-total basis. A discount usage amount has a customer and period. A filter applies either to the product or to one concrete variant. Context is what turns a primitive string or number into a business fact.

Every long-running promise has states

Payment confirmation, stock alerts, imports and integration events can be retried or interrupted. A binary success flag cannot express pending, no-op, partial result, cancellation or a recoverable error. Named states let systems resume and let people understand what happened.

Every operator action separates seeing from changing

Webhook history, ERP reconciliation views, preview tools and import summaries exist first to explain. Mutation is a separate command with permission, validation and audit. This protects the business from dashboards that quietly repair data according to assumptions the operator never approved.

Every guarantee has a proof level

We distinguish code presence, automated test evidence, runtime observation, provider acceptance and business outcome. These proof levels prevent an attractive interface from becoming the basis of a false claim. They also make the next verification action obvious. If rendering is proven but delivery is not, test the trigger and transport rather than rewriting the preview.

This operating model is the most reusable output of the project. Technologies will change. The need to assign ownership, persist state, expose recovery and bound claims will not.

What this means for a commerce leader

Architecture work is sometimes presented as a technical concern that can wait until after the commercial roadmap. This project showed the opposite. The architecture decisions determined whether the business could safely launch another market, delegate catalogue work, explain a customer’s benefit or recover an interrupted purchase. They were operating decisions expressed in software.

For a CEO or e-commerce director, the warning signs are usually visible without reading code. Teams disagree about which system contains the “real” value. Support needs an engineer to explain routine failures. A retry is considered dangerous because nobody knows whether the first attempt partly succeeded. Country, currency and language are used interchangeably in meetings. A bulk tool can change data but cannot preview its consequences. New commercial rules are implemented as checkout exceptions with no corresponding audit view.

The appropriate response is not necessarily a platform replacement. It is to make the hidden contracts explicit. Start with the decisions that cross several journeys: market identity, catalogue ownership, price provenance, payment completion and integration recovery. Give each one an owner and an observable state. Then assess individual features against those shared contracts instead of letting every team invent another version.

This approach also changes prioritisation. Reliability is not a separate clean-up phase after feature delivery. An operator history can be part of the acceptance criteria for an integration. A recovery path can be part of the checkout design. A semantic round-trip test can be part of an import feature. The investment is justified not by abstract technical purity but by the cost of making high-impact actions understandable and repeatable.

The project does not prove a universal return on that investment, and we do not attach an invented figure to it. It provides something more useful for future decisions: concrete evidence that business ambiguity can be converted into a testable commerce contract before it becomes an incident.

What Souk’s role looked like

Our role was not simply to implement requests in Medusa. It was to translate business language into contracts that engineering, operations and customer experience could share.

“Support several countries” became a separation of country, language, region and currency, plus a policy for cart transition and search metadata.

“Connect the ERP” became an object-level ownership matrix, named directions, reconciliation states and an operator-visible history.

“Give employees a discount” became identity, eligibility, period, usage, price adjustment and historical audit.

“Make payments reliable” became a resolver that treats browser return, provider state and order creation as separate evidence.

“Let teams import products” became a versioned data contract with explicit absence and destructive semantics.

“Send stock emails” became a persisted customer promise linked to the exact variant and a lifecycle that distinguishes waiting, selection, sending and delivery evidence.

This is the difference between custom development and commerce architecture. Custom development can make an isolated scenario work. Architecture makes the scenario explainable when another market is added, an external service responds late, an operator makes a mistake or a customer returns after the browser has forgotten the page.

It also requires restraint. We did not need to turn a local wishlist into a cloud service before the business promise required it. We did need to treat an employee allowance as a ledger because the promise already involved time and financial history. The architecture followed the consequence of the promise, not the visual size of the feature.

A decision framework for the next commerce problem

The project left us with a practical set of questions that we now apply to new requirements:

  1. What exact promise is being made to the customer or operator?
  2. Which facts determine whether the promise is true?
  3. Who owns each fact, and which systems may only mirror it?
  4. What is inferred, what is selected explicitly and what is validated by the backend?
  5. Which identity must remain stable across a retry or delayed event?
  6. What happens if the operation stops after any individual step?
  7. Which outcomes need names beyond success and failure?
  8. How can an operator understand the state without changing it?
  9. What is the smallest bounded repair action?
  10. What does the interface prove, and what requires a test, database record, provider event or inbox observation?
  11. Which historical information must survive a present-day change?
  12. What customer or business claim remains explicitly outside the available evidence?

These questions are intentionally technology-independent. Medusa’s composable primitives made it possible to express the answers cleanly, but the reasoning applies to any headless commerce platform connected to payments, ERP, markets and internal operations.

The thirteen focused architecture guides

This case study explains how the work fits together. Each guide below addresses one decision in greater depth:

  1. What happens when Mollie takes payment but Medusa has no order?
  2. How we connected Medusa to Odoo without losing control of product and stock data
  3. How we made Medusa–ERP integration failures visible to the e-commerce team
  4. How we made large Medusa catalogue imports safer for an e-commerce team
  5. How we kept Medusa prices aligned with exchange rates without losing commercial control
  6. How we added market-specific minimum-spend promotions to Medusa v2
  7. How we made one Medusa storefront work across countries, languages and markets
  8. How we made a multi-country Medusa storefront understandable to search engines
  9. How we added a Medusa wishlist without forcing customers to create an account
  10. How we fixed Medusa catalogue filters that showed combinations customers could not buy
  11. How we added back-in-stock alerts to Medusa, from product page to operator queue
  12. How we let the e-commerce team review every Medusa email before it is sent
  13. How we built an employee purchase programme on Medusa v2

Conclusion

The result of this project was a Medusa platform extended with thirteen capabilities that answered thirteen recognisable client problems. The discipline of the architecture is visible in how those capabilities work, but it is not a substitute for the capabilities themselves.

Country stopped being a proxy for language. Product stopped being a proxy for variant. A browser redirect stopped being a proxy for payment truth. An HTTP response stopped being a proxy for business success. A rendered email stopped being a proxy for delivery. A current entitlement stopped being a proxy for its financial history. Equal record counts stopped being a proxy for correct integration.

Those distinctions can sound technical. Their consequences are commercial. They determine whether a visitor sees the right price, whether support can recover an order, whether finance can explain a conversion, whether an employee benefit remains auditable, whether an import is safe to approve and whether a search engine receives the same market story as a customer.

Medusa v2 provided composable commerce primitives. Souk’s work was to define the contracts between them, build the necessary custom domain layers, give operators visibility and remain exact about what the implementation proved.

When every new market, integration or business rule creates another exception, the missing piece is rarely another plugin. It is usually a clear model of ownership, state and recovery. That is where commerce architecture begins.

FAQ

Common questions

What does this case study prove?

It proves the architecture and implementation decisions visible in the inspected project sources. It does not invent production scale, commercial results or client testimony that the available evidence cannot establish.

Why is the client anonymised?

The project is published under an explicit confidentiality boundary. The transferable architecture is public; names, domains, operational identifiers and client-specific data remain private.

When does a Medusa project need architecture rather than another plugin?

When new markets, integrations or policies create conflicting assumptions about ownership, state, recovery or audit. At that point the missing asset is a shared business contract, not another isolated feature.

Read next
Souk AI · online now

Turn the article into an implementation plan.

Ask how this applies to your store, your stack, or your current bottleneck.

01 Describe your current setup.02 Name the workflow or signal that feels unreliable.03 Get a practical first architecture back.
I can help map this article to your stack. Tell me what you sell, what platform you use, and where the medusa & architecture question hurts.
Continue the cluster

Commerce architecture in practice