@86d-app/shipping
v0.0.30
Published
Shipping zones and rates module for 86d commerce platform
Downloads
508
Maintainers
Readme
[!WARNING] This project is under active development and is not ready for production use. Please proceed with caution. Use at your own risk.
Shipping Module
📚 Documentation: 86d.app/docs/modules/shipping
Complete shipping management for the 86d commerce platform. Supports multi-zone rate configuration, named shipping methods with delivery estimates, carrier definitions with tracking URL generation, and full shipment lifecycle tracking.
Installation
npm install @86d-app/shippingUsage
import shipping from "@86d-app/shipping";
import { createModuleClient } from "@86d-app/core";
const client = createModuleClient([
shipping({
currency: "USD",
}),
]);Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| currency | string | undefined | Default currency for shipping prices |
Store Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /shipping/calculate | Calculate applicable rates for a destination and order |
| GET | /shipping/methods | List active shipping methods sorted by display order |
| GET | /shipping/carriers | List active shipping carriers |
| GET | /shipping/track/:id | Get shipment tracking info and URL |
POST /shipping/calculate
Request body: { country: string, orderAmount: number, weight?: number }
GET /shipping/track/:id
Returns: { shipment: { id, orderId, trackingNumber, status, shippedAt, deliveredAt, estimatedDelivery }, trackingUrl: string | null }
Admin Endpoints
Zones
| Method | Path | Description |
|---|---|---|
| GET | /admin/shipping/zones | List all shipping zones |
| POST | /admin/shipping/zones/create | Create a new shipping zone |
| PUT | /admin/shipping/zones/:id/update | Update a shipping zone |
| DELETE | /admin/shipping/zones/:id/delete | Delete a zone and all its rates |
| GET | /admin/shipping/zones/:id/rates | List rates for a zone |
| POST | /admin/shipping/zones/:id/rates/add | Add a rate to a zone |
| PUT | /admin/shipping/rates/:id/update | Update a shipping rate |
| DELETE | /admin/shipping/rates/:id/delete | Delete a shipping rate |
Methods
| Method | Path | Description |
|---|---|---|
| GET | /admin/shipping/methods | List shipping methods |
| POST | /admin/shipping/methods/create | Create a shipping method |
| PUT | /admin/shipping/methods/:id/update | Update a shipping method |
| DELETE | /admin/shipping/methods/:id/delete | Delete a shipping method |
Carriers
| Method | Path | Description |
|---|---|---|
| GET | /admin/shipping/carriers | List shipping carriers |
| POST | /admin/shipping/carriers/create | Create a shipping carrier |
| PUT | /admin/shipping/carriers/:id/update | Update a shipping carrier |
| DELETE | /admin/shipping/carriers/:id/delete | Delete a shipping carrier |
Shipments
| Method | Path | Description |
|---|---|---|
| GET | /admin/shipping/shipments | List shipments (filter by orderId, status) |
| POST | /admin/shipping/shipments/create | Create a shipment for an order |
| GET | /admin/shipping/shipments/:id | Get shipment details + tracking URL |
| PUT | /admin/shipping/shipments/:id/update | Update shipment details |
| PUT | /admin/shipping/shipments/:id/status | Update shipment status |
| DELETE | /admin/shipping/shipments/:id/delete | Delete a shipment |
Rate Calculation
calculateRates matches zones by country code, then filters each zone's rates by order amount and weight constraints:
- Zones with an empty
countries: []array match all destinations (wildcard / "rest of world"). - Rate conditions are optional — if not set, the condition is always satisfied.
- Results are sorted cheapest first.
const rates = await controller.calculateRates({
country: "US",
orderAmount: 5000, // in cents
weight: 1200, // optional, in grams
});
// => [{ rateId, zoneName, rateName, price }, ...]Shipment Status Lifecycle
pending → shipped → in_transit → delivered → returned
↓ ↓ ↓
failed failed failed
↓
pending (retry)Status transitions are enforced — invalid transitions return null. Timestamps shippedAt and deliveredAt are auto-set on the corresponding transitions.
Controller API
// ── Zones ──────────────────────────────────────────────────────────────
controller.createZone(params: {
name: string;
countries?: string[]; // ISO 3166-1 alpha-2 codes; empty = wildcard
isActive?: boolean;
}): Promise<ShippingZone>
controller.getZone(id: string): Promise<ShippingZone | null>
controller.listZones(params?: { activeOnly?: boolean }): Promise<ShippingZone[]>
controller.updateZone(id, params): Promise<ShippingZone | null>
controller.deleteZone(id: string): Promise<boolean> // cascades rates
// ── Rates ──────────────────────────────────────────────────────────────
controller.addRate(params: {
zoneId: string;
name: string;
price: number; // in cents
minOrderAmount?: number;
maxOrderAmount?: number;
minWeight?: number;
maxWeight?: number;
isActive?: boolean;
}): Promise<ShippingRate>
controller.getRate(id: string): Promise<ShippingRate | null>
controller.listRates(params: { zoneId: string; activeOnly?: boolean }): Promise<ShippingRate[]>
controller.updateRate(id, params): Promise<ShippingRate | null>
controller.deleteRate(id: string): Promise<boolean>
controller.calculateRates(params: { country: string; orderAmount: number; weight?: number }): Promise<CalculatedRate[]>
// ── Methods ────────────────────────────────────────────────────────────
controller.createMethod(params: {
name: string;
description?: string;
estimatedDaysMin: number;
estimatedDaysMax: number;
isActive?: boolean;
sortOrder?: number;
}): Promise<ShippingMethod>
controller.getMethod(id: string): Promise<ShippingMethod | null>
controller.listMethods(params?: { activeOnly?: boolean }): Promise<ShippingMethod[]>
controller.updateMethod(id, params): Promise<ShippingMethod | null>
controller.deleteMethod(id: string): Promise<boolean>
// ── Carriers ───────────────────────────────────────────────────────────
controller.createCarrier(params: {
name: string;
code: string; // normalized to lowercase
trackingUrlTemplate?: string; // e.g. "https://track.ups.com/{tracking}"
isActive?: boolean;
}): Promise<ShippingCarrier>
controller.getCarrier(id: string): Promise<ShippingCarrier | null>
controller.listCarriers(params?: { activeOnly?: boolean }): Promise<ShippingCarrier[]>
controller.updateCarrier(id, params): Promise<ShippingCarrier | null>
controller.deleteCarrier(id: string): Promise<boolean>
// ── Shipments ──────────────────────────────────────────────────────────
controller.createShipment(params: {
orderId: string;
carrierId?: string;
methodId?: string;
trackingNumber?: string;
estimatedDelivery?: Date;
notes?: string;
}): Promise<Shipment>
controller.getShipment(id: string): Promise<Shipment | null>
controller.listShipments(params?: { orderId?: string; status?: ShipmentStatus }): Promise<Shipment[]>
controller.updateShipment(id, params): Promise<Shipment | null>
controller.updateShipmentStatus(id, status: ShipmentStatus): Promise<Shipment | null>
controller.deleteShipment(id: string): Promise<boolean>
controller.getTrackingUrl(shipmentId: string): Promise<string | null>Example: Multi-zone Setup with Carriers
// Create carriers
const fedex = await controller.createCarrier({
name: "FedEx",
code: "fedex",
trackingUrlTemplate: "https://www.fedex.com/fedextrack/?trknbr={tracking}",
});
const ups = await controller.createCarrier({
name: "UPS",
code: "ups",
trackingUrlTemplate: "https://www.ups.com/track?tracknum={tracking}",
});
// Create shipping methods
await controller.createMethod({
name: "Standard Shipping",
estimatedDaysMin: 5,
estimatedDaysMax: 7,
sortOrder: 2,
});
await controller.createMethod({
name: "Express Shipping",
estimatedDaysMin: 1,
estimatedDaysMax: 2,
sortOrder: 1,
});
// Create zones and rates
const us = await controller.createZone({ name: "United States", countries: ["US"] });
await controller.addRate({ zoneId: us.id, name: "Standard", price: 599 });
await controller.addRate({ zoneId: us.id, name: "Free Shipping", price: 0, minOrderAmount: 5000 });
// Create a shipment for an order
const shipment = await controller.createShipment({
orderId: "order-42",
carrierId: fedex.id,
trackingNumber: "794644790132",
});
// Advance through lifecycle
await controller.updateShipmentStatus(shipment.id, "shipped");
await controller.updateShipmentStatus(shipment.id, "in_transit");
await controller.updateShipmentStatus(shipment.id, "delivered");
// Get tracking URL
const url = await controller.getTrackingUrl(shipment.id);
// => "https://www.fedex.com/fedextrack/?trknbr=794644790132"Types
interface ShippingZone {
id: string;
name: string;
countries: string[];
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
interface ShippingRate {
id: string;
zoneId: string;
name: string;
price: number;
minOrderAmount?: number;
maxOrderAmount?: number;
minWeight?: number;
maxWeight?: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
interface ShippingMethod {
id: string;
name: string;
description?: string;
estimatedDaysMin: number;
estimatedDaysMax: number;
isActive: boolean;
sortOrder: number;
createdAt: Date;
updatedAt: Date;
}
interface ShippingCarrier {
id: string;
name: string;
code: string;
trackingUrlTemplate?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
type ShipmentStatus = "pending" | "shipped" | "in_transit" | "delivered" | "returned" | "failed";
interface Shipment {
id: string;
orderId: string;
carrierId?: string;
methodId?: string;
trackingNumber?: string;
status: ShipmentStatus;
shippedAt?: Date;
deliveredAt?: Date;
estimatedDelivery?: Date;
notes?: string;
createdAt: Date;
updatedAt: Date;
}
interface CalculatedRate {
rateId: string;
zoneName: string;
rateName: string;
price: number;
}Store Components
ShippingEstimator
Shipping cost estimator form. Customers select a country and optionally enter an order total to see available shipping rates and prices.
Usage in MDX
<ShippingEstimator />ShippingOptions
Displays available shipping rates as selectable radio buttons. Auto-fetches rates based on provided country and order amount. Selects the cheapest option by default.
Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| country | string | Yes | ISO 3166-1 alpha-2 country code |
| orderAmount | number | Yes | Cart total in cents |
| weight | number | No | Total weight in grams |
| onSelect | (rate) => void | No | Callback when a rate is selected |
| selectedRateId | string | No | Pre-selected rate ID |
ShippingRateSummary
Displays a selected shipping method and its cost. Free shipping is highlighted in green.
Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| rateName | string | Yes | Name of the selected rate |
| zoneName | string | No | Name of the shipping zone |
| price | number | Yes | Price in cents (0 = free) |
Notes
- All prices are in cents (integer). Convert to display currency on the frontend.
- Carrier codes are always normalized to lowercase.
- Shipment status transitions are enforced — invalid transitions return null.
- Zone deletion cascades to all associated rates.
- Weight fields use grams as the unit.
