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 locationreserved_quantity: Reserved but not yet fulfilledincoming_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) orreplace(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:
- Phase 1: Use manual fulfillment (mark orders as fulfilled in admin, print packing slips manually)
- Phase 2: Integrate ShipStation for label generation and tracking
- 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:
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-v2keyword, 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:
- Stripe ACH: Enable
us_bank_accountpayment method in Stripe Dashboard. Use Stripe's Payment Element on the storefront to surface ACH. This is the recommended path -- fully automated, low effort. - Manual/System Provider: Customer pays via bank transfer outside Medusa. Merchant manually confirms payment in admin. Works for low volume.
- 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:
- Custom module: Create a
TaxExemptionmodule linked to Customer. Store exemption certificate data. Build a custom tax provider that checks exemption status before calculating. - Tax Rate Rules: Create a 0% tax rate rule for specific customer groups (research institutions). Assign tax-exempt customers to that group.
- 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:
- Build a scheduled job that aggregates order tax data monthly
- Export as CSV via a custom API route or admin page
- 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¶
Option A: Build a Custom Sync Module (Recommended for Medusa-Centric)¶
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:
- Use Medusa for all transaction data
- Build an admin dashboard page showing revenue, tax collected, expenses
- Export financial summaries as CSV for tax filing
- 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¶
- Create a module directory under
src/modules/ - Define data models using DML (Data Modeling Language):
- Create a service extending
MedusaService(auto-generates CRUD methods): - Register the module in
medusa-config.ts - Generate and run migrations:
yarn medusa db:generate myModule && yarn medusa db:migrate - Link to existing models via Module Links for cross-module relationships
- 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 |
Recommended Setup for Research Relay (Solo Dev)¶
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.
Recommended Architecture¶
+------------------------------------------------------------------+
| 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¶
- Medusa v2 Documentation
- Medusa v2 Release Notes
- Modules Documentation
- Workflow Documentation
- Admin Customization
- Deployment Guide
- Commerce Modules Reference
Payment¶
- Stripe Module Provider
- Custom Payment Provider Guide
- BTCPay Plugin (SGFGOV)
- medusa-plugin-btcpay on npm