Skip to content

MedusaJS as Operational Backbone for Research Relay LLC

Executive Summary

Verdict: MedusaJS-centric architecture is viable and recommended for a solo-dev RUO e-commerce operation.

MedusaJS v2 (currently at v2.13.1) is an open-source, headless commerce platform with a modular architecture that maps well to the Research Relay use case. Its 17 independent commerce modules cover products, orders, inventory, customers, payments, fulfillment, taxes, discounts, returns/exchanges, and more. The platform is designed for extensibility: custom modules, workflow hooks, scheduled jobs, admin dashboard extensions, and a full REST API make it possible to consolidate most operational functions into a single system.

Key findings:

  • Products & Inventory: Native multi-location inventory, stock tracking, and reservations. Custom fields for RUO-specific data (purity, COA links, storage requirements) can be added via Module Links and custom data models.
  • Payments: BTCPay Server plugin exists and is tagged as v2-compatible. Stripe (with ACH support) ships out of the box. Custom payment providers are straightforward to build.
  • Orders & Fulfillment: Full order lifecycle with returns, exchanges, and claims. ShipStation has an official integration guide. Custom fulfillment providers are supported.
  • Tax: Built-in tax regions/rates with Avalara and TaxJar integrations available. Custom tax providers can be built for any calculation logic.
  • Admin Dashboard: Fully extensible with custom widgets, pages, and settings. An internal ops dashboard can be built directly into the Medusa admin.
  • Accounting: No native QuickBooks/Xero integration exists. This is the primary gap -- you will need either a custom integration module or a lightweight external tool.
  • Deployment: Self-hostable on Docker/VPS for $12-80/month. PostgreSQL + Redis required. Medusa Cloud available at $29/month (hobby) or $299/month (production).

The Medusa-centric approach eliminates 3-5 separate SaaS subscriptions, provides a single source of truth for all commerce data, and gives a developer-owner full control over customization without vendor lock-in.


1. MedusaJS Core Capabilities (Current State)

Version Status

  • Current version: v2.13.1 (actively maintained, frequent patch releases)
  • v2 was a ground-up rewrite with 3,500+ PRs merged over 16 months
  • v1 is deprecated; all new development is on v2
  • Breaking changes happen in minor releases (not just major)

Architecture

MedusaJS v2 uses a four-layer stack:

Request Flow:
  API Routes (Express.js + optional Redis sessions)
    -> Workflows (business logic orchestration)
      -> Modules (domain-specific, isolated)
        -> Data Store (PostgreSQL via MikroORM)

17 Commerce Modules ship out of the box: - Product, Cart, Order, Payment, Fulfillment, Tax, Pricing, Promotion - Inventory, Stock Location, Sales Channel, Customer, Region - User, Auth, Notification, File

Each module is isolated -- no database-level dependencies between them. Modules communicate through workflows and module links, not direct DB joins.

What Ships Out of the Box

Feature Built-in? Notes
Product management Yes Full CRUD, variants, categories, collections, types
Inventory tracking Yes Multi-location, reservations, stock levels
Order management Yes Full lifecycle, edits, versioning
Customer management Yes Customer groups, addresses, auth
Payment processing Yes Stripe + PayPal out of box, custom providers supported
Fulfillment Yes Provider-based, shipping options/profiles
Tax calculation Yes Region-based rates, custom providers (Avalara, TaxJar)
Discounts/Promotions Yes Conditions, coupons, Buy X Get Y
Returns/Exchanges Yes Automated RMA flow, claims
Draft orders Yes Manual order creation (phone orders, B2B)
Sales channels Yes Multi-channel with publishable API keys
Notifications Yes Email (SendGrid), extensible
File storage Yes Local, S3-compatible
Auth Yes JWT, session, OAuth, social login

Admin Dashboard

The admin is a React application (Vite-based) served by the Medusa server: - Full management of all commerce entities - Light and dark mode - Extensible with custom widgets (inject into existing pages) and UI routes (new pages) - Settings pages can be added for custom configuration - Uses Medusa UI component library for consistent look/feel

API & Frontend

  • Headless: Full REST API (Store API + Admin API)
  • Next.js Starter Storefront provided
  • Any frontend framework works (React, Vue, Svelte, mobile apps)
  • Publishable API keys for storefront-specific scoping

Extensibility Architecture

