Shipping
Shipping calculation with real carrier API rates, fallback estimates, jar packing, PO Box detection, and address validation
Shipping
Shipping costs are calculated using real carrier API rates with automatic fallback to estimate-based rates. The system supports jar-grid packing, PO Box detection, weight-based pricing, and state-specific multipliers.
There is no free shipping
Shipping is charged on every order, without exception. A freeShippingThreshold that zeroed the
shipping line above a subtotal — defaulting to $50 — and a FREE_SHIPPING discount type were both
removed. With real carrier rates they were expensive: a six-jar order crossed the default and cost
the business about $11.88 to ship, a case about $35.74, with nothing charged to the customer.
Architecture
Calculation Strategy
Origin Resolution
The warehouse origin is resolved through lib/shipping/origin.ts — the admin setting first, then
the SHIPPING_ORIGIN_* environment variables. It returns nothing rather than a placeholder when
incomplete, and an unresolved origin sends the quote to flat-rate estimates rather than pricing
from a made-up address.
International Routing
Non-US orders fall back to flat-rate estimates ($24.99 carriage, $28.99 to the customer once the packaging fee is added; 7-14 business days). Real international carrier API support is planned.
Carrier API Call
For domestic orders, the system:
- Computes parcel dimensions from item weights and sizes
- Builds a
ShipmentRequestwith origin and destination addresses - Calls
getShippingRates()fromlib/shipping-api.ts - Sorts returned rates by cost (cheapest first)
- Returns the cheapest rate as the single standard shipping option
PO Box Filtering
If the destination is a PO Box (detected by regex patterns), only USPS rates are shown since UPS and FedEx cannot deliver to PO Boxes.
Fallback Estimates
If the carrier API fails or returns no rates, the system falls back to estimate-based rates with a fallback: true flag. Checkout is never blocked by shipping calculation failures.
Packaging Fee
A flat $4.00 packaging-and-materials fee is added to whichever rate the steps above produced —
live carrier rate, domestic estimate or international flat rate alike. It covers the box, the
dividers that protect glass jars, tape and label, none of which the carrier's price includes. It is
charged once per order and is not scaled by the state multiplier or the weight surcharge, which
price carriage rather than packaging. The constant lives in lib/shipping/handling-fee.ts; it is
not admin-configurable.
Rate Configuration
The flat-rate presets used for the estimate/fallback path are admin-configurable under
Settings → Shipping → Flat-rate presets. They are stored on the ShippingSettings singleton (as
whole cents) and read at quote time by lib/shipping/rate-config.ts; live carrier rates, when
available, are still used ahead of them. Every field is optional and falls back to the built-in
default below, so an unconfigured store quotes exactly what it always did. Shipping is charged on
every order — there is no free-shipping path.
Built-in defaults (before the $4.00 packaging fee, which is added to every quote):
| Method | Cost | Customer pays | Delivery |
|---|---|---|---|
| Standard | $6.99 | $10.99 | 3-5 business days |
| International | $24.99 | $28.99 | 7-14 business days |
Weight-Based Pricing
For orders over the threshold weight (default 5 lb), shipping switches from flat-rate to weight-based when that is higher:
baseCost = Math.max(
flatRate,
weightSurchargeBase + (totalPounds - thresholdLb) * weightSurchargePerLb
)
// defaults: flatRate $6.99, base $4.99, perPound $0.50, threshold 5 lbState Multipliers
Remote destinations carry a configurable cost multiplier (defaults below):
| State | Multiplier |
|---|---|
| Alaska (AK) | 1.5x |
| Hawaii (HI) | 1.5x |
| Puerto Rico (PR) | 2.0x |
PO Box Detection
The isPOBox() function checks address lines against common patterns:
const poBoxPatterns = [
/\bP\s*O\s+BOX\b/, // PO BOX, P.O. BOX
/\bPOST\s+OFFICE\s+BOX\b/, // POST OFFICE BOX
/\bP\s*O\s*B\b/, // POB, P.O.B
/^\s*BOX\s+\d+/, // BOX 123 (at start)
]Address Validation
The validateShippingAddress() function checks:
- Address line 1 (min 3 characters)
- City (min 2 characters)
- State (exactly 2 letters)
- Postal code (min 5 characters)
- Country (exactly 2 letters)
Frontend Preview
The getShippingEstimate() function provides a quick estimate for the cart page without detailed item info, based only on subtotal, state, and country.
The warehouse origin address and enabled carriers are stored in the ShippingSettings singleton
table and can be updated from the admin panel without code changes.
How is this guide?
Last updated on