Why your shipping rates arrive attached to the wrong classes
Shipping classes are referenced by internal ID, and those IDs differ on every install. Copying the configuration between sites quietly scrambles it.
Move a WooCommerce shipping configuration from staging to production and the zones look correct, the methods look correct, and the rates are attached to the wrong classes. Nothing errors. Customers are simply quoted the wrong price.
The mechanism
Shipping classes are taxonomy terms, so each one has a numeric term ID assigned in creation order on that particular installation. Rates reference the class by that ID. Two sites with identical class names will almost never have identical IDs, and a straight copy carries the numbers across without translating them.
Why exports do not save you
Generic settings exporters treat the rate table as opaque data. They faithfully reproduce the number, which is exactly the problem — the number is only meaningful in the database it came from.
The fix
Export classes alongside the rates, match them by name on the destination, build a map from old ID to new ID, and rewrite every reference during import. Where a class does not exist on the destination, create it first so the map is complete. I ended up writing this as a plugin because I had done it by hand often enough to know how easily one row gets missed — and because the row you miss is the one nobody notices until a customer is undercharged for a month.
Whatever you use, verify afterwards
Place a test order per shipping class on the destination site. It takes ten minutes and it is the only check that proves the mapping held.