Extension Points:
  - Custom Modules (new data models + services)
  - Module Links (relate custom data to existing models)
  - Workflow Hooks (inject logic into existing workflows)
  - Custom Workflows (new business logic)
  - Custom API Routes (new endpoints)
  - Admin Widgets (inject UI into existing pages)
  - Admin UI Routes (new admin pages)
  - Scheduled Jobs (cron-like tasks)
  - Subscribers (event-driven actions)
  - Custom CLI Scripts (one-off tasks)

2. Inventory Management

Built-in Capabilities

Medusa's Inventory Module provides:

  • Inventory Items: Each product variant with "Manage Inventory" enabled gets an inventory item
  • Multi-Location Support: Inventory levels are per-location (InventoryLevel model)
  • stocked_quantity: Available stock at a location
  • reserved_quantity: Reserved but not yet fulfilled
  • incoming_quantity: Expected incoming stock
  • Stock Locations: Separate Stock Location Module manages warehouses/locations
  • Reservations: When an order is placed, quantity is reserved. On fulfillment, stock is decremented and reservation cleared
  • Inventory Kits: Support for bundled/multi-part products
  • Sales Channel Scoping: Inventory availability is scoped by sales channel -> stock location links

Admin UI for Inventory

  • View all inventory items with SKU, title, in-stock quantity
  • Manage location availability per item
  • Edit stocked quantities per location
  • Create/edit/delete reservations
  • Search, filter, and sort inventory items

What Needs Custom Building for RUO

RUO Requirement Approach
Batch/Lot tracking Custom module with Lot data model (lot_number, manufacture_date, expiry_date), linked to InventoryItem via Module Link
COA tracking Custom module with COA data model (coa_url, analysis_date, purity_percentage), linked to Product or Lot
Purity tracking Custom field on product or variant via Module Link (e.g., purity: model.float())
Storage requirements Custom field on product (e.g., storage_temp: model.text(), storage_conditions: model.text())
Low stock alerts Scheduled job that queries inventory levels and sends notifications when below threshold
Expiry date tracking Part of the Lot module; scheduled job to flag items approaching expiry

Implementation Sketch: Lot Tracking Module

// src/modules/lot/models/lot.ts
import { model } from "@medusajs/framework/utils"

export const Lot = model.define("lot", {
  id: model.id().primaryKey(),
  lot_number: model.text(),
  manufacture_date: model.dateTime().nullable(),
  expiry_date: model.dateTime().nullable(),
  purity_percentage: model.float().nullable(),
  coa_url: model.text().nullable(),
  storage_requirements: model.text().nullable(),
  notes: model.text().nullable(),
})
// src/links/inventory-lot.ts
import { defineLink } from "@medusajs/framework/utils"
import LotModule from "../modules/lot"
import { Modules } from "@medusajs/framework/utils"

export default defineLink(
  { linkable: Modules.INVENTORY, isList: true },
  LotModule.linkable.lot
)

Effort estimate: 2-3 days for the module, link, admin widget, and basic CRUD API routes.


3. Order Management & Fulfillment

Order Lifecycle

Medusa tracks orders through a complete lifecycle:

Cart -> Order Placed -> Payment Authorized -> Payment Captured
  -> Fulfillment Created -> Shipped -> Delivered
  -> (optional) Return Requested -> Return Received -> Refunded

Fulfillment statuses: | Status | Description | |--------|-------------| | Not Fulfilled | Order placed, items pending | | Partially Fulfilled | Some items picked/packed | | Fulfilled | All items picked/packed | | Partially Shipped | Some items have tracking | | Shipped | All items shipped | | Partially Delivered | Some delivered | | Delivered | All delivered | | Partially Returned | Some items returned | | Returned | All items returned | | Canceled | Fulfillment canceled |

Order Features

  • Order Edits: Add/remove items after order is placed (creates new order version)
  • Draft Orders: Create orders manually (useful for phone/email orders)
  • Order Versioning: Each change (edit, return, exchange, claim) increments the order version
  • Payment Capture: Manual or automatic capture after authorization
  • Partial Fulfillment: Fulfill subsets of items independently
  • Order Notes/Activity: Activity timeline on each order

Returns, Exchanges, and Claims

  • Returns: Automated RMA flow. Customer or admin initiates. Return shipping method selected. Items received -> marked as received (quantity restored to inventory) or damaged (not restored). Refund processed.
  • Exchanges: Return items + send replacement items. Handles price differences.
  • Claims: For defective/incorrect items. Two types: refund (return + refund) or replace (return + send new items).

