npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

order-management

v0.0.74

Published

A starter for Medusa plugins.

Readme

Compatibility

This plugin is compatible with Medusa v2 (e.g. @medusajs/framework and @medusajs/medusa 2.11.x). Check package.json peer dependencies for the exact version range.

Features

  • Order Management: Cancel and reorder for both authenticated customers and guest orders
  • Authenticated Customer APIs: Store endpoints for logged-in customers to cancel orders (/store/orders/cancel/:order_id), reorder (/store/orders/reorder/:order_id), manage returns (/store/returns), and manage exchanges (/store/swaps)
  • Guest Order Portal: OTP-based look-up for guest users with full order management (view orders, initiate returns, cancel, reorder)
  • Guest Order Actions: Secure JWT-protected endpoints for guest users to view orders, initiate returns (via Medusa workflow), cancel orders, reorder (all under /store/guest-orders/:id/...)
  • Order Confirmation Emails: Optional email notifications when orders are placed (with "Claim Order" support for registered users)
  • Status-Based Notifications: Configurable email, SMS, and push notifications for order status changes (pending, shipped, delivered, canceled, etc.)
  • Return / Exchange Delivery Window: Configurable per-type windows (returnValidInDays, exchangeValidInDays) that block return or exchange requests submitted after N days from the fulfillment's delivered_at timestamp. Defaults to 7 days each if not configured. The window is checked only when the order has been marked as delivered; undelivered orders are unaffected.
  • Return Orders Admin Panel: Admin UI and API for return orders (list, filter, search, detail, reject); returns are stored in the Medusa ORDER module and created via createAndCompleteReturnOrderWorkflow
  • Exchanges Admin Panel: Admin UI and API for exchanges (list, detail, reject, cancel); exchanges are ORDER module order changes (type exchange) created by customers via Medusa core workflows (beginExchangeOrderWorkflow, etc.)
  • Medusa workflows: Customer returns use createAndCompleteReturnOrderWorkflow; customer exchanges use beginExchangeOrderWorkflow, orderExchangeRequestItemReturnWorkflow, orderExchangeAddNewItemWorkflow, confirmExchangeRequestWorkflow, and cancelBeginOrderExchangeWorkflow. Admin actions use confirmReturnReceiveWorkflow, cancelReturnWorkflow, and cancelBeginOrderExchangeWorkflow.
  • Payment details: Custom module payment_detail for customer payment methods (UPI, bank, card). Store CRUD at /store/payment-details. Type-driven validation — UPI requires upi_id; bank requires account_holder_name, bank_name, account_number, ifsc (optional branch_name); card requires card_holder_name, card_number, expiry_date, cvv. One default per customer; return creation requires at least one default payment method.
  • Refund payment mapping: Table refund_payment_mapping (one row per return: return_id, payment_id, is_refunded, refund_mode, images, details). A mapping is created automatically when a return is created. Customer can GET mapping (read-only for all fields) and PUT payment_id only while is_refunded = false; customer can also GET all mappings for an order to see if all refunds are successful. Only admin can set is_refunded, refund_mode (offline/online), images, and details.

Plugin structure (high level)

| Area | What the plugin provides | |------|---------------------------| | Store (authenticated) | /store/orders/cancel|reorder/:id, /store/returns, /store/swaps, /store/payment-details, /store/refund-payment-mapping/:return_id, /store/orders/:order_id/refund-payment-mappings | | Store (guest) | /store/otp/request|verify, /store/guest-orders, /store/guest-orders/:id/returns|cancel|reorder|swaps | | Admin | /admin/returns, /admin/swaps, /admin/refund-payment-mapping/:return_id, /admin/refund-payment-mapping/:return_id/mark-refunded, /admin/orders/:order_id/refund-context | | Modules | payment_detail, refund_payment_mapping; returns/exchanges use Medusa ORDER module | | Subscribers | Order confirmation email, status notifications, return/swap event sync |

Important APIs (quick reference)

| Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | Store (customer) | | | | | POST | /store/orders/cancel/:order_id | Customer | Cancel order | | POST | /store/orders/reorder/:order_id | Customer | Reorder (new cart) | | GET | /store/returns | Customer | List returns (optional ?order_id=) | | POST | /store/returns | Customer | Create return | | GET | /store/returns/:id | Customer | Get return | | POST | /store/returns/:id/cancel | Customer | Cancel return | | GET | /store/swaps | Customer | List swaps (optional ?order_id=) | | POST | /store/swaps | Customer | Create swap | | GET | /store/swaps/:id | Customer | Get swap | | POST | /store/swaps/:id/cancel | Customer | Cancel swap | | GET | /store/payment-details | Customer | List payment details | | POST | /store/payment-details | Customer | Create payment detail | | GET | /store/payment-details/:id | Customer | Get payment detail | | PUT | /store/payment-details/:id | Customer | Update payment detail | | POST | /store/payment-details/:id/make-default | Customer | Set default payment | | GET | /store/refund-payment-mapping/:return_id | Customer | Get mapping (own return) | | PUT | /store/refund-payment-mapping/:return_id | Customer | Update payment_id only | | GET | /store/orders/:order_id/refund-payment-mappings | Customer | List mappings for order | | Store (guest) | | | | | POST | /store/otp/request | — | Request OTP | | POST | /store/otp/verify | — | Verify OTP, get JWT | | GET | /store/guest-orders | Guest JWT | List guest orders | | GET | /store/guest-orders/:id | Guest JWT | Get guest order | | POST | /store/guest-orders/:id/returns | Guest JWT | Create return | | POST | /store/guest-orders/:id/cancel | Guest JWT | Cancel order | | POST | /store/guest-orders/:id/reorder | Guest JWT | Reorder | | GET/POST | /store/guest-orders/:id/swaps | Guest JWT | List / create swaps | | Admin | | | | | GET | /admin/returns | Admin | List returns | | GET | /admin/returns/:id | Admin | Get return | | POST | /admin/returns/:id/reject | Admin | Reject return | | GET | /admin/swaps | Admin | List swaps | | GET | /admin/swaps/:id | Admin | Get swap | | POST | /admin/swaps/:id/reject | Admin | Reject swap | | POST | /admin/swaps/:id/cancel | Admin | Cancel exchange | | GET | /admin/orders/:order_id/refund-context | Admin | Refund context (return + mapping + payment_detail) | | PUT | /admin/refund-payment-mapping/:return_id | Admin | Update refund_mode, images, details | | POST | /admin/refund-payment-mapping/:return_id/mark-refunded | Admin | Mark mapping as refunded | | POST | /admin/uploads | Admin | Upload file (Medusa core; used by widget for refund images) |

