Skip to content

MedusaJS v2 Development Setup Runbook

This runbook covers everything needed to bootstrap the Research Relay MedusaJS v2 development environment. Follow it step-by-step when setting up the project for the first time.

Separate Repository

Actual Medusa development happens in a separate repository, not this documentation repo (rr-bizops). This runbook documents the setup procedure for that project repo.


1. Prerequisites

1.1 Required Software

Software Version Notes
Node.js v20+ LTS v22 LTS recommended. v25+ is not supported with the Next.js Starter Storefront.
PostgreSQL 14+ Must be installed and running before project creation.
Redis 7+ Optional for local development. Required for production (event bus, workflow engine, caching).
Git Latest Required by create-medusa-app.
yarn or pnpm Latest pnpm is recommended for faster installations. This runbook uses yarn to match the project plan.

1.2 Verify Prerequisites

# Check Node.js version (must be v20+)
node -v

# Check PostgreSQL is running
pg_isready

# Check Git
git --version

# Check yarn
yarn --version

1.3 PostgreSQL Setup

If PostgreSQL is not yet running locally:

brew install postgresql@16
brew services start postgresql@16
# PostgreSQL is available via devenv or nix-shell
# See the project's devenv.nix for service configuration
docker run -d \
  --name medusa-postgres \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  postgres:16-alpine

1.4 Redis Setup (Optional for Development)

Redis is not required for local development. Medusa v2 uses in-memory implementations for the event bus and workflow engine in development mode. For production-like testing:

brew install redis
brew services start redis
docker run -d \
  --name medusa-redis \
  -p 6379:6379 \
  redis:7-alpine

2. Project Initialization

2.1 Scaffold the Medusa Project

yarn dlx create-medusa-app@latest research-relay --with-nextjs-starter

This command:

  • Creates a Medusa application in a research-relay/ directory
  • Sets up a PostgreSQL database named research-relay
  • Installs the Medusa server (Node.js) and embedded Vite admin dashboard
  • Installs the Next.js Starter Storefront in a research-relay-storefront/ directory
  • Prompts you to create an initial admin user

If you already have a database or want to skip database creation:

# Use an existing database
yarn dlx create-medusa-app@latest research-relay \
  --db-url postgres://postgres@localhost/research-relay \
  --with-nextjs-starter

# Skip database setup entirely (configure later)
yarn dlx create-medusa-app@latest research-relay \
  --skip-db \
  --with-nextjs-starter

2.2 Verify Initial Setup

After the installation completes:

cd research-relay

# The Medusa server should be running at http://localhost:9000
# The admin dashboard should be at http://localhost:9000/app
# The storefront (if installed) at http://localhost:8000

Stop the server (Ctrl+C), then verify the file structure exists:

ls -la src/
# Should show: admin/  api/  jobs/  links/  modules/  scripts/  subscribers/  workflows/

2.3 Initialize Git Repository

cd research-relay
git init
git add .
git commit -m "Initial Medusa v2 scaffold"

3. Project Directory Structure

After scaffolding and adding all custom modules from the project plan, the full directory structure will be:

