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

@devx-retailos/order

v1.3.0

Published

Order module for retailOS. Manages POS orders, line items, and payments with built-in discount validation.

Readme

@devx-retailos/order

POS order management for Medusa v2: orders, line items, payments, discount validation, advance orders with deposits and fulfillment stages, returns & policy-driven refunds, and daily / day-close reporting.

Part of retailOS, a Medusa v2 SDK for offline-store POS systems. Each @devx-retailos/* package is an independently installable Medusa plugin; a brand backend composes the ones it needs in medusa-config.ts.

Installation

npm install @devx-retailos/order

Peer dependencies: @medusajs/framework and @medusajs/medusa ^2.15.0. @devx-retailos/discount is an optional peer — required only if you place orders with discount_id or coupon_code.

Setup

// medusa-config.ts
module.exports = defineConfig({
  plugins: [
    { resolve: "@devx-retailos/rbac", options: {} },
    { resolve: "@devx-retailos/order", options: {} },
  ],
})

The module registers under the key retailos_order (exported as ORDER_MODULE) and registers its permission keys with @devx-retailos/rbac at boot.

Options

| Option | Type | Default | Purpose | | --- | --- | --- | --- | | refundPolicy | RefundPolicyConfig | ≤10d → 100%, ≤30d → 50%, beyond → ineligible; defective goods full refund | Time-based refund limitation used by the return flow. | | refundMatrix | RefundMatrix | { default: "ORIGINAL_TENDER" } | Decides the refund form (original tender / store credit / exchange-only), with optional per-category overrides. |

Both are read by the default refund-policy resolver; a brand can instead register a dynamic resolver (see Extension points).

Usage

import { ORDER_MODULE, type OrderModuleService } from "@devx-retailos/order"

const orders: OrderModuleService = container.resolve(ORDER_MODULE)

// Place a standard order (discount validated via @devx-retailos/discount if provided)
const { order } = await orders.placeOrder({
  organization_id: "org_...",
  store_id: "store_...",
  currency: "INR",
  line_items: [{ id: "tmp-1", name: "T-Shirt", quantity: 2, unit_price: 499 }],
  coupon_code: "WELCOME10",
  placed_by_employee_id: "emp_...",
})

// Confirm: records the payment, sets status "confirmed", persists a permanent
// tax invoice (via @devx-retailos/invoice), records a stock issue (via
// @devx-retailos/inventory), and pushes the order to the registered ecommerce
// adapter (status becomes "synced_to_ecommerce"). All side modules are optional.
const result = await orders.confirmOrder(order.id, {
  payment: { method: "cash", amount: 998 },
})

Advance orders

Passing advance_deposit_amount to placeOrder creates an advance order: the deposit is recorded, balance_due is tracked, and the order enters the first stage of its stage provider. Advance orders cannot use confirmOrder — collect the remainder with collectBalance:

const { order } = await orders.placeOrder({
  // ...as above
  advance_deposit_amount: 500,
  promised_fulfillment_date: new Date("2026-07-01"),
})

await orders.transitionStage(order.id, "processing", { changed_by_employee_id: "emp_..." })
await orders.collectBalance(order.id, [{ method: "card", amount: 1500 }])

listAdvanceOrders({ organization_id, fulfillment_stage?, promised_before?, promised_after? }) queries open advance orders.

Reports

const daily = await orders.dailyReport({ organization_id: "org_...", store_id: "store_...", date: "2026-06-11" })
// → order_count, subtotal, discount_total, tax_total, grand_total, payment_methods[]

const close = await orders.dayCloseReport({ organization_id: "org_...", store_id: "store_...", date: "2026-06-11" })
// → daily report + closed_at, opening_total, closing_total, variance

totalsForStore({ organization_id, store_id?, status? }) returns simple revenue aggregates.

Returns & refunds

A return is a new document — it never edits the original order. Refunds are based on what the customer actually paid per line (line subtotal − line discount − its prorated share of any order-level discount + tax), never on today's shelf price. A time-based policy then scales the refundable amount, and a matrix decides the form.

// 1. Preview — compute refundable amounts + eligibility without persisting
const preview = await orders.previewReturn({
  original_order_id: "ord_...",
  returning_store_id: "store_...",        // may differ from the selling store (cross-store)
  lines: [{ original_line_item_id: "oli_...", quantity: 1 }],
})
// → { refund_total, refund_multiplier_percent, refund_form, days_since_purchase, eligible, ... }

// 2. Create — validates eligibility, atomically claims returned quantity
//    (cumulative returns can never exceed what was purchased), persists the return
const { return: ret } = await orders.createReturn({
  original_order_id: "ord_...",
  returning_store_id: "store_...",
  lines: [{ original_line_item_id: "oli_...", quantity: 1 }],
  processed_by_employee_id: "emp_...",
})

// 3. Complete — quality check → mirror to Unicommerce → execute the refund → close
await orders.completeReturn(ret.id, {
  quality_results: [{ original_line_item_id: "oli_...", quality_result: "GOOD" }],
})

The refund is executed by form: STORE_CREDIT issues a @devx-retailos/gift-voucher voucher; ORIGINAL_TENDER delegates to a registered ReturnRefundExecutor (or records a pending refund payment if none is registered); EXCHANGE_ONLY records no monetary refund. On completion the return also (best-effort, when the relevant module is installed) mirrors to Unicommerce, persists a credit note via @devx-retailos/invoice, records a stock receive via @devx-retailos/inventory, and — for a cross-store return — writes an inter-store settlement (store Y refunded on store X's behalf). Missing sibling modules degrade gracefully — a refund is never lost because a mirror is unavailable.

Exchanges

An exchange is one return leg plus one new sale under a single id, committed as a saga:

const { exchange, return: ret, new_order } = await orders.createExchange({
  original_order_id: "ord_...",
  returning_store_id: "store_...",
  return_lines: [{ original_line_item_id: "oli_...", quantity: 1, quality_result: "GOOD" }],
  new_sale: {                       // the new item at today's price — a fresh order + invoice
    organization_id: "org_...",
    store_id: "store_...",
    currency: "INR",
    line_items: [{ id: "tmp-1", name: "New Tee", quantity: 1, unit_price: 2500 }],
  },
  courier_pickup: false,            // true → raise a Unicommerce reverse pickup for the old item
  processed_by_employee_id: "emp_...",
})
// settle = new_invoice − credit_note: positive → customer pays the difference,
// negative → surplus issued as store credit.

The old item is returned as EXCHANGE_ONLY (restocked + credit note, no cash refund) and the new item goes out as a normal order — with its own fresh invoice and a Unicommerce sale order. If the sale leg fails before the return completes, the new order is cancelled and the return is rolled back — you never end up with half an exchange. For a courier exchange (courier_pickup: true), the old item is collected via a Unicommerce reverse pickup (the brand's UC adapter must implement createReversePickup) rather than a counter drop-off.

Inter-store settlement

Cross-store returns and exchanges write an inter-store settlement entry (debtor = selling store, creditor = returning store). List open entries with GET /admin/retailos/settlements and square them up with settleInterStore(id) / POST /admin/retailos/settlements/:id/settle.

Extension points

OrderEcommerceAdapter

Pushes confirmed POS orders to an ecommerce platform (Shopify, WooCommerce, …). Register an implementation in the container under the ORDER_ECOMMERCE_ADAPTER key ("order_ecommerce_adapter"). If no adapter is registered, confirmed orders stay at status "confirmed" and can be retried later; sync failures are recorded in the order's metadata.

import type { OrderEcommerceAdapter, NormalizedOrderForEcommerce } from "@devx-retailos/order"

const adapter: OrderEcommerceAdapter = {
  async createOrder(order: NormalizedOrderForEcommerce) {
    return { ecommerce_order_id: "ext_123" }
  },
  async cancelOrder(ecommerce_order_id: string) {},
}

AdvanceOrderStageProvider

Defines the fulfillment stages of advance orders. The built-in DefaultStageProvider ships stages pending → processing → ready → fulfilled / cancelled (fulfilled and cancelled are terminal).

import type { AdvanceOrderStageProvider } from "@devx-retailos/order"

const madeToOrder: AdvanceOrderStageProvider = {
  name: "made-to-order",
  stages: () => ["pending", "in_production", "qc", "ready", "fulfilled", "cancelled"],
  isTransitionAllowed: (from, to) => from !== "fulfilled" && from !== "cancelled",
}

orders.registerStageProvider(madeToOrder)
// then: placeOrder({ ..., advance_deposit_amount: 500, stage_provider: "made-to-order" })

RefundPolicyResolver

Decides the time tiers ("how much") and the refund matrix ("in what form") for a return. The default resolver reads the refundPolicy / refundMatrix plugin options. Register a custom resolver to vary policy per store, category, or product:

import type { RefundPolicyResolver } from "@devx-retailos/order"

const resolver: RefundPolicyResolver = {
  name: "seasonal",
  resolve: (ctx) => ({ policy: {/* tiers */}, matrix: { default: "STORE_CREDIT" } }),
}
orders.registerRefundPolicyResolver(resolver)