Fulfillment Integrations

Provider Status Notes
ShipStation Official guide + community plugin Full integration: label generation, carrier selection, rate retrieval. API key setup documented.
Shippo Community plugin (may need v2 update) Plugin exists but last updated ~2 years ago. May need adaptation.
EasyPost No existing plugin Build custom fulfillment module provider following ShipStation pattern
Manual/Custom Built-in Default fulfillment provider for manual processing

Custom Fulfillment Provider Pattern

For Research Relay, the recommended approach:

  1. Phase 1: Use manual fulfillment (mark orders as fulfilled in admin, print packing slips manually)
  2. Phase 2: Integrate ShipStation for label generation and tracking
  3. Phase 3: Build custom fulfillment module if needed for specific carrier APIs

Packing Slips & Invoices

Medusa does not generate PDF packing slips or invoices out of the box. Options:

  • Custom workflow + PDF generation: Build a workflow that generates PDFs using a library (e.g., @react-pdf/renderer, pdfkit). Include RUO disclaimers in the template.
  • ShipStation integration: ShipStation can generate packing slips with custom templates.
  • Custom admin widget: Add a "Print Packing Slip" button to the order detail page that calls a custom API route generating the PDF.

RUO Disclaimer: Add as a configurable text block in the packing slip template. Example:

"For Research Use Only (RUO). Not for human consumption.
Not for diagnostic or therapeutic use."

4. Payment Integration

Built-in Payment Providers

Provider Status Details
Stripe Ships with Medusa Full integration: cards, Apple Pay, Google Pay, ACH via Payment Element
PayPal Official provider Standard PayPal checkout
System (Manual) Built-in Placeholder for offline payments (COD, wire transfer, manual BTC)

BTCPay Server Integration

  • Package: medusa-plugin-btcpay (v0.0.6 on npm)
  • Compatibility: Tagged with medusa-v2 keyword, designed for v2
  • Repository: SGFGOV/medusa-payment-plugins
  • Features: Bitcoin payment via self-hosted BTCPay Server, TypeScript, e2e tested
  • Official recognition: Listed in Medusa's integration directory
  • Lightning support: BTCPay Server itself supports Lightning Network; the plugin delegates to BTCPay Server which handles Lightning

Risk assessment: The plugin is at v0.0.6, which is early. Some Medusa v2 plugins have had compatibility issues with module resolution. Plan to allocate time for testing and potentially forking/fixing the plugin.

Fallback strategy: If the plugin doesn't work cleanly, building a custom BTCPay payment provider is straightforward:

// src/modules/btcpay-payment/service.ts
import { AbstractPaymentProvider } from "@medusajs/framework/utils"

class BTCPayPaymentService extends AbstractPaymentProvider<Options> {
  static identifier = "btcpay"

  // Implement: initiatePayment, authorizePayment,
  // capturePayment, refundPayment, etc.
  // Use BTCPay Server Greenfield API
}

ACH Payment

No dedicated ACH plugin exists. Two approaches:

  1. Stripe ACH: Enable us_bank_account payment method in Stripe Dashboard. Use Stripe's Payment Element on the storefront to surface ACH. This is the recommended path -- fully automated, low effort.
  2. Manual/System Provider: Customer pays via bank transfer outside Medusa. Merchant manually confirms payment in admin. Works for low volume.
  3. Custom Provider: Build a payment module using Plaid or Dwolla APIs for direct ACH. Higher effort, only needed if avoiding Stripe fees.

Multi-Payment-Method Checkout

Medusa supports multiple payment providers per region. A customer sees all enabled providers during checkout and selects one. Each payment creates a payment session tied to the chosen provider.

Recommended setup for Research Relay: - BTCPay Server (Bitcoin/Lightning) -- primary - Stripe (ACH Direct Debit) -- for customers who prefer bank transfer - Stripe (Cards) -- future addition if needed

Payment Reconciliation

Medusa tracks all payment state transitions: - Payment sessions -> authorized -> captured -> refunded - Order-level payment status tracking - Admin UI shows payment status and allows manual capture/refund - Webhook handlers for async payment events (Stripe webhooks, BTCPay Server webhooks)