research-relay/
├── .medusa/                    # Auto-generated types (do NOT modify or commit)
├── src/
│   ├── admin/                  # Admin dashboard customizations
│   │   ├── routes/             # Custom UI routes (new pages)
│   │   │   ├── compliance/     # Compliance management pages
│   │   │   │   └── page.tsx
│   │   │   └── settings/       # Custom settings pages
│   │   │       └── compliance/
│   │   │           └── page.tsx
│   │   └── widgets/            # Widgets injected into existing pages
│   │       └── product-compliance-widget.tsx
│   ├── api/                    # Custom API routes
│   │   ├── admin/              # Admin-authenticated routes
│   │   │   ├── compliance/
│   │   │   │   └── route.ts
│   │   │   └── coa/
│   │   │       └── route.ts
│   │   ├── store/              # Customer-authenticated routes
│   │   │   └── compliance/
│   │   │       └── route.ts
│   │   ├── hooks/              # Webhook endpoints
│   │   │   └── btcpay/
│   │   │       └── route.ts
│   │   └── middlewares.ts      # Route middleware definitions
│   ├── jobs/                   # Scheduled jobs (cron tasks)
│   │   └── sync-accounting.ts
│   ├── links/                  # Module links (cross-module associations)
│   │   ├── product-compliance.ts
│   │   ├── cart-attestation.ts
│   │   └── order-attestation.ts
│   ├── modules/                # Custom modules
│   │   ├── product-compliance/ # RUO compliance module
│   │   │   ├── models/
│   │   │   │   ├── lot.ts
│   │   │   │   ├── coa.ts
│   │   │   │   ├── purity-record.ts
│   │   │   │   └── ruo-disclaimer.ts
│   │   │   ├── migrations/
│   │   │   ├── loaders/
│   │   │   ├── __tests__/
│   │   │   ├── service.ts
│   │   │   └── index.ts
│   │   ├── compliance-checkout/# Checkout compliance module
│   │   │   ├── models/
│   │   │   │   └── checkout-attestation.ts
│   │   │   ├── migrations/
│   │   │   ├── __tests__/
│   │   │   ├── service.ts
│   │   │   └── index.ts
│   │   ├── btcpay/             # BTCPay payment provider
│   │   │   ├── __tests__/
│   │   │   ├── client.ts
│   │   │   ├── types.ts
│   │   │   ├── service.ts
│   │   │   └── index.ts
│   │   ├── authorize-net/      # Authorize.net payment provider
│   │   │   ├── __tests__/
│   │   │   ├── client.ts
│   │   │   ├── types.ts
│   │   │   ├── service.ts
│   │   │   └── index.ts
│   │   ├── shipstation/        # ShipStation fulfillment provider
│   │   │   ├── __tests__/
│   │   │   ├── client.ts
│   │   │   ├── types.ts
│   │   │   ├── service.ts
│   │   │   └── index.ts
│   │   └── avalara/            # Avalara tax provider
│   │       ├── __tests__/
│   │       ├── types.ts
│   │       ├── service.ts
│   │       └── index.ts
│   ├── scripts/                # Custom CLI scripts
│   │   └── seed.ts
│   ├── subscribers/            # Event handlers
│   │   ├── order-placed.ts
│   │   └── payment-captured.ts
│   └── workflows/              # Custom workflow definitions
│       ├── create-compliant-product.ts
│       ├── process-btcpay-payment.ts
│       └── validate-compliance-checkout.ts
├── medusa-config.ts            # Application configuration
├── package.json
├── tsconfig.json
└── .env                        # Environment variables (do NOT commit)

3.1 Key Conventions

Customization Location Pattern
Custom modules src/modules/<name>/ Module() export in index.ts, service in service.ts, models in models/
Module providers src/modules/<name>/ ModuleProvider() export in index.ts for payment/fulfillment/tax
Custom API routes src/api/<path>/route.ts Export named HTTP methods (GET, POST, etc.)
Subscribers src/subscribers/<name>.ts Default export = handler function, named export config = event subscription
Scheduled jobs src/jobs/<name>.ts Default export = job function, named export config = schedule
Workflows src/workflows/<name>.ts createWorkflow() composing createStep() functions
Admin widgets src/admin/widgets/<name>.tsx React component + defineWidgetConfig()
Admin UI routes src/admin/routes/<path>/page.tsx React component + defineRouteConfig()
Module links src/links/<name>.ts defineLink() between module data models

4. Environment Variables

4.1 Environment Variable Template

Create .env in the project root (this file must NOT be committed):

# ============================================================
# Research Relay -- Medusa v2 Environment Variables
# ============================================================
# Copy this file to .env and fill in values.
# Do NOT commit .env to version control.
# ============================================================

# ----------------------------------------------------------
# Core / Database
# ----------------------------------------------------------
DATABASE_URL=postgres://postgres@localhost/research-relay
NODE_ENV=development

# ----------------------------------------------------------
# Redis (optional for dev, required for production)
# ----------------------------------------------------------
# REDIS_URL=redis://localhost:6379