ReturnRefundExecutor

Executes an ORIGINAL_TENDER refund against the payment gateway. Register an implementation under the RETURN_REFUND_EXECUTOR key — typically wrapping @devx-retailos/payments refund with your own PaymentCart context. If none is registered, completeReturn records a pending refund payment for the brand to reconcile out of band.

import type { ReturnRefundExecutor } from "@devx-retailos/order"

const executor: ReturnRefundExecutor = {
  async executeRefund(ctx) {
    // ctx: { return_id, original_order_id, amount, currency, store_id, ... }
    return { status: "succeeded", reference: "gw_txn_123" }
  },
}
// container.register(RETURN_REFUND_EXECUTOR, asValue(executor))

Permissions

Registered via @devx-retailos/rbac (also exported as ORDER_PERMISSIONS from @devx-retailos/order/permissions):

| Key | Description | | --- | --- | | order.read | View orders and order details | | order.create | Place new orders | | order.update | Update order status, notes, and metadata | | order.cancel | Cancel orders | | order.refund | Mark orders as refunded | | order.void | Void orders | | order.return | Create and complete returns against orders | | order.return.approve | Approve returns outside the standard refund policy | | order.exchange | Create exchanges (return plus new sale) | | order.settlement | View and settle inter-store settlement ledger entries | | order.report | View order totals and revenue aggregates | | order.discount_apply | Place orders with a discount or coupon attached | | order.advance_stage | Transition an advance order's fulfillment stage | | order.collect_balance | Capture balance payment on an advance order | | order.invoice | Generate and download GST-compliant PDF invoices | | order.report.daily | View daily sales report and revenue aggregates | | order.report.day-close | Run the end-of-day Z-report and close the trading session |