Configuration

The plugin can be configured in your medusa-config.js file:

import { defineConfig } from "@medusajs/framework/utils"

module.exports = defineConfig({
  // ...
  plugins: [
    {
      resolve: "order-management",
      options: {
        // Required options
        storefrontUrl: process.env.STOREFRONT_URL || "http://localhost:8000",
        jwtSecret: process.env.JWT_SECRET || "medusa-secret-guest-access",
        
        // Email template options
        email: {
          orderConfirmTemplate: "src/templates/emails/order-confirmation.html", // Path to HTML template file
          otpTemplate: "src/templates/emails/otp-verification.html", // Path to HTML template file (required)
        },
        
        // Optional SMTP configuration (for email delivery)
        smtp: {
          enabled: process.env.FORCE_SMTP_REDELIVER === "true",
          host: process.env.SMTP_HOST,
          port: process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined,
          secure: process.env.SMTP_SECURE === "true",
          auth: {
            user: process.env.SMTP_AUTH_USER,
            pass: process.env.SMTP_AUTH_PASS,
          },
          from: process.env.SMTP_FROM || process.env.SMTP_AUTH_USER,
        },

        // Optional: number of days after delivery within which returns are allowed (default: 7)
        returnValidInDays: 7,

        // Optional: number of days after delivery within which exchanges are allowed (default: 7)
        exchangeValidInDays: 7,

        // Optional status-based notification configuration
        notifications: {
          enabled: true,
          statusConfig: {
            "pending": [
              {
                template: "src/templates/emails/order-placed.html",
                channel: "email"
              },
              {
                template: "src/templates/sms/order-placed.txt",
                channel: "sms",
                getRecipient: (order, _templateData) =>
                  order.shipping_address?.phone || order.billing_address?.phone
              },
              {
                template: "src/templates/push/order-placed.txt",
                channel: "push"
              }
            ],
            "shipped": [
              {
                template: "src/templates/emails/order-shipped.html",
                channel: "email"
              }
            ],
            "delivered": [
              {
                template: "src/templates/emails/order-delivered.html",
                channel: "email"
              }
            ],
            "canceled": [
              {
                template: "src/templates/emails/order-canceled.html",
                channel: "email"
              }
            ]
          }
        },
      },
    },
  ],
})

Configuration Options

Required Options:

  • storefrontUrl - Your storefront URL (required)
  • jwtSecret - JWT secret for guest order tokens (required)

Email Template Options:

  • email.orderConfirmTemplate - Path to HTML template file for order confirmation emails (optional)
  • email.otpTemplate - Path to HTML template file for OTP verification emails (required for guest OTP functionality)

Optional SMTP Options:

  • smtp.enabled - Enable SMTP email delivery (default: false)
  • smtp.host - SMTP server host (required if enabled)
  • smtp.port - SMTP server port (required if enabled)
  • smtp.secure - Use secure connection (default: true)
  • smtp.auth.user - SMTP username (required if enabled)
  • smtp.auth.pass - SMTP password (required if enabled)
  • smtp.from - From email address (optional, defaults to smtp.auth.user)

Optional Return / Exchange Window Options:

  • returnValidInDays — Number of days after fulfillment.delivered_at within which a customer may request a return. Defaults to 7. Set to a higher value to allow a longer window; the check is skipped entirely if the fulfillment has no delivered_at timestamp.
  • exchangeValidInDays — Number of days after fulfillment.delivered_at within which a customer may request an exchange. Defaults to 7. Independent from returnValidInDays so you can allow different windows for each action type.

When the window has expired the API returns:

{
  "type": "RETURN_WINDOW_EXPIRED",
  "message": "Returns and exchanges are only allowed within 7 days of delivery. The 7 days window for this order has expired.",
  "delivered_at": "2026-03-07T10:46:59.153Z",
  "window_days": 7
}