# ----------------------------------------------------------
# HTTP / CORS / Auth
# ----------------------------------------------------------
STORE_CORS=http://localhost:8000
ADMIN_CORS=http://localhost:9000
AUTH_CORS=http://localhost:8000,http://localhost:9000
JWT_SECRET=your-super-secret-jwt-key-change-me
COOKIE_SECRET=your-super-secret-cookie-key-change-me

# ----------------------------------------------------------
# BTCPay Server (Phase C)
# ----------------------------------------------------------
# BTCPAY_SERVER_URL=https://btcpay.researchrelay.com
# BTCPAY_STORE_ID=
# BTCPAY_API_KEY=
# BTCPAY_WEBHOOK_SECRET=

# ----------------------------------------------------------
# Authorize.net (Phase D)
# ----------------------------------------------------------
# AUTHNET_API_LOGIN_ID=
# AUTHNET_TRANSACTION_KEY=
# AUTHNET_SIGNATURE_KEY=
# AUTHNET_ENVIRONMENT=sandbox

# ----------------------------------------------------------
# Stripe ACH (conditional -- uncomment if approved)
# ----------------------------------------------------------
# STRIPE_API_KEY=

# ----------------------------------------------------------
# Avalara / AvaTax (Phase G)
# ----------------------------------------------------------
# AVALARA_USERNAME=
# AVALARA_PASSWORD=
# AVALARA_COMPANY_CODE=
# AVALARA_COMPANY_ID=
# AVALARA_ENVIRONMENT=sandbox

# ----------------------------------------------------------
# SendGrid (Phase H+)
# ----------------------------------------------------------
# SENDGRID_API_KEY=
# SENDGRID_FROM=orders@researchrelay.com

# ----------------------------------------------------------
# ShipStation (Phase F)
# ----------------------------------------------------------
# SHIPSTATION_API_KEY=

# ----------------------------------------------------------
# Production Worker Mode (deployment only)
# ----------------------------------------------------------
# MEDUSA_WORKER_MODE=shared
# DISABLE_MEDUSA_ADMIN=false
# MEDUSA_BACKEND_URL=https://api.researchrelay.com

4.2 Generate Secrets

For JWT_SECRET and COOKIE_SECRET, generate cryptographically secure random strings:

# Using openssl
openssl rand -hex 32

# Or using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

5. Medusa Configuration

5.1 medusa-config.ts

The medusa-config.ts file is the central configuration file. After scaffolding, update it to match the project plan. The full configuration with all modules registered:

import { loadEnv, defineConfig, Modules } from "@medusajs/framework/utils"

loadEnv(process.env.NODE_ENV || "development", process.cwd())