For reconciliation with bank accounts, you would build a custom workflow or scheduled job that exports payment data for matching against bank statements.


5. Tax Handling

Built-in Tax System

Medusa's Tax Module provides:

  • Tax Regions: Geographic regions with specific tax settings (country, state/province level)
  • Tax Rates: Percentage-based rates per region
  • Tax Rate Rules: Override default rates for specific product types, products, or custom conditions
  • Combinable Rates: Child regions can inherit parent region rates
  • Tax-Inclusive Pricing: Supported at the region level
  • Tax Providers: Pluggable calculation logic per region

Default Tax Provider

The built-in system tax provider performs basic calculation using the configured rates. Suitable for simple setups where you manually configure rates per region.

Third-Party Tax Providers

Provider Status Notes
Avalara (AvaTax) Official guide + community plugin Real-time tax calculation, transaction tracking, auto-compliance. Plugin: @u11d/medusa-avalara. Supports tax-inclusive pricing.
TaxJar Community guide (Rigby) Implement ITaxProvider interface. Product tax codes supported.
Custom Build your own Implement getTaxLines() method. Can use any tax API or lookup table.

California Sales Tax Compliance

For Research Relay (CA-based): - Configure a US tax region with CA as a child region - Set CA state rate (currently 7.25% base) with district taxes as applicable - Recommended: Use Avalara for automatic address-level tax calculation in CA (district rates vary significantly) - Alternatively, use a flat CA rate for simplicity and adjust quarterly

Tax-Exempt Customer Handling

Medusa does not have a built-in "tax exempt" flag on customers out of the box. Approaches:

  1. Custom module: Create a TaxExemption module linked to Customer. Store exemption certificate data. Build a custom tax provider that checks exemption status before calculating.
  2. Tax Rate Rules: Create a 0% tax rate rule for specific customer groups (research institutions). Assign tax-exempt customers to that group.
  3. Avalara integration: Avalara handles exemption certificates natively. Pass customer exemption data to Avalara in the tax calculation context.

Tax Reporting / Export

Medusa does not generate tax reports. You would:

  1. Build a scheduled job that aggregates order tax data monthly
  2. Export as CSV via a custom API route or admin page
  3. Or push data to an accounting system (see Section 6)

6. Accounting Integration

Current State

There is no native MedusaJS integration with QuickBooks, Xero, or any accounting software. This is the most significant gap in the Medusa-centric approach.

Options

Build a custom module that pushes financial data to an accounting system:

// src/modules/accounting-sync/service.ts
class AccountingSyncService {
  // Methods to sync:
  // - Orders -> Invoices in QuickBooks/Xero
  // - Payments -> Payment records
  // - Refunds -> Credit notes
  // - Products -> Items/services
}

Trigger via: - Subscribers: Listen to order.placed, order.payment_captured, order.refunded events - Scheduled job: Batch sync daily/hourly

API targets: - QuickBooks Online API: Well-documented REST API, OAuth2 auth - Xero API: Similar quality, OAuth2 - Hledger/Beancount: If you want plain-text accounting (dev-friendly, no SaaS needed)

Effort estimate: 3-5 days for basic invoice sync to QuickBooks/Xero.

Option B: Use Medusa as Source of Truth + Lightweight Accounting

Since Research Relay is a single-operator business:

  1. Use Medusa for all transaction data
  2. Build an admin dashboard page showing revenue, tax collected, expenses
  3. Export financial summaries as CSV for tax filing
  4. Use a minimal accounting tool (Wave -- free, or GnuCash) only for tax filing and bank reconciliation

This avoids the complexity of a real-time accounting integration while still having clean financial data.

Option C: Middleware Approach

Use a tool like A2X or Webgility that can bridge e-commerce platforms to accounting software. However, these typically integrate with Shopify/WooCommerce, not Medusa directly. Would require exposing Medusa data in a compatible format.

Revenue Reporting

Build directly in Medusa admin: - Custom admin UI route showing revenue by period, product, customer - Use Medusa's Query API to aggregate order and payment data - Export functionality via custom API route

Recommendation

Start with Option B (Medusa as source of truth + CSV export). Move to Option A (custom QuickBooks/Xero sync) once order volume justifies the development effort. A solo operation processing <100 orders/month does not need real-time accounting sync.


7. Custom Modules / Extensions