API routes

All routes live under the authenticated Medusa admin scope. The report routes enforce their permission via @devx-retailos/rbac.

| Method | Path | Purpose | Permission | | --- | --- | --- | --- | | GET | /admin/retailos/orders | List orders (filterable) | — | | POST | /admin/retailos/orders | Place an order (standard or advance) | — | | GET | /admin/retailos/orders/:id | Retrieve order + line items + payments | — | | POST | /admin/retailos/orders/:id | Update status / notes / metadata | — | | DELETE | /admin/retailos/orders/:id | Cancel an order | — | | POST | /admin/retailos/orders/:id/confirm | Confirm + push to ecommerce adapter | — | | POST | /admin/retailos/orders/:id/returns/preview | Preview refundable amounts + eligibility | order.return | | POST | /admin/retailos/orders/:id/returns | Create a return | order.return | | POST | /admin/retailos/orders/:id/returns/:returnId/complete | QC → mirror → refund → close | order.return | | POST | /admin/retailos/orders/:id/exchanges | Create an exchange (return + new sale) | order.exchange | | GET | /admin/retailos/settlements | List inter-store settlement entries | order.settlement | | POST | /admin/retailos/settlements/:id/settle | Mark a settlement squared up | order.settlement | | POST | /admin/retailos/orders/:id/balance | Collect balance on an advance order | order.collect_balance | | PUT | /admin/retailos/orders/:id/stage | Transition an advance order's stage | order.advance_stage | | GET | /admin/retailos/orders/totals | Revenue aggregates per org/store | — | | GET | /admin/retailos/orders/reports/daily | Daily sales report | order.report.daily | | POST | /admin/retailos/orders/reports/day-close | End-of-day close report | order.report.day-close |

Errors

All extend RetailOSError from @devx-retailos/core; switch on err.code:

  • RETAILOS_ORDER_NOT_FOUND
  • RETAILOS_ORDER_INVALID_STATUS
  • RETAILOS_ORDER_DEPOSIT_EXCEEDS_TOTAL
  • RETAILOS_ORDER_INVALID_STAGE_TRANSITION
  • RETAILOS_ORDER_NOT_ADVANCE
  • RETAILOS_ORDER_CONFIRM_BLOCKED_FOR_ADVANCE
  • RETAILOS_ORDER_OVER_RETURN
  • RETAILOS_ORDER_RETURN_NOT_ELIGIBLE
  • RETAILOS_ORDER_RETURN_NOT_FOUND
  • RETAILOS_ORDER_RETURN_INVALID_STATUS

Related packages

  • @devx-retailos/core — shared types, RetailOSError, Logger, permission registry.
  • @devx-retailos/rbac — roles and permission checks the order routes rely on.
  • @devx-retailos/discount — discount/coupon validation used by placeOrder (optional).
  • @devx-retailos/payments — pluggable payment adapters, split tenders, refunds.
  • @devx-retailos/gift-voucher — store-credit vouchers issued on STORE_CREDIT refunds (optional).
  • @devx-retailos/invoice — GST-compliant PDF invoices generated from these orders.
  • @devx-retailos/products — catalog mirror that supplies product/variant data.

License

MIT