module.exports = defineConfig({
  projectConfig: {
    databaseUrl: process.env.DATABASE_URL,
    redisUrl: process.env.REDIS_URL,
    http: {
      storeCors: process.env.STORE_CORS!,
      adminCors: process.env.ADMIN_CORS!,
      authCors: process.env.AUTH_CORS!,
      jwtSecret: process.env.JWT_SECRET || "supersecret",
      cookieSecret: process.env.COOKIE_SECRET || "supersecret",
    },
    workerMode: process.env.MEDUSA_WORKER_MODE as
      | "shared"
      | "worker"
      | "server"
      | undefined,
  },
  admin: {
    disable: process.env.DISABLE_MEDUSA_ADMIN === "true",
    backendUrl: process.env.MEDUSA_BACKEND_URL,
  },
  modules: [
    // Custom modules
    { resolve: "./src/modules/product-compliance" },
    { resolve: "./src/modules/compliance-checkout" },

    // Payment providers
    {
      resolve: "@medusajs/medusa/payment",
      options: {
        providers: [
          {
            resolve: "./src/modules/btcpay",
            id: "btcpay",
            options: {
              serverUrl: process.env.BTCPAY_SERVER_URL,
              storeId: process.env.BTCPAY_STORE_ID,
              apiKey: process.env.BTCPAY_API_KEY,
              webhookSecret: process.env.BTCPAY_WEBHOOK_SECRET,
            },
          },
          {
            resolve: "./src/modules/authorize-net",
            id: "authorize-net",
            options: {
              apiLoginId: process.env.AUTHNET_API_LOGIN_ID,
              transactionKey: process.env.AUTHNET_TRANSACTION_KEY,
              signatureKey: process.env.AUTHNET_SIGNATURE_KEY,
              environment: process.env.AUTHNET_ENVIRONMENT || "sandbox",
            },
          },
        ],
      },
    },

    // Tax provider
    {
      resolve: "@medusajs/medusa/tax",
      options: {
        providers: [
          {
            resolve: "./src/modules/avalara",
            id: "avalara",
            options: {
              username: process.env.AVALARA_USERNAME,
              password: process.env.AVALARA_PASSWORD,
              companyCode: process.env.AVALARA_COMPANY_CODE,
              companyId: parseInt(process.env.AVALARA_COMPANY_ID || "0"),
              appEnvironment: process.env.AVALARA_ENVIRONMENT || "sandbox",
            },
          },
        ],
      },
    },

    // Notification provider
    {
      resolve: "@medusajs/medusa/notification",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/notification-sendgrid",
            id: "sendgrid",
            options: {
              channels: ["email"],
              api_key: process.env.SENDGRID_API_KEY,
              from: process.env.SENDGRID_FROM,
            },
          },
        ],
      },
    },

    // Fulfillment provider
    {
      resolve: "@medusajs/medusa/fulfillment",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/fulfillment-manual",
            id: "manual",
          },
          {
            resolve: "./src/modules/shipstation",
            id: "shipstation",
            options: {
              api_key: process.env.SHIPSTATION_API_KEY,
            },
          },
        ],
      },
    },
  ],
})

Incremental Registration

You do not need all modules registered from the start. Register them as you build each phase. Start Phase A with just the vanilla config (no custom modules), and add modules as each phase is completed.


6. Database Setup and Migrations

6.1 Initial Database Setup

The create-medusa-app CLI creates the database automatically. If you need to set it up manually:

# Create the database
yarn medusa db:create --db research-relay

# Or set up database, run migrations, and sync links in one command
yarn medusa db:setup --db research-relay

6.2 Generating Migrations

When you create or modify data models in a custom module, generate migration files:

# Generate migrations for a specific module
yarn medusa db:generate productComplianceModule

# Generate migrations for multiple modules at once
yarn medusa db:generate productComplianceModule complianceCheckoutModule

This creates migration files in the module's migrations/ directory based on the data model definitions.

6.3 Running Migrations

# Run all pending migrations and sync links
yarn medusa db:migrate

# Skip link syncing (rare)
yarn medusa db:migrate --skip-links

6.4 Rolling Back Migrations

# Rollback the last migration for a specific module
yarn medusa db:rollback productComplianceModule

6.5 Seeding Data

# Run the default seed script
yarn medusa exec ./src/scripts/seed.ts

A custom seed script for Research Relay should:

  1. Create test products with variants (peptides with different sizes)
  2. Create lots linked to product variants
  3. Create COAs with purity records for each lot
  4. Create a sample RUO disclaimer
  5. Set up test payment providers in regions
  6. Configure a US region with USD currency

7. Development Workflow

7.1 Start the Development Server

# Start Medusa server + admin dashboard (watches for changes)
yarn dev

This starts:

  • Medusa server at http://localhost:9000
  • Admin dashboard at http://localhost:9000/app
  • File watcher that recompiles on changes to src/ (admin customizations are hot-reloaded)

7.2 Start the Storefront

In a separate terminal, from the storefront directory:

cd research-relay-storefront
yarn dev

The Next.js Starter Storefront runs at http://localhost:8000.

7.3 Package Scripts Reference

These are the standard scripts in package.json:

{
  "scripts": {
    "dev": "medusa develop",
    "build": "medusa build",
    "start": "medusa start",
    "seed": "medusa exec ./src/scripts/seed.ts",
    "predeploy": "medusa db:migrate",
    "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit",
    "test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit",
    "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit"
  }
}

