Reza Heydari
← Index / Selected work

Case 04FAHA Scrubs · fahascrubs.com

Made-to-order commerce for medical scrubs

A WooCommerce shop rebuilt as a custom Persian commerce platform — name printing, made-to-measure, corporate pricing — cut over on the same domain with every indexed URL intact.

Role
Full-Stack Engineer — sole developer, design to deployment
Timeline
Jul 2026 – Oct 2026 · 10 wks to launch
Status
Live
Stack
TypeScriptNext.jsMedusa v2PostgreSQLRedisDocker

The problem

FAHA sells medical scrubs, lab coats and caps across Iran, through its own shop and a franchise network of 13 branches. Much of what it sells is personalised: a name printed on the chest, a sleeve shortened, a garment cut to the wearer's own measurements — and a hospital may order a hundred sets at once. A stock WooCommerce install modelled none of that.

Replacing the store could not cost what it had already earned. The old site ranked on Google for its core keyword, so all 308 indexed URLs had to keep resolving on the same domain, and past orders and customers had to come across with order numbering continuing where WooCommerce left off.

Constraints

  1. 01

    Hosted inside Iran: Vercel and AWS are unreachable from there, so the platform runs self-hosted on a single server behind ArvanCloud's CDN, and builds have to work through a local npm mirror.

  2. 02

    A same-domain cutover. The new platform replaced WordPress on fahascrubs.com overnight — URLs, redirects, accounts and order numbers had to be right from the first minute, not patched in the following week.

  3. 03

    Persian end to end: right-to-left layout, Persian digits, Persian text normalised so differently typed letters still match, and Toman prices converted to Rial only at the payment gateway.

What I built

The storefront on Next.js 16: 15 categories and 22 colour landing pages, product pages with per-size pricing and a colour × size stock grid, 24 size-guide tables, reviews with structured data, the migrated blog, guest checkout with phone-OTP sign-in, five couriers, Zarinpal payments and an order-tracking timeline.

The made-to-order layer: a name-print studio with a live preview — Persian or Latin fonts picked by the alphabet you type, four positions, three thread colours — plus tailoring adjustments and a made-to-measure option that takes body measurements instead of a size. Corporate orders get automatic 5–20% tiers from ten garments up and a quote request above 200.

The back-end on Medusa v2 with a custom FAHA module, a custom OTP auth provider and a custom Zarinpal payment provider — and FAHA's own Persian staff panel: orders and a printable production queue, inventory, size charts, print and tailoring prices, a CMS for articles and pages, and four staff roles.

The operations around it: Docker Compose with Postgres 16 and Redis 7 behind Caddy, a scripted check of every legacy URL before and after launch, and nightly database backups to ArvanCloud object storage with 30-day retention.

Architecture: a Persian storefront and FAHA's staff panel both run on a Medusa v2 core that re-derives every price on the server, verifies payments with Zarinpal, and stores orders in Postgres — all on one server inside Iran that pulls its own deploys.one server in Iran · ArvanCloud edge · deploys pulled from main every 2 minCustomerphone-OTP sign-inFAHA stafffour rolesStorefrontNext.js 16 · RTLname-print studiomade-to-measureStaff panelpaid order → job sheetstock per colour × sizeMedusa v2 corecustom FAHA moduleprices re-derived server-sideOTP auth · Zarinpal providerPostgres · Redisnightly backup → S3Zarinpal · SMSverified server-to-serverordersrunscheckoutfulfilsEvery garment made to order — every price derived on the server
Fig. 04 — system shape, simplified

The hard decision

Deploys the server pulls, instead of CI pushing them

The usual pipeline has CI build the app and push it to the server. That assumes the server is reachable from GitHub's runners and the build can reach the public npm registry — and from inside Iran, neither is something to bet a shop on.

So the direction is inverted. A systemd timer on the server polls main every two minutes, builds on the box through an Iranian npm mirror, and only switches over once the build succeeds. A failed build retries with backoff while the previous release keeps serving — a deploy can be slow, but it can never take the store down.

Security & reliability

  • Prices are re-derived on the server — printing, tailoring and corporate discounts are never trusted from the browser.

  • Payments are verified server-to-server with Zarinpal when the customer returns from the bank, before an order is confirmed.

  • Phone-OTP sign-in through a custom Medusa auth provider, with a second SMS provider as fallback.

  • Staff access split across four roles, rate limiting at Caddy, and nightly off-site database backups.

Results

weeks from first commit to live cutover
10
indexed URLs, every one still resolving
308
automated tests, green on every deploy
550+

What I'd do differently

I'd get the catalogue re-photographed before building the name-print preview, not after. The live overlay on product photos is switched off until the re-shoot, so the store's most distinctive feature launched only half-visible.