# retail.shipping-rate
Looks up a delivery charge on a rate card: pick the service and zone, find the
cheapest weight band the parcel fits on the order date, and waive the charge
when the basket reaches the free-delivery threshold.
## The rate card is an argument
Delivery prices are the merchant's own commercial terms, negotiated with a
carrier and changed whenever the merchant likes, not published rules. So the
table is passed in as `bands`, not shipped as registry data; each row still
carries `validFrom` and `validTo`, so one card can hold last year's prices and
this year's and the order date picks between them. The table below, used by
the vectors, is **illustrative only**: it is not any carrier's price list.
| service | zone | up to | longest side | divisor | step | price | free over | from | to |
|---|---|---|---|---|---|---|---|---|---|
| standard | UK | 2 kg | 450 mm | none | 1 g | £3.95 | £50 | 2026-01-01 | |
| standard | UK | 10 kg | 1000 mm | 5000 | 500 g | £6.95 | £50 | 2026-01-01 | |
| standard | UK | 30 kg | 1500 mm | 5000 | 1 kg | £12.95 | never | 2026-01-01 | |
| express | UK | 10 kg | 1000 mm | 5000 | 500 g | £9.95 | never | 2026-01-01 | |
| standard | EU | 2 kg | 450 mm | none | 1 g | £9.95 | never | 2026-01-01 | |
| standard | UK | 2 kg | 450 mm | none | 1 g | £3.50 | £40 | 2025-01-01 | 2025-12-31 |
## How a band is chosen
A band matches when its service and zone are the ones asked for, the order
date is within its dates (both ends inclusive), the parcel's longest side is
within `maxLengthMm`, and the parcel's chargeable weight in that band is
within `maxGrams`. Chargeable weight depends on the band: with a
`volumetricDivisor` it is the greater of actual and volumetric weight
(`retail.volumetric-weight`), rounded up to the band's step; without one it is
the actual weight rounded up to the step. Of the matching bands the one with
the smallest `maxGrams` wins, then the cheaper, then the first listed.
No matching band is an error that names the service, zone and date. That is
deliberate: a checkout that quietly charges nothing for a parcel it cannot
ship is worse than one that refuses it.
## Free delivery
When the band has `freeOver` and `basketTotal` is at least that amount, the
price is zero and `free` is true; `standardPrice` still says what it would
have cost, for "you saved £3.95" messages. The threshold is compared exactly,
so £49.99 is not £50. Which total counts (before or after coupons, with or
without VAT) is the merchant's rule: pass that total.
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in onDate; adds tests.