7.4 Common CLI Commands

# Start dev server
yarn dev

# Generate types (auto-runs during dev, but can run manually)
yarn medusa generate

# Database commands
yarn medusa db:generate <module_name>     # Generate migration files
yarn medusa db:migrate                     # Run pending migrations + sync links
yarn medusa db:rollback <module_name>      # Rollback last migration
yarn medusa db:setup --db <name>           # Create DB + migrate + sync

# Run a custom script
yarn medusa exec ./src/scripts/<script>.ts

# Build for production
yarn build

# Start production server
yarn start

8. Testing

8.1 Test Structure

Tests live alongside the code they test, in __tests__/ directories:

src/
├── modules/
│   └── product-compliance/
│       └── __tests__/
│           ├── service.test.ts
│           └── models.test.ts
├── workflows/
│   └── __tests__/
│       └── create-compliant-product.test.ts
└── api/
    └── admin/
        └── compliance/
            └── __tests__/
                └── route.test.ts

8.2 Running Tests

# Unit tests
yarn test:unit

# Integration tests (HTTP / API routes)
yarn test:integration:http

# Integration tests (modules)
yarn test:integration:modules

8.3 Test Dependencies

The project uses Jest with SWC for fast compilation. Required dev dependencies:

{
  "devDependencies": {
    "@medusajs/test-utils": "2.x",
    "@swc/core": "1.5.7",
    "@swc/jest": "^0.2.36",
    "@types/jest": "^29.5.13",
    "jest": "^29.7.0"
  }
}

8.4 Testing Strategy by Module Type

Module Type Test Approach What to Test
Custom modules (compliance) Unit tests Data model validation, service methods, custom business logic
Payment providers (BTCPay, Authorize.net) Unit tests with mocked HTTP Webhook signature verification, status mapping, error handling
Workflows Integration tests Step composition, compensation/rollback logic, end-to-end flow
API routes HTTP integration tests Request validation (Zod), auth/authz, response format
Admin widgets Manual + Storybook (optional) UI renders correctly with mock data

9. Custom Module Quick-Start Patterns

9.1 Creating a Standard Module

This pattern applies to the Product Compliance Module and Compliance Checkout Module:

Step 1: Define the data model

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

const MyModel = model.define("my_model", {
  id: model.id().primaryKey(),
  name: model.text(),
  // ... other fields
  metadata: model.json().nullable(),
})

export default MyModel

Step 2: Create the service

// src/modules/<name>/service.ts
import { MedusaService } from "@medusajs/framework/utils"
import MyModel from "./models/my-model"

class MyModuleService extends MedusaService({
  MyModel,
}) {
  // Auto-generated CRUD: createMyModels, retrieveMyModel,
  // listMyModels, updateMyModels, deleteMyModels
}

export default MyModuleService

Step 3: Create the module definition

// src/modules/<name>/index.ts
import MyModuleService from "./service"
import { Module } from "@medusajs/framework/utils"

export const MY_MODULE = "myModule"

export default Module(MY_MODULE, {
  service: MyModuleService,
})

Step 4: Register in medusa-config.ts

modules: [
  { resolve: "./src/modules/<name>" },
]

Step 5: Generate and run migrations

yarn medusa db:generate myModule
yarn medusa db:migrate

9.2 Creating a Payment Provider Module

This pattern applies to the BTCPay and Authorize.net modules:

Step 1: Implement the provider service

// src/modules/<name>/service.ts
import { AbstractPaymentProvider } from "@medusajs/framework/utils"
import { Logger } from "@medusajs/framework/types"

type Options = {
  apiKey: string
  // ... provider-specific options
}

type InjectedDependencies = {
  logger: Logger
}

class MyPaymentProviderService extends AbstractPaymentProvider<Options> {
  static identifier = "my-provider"

  protected logger_: Logger
  protected options_: Options

  constructor(container: InjectedDependencies, options: Options) {
    super(container, options)
    this.logger_ = container.logger
    this.options_ = options
  }

