Shipping and fulfilment records
The flat shipping fee and how it is charged, creating shipment records, assigning them to orders, shipping addresses, and the tracking timeline the customer sees.
Shipping in this store is two unrelated things that share a name, and confusing them is the fastest way to mis-price your catalogue:
- The shipping fee — one flat amount, set once for the whole store, added to a checkout that contains a physical product. This is what the customer pays.
- Shipment records — your own log of a consignment: load id, carrier, vehicle, weight, status. This is what you track. Nothing in a shipment record affects what anyone was charged.
The shipping fee
Two settings, on /admin/ecommerce/settings:
How it is applied:
- Once per checkout, not once per line. A three-item cart is charged one shipping fee. This matters because a checkout produces one order per product, so the fee lands on one of those orders and the others read zero.
- Only for physical products. A cart of downloadable items is never charged shipping, whatever the setting says.
- A
FREE_SHIPPINGdiscount zeroes it for that checkout. See Discounts. - It is charged in the product's currency and wallet type, like everything else on the order.
There are no shipping zones, no per-country rates, no weight bands, no per-product overrides and no free-shipping threshold. Every physical checkout pays the same number regardless of where it is going or what is in it.
If your real costs vary, either price that variance into the products or absorb it. There is no setting that will do it for you.
Where the money goes
Shipping and tax are credited to the platform wallet separately from store revenue, and deliberately not recorded as profit. You still owe the carrier and the tax authority; counting them as margin would overstate every physical order.
They appear on the ledger as an ORDER_PASSTHROUGH credit against the order.
Cancelling an order reverses that credit at the same time it refunds the buyer,
so a cancellation does not leave you holding shipping you never spent.
Shipment records
Admin → E-commerce → Sales → Shipping (/admin/ecommerce/shipping).
A shipment is a free-text record of a physical consignment. Every field is typed in — there is no carrier API, no rate lookup and no label printing.
| Field | Required | Notes |
|---|---|---|
loadId |
Yes | Your reference or the carrier's tracking number. Free text |
loadStatus |
Yes | PENDING, TRANSIT, DELIVERED or CANCELLED |
shipper |
Yes | Who is sending it |
transporter |
Yes | The carrier |
vehicle |
Yes | Vehicle or service identifier |
goodsType |
Yes | What class of goods |
description |
Yes | Free text |
weight |
Yes | Numeric, unitless — pick a unit and be consistent |
volume |
Yes | Numeric, unitless |
cost |
No | What the shipment costs you, not what the buyer paid |
tax |
No | Tax on that cost |
deliveryDate |
No | Expected or actual delivery |
They are recorded for your own reconciliation and never appear on an order, an
invoice or a refund. The customer's shipping charge comes from
ecommerceDefaultShippingCost and is stored on the order row.
weight and volume carry no units anywhere in the product. Decide on
kilograms and cubic metres (or pounds and cubic feet), write it in your internal
notes, and stick to it — otherwise the column is noise.
Assigning a shipment to an order
Shipments are created independently, then linked. Open the order at
/admin/ecommerce/order/<id> and assign an existing shipment. That writes the
shipment's id to the order's shippingId.
The relationship is many orders to one shipment, which is genuinely useful: create one shipment for a courier run and attach every order on it. Advancing that shipment's status moves the tracking view for all of them at once.
Assigning requires edit.ecommerce.order.
Shipping addresses
Each order can hold one address: name, email, phone, street, city, state, postal code, country.
- On checkout, the address the buyer typed is saved — but only for physical products, and only if the storefront sent one. Digital-only checkouts have no address.
- In admin, you can add one to an order that has none or correct one that is wrong.
Phone numbers are normalised to canonical E.164 (+ followed by digits, capped
at 15) on both paths, so whatever format the buyer typed is accepted and stored
consistently. Do not try to pre-format it.
The checkout endpoint treats the address as optional. If an order arrives with nothing to ship to, add it from the order screen before you mark the order complete — there is no validation that will stop you completing an unaddressable order.
What the customer sees
Tracking. GET /api/ecommerce/order/{id}/track returns the order id, its
status, the assigned shipment (load id, load status, shipper, transporter,
vehicle) and a timeline assembled from both. Customers reach it from their
order page.
The timeline is only as good as your discipline: an order marked COMPLETED
with a shipment still sitting at PENDING tells the buyer nothing useful.
Their shipments list. /ecommerce/shipping shows every shipment attached to
one of their orders, with the order, its items and the address. It is scoped to
the signed-in user by the order join — a customer cannot see anyone else's
consignments.
It is a buyer-facing route gated on authentication alone. If it ever answers
403 for ordinary customers, an admin permission has been attached to it by
mistake — the User role does not hold access.ecommerce.shipping and never
will.
A working fulfilment loop
-
Batch the day's
PENDINGorders. Filter the orders table by status. -
Check every one has an address. Add missing ones now, not after the van has gone.
-
Create one shipment per consignment. Load id from the carrier, status
PENDING, real weight and volume, your own cost incost. -
Assign it to each order it covers.
-
Mark those orders
COMPLETED. This sends the status-update email and closes the order. Remember it is a one-way door — do it when the goods have actually left. -
Advance the shipment to
TRANSITwhen it is collected andDELIVEREDwhen it lands. Customers see both. -
If a consignment is lost, set the shipment to
CANCELLEDand decide separately what to do with the orders. Cancelling the order refunds the buyer in full and puts stock back — which is usually right, but it is a money decision, not a logistics one.
Turning shipping off
Set ecommerceShippingEnabled to off and physical orders stop being charged a
fee. Everything else keeps working: shipment records, assignment, addresses and
tracking are all independent of the setting. It is a perfectly reasonable way
to run a store with free delivery priced into the products.
Next: Discounts or Store settings.