Shipping
Rates a store configures, matched against destination, parcel weight and order subtotal. No carrier account, and Stripeâs hosted checkout is untouched.
The model
Section titled âThe modelâZones group countries. A zone naming no countries is the catch-all, so a store can price the world without listing it. Explicit listings win over the catch-all regardless of position, so adding ârest of worldâ can never quietly capture a country a specific zone already prices.
Rates hang off zones, optionally, and are bounded by parcel weight and parcel
subtotal, inclusively at both ends, with null meaning unbounded. âFree over $50â
is a rate with minSubtotalCents: 5000 and a price of zero. âHeavy parcels cost
moreâ is two rates with adjacent weight bands. Each rate carries its own tax
behaviour, because postage is taxable in some jurisdictions and not others.
Two behaviours to state up front:
- An unpinned rate applies everywhere. A flat-rate store needs one rate and no zones at all.
- Every matching rate is offered, cheapest first. Not one winner. That is what a standard-versus-express pair needs.
Resolution is one pure function, resolveShippingRates in shared/shipping.ts,
called by both the cart quote and the checkout route so the two cannot disagree.
The rates are passed inline on the Checkout Session; there are no Stripe Shipping
Rate objects to keep in sync.
Why the cart asks for a country
Section titled âWhy the cart asks for a countryâIt looks like a UX mistake until you know why. Hosted Checkout collects the address after the session exists, so a zone-priced store has to know the destination before it can price postage. The session is then restricted to that country, so a buyer cannot keep a domestic rate on an international address: the line-item price-integrity rule, applied to postage. Anyone who reads the cart and thinks âIâll move this to the Stripe pageâ needs to hit this paragraph first.
Three silent failures
Section titled âThree silent failuresâfindCoverageGaps exists because these are invisible. Treat the adminâs warnings
as a routine check, not a curiosity.
- A coverage gap ships free. When no rate matches, the buyer is offered nothing and pays no postage. The order still completes. Deliberate, since inventing a price would be worse, but it means a misconfiguration is discovered when a parcel arrives with no postage on it. The Overview warns when a store has live physical products and no rates, and when a zone covers a country no rate can price.
- No recorded weights means a 0 g parcel, which matches the lightest weight band. Weight bounds configured before variant weights are filled in do not fail closed; everything quietly qualifies for the cheapest band.
- Subtotal bounds count physical lines only. A $40 download does not push a $10 box over a free-shipping threshold. On an upper bound it is worse: without this rule a digital-heavy cart sails past every bandâs ceiling, matches nothing, and ships free.
Digital lines are excluded, not zeroed
Section titled âDigital lines are excluded, not zeroedâGiving a download weightGrams: 0 looks equivalent and is not. A zero-gram line
still participates, so a cart of nothing but PDFs would report a 0 g parcel, match
the lightest band, and charge the buyer postage on a parcel that does not exist.
physicalLines, requiresShipping and parcelFor are the API; a cart with no
physical line collects no address at all, and a mixed cart is priced on its
physical lines. The reasoning generalises to anything anyone adds later.
What was deliberately not built
Section titled âWhat was deliberately not builtâLive carrier rates, address validation, label purchase, tracking. Live rates need
ui_mode: 'elements', which means Beluga owning the checkout page, and with it the
card fields and PCI scope that Stripeâs hosted page keeps off your server. The trade, the provider
evaluation (Shippo, EasyPost, Easyship), and a middle path that quotes on the cart
page are in docs/shipping.md in the repository. Treat it as a payments phase
with a shipping payload, not an increment on this.