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-process-engine

v2.0.1

Published

A lightweight engine for e-commerce order processing.

Readme

order-process-engine

A lightweight TypeScript/JavaScript engine for calculating e-commerce order totals.

It helps you compute:

  • Subtotal
  • Marketplace fee
  • Shipping charges (with free-shipping threshold)
  • Tax
  • Final total

The package can be used in two ways:

  • As a class (OrderEngine) with configurable defaults
  • As standalone utility functions (calcMarketplaceFee, calcShipping, calcTax, roundToTwo)

Table of Contents

Why this package

Order totals can get repetitive and error-prone when fee/tax/shipping logic is spread across multiple places.

This package centralizes that logic in a tiny, typed module so you can:

  • Keep calculations consistent
  • Reuse logic across backend/frontend services
  • Get IDE autocomplete and compile-time checks

Features

  • TypeScript-first API with exported types
  • Configurable fee, shipping, and tax values
  • Free-shipping threshold logic
  • Utility exports if you do not need a class
  • Builds for both CommonJS and ESM with type declarations
  • Dynamic pricing rules for promotions, shipping, fee, and tax
  • Context-aware pricing using region/channel/customer metadata
  • Optional async rule and tax providers for DB/API-backed pricing
  • Explainable outputs with applied rules and pricing breakdown

Installation

npm install order-process-engine

If you are developing this repository locally:

npm install
npm run build

Using in other projects

Use OrderEngine and choose one of these patterns:

1) Default configuration (18% tax + default fees/shipping)

import { OrderEngine, type OrderItem } from "order-process-engine";

const items: OrderItem[] = [
  { price: 40, quantity: 2 },
  { price: 20, quantity: 1 },
];

const engine = new OrderEngine();
const result = engine.calculate(items);

console.log(result);
/*
{
  subtotal: 100,
  marketplaceFee: 10.5,
  shippingCharges: 0,
  tax: 18,
  total: 118
}
*/

2) Custom configuration (your own tax/fees/shipping)

import { OrderEngine, type OrderItem } from "order-process-engine";

const items: OrderItem[] = [
  { price: 1200, quantity: 1 },
  { price: 250, quantity: 2 },
];

const engine = new OrderEngine({
  tax: 0.12, // 12%
  mktFeePercent: 0.08, // 8%
  mktFeeFixed: 2.5,
  shippingBase: 40,
  shippingThreshold: 2000,
});

const result = engine.calculate(items);
console.log(result);

JavaScript (CommonJS) usage is the same concept:

const { OrderEngine } = require("order-process-engine");

const engine = new OrderEngine({
  tax: 0.07,
  mktFeePercent: 0.1,
  mktFeeFixed: 1,
});
const result = engine.calculate([{ price: 300, quantity: 2 }]);

console.log(result);

3) Dynamic pricing intelligence (rules + context)

import {
  OrderEngine,
  type OrderItem,
  type PricingContext,
  type PricingRuleSet,
} from "order-process-engine";

const items: OrderItem[] = [
  { price: 1200, quantity: 1, category: "electronics", weight: 1.2 },
  { price: 250, quantity: 2, category: "books", weight: 0.4 },
];

const ruleSet: PricingRuleSet = {
  promotions: [
    {
      id: "vip-10",
      name: "VIP 10% Off",
      type: "promotion",
      scope: "order",
      discountMode: "percent",
      discountValue: 0.1,
      priority: 10,
      when: [{ field: "context.customerTier", op: "eq", value: "vip" }],
    },
  ],
  shipping: [
    {
      id: "express-app-flat",
      name: "App Channel Shipping",
      type: "shipping",
      mode: "flat",
      flatAmount: 25,
      priority: 10,
      when: [{ field: "context.channel", op: "eq", value: "app" }],
    },
  ],
  tax: [
    {
      id: "in-default-tax",
      name: "India GST",
      type: "tax",
      rate: 0.18,
      priority: 10,
      when: [{ field: "context.region", op: "eq", value: "IN" }],
    },
  ],
};

const context: PricingContext = {
  tenantId: "store-42",
  region: "IN",
  channel: "app",
  customerTier: "vip",
  couponCodes: ["SUMMER10"],
};

const engine = new OrderEngine({ ruleSet });
const result = engine.calculate(items, context);

console.log(result.appliedRules);
console.log(result.breakdown);

Quick start

Class-based usage

import { OrderEngine, type OrderItem } from "order-process-engine";

const items: OrderItem[] = [
  { price: 29.99, quantity: 2 },
  { price: 15.5, quantity: 1 },
];

const engine = new OrderEngine();
const result = engine.calculate(items);