  // Must implement: initiatePayment, authorizePayment, capturePayment,
  // refundPayment, cancelPayment, deletePayment, retrievePayment,
  // updatePayment, getWebhookActionAndData
}

export default MyPaymentProviderService

Step 2: Create the module provider definition

// src/modules/<name>/index.ts
import MyPaymentProviderService from "./service"
import { ModuleProvider, Modules } from "@medusajs/framework/utils"

export default ModuleProvider(Modules.PAYMENT, {
  services: [MyPaymentProviderService],
})

Step 3: Register in medusa-config.ts

{
  resolve: "@medusajs/medusa/payment",
  options: {
    providers: [
      {
        resolve: "./src/modules/<name>",
        id: "my-provider",
        options: {
          apiKey: process.env.MY_PROVIDER_API_KEY,
        },
      },
    ],
  },
}

Webhooks are automatically handled at POST /hooks/payment/<identifier>_<id> (e.g., /hooks/payment/btcpay_btcpay).


10. Production Build and Deployment

10.1 Build

yarn build

This creates a .medusa/server/ directory containing:

  • Compiled JavaScript from src/
  • Production admin dashboard build
  • package.json and lock file for production dependencies

10.2 Start in Production

cd .medusa/server && yarn install && yarn predeploy && yarn start

The predeploy script runs medusa db:migrate to ensure all migrations are applied.

10.3 Production Environment Variables

For production, set these additional variables:

MEDUSA_WORKER_MODE=server           # For the server instance
# MEDUSA_WORKER_MODE=worker         # For the worker instance
DISABLE_MEDUSA_ADMIN=false          # false for server, true for worker
REDIS_URL=redis://<production-redis-url>
MEDUSA_BACKEND_URL=https://api.researchrelay.com

In production, deploy two instances of the Medusa application:

  1. Server mode -- handles API requests and serves the admin dashboard
  2. Worker mode -- handles background tasks (scheduled jobs, subscribers)

11. Implementation Roadmap

Each phase builds on the previous one. Only register modules in medusa-config.ts as you reach each phase.

Phase A: Vanilla Medusa Setup + Basic Product Catalog

Goal: Working Medusa v2 instance with products, regions, and basic configuration.

  • Scaffold project with create-medusa-app
  • Configure medusa-config.ts with database, CORS, and session settings
  • Set up PostgreSQL database
  • Create initial admin user
  • Configure US region with USD currency
  • Create product categories for peptide/chemical classifications
  • Add sample products with variants (sizes, quantities)
  • Install and configure Next.js Starter Storefront
  • Verify storefront can browse products and admin can manage catalog

Done when: Admin dashboard is accessible, products are visible in storefront, cart and basic checkout flow works with the system (manual) payment provider.


Phase B: Product Compliance Module (Lot Tracking, COAs)

Goal: Custom module for lot tracking, COA management, and purity records linked to products.

  • Create src/modules/product-compliance/ with data models: Lot, Coa, PurityRecord, RuoDisclaimer
  • Create module service extending MedusaService
  • Register module in medusa-config.ts
  • Define module link: ProductVariant <-> Lot
  • Generate and run migrations (yarn medusa db:generate productComplianceModule)
  • Create admin and store API routes for lot/COA CRUD
  • Create subscribers for compliance events
  • Write unit tests

Done when: Admin can create lots and upload COAs, lots are linked to product variants, public lot lookup works, COA PDFs can be downloaded.


Phase C: BTCPay Payment Integration

Goal: Accept BTC and Lightning payments through self-hosted BTCPay Server.

  • Create src/modules/btcpay/ with payment provider service
  • Implement all AbstractPaymentProvider methods
  • Implement getWebhookActionAndData for BTCPay webhook events
  • Create BTCPay Greenfield API client
  • Register as payment provider in medusa-config.ts
  • Configure webhook URL in BTCPay Server: POST /hooks/payment/btcpay_btcpay
  • Test end-to-end with BTCPay regtest mode
  • Write unit tests for webhook handler and status mapping