How Custom Modules Work in Medusa v2

  1. Create a module directory under src/modules/
  2. Define data models using DML (Data Modeling Language):
    import { model } from "@medusajs/framework/utils"
    
    export const MyModel = model.define("my_model", {
      id: model.id().primaryKey(),
      name: model.text(),
      value: model.float(),
      metadata: model.json().nullable(),
    })
    
  3. Create a service extending MedusaService (auto-generates CRUD methods):
    import { MedusaService } from "@medusajs/framework/utils"
    import { MyModel } from "./models/my-model"
    
    class MyModuleService extends MedusaService({ MyModel }) {}
    export default MyModuleService
    
  4. Register the module in medusa-config.ts
  5. Generate and run migrations: yarn medusa db:generate myModule && yarn medusa db:migrate
  6. Link to existing models via Module Links for cross-module relationships
  7. Build API routes to expose the module's functionality

Available DML Property Types

  • model.id(), model.text(), model.number(), model.bigNumber()
  • model.float(), model.boolean(), model.dateTime()
  • model.json(), model.array(), model.enum()
  • Properties support: .nullable(), .default(), .primaryKey(), .unique(), .index(), .translatable()
  • Automatic: created_at, updated_at, deleted_at

Building an Internal Ops Dashboard

Yes, you can build a full internal ops dashboard within the Medusa admin:

  • Admin UI Routes: Add new pages at any path (e.g., /ops/dashboard, /ops/inventory-alerts, /ops/lot-tracking)
  • Admin Widgets: Inject custom sections into existing product/order/customer pages
  • Admin Settings Pages: Add configuration pages under Settings
  • React components: Use Medusa UI component library for consistent styling
  • Data fetching: Custom admin pages can call both standard Medusa API routes and your custom API routes

Example: An RUO ops page showing expiring lots, low stock items, and pending orders:

// src/admin/routes/ops/dashboard/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"

const OpsDashboard = () => {
  // Fetch from custom API routes:
  // - /admin/custom/lots/expiring
  // - /admin/custom/inventory/low-stock
  // - /admin/custom/orders/pending
  return (
    <Container>
      <Heading level="h1">Operations Dashboard</Heading>
      {/* Render expiring lots, low stock alerts, pending orders */}
    </Container>
  )
}

export const config = defineRouteConfig({
  label: "Operations",
})

export default OpsDashboard

Workflow Engine / Automation

Workflows are Medusa's primary mechanism for business logic: - Composed of steps (individual actions with compensation/rollback) - Support conditions (when-then) - Support long-running execution (async steps, human-in-the-loop) - Durable execution: Can span hours, days, or weeks - Built-in retry with configurable backoff - Hooks: Inject custom logic into existing Medusa workflows

Scheduled Jobs (cron tasks): - Define in src/jobs/ directory - Standard cron expression syntax - Access full Medusa container (all modules, services) - Execute workflows within jobs

Subscribers (event-driven): - Listen to any Medusa event (order.placed, product.created, etc.) - Execute workflows in response - Useful for: sending notifications, syncing to external systems, triggering fulfillment

Custom Modules for Research Relay

Module Purpose Priority
Lot Tracking Batch/lot numbers, expiry dates, COA links High
RUO Compliance Disclaimer templates, customer verification High
Financial Reports Revenue dashboards, tax summaries, CSV export Medium
Accounting Sync Push orders/payments to QuickBooks/Xero Medium
Low Stock Alerts Scheduled job + notification on low inventory Medium
Custom Packing Slip PDF generation with RUO disclaimers Medium
BTCPay Payment Custom provider if plugin doesn't work Low (fallback)

8. Deployment & Hosting

Requirements

  • PostgreSQL (15+ recommended)
  • Redis (for sessions, pub/sub, caching)
  • Node.js (18+ required)
  • Minimum 2GB RAM for production (admin build requires it)

Production Architecture

                    +-----------------+
                    |   Storefront    |
                    | (Next.js/etc)   |
                    +--------+--------+
                             |
                    +--------v--------+
                    | Medusa Server   |
                    | (API + Admin)   |
                    +--------+--------+
                             |
              +--------------+--------------+
              |                             |
     +--------v--------+          +--------v--------+
     | Medusa Worker    |          |   PostgreSQL    |
     | (Background jobs)|          |   Database      |
     +------------------+          +-----------------+
                                            |
                                   +--------v--------+
                                   |     Redis       |
                                   +-----------------+