console.log(result);
/*
{
  subtotal: 100,
  marketplaceFee: 10.5,
  shippingCharges: 0,
  tax: 18,
  total: 118
}
*/

Utility-function usage

import {
  calcMarketplaceFee,
  calcShipping,
  calcTax,
  roundToTwo,
} from "order-process-engine";

const subtotal = 75.48;
const fee = calcMarketplaceFee(subtotal, 0.1, 0.5);
const shipping = calcShipping(subtotal, 10, 100);
const tax = calcTax(subtotal, 0.18);
const total = roundToTwo(subtotal + shipping + tax);

How calculations work

Given items:

[{ price, quantity }, ...]

The engine computes:

  1. subtotal = sum(price * quantity)
  2. marketplaceFee = subtotal * feePercent + feeFixed
  3. shippingCharges = subtotal >= freeShippingThreshold ? 0 : shippingBase
  4. tax = roundToTwo(subtotal * taxRate)
  5. total = subtotal + shippingCharges + tax

With pricing intelligence enabled, the engine uses this staged flow:

  1. subtotal = sum(price * quantity)
  2. Apply promotions (order/item scope + conditions + priority)
  3. taxableSubtotal = subtotal - discountTotal
  4. Resolve shipping from matching shipping rule (or fallback)
  5. Resolve marketplace fee from matching fee rule (or fallback)
  6. Resolve tax from tax rule or taxProvider
  7. total = taxableSubtotal + shippingCharges + tax

API reference

OrderEngine

Constructor

new OrderEngine(customConfig?)

customConfig is optional and can override any of:

v1 static config:

  • mktFeePercent (number)
  • mktFeeFixed (number)
  • shippingBase (number)
  • shippingThreshold (number)
  • tax (number)

v2 pricing intelligence config:

  • ruleSet (PricingRuleSet)
  • ruleSetProvider ((context) => PricingRuleSet | Promise<PricingRuleSet>)
  • taxProvider ((taxableSubtotal, context) => number | Promise<number>)
  • cacheTtlMs (reserved for provider-layer cache strategies)

Method

calculate(items: OrderItem[]): CalculationResult
calculate(items: OrderItem[], context: PricingContext): CalculationResult
calculateAsync(
  items: OrderItem[],
  context?: PricingContext,
): Promise<CalculationResult>

Notes:

  • calculate(...) supports static rules and synchronous providers.
  • If your provider returns a Promise, use calculateAsync(...).

OrderItem

interface OrderItem {
  price: number;
  quantity: number;
  weight?: number;
  category?: string;
}

Notes:

  • weight and category are currently optional metadata fields and are not used in core calculations.

CalculationResult

interface CalculationResult {
  subtotal: number;
  marketplaceFee: number;
  shippingCharges: number;
  tax: number;
  total: number;
  discountTotal?: number;
  appliedRules?: AppliedRule[];
  breakdown?: {
    discountTotal: number;
    taxableSubtotal: number;
  };
}

PricingContext

interface PricingContext {
  tenantId?: string;
  region?: string;
  postalCode?: string;
  channel?: "web" | "app" | "marketplace";
  currency?: string;
  customerId?: string;
  customerTier?: "new" | "regular" | "vip";
  couponCodes?: string[];
  now?: Date | string;
}

PricingRuleSet

interface PricingRuleSet {
  promotions?: PromotionRule[];
  shipping?: ShippingRule[];
  marketplaceFee?: MarketplaceFeeRule[];
  tax?: TaxRule[];
}

calcMarketplaceFee(price, percent, fixed)

Returns:

price * percent + fixed;

calcShipping(total, base, threshold)

Returns:

  • 0 if total >= threshold
  • base otherwise

calcTax(subtotal, taxRate)

Returns tax rounded to 2 decimals:

roundToTwo(subtotal * taxRate);

roundToTwo(num)

Rounding helper:

Math.round((num + Number.EPSILON) * 100) / 100;

Configuration

Default runtime configuration used by OrderEngine:

| Key | Default | Meaning | | ------------------- | ------- | ------------------------------------- | | mktFeePercent | 0.1 | 10% variable marketplace fee | | mktFeeFixed | 0.5 | Fixed fee per order | | shippingBase | 10 | Flat shipping under threshold | | shippingThreshold | 100 | Free shipping starts at this subtotal | | tax | 0.18 | 18% tax |

You can override any of these:

import { OrderEngine } from "order-process-engine";

const engine = new OrderEngine({
  mktFeePercent: 0.12,
  mktFeeFixed: 1.0,
  shippingBase: 7.5,
  shippingThreshold: 80,
  tax: 0.07,
});

Examples

Example 1: Free shipping case

import { OrderEngine } from "order-process-engine";

