Usage
The simulator
Weight → Simulator is the screen to spend time on before going live. It quotes either a real cart, by order number, or a package you make up — a weight, a quantity, a subtotal and a destination — and shows, for every method:
- the price, and whether the method is available at all
- how the cart was packed, box by box, with each package's real and billable weight
- every rule in order, whether it matched, and if it did not, which condition it failed
- every component of every charge: the handling fee, the weight steps and their arithmetic, the per-item charge, the percentage, and any clamp that moved the number
The simulator is not a re-implementation. Rater::simulate() routes a made-up package through the
same _ratePackage() and the same clamp as a real checkout, which is what makes a preview
trustworthy: Rater::quote() is the only place in the plugin a price is computed. The Commerce
shipping method, the simulator, the Twig variable, the JSON endpoint and the console all read the
same Quote.
At checkout
Nothing to do. Each enabled method registers itself as a Commerce shipping method, so it appears in
cart.availableShippingMethodOptions next to Commerce's own and works with an unmodified checkout
template.
Its handle is weight-<handle> — that is what goes in shippingMethodHandle when a customer picks
it.
Twig
Use craft.weight when you want Weight's own numbers rather than just the price.
{% set quotes = craft.weight.quotes() %}
{% for quote in quotes %}
{% if quote.available %}
<label>
<input type="radio" name="shippingMethodHandle"
value="{{ quote.method.shippingMethodHandle }}">
{{ quote.method.customerLabel }} —
{{ quote.isFree ? 'Free' : quote.total|commerceCurrency(cart.currency) }}
{% if quote.packageCount > 1 %}
<small>{{ quote.packageCount }} parcels</small>
{% endif %}
</label>
{% endif %}
{% endfor %}
craft.weight.quotes(order = cart) | Every method's quote, cheapest first, keyed by handle |
craft.weight.quote(handle, order = cart) | One method's quote |
craft.weight.packages(handle, order = cart) | How the cart would be packed |
craft.weight.progress(order = cart) | How far the cart is from free shipping |
A Quote carries available, unavailableReason, total, subtotal, isFree, packageCount,
totalWeight, billableWeight, boxCost, unpackable, clampNote, packages and
matchedRules.
isFree deliberately requires that something was actually packed. A cart with nothing shippable in
it is quoted as available at zero — otherwise a digital-only order could not check out — but it is
never reported as free shipping, because congratulating an empty basket is worse than saying
nothing.
Free-shipping progress
{% set progress = craft.weight.progress() %}
{% if progress and progress.message %}
<p>{{ progress.message }}</p>
{% endif %}
Progress works off whatever your free-shipping rule is actually conditioned on — a subtotal, a
quantity or a weight — and picks the matching message template from the plugin settings, filling in
{remaining}. Once the cart qualifies, it returns the qualifying message instead.
JSON
GET /actions/weight/cart/quote
Returns the same numbers for the requester's own cart, plus the free-shipping progress. It is
anonymous, because it only ever describes your own basket. Send Accept: application/json.
{
"methods": [
{
"handle": "standard",
"shippingMethodHandle": "weight-standard",
"name": "Standard delivery (3–5 days)",
"description": null,
"available": true,
"price": 17.6,
"free": false,
"packages": 1,
"weight": 6,
"boxes": []
}
],
"progress": {
"eligible": false,
"type": "amount",
"remaining": 22.4,
"threshold": 75,
"message": "Spend 22.40 more for free shipping",
"method": "standard",
"rule": "Free over 75"
}
}
type is amount, qty or weight — whichever the nearest free-shipping rule is conditioned on.
remaining is null when there is no free-shipping rule to make progress towards.
Console
php craft weight/methods # every method and its rules
php craft weight/methods/export --file=w.json # methods, rules and boxes as JSON
php craft weight/methods/import --file=w.json # ...and back, matching on handle
php craft weight/rates/quote <orderNumber> # what every method costs for one real cart
php craft weight/rates/table standard 0.5 20 0.5 --country=GB
php craft weight/presets # carrier-shaped rate structures
php craft weight/presets/show <name> # print one as importable JSON
php craft weight/presets/import <name> # create its methods, disabled
weight/rates/table prints a rate ladder for one method — from 0.5 to 20 in steps of 0.5 in that
example — which is the thing you actually want to eyeball before going live. It takes
--country, --area, --postal, --subtotal, --store and --verbose.
weight/rates/quote takes the same destination options, so you can ask what a real cart would have
cost to somewhere it was not going.
Because methods, rules and boxes live in the database rather than project config, export and
import are how a rate table moves from staging to production.
How the cart becomes packages
Packer::pack() is the only place it happens, and both it and the rater memoize on a full cart
signature — line items, quantities, weights, prices, shipping categories, address, coupon — never
on the order number alone. An order number is stable across a recalculation, which is exactly how a
stale price ends up on a changed cart.
Line items that are not shippable, or that already carry free shipping, are excluded before packing starts, so the weight being charged for is the weight actually being shipped.
About the box packer
Three-dimensional bin packing is NP-hard and nobody solves it exactly during a checkout request. Weight's packer is first-fit-decreasing with a volume-and-dimension fit test: an item goes in a box if each of its dimensions fits the box's, the box has volume left, and the box has weight left.
It does not model the geometry of what is already in the box, so it errs towards more boxes rather than fewer — the direction that overcharges no carrier — and every decision it makes is visible in the simulator.