Done when: Customer can select BTC at checkout, BTCPay invoice is created, webhooks process payment, order is created on successful payment.


Phase D: Authorize.net Payment Integration

Goal: Accept card payments via Authorize.net through high-risk ISO.

  • Create src/modules/authorize-net/ with payment provider service
  • Implement all AbstractPaymentProvider methods
  • Create Authorize.net API client
  • Configure storefront to use Accept.js for PCI-compliant card tokenization
  • Register as payment provider in medusa-config.ts
  • Test with Authorize.net sandbox
  • Write unit tests

Done when: Card authorization succeeds via sandbox, capture works on fulfillment, refund and void operations work, fraud hold webhooks are handled.


Phase E: Compliance Checkout Flow

Goal: Enforce RUO acknowledgment, age verification, and research-use attestation at checkout.

  • Create src/modules/compliance-checkout/ with CheckoutAttestation data model
  • Create module links to Cart and Order
  • Create store API route for submitting attestation
  • Create workflow step to validate compliance before cart completion
  • Hook into cart completion workflow
  • Create subscriber on order.placed to archive attestation
  • Build storefront UI components for attestation form
  • Write tests for validation logic

Done when: Checkout requires RUO acknowledgment + age verification + research attestation, cart cannot complete without valid attestation, attestation data is stored with the order.


Phase F: Shipping + Fulfillment (ShipStation)

Goal: Integrate ShipStation for shipping label generation and fulfillment management.

  • Create src/modules/shipstation/ with fulfillment provider service
  • Implement AbstractFulfillmentProviderService methods
  • Create ShipStation API client
  • Register as fulfillment provider in medusa-config.ts
  • Configure shipping options in admin
  • Test shipping rate calculation and label purchase

Done when: Shipping rates appear at checkout, fulfillment creates ShipStation shipment, labels can be purchased, tracking numbers sync back to Medusa.


Phase G: Tax Calculation (Avalara)

Goal: Integrate Avalara/AvaTax for accurate tax calculation.

  • Create src/modules/avalara/ with tax provider service implementing ITaxProvider
  • Implement getTaxLines() using AvaTax SDK
  • Register as tax provider in medusa-config.ts
  • Create subscribers for product sync and transaction commit
  • Test with Avalara sandbox

Done when: Tax is calculated correctly during checkout, tax line items appear on cart/order, transactions are committed in Avalara.


Phase H: Admin UI Customizations

Goal: Custom admin dashboard widgets and pages for compliance management.

  • Product detail widget showing lot/COA data
  • Order detail widget showing compliance attestation
  • Compliance dashboard UI route (/admin/compliance)
  • Compliance settings page (/admin/settings/compliance)
  • COA upload form and lot management pages
  • Style using @medusajs/ui components

Done when: Product pages show linked lots and COAs, order pages show attestation, full compliance dashboard is functional.


Phase I: Accounting Sync Automation

Goal: Automated sync of order/payment data to Zoho Books with Mercury bank reconciliation.

  • Scheduled job for daily order sync to Zoho Books
  • Subscriber on order.placed to create Zoho invoice
  • Subscriber on order.payment_captured to record payment
  • Scheduled job for Mercury bank reconciliation
  • Admin settings page for accounting configuration

Done when: Orders automatically create Zoho invoices, payments record receipts, Mercury transactions reconcile against Medusa orders.


Resource URL
Medusa v2 Documentation docs.medusajs.com
Medusa CLI Reference docs.medusajs.com/resources/medusa-cli
Create Medusa App Reference docs.medusajs.com/resources/create-medusa-app
Payment Provider Guide docs.medusajs.com/resources/references/payment/provider
Module Development Guide docs.medusajs.com/learn/fundamentals/modules
Data Model Language (DML) docs.medusajs.com/learn/fundamentals/data-models
Workflows Guide docs.medusajs.com/learn/fundamentals/workflows
Deployment Guide docs.medusajs.com/learn/deployment/general
RR Project Plan Medusa Project Plan
BTCPay Architecture BTCPay Architecture
ISO Options ISO Options