Deploy two instances: 1. Server mode (MEDUSA_WORKER_MODE=server): Handles API requests, serves admin dashboard 2. Worker mode (MEDUSA_WORKER_MODE=worker): Processes background tasks (scheduled jobs, subscribers)

Hosting Options & Cost Estimates

Platform Monthly Cost Pros Cons
VPS + Docker (Hetzner/DigitalOcean) $12-50 Cheapest, full control, BTC-payable (some providers) Full DevOps responsibility
Railway $40-80 Easy deployment, usage-based, fast Costs scale with usage
Render $45-65 Predictable pricing, managed DB Less flexible than Railway
Medusa Cloud (Hobby) $29 Fully managed, zero DevOps Limited scaling, depends on Medusa Inc.
Medusa Cloud (Pro) $299+ Production-ready, auto-scaling Expensive for small operation

Phase 1 (Launch): Self-host on a VPS with Docker

Hetzner CX22 (2 vCPU, 4GB RAM): ~$5/month
  - Medusa server + worker (Docker Compose)
  - PostgreSQL (containerized)
  - Redis (containerized)
  - BTCPay Server (separate container or separate VPS)
  - Caddy/nginx for reverse proxy + TLS

Total: ~$10-20/month (excluding BTCPay Server hosting)

Phase 2 (Growth): Separate DB to managed PostgreSQL, add monitoring

VPS for Medusa: $10-20/month
Managed PostgreSQL: $15-25/month
BTCPay Server VPS: $5-10/month
Total: ~$30-55/month

Docker Compose Example

services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: medusa-store
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

  medusa-server:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@postgres/medusa-store
      REDIS_URL: redis://redis:6379
      MEDUSA_WORKER_MODE: server
    depends_on:
      - postgres
      - redis
    ports:
      - "9000:9000"

  medusa-worker:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@postgres/medusa-store
      REDIS_URL: redis://redis:6379
      MEDUSA_WORKER_MODE: worker
      DISABLE_MEDUSA_ADMIN: "true"
    depends_on:
      - postgres
      - redis

Storefront Hosting

The storefront (Next.js) can be: - Co-hosted on the same VPS (Docker container) - Deployed to Vercel (free tier works for small traffic) - Deployed to Cloudflare Pages


9. Comparison: Medusa-Centric vs SaaS Patchwork

Option A: Medusa-Centric

Core:
  MedusaJS (products, orders, inventory, customers,
            payments, fulfillment, tax, discounts,
            returns, admin dashboard)

Payment providers:
  BTCPay Server (BTC/Lightning) -- self-hosted
  Stripe (ACH, future cards)

Shipping:
  ShipStation integration (labels, tracking)

External (minimal):
  Bank account (Mercury)
  Tax filing (TurboTax / CPA)
  Accounting (Wave free, or CSV export from Medusa)

Option B: SaaS Patchwork

Storefront:    Shopify Basic ($39/mo) or WooCommerce (~$30/mo hosting)
Accounting:    Zoho Books ($15/mo) or QuickBooks ($30/mo)
Inventory:     Shopify built-in or inFlow ($89/mo)
Shipping:      ShipStation ($25/mo) or Shippo (pay-per-label)
Tax:           TaxJar ($19/mo) or Avalara ($50+/mo)
Payments:      Shopify Payments + BTCPay (via plugin)
CRM:           Shopify built-in or HubSpot (free tier)
Bank:          Mercury

Comparison Table

Dimension Medusa-Centric (A) SaaS Patchwork (B)
Monthly cost $10-55 (hosting) + Stripe fees $130-300+ (subscriptions) + payment fees
Admin panels 1 (Medusa admin) 4-6 separate dashboards
Source of truth Single (PostgreSQL) Fragmented across services
Customizability Unlimited (you own the code) Limited to each platform's API/settings
BTC/Lightning First-class (BTCPay) Plugin quality varies, some platforms hostile
RUO-specific features Build exactly what you need Shoehorn into generic tools
Setup time 2-4 weeks (dev work) 1-2 weeks (config + integration)
Maintenance Server ops + code updates SaaS updates (mostly automatic)
Data portability Full (you own the database) Export varies by vendor
Vendor lock-in None (open source) Moderate to high
Scaling cost Linear (server resources) Often percentage-of-GMV fees
Dev skills required TypeScript, Node.js, React, PostgreSQL Basic admin skills + some API config
Single-operator overhead Low once built Medium (juggling multiple tools)
Downtime risk Your responsibility Distributed across vendors