const engine = new OrderEngine();

const result = engine.calculate([
  { price: 40, quantity: 2 },
  { price: 30, quantity: 1 },
]);

// subtotal = 110
// shippingCharges = 0 (because subtotal >= 100)
console.log(result);

Example 2: Custom tax and shipping policy

import { OrderEngine } from "order-process-engine";

const engine = new OrderEngine({
  shippingBase: 5,
  shippingThreshold: 50,
  tax: 0.0825,
});

const result = engine.calculate([{ price: 20, quantity: 2 }]);
console.log(result);

Example 3: Utility-only integration

import {
  calcMarketplaceFee,
  calcShipping,
  calcTax,
  roundToTwo,
} from "order-process-engine";

function computeOrder(subtotal: number) {
  const fee = calcMarketplaceFee(subtotal, 0.1, 0.5);
  const shipping = calcShipping(subtotal, 10, 100);
  const tax = calcTax(subtotal, 0.18);

  return {
    subtotal,
    fee,
    shipping,
    tax,
    total: roundToTwo(subtotal + shipping + tax),
  };
}

Example 4: Dynamic rules from a provider (async)

import { OrderEngine } from "order-process-engine";

const engine = new OrderEngine({
  async ruleSetProvider(context) {
    // Example: fetch from DB/remote config per tenant-region
    const ruleSet = await fetch(
      `https://pricing.example.com/rules?tenant=${context.tenantId}&region=${context.region}`,
    ).then((res) => res.json());

    return ruleSet;
  },
});

const result = await engine.calculateAsync(
  [{ price: 999, quantity: 1, category: "electronics", weight: 1.4 }],
  {
    tenantId: "store-42",
    region: "IN",
    channel: "web",
    customerTier: "regular",
  },
);

console.log(result.total);
console.log(result.appliedRules);

Example 5: External tax provider

import { OrderEngine } from "order-process-engine";

const engine = new OrderEngine({
  taxProvider: async (taxableSubtotal, context) => {
    const response = await fetch("https://tax.example.com/quote", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ taxableSubtotal, region: context.region }),
    }).then((res) => res.json());

    return response.taxAmount;
  },
});

const result = await engine.calculateAsync([{ price: 100, quantity: 2 }], {
  region: "US-CA",
});

console.log(result.tax);

Example 6: v1 to v2 migration (minimal changes)

If you already use static config in v1, you only need to add a ruleSet, pass a context, and keep the rest of your flow the same.

Before (v1 static config):

import { OrderEngine, type OrderItem } from "order-process-engine";

const items: OrderItem[] = [
  { price: 1200, quantity: 1 },
  { price: 250, quantity: 2 },
];

const engine = new OrderEngine({
  tax: 0.12,
  mktFeePercent: 0.08,
  mktFeeFixed: 2.5,
  shippingBase: 40,
  shippingThreshold: 2000,
});

const result = engine.calculate(items);
console.log(result.total);

After (v2 context + rules):

import {
  OrderEngine,
  type OrderItem,
  type PricingContext,
  type PricingRuleSet,
} from "order-process-engine";

const items: OrderItem[] = [
  { price: 1200, quantity: 1, category: "electronics", weight: 1.2 },
  { price: 250, quantity: 2, category: "books", weight: 0.4 },
];

const ruleSet: PricingRuleSet = {
  promotions: [
    {
      id: "vip-10",
      name: "VIP 10% Off",
      type: "promotion",
      scope: "order",
      discountMode: "percent",
      discountValue: 0.1,
      when: [{ field: "context.customerTier", op: "eq", value: "vip" }],
    },
  ],
  shipping: [
    {
      id: "in-flat-shipping",
      name: "IN Flat Shipping",
      type: "shipping",
      mode: "flat",
      flatAmount: 25,
      when: [{ field: "context.region", op: "eq", value: "IN" }],
    },
  ],
  tax: [
    {
      id: "in-tax",
      name: "IN Tax",
      type: "tax",
      rate: 0.18,
      when: [{ field: "context.region", op: "eq", value: "IN" }],
    },
  ],
};

const context: PricingContext = {
  tenantId: "store-42",
  region: "IN",
  customerTier: "vip",
};

const engine = new OrderEngine({ ruleSet });
const result = engine.calculate(items, context);

console.log(result.total);
console.log(result.appliedRules);

Minimal migration checklist:

  1. Keep OrderEngine and calculate as your entry points.
  2. Add ruleSet to constructor config.
  3. Pass context as the second argument in calculate(items, context).
  4. Optionally read result.appliedRules and result.breakdown for explainable pricing.
  5. If your rules come from APIs/DB, switch to calculateAsync(...).

License

MIT