- Model the dimensions the business actually prices: destination, weight band and currency.
- Keep zone membership separate from the price table so geography can evolve cleanly.
- Define boundaries precisely; one gram should not fall between two bands or match both.
- Give operators a calculator that explains the selected rule before it reaches a real order.
- Use the same resolver for storefront checkout, draft orders and later order edits.
The client problem: a flat rate could not represent the delivery promise
Shipping looks simple when every parcel leaves one warehouse and every customer pays the same amount. The client’s reality combined several destinations, package weights and currencies. A light parcel to one country could use one price; a heavier parcel or another zone needed another. The storefront had to quote the right amount, but the operations team also needed to understand and maintain the table.
Hardcoding conditions into checkout would have produced the correct answer only until the next tariff change. A spreadsheet would have been easier to edit but disconnected from the order. The requirement was therefore larger than a calculation: represent the shipping policy inside commerce, let operators change it safely and make the selected result explainable.
Turn the carrier spreadsheet into an explicit business model
A commercial grouping such as domestic, neighbouring markets or a wider region. A zone is a policy container, not a price by itself.
The actual destination selects the eligible zone. Country membership needs one authoritative rule so checkout and Admin agree.
Each band has clear inclusive and exclusive boundaries. Gaps and overlaps are rejected before publication.
The rate belongs to the market currency being quoted. Converting an arbitrary base price at checkout is a separate business choice, not an invisible default.
Make rate resolution deterministic
At calculation time the resolver receives destination, package weight and commercial currency. It finds the applicable zone, selects exactly one weight band and returns the configured price. If no rule exists, the system should return a named configuration gap rather than quietly choosing zero or the nearest number.
Boundary semantics matter. If one band ends at five kilograms and the next begins at five, the contract must say which owns exactly five. The same unit must be used when product weights are aggregated. Missing weights need an explicit policy because a single unweighted product can make the parcel total meaningless.
The calculation should also return an explanation suitable for support and Admin: destination matched zone A, total weight matched band B, market currency selected price C. That trace turns a surprising shipping amount into a question the team can answer.
See the original shipping-rate module
The Admin calculator is not decoration. It is the safety mechanism.
Configuration interfaces often let teams save data but give them no way to ask what the data will do. We added a calculator where an operator enters a country and package weight and sees the returned price. This shortens the path between editing a table and understanding its effect.
The calculator should use the same resolver as checkout. A separate preview formula would create false confidence. Useful test cases include every boundary, the heaviest supported parcel, countries at the edge of each zone, unsupported destinations and each active currency. When a tariff changes, the merchant can verify representative journeys before customers encounter them.
Treat rate tables as commercial data with a lifecycle
Carrier prices change. Countries move between commercial zones. The business may negotiate a new table that starts next month while current orders still need the old promise. Editing rows in place without dates or history makes it impossible to explain why two otherwise similar orders received different prices.
A mature module can version tariff sets with an effective period and a draft state. Operators prepare the next table, validate representative scenarios and publish it deliberately. Existing orders retain the amount and rule reference used when they were confirmed; new calculations use the active version. Rollback means reactivating a known table rather than reconstructing yesterday’s cells from memory.
Imports can accelerate large updates, but they need the same validation as the Admin form: recognised countries and currencies, valid numeric weights, non-negative prices, no overlapping bands and no uncovered interval inside a zone that claims continuity. A preview should summarise additions, changes and removals before the new table becomes active.
Separate the quoted rate from the fulfilment promise
A price table answers what the merchant will charge. It does not by itself prove that a carrier serves the address, that the parcel fits the service or that delivery will take a particular number of days. Combining those questions into one amount makes the checkout look certain while hiding important operational constraints.
The eligibility layer should first identify viable fulfilment options for the destination and parcel. The rate resolver then prices the selected commercial option. Delivery estimates, surcharges and restricted goods can participate through explicit rules where the client needs them. This layered journey lets the interface explain both availability and price.
After launch, compare quoted amounts with actual carrier costs and exceptions. Large differences may reveal an outdated tariff, missing product weight or a packaging assumption that does not hold. That feedback should improve the policy without silently changing historical orders. Shipping architecture is complete only when the price can be operated after the first successful checkout.
Use one shipping contract across every way an order is created
Storefront checkout is only one order-entry path. B2B teams create draft orders, account managers edit orders and support may change quantities after a conversation. If those paths do not invoke the same shipping policy, the amount becomes dependent on who created the order rather than what is being shipped.
We therefore treat shipping resolution as a reusable commerce capability. Product changes trigger a new package weight; destination or market changes trigger a new eligibility check; the returned amount is applied through the relevant order workflow. Operators see the consequence while they are editing, not after the customer receives a revised invoice.
Why Medusa made the client-specific rule practical
The client did not need an app marketplace approximation of its tariff table. It needed its own shipping policy to participate in orders alongside Medusa regions, carts and fulfilment options. A custom module gave the rules a clear home, while workflows and Admin extensions connected them to daily operations.
This is why platform selection is an architecture decision. Medusa supplied the commerce engine and the extension points; we added the missing business capability without creating a detached calculator. The result is a rate the storefront can quote, an operator can test and support can explain from the same underlying contract.
Common questions
Some merchants choose that policy, but carrier pricing and commercial rounding can differ by market. Explicit currency prices make the promise intentional and reviewable.
Return a clear unavailable or configuration-error state. A silent zero rate or arbitrary fallback can create a loss-making delivery promise.
Yes. Storefront, draft-order and order-edit journeys should resolve shipping from the same contract so the price does not depend on the interface used.