Verdict

For a developer-owner who wants to minimize ongoing admin overhead and values a single source of truth, Option A (Medusa-centric) is clearly superior. The upfront development investment (2-4 weeks) pays for itself within 3-4 months in reduced SaaS costs, and provides unlimited customizability going forward.

The SaaS patchwork makes sense for non-technical operators who need to launch fast and don't mind higher ongoing costs. It does not make sense for a developer who will be maintaining the system themselves.


10. What Medusa Cannot Do (Gaps)

External Tools Still Required

Function Why External Recommended Tool
Business bank account Legal/regulatory requirement Mercury (BTC-friendly)
Tax filing Government filing requires certified submission TurboTax, CPA, or state portal
Certified tax calculation (if required) Medusa's default is basic rate lookup Avalara ($50+/mo) or TaxJar ($19+/mo)
Accounting / bookkeeping No native double-entry accounting Wave (free), CSV export, or custom sync
Email delivery Medusa needs an email provider SendGrid (free tier: 100/day)
Domain / DNS Standard infrastructure Cloudflare (free tier)
SSL certificates Standard infrastructure Let's Encrypt (free, auto via Caddy)
Monitoring / alerts Server health monitoring Uptime Kuma (self-hosted, free)
Backups Database backup strategy pg_dump + cron + offsite storage

Hard to Build Custom

Feature Difficulty Notes
Certified tax calculations Hard Nexus rules, jurisdiction lookups, exemption handling. Use Avalara/TaxJar instead.
PCI-compliant card processing Hard Don't build this. Use Stripe.
USPS/UPS/FedEx direct API Medium Use ShipStation as aggregator instead of direct carrier APIs.
Real-time bank reconciliation Medium Bank APIs are messy. Use CSV matching or Plaid.
PDF invoice/packing slip generation Medium No built-in solution. Needs custom code with a PDF library.
Complex discount rules Low Medusa v2's Promotion module handles most cases (Buy X Get Y, conditional logic).

Rough Edges in Medusa v2

  • Plugin ecosystem is young: Many v1 plugins haven't been ported to v2. Community plugins may need testing/fixing.
  • Documentation: Good for core concepts, but advanced patterns sometimes lack examples.
  • Admin customization: Powerful but requires React knowledge. No drag-and-drop page builder.
  • No built-in PDF generation: Common e-commerce need that requires custom code.
  • No built-in analytics: Analytics module exists (PostHog integration) but is infrastructure-level, not business analytics.
  • Production node_modules size: ~837MB after recent cleanup. Significant for deployment.
  • Breaking changes in minor versions: Need to pin versions carefully and test before upgrading.

+------------------------------------------------------------------+
|                    RESEARCH RELAY LLC                              |
|                    Infrastructure                                 |
+------------------------------------------------------------------+
|                                                                    |
|  +-------------------+     +----------------------------------+   |
|  |   Next.js         |     |        MedusaJS v2               |   |
|  |   Storefront      |---->|   +----------------------------+ |   |
|  |                   |     |   | Commerce Modules           | |   |
|  | - Product pages   |     |   | - Products + RUO metadata  | |   |
|  | - Cart/Checkout   |     |   | - Orders + RMA flow        | |   |
|  | - Customer auth   |     |   | - Inventory + Lot tracking | |   |
|  | - Order history   |     |   | - Customers                | |   |
|  +-------------------+     |   | - Tax (Avalara or manual)  | |   |
|                            |   | - Promotions               | |   |
|                            |   +----------------------------+ |   |
|                            |                                  |   |
|                            |   +----------------------------+ |   |
|                            |   | Custom Modules             | |   |
|                            |   | - Lot/COA tracking         | |   |
|                            |   | - RUO compliance           | |   |
|                            |   | - Financial reports        | |   |
|                            |   | - Accounting export        | |   |
|                            |   | - Packing slip generator   | |   |
|                            |   +----------------------------+ |   |
|                            |                                  |   |
|                            |   +----------------------------+ |   |
|                            |   | Payment Providers          | |   |
|  +-------------------+     |   | - BTCPay (BTC/Lightning)   | |   |
|  |   BTCPay Server   |<----|   | - Stripe (ACH, cards)      | |   |
|  |   (self-hosted)   |     |   +----------------------------+ |   |
|  |   - BTC wallet    |     |                                  |   |
|  |   - Lightning node|     |   +----------------------------+ |   |
|  +-------------------+     |   | Fulfillment Providers      | |   |
|                            |   | - ShipStation integration  | |   |
|  +-------------------+     |   | - Manual (Phase 1)         | |   |
|  |   PostgreSQL      |<----|   +----------------------------+ |   |
|  +-------------------+     |                                  |   |
|                            |   +----------------------------+ |   |
|  +-------------------+     |   | Admin Dashboard            | |   |
|  |   Redis           |<----|   | - Stock Medusa admin       | |   |
|  +-------------------+     |   | - Custom ops pages         | |   |
|                            |   | - Lot/COA management       | |   |
|                            |   | - Financial reports        | |   |
|                            |   | - Low stock alerts         | |   |
|                            |   +----------------------------+ |   |
|                            +----------------------------------+   |
|                                                                    |
|  External Services (minimal):                                      |
|  +-------------+ +----------+ +----------+ +-------------------+  |
|  | Mercury     | | SendGrid | | Avalara  | | ShipStation       |  |
|  | (banking)   | | (email)  | | (tax)    | | (shipping labels) |  |
|  +-------------+ +----------+ +----------+ +-------------------+  |
+------------------------------------------------------------------+

