The rule that shaped everything: before building anything, decide which system owns the data. Medusa owns commerce state — products, carts, orders, payments, stock — and PostgreSQL stores it. Object storage holds image files; the database holds only their URLs. The payment provider owns the external transaction, and a browser redirect is never treated as proof of payment.
The storefront only presents. It reads through the Medusa SDK, keeps one cart-id cookie, and never computes a price or stores an order. Nothing about commerce lives in JSON files, Nuxt server memory or localStorage.
Real screens from the running shop and its admin, taken on 15 October 2026. Admin captures show catalog data only — orders and customer pages are left out on purpose.
┌──────────────────────────────────────────────┐
│ Edge — Caddy │
│ HTTPS · 3 hosts (shop / admin / automation) │
│ allow-list of public Medusa paths │
└───────────────────────┬──────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Application │
│ Nuxt 4 storefront (SSR, presents only) │
│ Medusa v2 server · Medusa worker │
│ Admin dashboard (own host, Russian) │
└───────────────────────┬──────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ State │
│ PostgreSQL 17 · Redis 7 · S3-compatible │
└───────────────────────┬──────────────────────┘
▼ always from Medusa
┌──────────────────────────────────────────────┐
│ Integrations │
│ Robokassa (live) / YooKassa · ApiShip (live) │
│ / CDEK · DaData · Yandex ID · Postbox │
│ · Telegram · n8n · LLM providers (1 port) │
└───────────────────────┬──────────────────────┘
▼ private network, one scoped token
┌──────────────────────────────────────────────┐
│ Hermes agent container (owner's assistant) │
│ sees shop data only via 13 typed tools │
└──────────────────────────────────────────────┘Medusa runs as a server and a worker container; the Redis-backed modules (event bus, cache, workflow engine, locking) are what make that split safe. Payment and delivery are Medusa provider modules — Robokassa and ApiShip are live, YooKassa and CDEK are built behind the same interfaces.
Admin runs on its own host, so a storefront bug cannot act as a logged-in admin. Zod validates every API boundary, since the storefront is untrusted input.
Orders, carts and payment state are where bugs cost money. Reusing tested core flows beats re-deriving them, and the admin comes for free. Extension order: what Medusa provides, then a module, workflow, route, subscriber or provider, and only then custom code.
I inherit Medusa's gaps and close them with tests. Its computed order fields are not returned by query.graph — the golden-number tests caught it.
The amount always comes from the server-side cart, every notification's signature is verified, and handling is idempotent. A browser redirect proves nothing, and providers do retry.
More code and tests per provider. The payoff: test and live credentials differ only in configuration.
Callbacks, order.placed and double clicks can all arrive twice. A unique index on the natural key, a reserve-or-read step and first-write-wins mean a repeat creates no second payment and no double stock decrement.
A unique index and a reconcile step on every feature that touches money or stock. Concurrency tests fire two approvals at once and expect exactly one effect.
Raw API shapes are narrowed, then mapped into presentation types by one mapper file. Pages never call the SDK, and contract fields are only ever added.
One mapping layer, and no `any` at any external boundary.
Each integration turns on only when its environment variables exist. One codebase serves shops with different needs, and a missing key never takes checkout down.
Every integration needs a test for its off state.
Docker Compose with Caddy, a nightly database dump and a throwaway stage stack with a time-to-live. The load does not justify an orchestrator.
No high availability. Heavy services have memory limits, and the plan says when to move them to a second machine.
The catalog is small, so retrieval is not the point — the owner's time is. Hermes runs in its own container for the Telegram chat and schedule, and Medusa is the only door to shop data, through 13 typed tools. The agent is on stage and ready; the production switch stays off until I have watched it on real use.
Digest, sales, top products, stock, abandoned carts, customers. Aggregates only, built by the server, so the model formats numbers it did not compute.
Product text, price, stock, reply draft, page text, percentage promotion. They store a proposal and change nothing.
The owner sees old and new values in Admin and approves. Medusa runs an existing workflow once, even on a double click.
Each tool call and decision is logged by tool, person and outcome — never arguments or message text.
payment_status and fulfillment_status are computed by its routes and are not returned by query.graph. The fix reads payment collections and fulfillment timestamps instead, and the test now guards it.1,341 backend and 303 storefront, plus HTTP integration suites and Playwright catalog flows on desktop and mobile.
Media storage, runtime AI settings, sealed secrets, RBAC, n8n, plugins, ApiShip and the agent — each written down.
Money, orders and publication are never direct writes; legal pages cannot be drafted by the agent at all.
Husky refuses commits on main and refuses payment, inventory, order or certificate code without a test in the same commit. Push runs lint, typecheck and tests; CI runs the checks before anything merges. Risky changes go to a throwaway stage stack first.
Releases are trunk-based: release.sh bumps the version, writes the changelog and tags it; migrations run as a separate step before the new server starts. I shipped v1.4.0 this way, with a database migration — and rollback is a redeploy of the previous tag.
FOR FREELANCE / PROJECT CLIENTS
If you need a real shop — payments, delivery, a localized admin and tools your team can actually use — this is the whole path, delivered and operated by one engineer: money paths that survive retries and forged callbacks, and AI that proposes while a human approves.
FOR FULL-TIME / HIRING TEAMS
One person owned architecture, backend, storefront, infrastructure and release process, with 1,644 unit tests, 13 ADRs and hooks that block untested money code. Every choice above comes with its written cost.