Optional Status-Based Notification Options:

  • notifications.enabled - Enable status-based notifications (default: false)
  • notifications.statusConfig - Map of order status to notification configurations
    • Each status can have multiple notification configurations
    • Each configuration requires:
      • template - Path to template file (HTML for email, text for SMS/push)
      • channel - Notification channel (supports any string: "email", "sms", "push", or any custom channel)
    • Optional per-config:
      • getRecipient(order, templateData) - Function that receives the order and template data and returns the recipient for this notification (e.g. mobile number for SMS, customer_id for push). If omitted, the default resolver for the channel is used (e.g. order.email for email, shipping_address.phone for SMS, order.customer_id for push).
    • Push channel: For channel: "push", the recipient is treated as a customer_id. The code loads all active device tokens for that customer from the notification_tokens module (e.g. when using the medusa-notification-token-management plugin) and sends one push notification to each token. If the token module is not installed or the customer has no tokens, no push is sent.
    • SMS channel: The plugin sends the SMS body in a form compatible with medusa-twilio-sms (content.text) and as text/body/data.text for other adapters. If you use medusa-twilio-sms and patch it to also accept string content or data.text, use npx patch-package medusa-twilio-sms after editing so the fix persists across installs.

Notes:

  • storefrontUrl and jwtSecret are required. All other options are optional.
  • Order confirmation emails are only sent if a template path is provided. If no template is configured, emails will not be sent.
  • OTP template is mandatory for guest OTP functionality. The guest OTP request API will return an error if email.otpTemplate is not configured.
  • OTP templates should include {{otp}} placeholder for the verification code.
  • SMTP configuration is optional. If not enabled, emails will use Medusa's default email provider.
  • Status-based notifications are optional. If enabled, notifications will be sent whenever an order reaches a configured status.

Payment detail module

The plugin includes a payment_detail module for storing customer payment methods (e.g. UPI, bank, card). To use it:

  1. Register the module in medusa-config.js:
import orderManagement from "order-management"