Quick-Start Roadmap

Phase 1: Foundation (Weeks 1-2)

Goal: Get a working Medusa instance with basic product catalog and BTC payments.

  • Set up Medusa v2 project with Docker Compose (PostgreSQL + Redis + Medusa)
  • Configure products: create initial product catalog with variants, pricing, images
  • Set up BTCPay Server (separate VPS or container)
  • Integrate BTCPay payment provider (test with existing plugin, build custom if needed)
  • Configure tax region for California (manual rates initially)
  • Set up basic inventory tracking (single location)
  • Deploy Next.js storefront (can use Medusa starter template)
  • Test end-to-end: browse -> add to cart -> pay with BTC -> order created

Phase 2: RUO-Specific Features (Weeks 3-4)

Goal: Add custom modules for RUO compliance and lot tracking.

  • Build Lot Tracking module (lot number, expiry, purity, COA link)
  • Link Lot module to Inventory items
  • Build admin widget for lot management on product/inventory pages
  • Add RUO compliance fields to products (storage requirements, handling notes)
  • Build packing slip PDF generator with RUO disclaimer
  • Set up low stock alert scheduled job
  • Build expiring lot alert scheduled job
  • Add custom admin ops dashboard page

Phase 3: Payment & Shipping (Weeks 5-6)

Goal: Add ACH payments and shipping integration.

  • Configure Stripe payment provider (ACH via Payment Element)
  • Integrate ShipStation for label generation
  • Build shipping label workflow (order fulfilled -> label generated -> tracking number saved)
  • Set up email notifications (SendGrid): order confirmation, shipping notification
  • Test multi-payment checkout (BTC + ACH options)
  • Configure return/exchange workflow

Phase 4: Financial & Tax (Weeks 7-8)

Goal: Set up proper tax handling and financial reporting.

  • Evaluate Avalara vs manual tax rates (based on customer location distribution)
  • If needed, integrate Avalara tax provider
  • Build tax-exempt customer handling (research institution flag)
  • Build financial reporting admin page (revenue by period, product, customer)
  • Build CSV export for accounting data
  • Set up scheduled job for daily/weekly financial summary
  • Evaluate accounting tool integration (Wave, QuickBooks) based on volume

Phase 5: Polish & Production (Weeks 9-10)

Goal: Production hardening and operational polish.

  • Set up production deployment (separate server + worker instances)
  • Configure database backups (pg_dump + offsite)
  • Set up monitoring (Uptime Kuma or similar)
  • Security review: CORS, API keys, secrets management
  • Load testing with realistic data
  • Build customer portal features (order history, return requests)
  • Documentation for operational procedures

Ongoing

  • Monitor Medusa releases and upgrade as needed (pin versions, test before upgrading)
  • Build additional custom modules as operational needs emerge
  • Iterate on admin dashboard based on daily usage patterns
  • Add accounting system integration when order volume justifies it

References

Official Medusa Documentation

Payment

Tax

Fulfillment

Inventory

Hosting