module.exports = defineConfig({
  modules: [
    // ... other modules
    orderManagement.modules["payment-detail"],
  ],
  plugins: [
    { resolve: "order-management", options: { storefrontUrl: "...", jwtSecret: "..." } },
  ],
})
  1. Run migrations from your Medusa project (or from the plugin directory):
    npx medusa plugin:db:generate (if using the plugin's migrations from the app).

  2. Store API (authenticated customers only):

    • GET /store/payment-details – list (scoped to customer_id)
    • GET /store/payment-details/:id – get one
    • POST /store/payment-details – create (type, detail_json only; is_default is rejected)
    • PUT /store/payment-details/:id – update (no is_default; use make-default endpoint)
    • DELETE /store/payment-details/:id – delete
    • POST /store/payment-details/:id/make-defaultonly way to set a payment as default (idempotent, atomic)
  3. Validation: type must be one of upi, bank, card. detail_json is type-driven:

    • type: "upi"detail_json.upi_id required
    • type: "bank"account_holder_name, bank_name, account_number, ifsc required; branch_name optional
    • type: "card"card_holder_name, card_number, expiry_date (MM/YY), cvv required
  4. Default payment: At least one record per customer must have is_default: true. Only one default per customer (enforced by DB unique partial index and service logic). The only way to change which record is default is POST /store/payment-details/:id/make-default; create/update APIs reject is_default in the body. Creating a return (POST /store/returns) is blocked if the customer has no default payment method.

Refund payment mapping module

The plugin includes a refund_payment_mapping module: one row per return linking a return to a payment (e.g. for refund destination), with is_refunded, refund_mode, images, and details.

  • Table: refund_payment_mappingid, return_id (unique), payment_id, is_refunded (default false), refund_mode (text, nullable; admin-only: use "offline" or "online"), images (JSON array of URLs), details (text, nullable; admin-only notes), timestamps.
  • Auto-create: When a return is created (store or guest), a mapping row is created; optional refund_payment_id can be sent in the create-return body.
  • Store API:
    • GET /store/refund-payment-mapping/:return_id — get mapping for one return (customer must own the return). Returns all fields read-only; customer can see is_refunded, refund_mode, images, details.
    • PUT /store/refund-payment-mapping/:return_id — body { payment_id } only; allowed only while is_refunded = false. Request body must not include refund_mode, images, or details (admin-only).
    • GET /store/orders/:order_id/refund-payment-mappings — list all refund payment mappings for the order’s returns (customer must own the order). Use to show “all refunds successful” vs “some pending” (check is_refunded on each).
  • Admin API:
    • PUT /admin/refund-payment-mapping/:return_id — update admin-only fields. Body: { refund_mode?: "offline" | "online" | null, images?: string[], details?: string | null } (partial; only provided keys are updated).
    • POST /admin/refund-payment-mapping/:return_id/mark-refunded — set is_refunded = true (idempotent).
  • Order detail integration: GET /admin/orders/:order_id/refund-context — aggregated read returning return_id, refund_mapping (id, payment_id, is_refunded, refund_mode, images, details), and payment_detail (id, type, detail_json, is_default) resolved from the customer's default payment detail. Returns null blocks when no return or mapping exists. An admin widget at order.details.after shows refund payment method, Refund mode (Offline/Online), Details, Images (with upload via Medusa POST /admin/uploads), and Mark refunded (when is_refunded = false). Only admin can change refund_mode, images, and details.
  • Registration: Add the module in medusa-config.js (e.g. orderManagement.modules["refund-payment-mapping"]) and run migrations.

Return Orders Admin Panel

The plugin includes a dedicated section in the Medusa Admin Panel for managing customer return orders. This feature provides administrators with comprehensive tools to view, search, filter, and manage all return orders.

Features

  • List View: View all return orders in a table format with key information
  • Search: Search returns by return ID, order ID, or customer email
  • Filtering: Filter returns by status (requested, received, requires_action, completed, canceled)
  • Detail Pages: View detailed information for each return order including:
    • Return information (ID, status, refund amount, reason, note)
    • Related order information (order ID, customer, totals)
    • Return items with quantities
    • Status history timeline
    • Metadata
  • Status Management: Update return status with validation and status history tracking
  • Pagination: Load more returns with pagination support

Accessing Return Orders

Once the plugin is installed and configured, you can access the Return Orders section from the Admin Panel sidebar. The section appears as "Return Orders" with a return icon.

API Endpoints

The plugin provides admin API endpoints for return orders:

  • GET /admin/returns - List all return orders (filtering, search, pagination)
  • GET /admin/returns/:id - Get detailed information for a specific return
  • POST /admin/returns/:id/reject - Reject the return

Full request/response details are in the Standalone Return Flow section below.

Return Statuses

The plugin supports the following return statuses:

  • requested - Return has been requested by the customer
  • received - Return items have been received
  • requires_action - Return requires manual intervention
  • completed - Return has been completed
  • canceled - Return has been canceled

Status updates are tracked in the return's metadata with timestamps and admin user IDs.

One active return or exchange per item (Medusa v2)

Medusa v2 does not allow multiple active return/exchange requests for the same order item at the same time. Once an item is part of a return or a swap (exchange), that quantity is reserved until the request is completed or canceled. Duplicate requests for the same quantity will fail validation (prevents double refunds / double fulfillment).

To allow a new request, the first one must be closed or canceled:

| State | Action | Effect | |-------|--------|--------| | Swap (exchange) pending / not confirmed | POST /admin/swaps/{swap_id}/reject or POST /admin/swaps/{swap_id}/cancel | Releases reserved quantities; item becomes eligible again. | | Return created but not received yet | POST /admin/returns/{return_id}/reject | Closes return flow; quantities unlocked. | | Exchange already created in Medusa | POST /admin/swaps/{swap_id}/cancel | Cancels the exchange; same unlock behavior. |

Storefront gating: Before showing "Request Return" or "Request Exchange", check the order (e.g. GET /admin/orders/{id} or your store API) and block if the item has an active return, active swap (exchange), or claim in progress. Show status instead (e.g. "Return in review", "Exchange pending", "Request rejected — retry available").

Operational pattern: When a customer creates a swap, the exchange is created directly (no approval step). Use statuses such as rejected, cancelled, completed and only allow a new request when status is rejected or cancelled.

Guest Order Portal

The Guest Order Portal allows users who placed orders without an account to view their order status, cancel orders, reorder, initiate returns, and download invoices securely via an OTP (One-Time Password) system.

Security Features

  • Strict Separation: Guest orders are strictly filtered by unique guest customer IDs. Registered account orders will never be exposed in the guest portal.
  • Access Control: The portal uses short-lived JWT tokens issued upon successful OTP verification.
  • Account Protection: Emails belonging to registered accounts are blocked from the guest OTP flow to prevent unauthorized access and encourage secure logins.

Complete API Documentation

For detailed API documentation, usage examples, error handling, and security best practices, see the Guest Order API Guide.

Store API Endpoints

The plugin provides the following store API endpoints for the Guest Order Portal:

Authentication

  • POST /store/otp/request - Request an OTP for an email or phone number.
  • POST /store/otp/verify - Verify the OTP and receive a guest JWT token.

Guest Order Management

  • GET /store/guest-orders - List summary of orders for the verified guest identifier.
  • GET /store/guest-orders/:id - Get full details for a specific guest order (includes items, shipping status, etc.).

Guest Order Actions

  • GET /store/guest-orders/:id - Get full details for a specific guest order (requires guest JWT).
  • POST /store/guest-orders/:id/returns - Initiate a return request for a guest order.
  • POST /store/guest-orders/:id/cancel - Cancel a guest order.
  • POST /store/guest-orders/:id/reorder - Reorder a guest order (creates a new cart with the same items).
  • GET /store/guest-orders/:id/swaps - List exchange requests for the guest order.
  • POST /store/guest-orders/:id/swaps - Create an exchange request (return items + new items).
  • GET /store/guest-orders/:id/swaps/:swap_id - Get one exchange request.
  • POST /store/guest-orders/:id/swaps/:swap_id/cancel - Cancel an exchange request.

Note: All guest order endpoints require the guest JWT in the Authorization: Bearer <token> header or cookie.

Authenticated Customer Order APIs

Logged-in customers (storefront with customer auth) can use these endpoints without the guest portal:

  • POST /store/orders/cancel/:order_id - Cancel an order (customer must own the order).
  • POST /store/orders/reorder/:order_id - Reorder (creates a new cart with the same items).
  • GET /store/returns, POST /store/returns, GET /store/returns/:id, POST /store/returns/:id/cancel - List and create returns (filtered by customer).
  • GET /store/swaps, POST /store/swaps, GET /store/swaps/:id, POST /store/swaps/:id/cancel - List and create exchanges (filtered by customer).

All require Authorization: Bearer <customer_token>.

Email Templates

The plugin supports custom HTML templates for order confirmation emails. Templates use variable replacement with {{variable_name}} syntax.

Creating Templates

Create HTML template files for email notifications. Place them in your project, for example:

your-project/
  src/
    templates/
      emails/
        order-confirmation.html

Example Email Template (src/templates/emails/order-confirmation.html):

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background-color: #ffffff; }
        .order-info { background-color: #f5f5f5; padding: 15px; margin: 15px 0; border-radius: 5px; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Order Confirmation</h1>
        </div>
        <div class="content">
            <div class="order-info">
                <p><strong>Order ID:</strong> {{order_id}}</p>
                <p><strong>Status:</strong> {{order_status}}</p>
                <p><strong>Total:</strong> {{order_total}}</p>
                <p><strong>Email:</strong> {{order_email}}</p>
                <p><strong>Date:</strong> {{order_date}}</p>
            </div>
            <p>Thank you for your order!</p>
        </div>
    </div>
</body>
</html>

Template Variables

The following variables are available in order confirmation email templates:

| Variable | Description | Example | |----------|-------------|---------| | {{order_id}} | Order ID | order_123 | | {{order_status}} | Order status | pending | | {{order_total}} | Order total amount | 99.99 | | {{currency_code}} | Order currency (ISO 4217, lowercase, e.g. usd, inr) | inr | | {{order_email}} | Customer email address | [email protected] | | {{order_date}} | Order date/time (localized, long date + short time) | January 15, 2024 at 10:30 AM | | {{order_items}} | Order items array (JSON stringified) | [{"title":"Product","quantity":1}] | | {{shipping_address}} | Shipping address object (JSON stringified) | {"first_name":"John",...} | | {{billing_address}} | Billing address object (JSON stringified) | {"first_name":"John",...} | | {{is_registered}} | Whether the customer email has a registered account | true | | {{claim_link}} | Link for registered users to claim their guest order | http://.../claim?order_id=... |

Status-Based Notifications

The plugin supports automatic notifications (email and SMS) based on order status changes. When an order reaches a configured status, the plugin will automatically send all configured notifications for that status.

📖 For detailed documentation, see the Notifications Guide

Features

  • Multi-Channel Support: Send notifications via email and/or SMS
  • Status-Based Configuration: Configure different notifications for different order statuses
  • Multiple Notifications Per Status: Send multiple notifications for a single status change
  • Template-Based: Use custom templates for each notification
  • Automatic Triggering: Notifications are automatically triggered when order status changes

Configuration

Configure status-based notifications in your medusa-config.js:

{
  resolve: "order-management",
  options: {
    // ... other options ...
    notifications: {
      enabled: true,
      statusConfig: {
        // Status name as key
        "pending": [
          {
            template: "src/templates/emails/order-placed.html",
            channel: "email"
          },
          {
            template: "src/templates/sms/order-placed.txt",
            channel: "sms"
          }
        ],
        "shipped": [
          {
            template: "src/templates/emails/order-shipped.html",
            channel: "email"
          }
        ],
        "delivered": [
          {
            template: "src/templates/emails/order-delivered.html",
            channel: "email"
          },
          {
            template: "src/templates/sms/order-delivered.txt",
            channel: "sms"
          }
        ],
        "canceled": [
          {
            template: "src/templates/emails/order-canceled.html",
            channel: "email"
          }
        ]
      }
    }
  }
}

Supported Channels

The notification system supports dynamic channel types, allowing you to configure any channel supported by your notification service:

  • email: Sends email notifications to the customer's email address
  • sms: Sends SMS notifications to the customer's phone number (from shipping address)
  • push: Sends push notifications (browser/mobile notifications) to the customer's device
  • Any custom channel: Configure any channel name supported by your notification service (e.g., whatsapp, slack, webhook, etc.)

The system automatically resolves recipients and sends notifications through the appropriate channel handler.

Triggered Events

The notification system listens to the following Medusa events:

  • order.updated - General order status changes
  • order.placed - When an order is first placed
  • order.shipment_created - When a shipment is created
  • order.fulfillment_created - When fulfillment starts
  • order.completed - When an order is completed
  • order.canceled - When an order is canceled
  • delivery.created - When an order is delivered

Using Push Notifications

The plugin now supports push notifications (browser/mobile push) alongside email and SMS. Here's how to configure them:

{
  resolve: "order-management",
  options: {
    // ... other options ...
    notifications: {
      enabled: true,
      statusConfig: {
        "shipped": [
          {
            template: "src/templates/emails/order-shipped.html",
            channel: "email"
          },
          {
            template: "src/templates/push/order-shipped.txt",
            channel: "push"  // Browser/mobile push notification
          }
        ],
        "delivered": [
          {
            template: "src/templates/emails/order-delivered.html",
            channel: "email"
          },
          {
            template: "src/templates/sms/order-delivered.txt",
            channel: "sms"
          },
          {
            template: "src/templates/push/order-delivered.txt",
            channel: "push"  // Browser/mobile push notification
          }
        ]
      }
    }
  }
}

Push Notification Template Example (src/templates/push/order-shipped.txt):

📦 Your order {{order_id}} has been shipped! Track your package to stay updated.

Important Notes:

  • Push notifications require a configured notification service provider (e.g., FCM, OneSignal, Web Push API)
  • The recipient resolver for push notifications uses the customer's email to identify the device token
  • You may need to implement custom device token registration in your storefront
  • Push notification templates should be concise (similar to SMS)

Creating Notification Templates

Note: The plugin includes example templates in src/templates/emails/ and src/templates/sms/ that you can use as a starting point.

Email Templates

Create HTML templates for email notifications. Templates use the same variable replacement syntax as order confirmation emails:

Example: Order Shipped Email (src/templates/emails/order-shipped.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background-color: #2196F3; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background-color: #ffffff; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Your Order Has Shipped!</h1>
        </div>
        <div class="content">
            <p>Hi there,</p>
            <p>Great news! Your order <strong>{{order_id}}</strong> has been shipped and is on its way to you.</p>
            <p><strong>Order Total:</strong> {{order_total}}</p>
            <p><strong>Shipped on:</strong> {{order_date}}</p>
            <p>Thank you for your purchase!</p>
        </div>
    </div>
</body>
</html>

SMS Templates

Create plain text templates for SMS notifications:

Example: Order Shipped SMS (src/templates/sms/order-shipped.txt)

Your order {{order_id}} has been shipped! Total: {{order_total}}. Thank you for your purchase.

Available Template Variables

All notification templates have access to the same variables as order confirmation emails:

  • {{order_id}} - Order ID
  • {{order_status}} - Current order status
  • {{order_total}} - Order total amount
  • {{order_email}} - Customer email address
  • {{order_date}} - Order creation/update date
  • {{order_items}} - Order items array (JSON stringified)
  • {{shipping_address}} - Shipping address object (JSON stringified)
  • {{billing_address}} - Billing address object (JSON stringified)
  • {{is_registered}} - Whether the customer has a registered account
  • {{claim_link}} - Link for registered users to claim their order

Common Status Names

Common Medusa order statuses you can configure notifications for:

  • pending - Order placed, payment pending
  • awaiting - Awaiting fulfillment
  • fulfilled - Order fulfilled
  • shipped - Order shipped
  • delivered - Order delivered
  • completed - Order completed
  • canceled - Order canceled
  • requires_action - Order requires action

Note: You can configure notifications for any custom status your store uses.

Extending with Custom Channels

The notification system is designed to be extensible. Any channel name you configure will automatically be handled by the notification service. For example:

notifications: {
  enabled: true,
  statusConfig: {
    "shipped": [
      {
        template: "src/templates/whatsapp/order-shipped.txt",
        channel: "whatsapp"  // Custom channel
      },
      {
        template: "src/templates/slack/order-shipped.txt",
        channel: "slack"  // Custom channel
      }
    ]
  }
}

Channel Handler Flow:

  1. System detects configured channel (e.g., whatsapp, slack)
  2. Resolves recipient using channel-specific resolver (falls back to email/customer ID)
  3. Loads and renders the specified template
  4. Sends notification via Medusa's notification service with the channel name
  5. Your configured notification provider handles the actual delivery

Requirements:

  • Configure a notification provider that supports your custom channel
  • The provider should handle the channel name in its notification payload
  • Implement custom recipient resolvers if needed (by default, uses email or customer ID)

Best Practices

  1. Keep SMS/Push Messages Short: SMS and push notifications have character limits, keep messages concise
  2. Use Descriptive Subject Lines: For email templates, include clear subject information
  3. Test Templates: Test your templates with sample data before going live
  4. Provide Value: Only send notifications that provide value to customers
  5. Include Contact Info: Add customer support contact information in templates
  6. Mobile-Friendly Emails: Design email templates that work well on mobile devices
  7. SMS/Push Opt-In: Ensure customers have opted in to receive SMS and push notifications
  8. Channel Fallbacks: Consider configuring multiple channels (e.g., email + push) for important notifications

Example: Complete Notification Flow

// In medusa-config.js
notifications: {
  enabled: true,
  statusConfig: {
    // Order placed
    "pending": [
      {
        template: "src/templates/emails/order-placed.html",
        channel: "email"
      }
    ],
    
    // Order processing
    "awaiting": [
      {
        template: "src/templates/emails/order-processing.html",
        channel: "email"
      }
    ],
    
    // Order shipped
    "shipped": [
      {
        template: "src/templates/emails/order-shipped.html",
        channel: "email"
      },
      {
        template: "src/templates/sms/order-shipped.txt",
        channel: "sms"
      }
    ],
    
    // Order delivered
    "delivered": [
      {
        template: "src/templates/emails/order-delivered.html",
        channel: "email"
      },
      {
        template: "src/templates/sms/order-delivered.txt",
        channel: "sms"
      },
      {
        template: "src/templates/push/order-delivered.txt",
        channel: "push"
      }
    ]
  }
}

This configuration will:

  1. Send an email when the order is placed (pending)
  2. Send an email when the order starts processing (awaiting)
  3. Send both email and SMS when the order is shipped (shipped)
  4. Send email, SMS, and push notifications when the order is delivered (delivered)

Customer-Initiated Swaps

The plugin includes a complete swap/exchange feature that allows customers to request item swaps directly from the storefront. This extends Medusa v2's native OrderExchange functionality with customer-facing APIs and admin management tools.

Features

  • Customer-Initiated Swaps: Customers create swaps from the storefront; the exchange is created directly (no approval step).
  • Complete Status Lifecycle: Full status tracking from creation → return_startedreturn_shippedreturn_receivednew_items_shippedcompleted
  • Admin Management: Admin panel for viewing, rejecting, and cancelling swaps (no approve step; exchange exists as soon as the customer submits).
  • Price Difference Calculation: Automatic calculation of price differences between returned and new items
  • Status History: Complete audit trail of all status changes

One active return or exchange per item

Medusa v2 allows only one active return or exchange per order item at a time. To allow a new swap/return for the same item, cancel or reject the existing one first (see One active return or exchange per item in the Return Orders section).

Swap Status Flow

When a customer creates a swap, the exchange is created and confirmed immediately. Status then progresses as:

(created with exchange) → return_started → return_shipped → return_received → new_items_shipped → completed
                      ↘ rejected
                      ↘ cancelled (from any status except completed)

Storefront API Endpoints (Authenticated Customers)

All store swap endpoints require customer authentication (Authorization: Bearer <customer_token>).

Create Swap Request

POST /store/swaps
Authorization: Bearer <customer_token>
Content-Type: application/json

{
  "order_id": "order_123",
  "return_items": [
    {
      "id": "item_123",
      "quantity": 1,
      "reason": "Wrong size"
    }
  ],
  "new_items": [
    {
      "variant_id": "variant_456",
      "quantity": 1
    }
  ],
  "reason": "Size exchange",
  "note": "Please send size M instead"
}

List Customer Swaps

GET /store/swaps?order_id=order_123&limit=100&offset=0
Authorization: Bearer <customer_token>

Optional query: order_id to filter by order; limit and offset for pagination.

Get Swap Details

GET /store/swaps/{swap_id}
Authorization: Bearer <customer_token>

Cancel Swap

POST /store/swaps/{swap_id}/cancel
Authorization: Bearer <customer_token>

Admin API Endpoints

List All Swaps

GET /admin/swaps?status=requested&order_id=order_123&limit=50&offset=0

Get Swap Details

GET /admin/swaps/{swap_id}

Reject Swap

POST /admin/swaps/{swap_id}/reject
Content-Type: application/json

{
  "reason": "Item out of stock"
}

Cancel Exchange

POST /admin/swaps/{swap_id}/cancel

Cancels an exchange that has already been created in Medusa (has exchange_id). Only available when the swap is not already completed, rejected, or cancelled.

Admin UI

The plugin includes an admin UI for managing swaps:

  • Swaps List Page: View all swaps with filtering by status, search by order ID, and pagination
  • Swap Detail Page: View swap information including swap details, order, return items, new items, and status history. Actions: Reject and Cancel Exchange

Access the Swaps section from the Admin Panel sidebar.

Storefront Helpers

The plugin exports helpers for use in custom workflows or server-side code (e.g. within the Medusa backend). Import from the built plugin:

import { createSwapRequest, getSwaps, getSwap, cancelSwap } from "order-management/helpers"

// Create a swap request (order_id in payload)
const swap = await createSwapRequest({
  orderId: "order_123",
  returnItems: [
    { id: "item_123", quantity: 1, reason: "Wrong size" }
  ],
  newItems: [
    { variant_id: "variant_456", quantity: 1 }
  ],
  reason: "Size exchange",
  note: "Please send size M"
}, container)

// Get swaps (optionally filtered by order)
const swaps = await getSwaps("order_123", container)

// Get a specific swap
const swapDetails = await getSwap("swap_123", container)

// Cancel a swap
const cancelledSwap = await cancelSwap("swap_123", container)

Return helpers are available from order-management/helpers/returns (see Standalone Return Flow section).

Status Transitions

The swap status flow enforces valid transitions (exchange is created directly on customer request; no approval step):

  • requested / initial → rejected, cancelled, or progress to return_started
  • return_startedreturn_shipped, cancelled
  • return_shippedreturn_received, cancelled
  • return_receivednew_items_shipped, cancelled
  • new_items_shippedcompleted, cancelled
  • rejected → (terminal state)
  • completed → (terminal state)
  • cancelled → (terminal state)

Invalid status transitions will be rejected with a descriptive error message.

Return created_by: When an exchange is created, a return is created by Medusa's workflow without created_by. The plugin sets the return's created_by in store and guest swap routes after the workflow runs, so it matches the order change creator. A subscriber on order.return_requested syncs created_by from the order change when the event is emitted. For exchanges created in the admin and completed via Medusa's admin API, the exchange workflow does not emit that event, so the return's created_by may remain unset unless Medusa adds support (e.g. passing created_by into the workflow or emitting the event).

Standalone Return Flow

The plugin includes a comprehensive standalone return management system that allows customers to initiate returns directly from the storefront. This extends Medusa v2's native Return functionality with customer-facing APIs, admin management tools, and complete status lifecycle tracking.

Features

  • Customer-Initiated Returns: Customers can request returns for their orders directly from the storefront
  • Complete Status Lifecycle: Full status tracking from requestedapprovedreceivedrefundedcompleted
  • Admin Management: Complete admin panel with action buttons for approving, rejecting, marking as received, and processing refunds
  • Automatic Refund Calculation: Automatic calculation of refund amounts based on returned items
  • Status History: Complete audit trail of all status changes with timestamps and admin IDs
  • Auto-Linking: Automatic linking between Medusa returns and custom return module via event subscribers
  • Multi-Channel Notifications: Support for email, SMS, and push notifications for return status changes

Return Status Flow

requested → approved → received → refunded → completed
         ↘ rejected
         ↘ cancelled (by customer, only when requested)

Storefront API Endpoints

Create Return Request

POST /store/returns
Authorization: Bearer <customer_token>
Content-Type: application/json

{
  "order_id": "order_123",
  "return_items": [
    {
      "id": "item_123",
      "quantity": 1,
      "reason": "Defective item",
      "variant_id": "variant_123"  // Optional
    }
  ],
  "reason": "Product defective",
  "note": "Item arrived damaged"
}

List Customer Returns

GET /store/returns?order_id=order_123
Authorization: Bearer <customer_token>

Get Return Details

GET /store/returns/{return_id}
Authorization: Bearer <customer_token>

Create Return for Order

POST /store/orders/{order_id}/returns
Authorization: Bearer <customer_token>
Content-Type: application/json

{
  "return_items": [
    {
      "id": "item_123",
      "quantity": 1,
      "reason": "Wrong size"
    }
  ],
  "reason": "Size issue",
  "note": "Ordered wrong size"
}

Cancel Return

POST /store/returns/{return_id}/cancel
Authorization: Bearer <customer_token>

Note: Returns can only be cancelled when status is requested.

Admin API Endpoints

List All Returns

GET /admin/returns?status=requested&order_id=order_123&limit=50&offset=0

Get Return Details

GET /admin/returns/{return_id}

Reject Return

POST /admin/returns/{return_id}/reject
Content-Type: application/json

{
  "reason": "Outside return window"
}

Admin UI

The plugin includes a complete admin UI for managing returns:

  • Returns List Page: View all returns with filtering by status, search by return/order ID, and pagination
  • Return Detail Page: View complete return information including:
    • Return details (ID, status, dates, refund amount, refund status)
    • Action button: Reject (for requested returns)
    • Medusa return information (when linked)
    • Related order information
    • Return items list
    • Status history timeline

Action Buttons by Status:

  • requested: Show "Approve & Process Return" and "Reject Return" buttons
  • approved: Show "Mark as Received" button
  • received: Show "Process Refund" button

Access the Returns section from the Admin Panel sidebar at /returns.

Status Transitions

The return status flow enforces valid transitions:

  • requestedapproved, rejected, cancelled
  • approvedreceived, cancelled
  • rejected → (terminal state)
  • receivedrefunded
  • refundedcompleted
  • completed → (terminal state)
  • cancelled → (terminal state)

Invalid status transitions will be rejected with a descriptive error message.

Event Subscribers

The return flow includes automatic event handling:

Return Created Subscriber

  • Listens to: return.created, order.return_requested
  • Automatically links Medusa returns to custom return module
  • Updates return with medusa_return_id
  • Tracks linking history in metadata

Return Received Subscriber

  • Listens to: return.received
  • Automatically updates return status to received
  • Tracks receipt in status history

Return Refunded Subscriber

  • Listens to: refund.created, return.refund_processed
  • Automatically updates return status to refunded
  • Updates refund_status to refunded
  • Tracks refund amount and timestamp

Refund Calculation

Refund amounts are automatically calculated based on:

  • Item unit prices at time of order
  • Quantity of items being returned
  • Stored in refund_amount field (in cents)

Example calculation:

refund_amount = Σ (item.unit_price * return_quantity) for each returned item

Eligibility and Validation

Returns are validated for:

  • Order Ownership: Customer must own the order (or guest token must match the order email)
  • Default Payment Method: Customer must have a default payment method before creating a return
  • Delivery Window: Return is blocked if more than returnValidInDays days have passed since fulfillment.delivered_at. If the fulfillment has no delivered_at the check is skipped. Configure in plugin options (default: 7 days)
  • Exchange Window: Exchange is blocked if more than exchangeValidInDays days have passed since fulfillment.delivered_at (default: 7 days)
  • Duplicate Prevention: Cannot create a new return or exchange while one is already pending or requested for the same order

Helper Functions

The plugin provides helper functions for return operations:

import {
  getOrderReturnData,
  validateReturnWindow,
  calculateRefundAmount,
  canApproveReturn,
  canCancelReturn,
  canMarkAsReceived,
  canProcessRefund,
} from "order-management/helpers/returns"

// Get order data for return
const returnData = await getOrderReturnData("order_123", container)

// Validate return window
const isValid = validateReturnWindow(order.created_at, 30) // 30 days

// Calculate refund amount
const refund = calculateRefundAmount(returnItems, orderItems)

// Check status permissions
const canApprove = canApproveReturn(return.status)
const canCancel = canCancelReturn(return.status)
const canReceive = canMarkAsReceived(return.status)
const canRefund = canProcessRefund(return.status)

Integration with Medusa Returns

The custom return module integrates seamlessly with Medusa's native return functionality:

  1. Return Creation: When approved, creates a Medusa return entity via orderModuleService.createReturn()
  2. Linking: Stores medusa_return_id for bidirectional linking
  3. Status Sync: Event subscribers automatically sync status changes from Medusa to custom module
  4. View in Medusa: Admin UI provides direct links to view returns in Medusa admin

Metadata Structure

Returns store rich metadata for audit trails:

{
  metadata: {
    created_at: "2026-02-05T10:30:00Z",
    customer_id: "cus_123",
    approved_at: "2026-02-05T11:00:00Z",
    approved_by: "admin_456",
    received_at: "2026-02-10T14:20:00Z",
    refunded_at: "2026-02-10T15:00:00Z",
    medusa_return_id: "ret_789",
    medusa_return_linked_at: "2026-02-05T11:00:30Z",
    status_history: [
      {
        status: "requested",
        timestamp: "2026-02-05T10:30:00Z",
        customer_id: "cus_123"
      },
      {
        status: "approved",
        timestamp: "2026-02-05T11:00:00Z",
        admin_id: "admin_456"
      },
      {
        status: "received",
        timestamp: "2026-02-10T14:20:00Z",
        admin_id: "admin_456"
      },
      {
        status: "refunded",
        timestamp: "2026-02-10T15:00:00Z",
        admin_id: "admin_456",
        refund_amount: 5000  // in cents
      }
    ]
  }
}

Getting Started

Visit the Quickstart Guide to set up a server.

Visit the Plugins documentation to learn more about plugins and how to create them.

Visit the Docs to learn more about our system requirements.

What is Medusa

Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.

Learn more about Medusa’s architecture and commerce modules in the Docs.

Community & Contributions

The community and core team are available in GitHub Discussions, where you can ask for support, discuss roadmap, and share ideas.

Join our Discord server to meet other community members.

Other channels