Bug: Weight-based shipping rate tiers — remaining issues after PR #2382
Summary
PR #2382 partially fixed weight-based shipping by normalizing product units to kg via the Cartalyst Converter. However, several issues remain that make weight-based shipping unreliable in production.
Remaining Issues
1. weight_unit hardcoded to 'kg' — not read from shipping method data
ShipBy.php hardcodes 'weight.kg' in the Converter call:
$tier = $cart->lines->sum(
fn ($line) => $line->purchasable->weight->to('weight.kg')->convert()->getValue() * $line->quantity
);
The Shipping Method form has a weight_unit select field (kg, g, lbs) that is saved to the data JSON column, but ShipBy never reads it. If a merchant selects grams as the unit, tiers would be entered in grams (e.g., min 500g) but the cart weight is always converted to kg (e.g., 0.5), making 0.5 >= 500 always false.
Similarly, ManageShippingRates.php hardcodes 'kg' in the field suffix and helper text:
->suffix(fn (Get $get) => static::isWeightCharge($get) ? 'kg' : null)
The selected weight_unit is never used to display the correct unit or to convert weights accordingly.
2. Float truncation on save — decimals are lost
ManageShippingRates.php saves weight tiers with:
$price['min_quantity'] = (int) $price['min_quantity'];
Since min_quantity is an integer column, an admin entering 2.5 kg gets (int) 2.5 = 2 stored. The 0.5 kg is silently lost. Tier boundaries become inaccurate.
3. Float $tier value causes truncation and precision bugs
PricingManager::qty() declares public function qty(int $qty). At runtime, $tier for weight mode is a float (e.g., 2.5). PHP truncates this to 2 when passed to qty(). While ShipBy applies its own filter with the original $tier, the truncated value in PricingManager can affect base price selection.
Additionally, IEEE 754 float arithmetic means 0.1 + 0.2 != 0.3, which can cause intermittent comparison failures in $tier >= $price->min_quantity.
Suggested Fix
Restore the ×100 storage scale (consistent with cart_total mode), make weight_unit configurable and used throughout, and use (int) round() to eliminate float precision issues:
- Store: Admin enters
2.5 → stored as 250 (×100, integer, no truncation)
- Runtime:
(int) round(2.5 * 100) = 250 (integer comparison, no float bugs)
- Display:
÷100 on hydrate → shows 2.5 again
- Unit: Read
$data['weight_unit'] ?? 'kg' and convert all weights to that unit
- Admin: Dynamic label/suffix/helper showing the configured unit
Bug: Weight-based shipping rate tiers — remaining issues after PR #2382
Summary
PR #2382 partially fixed weight-based shipping by normalizing product units to kg via the Cartalyst Converter. However, several issues remain that make weight-based shipping unreliable in production.
Remaining Issues
1.
weight_unithardcoded to'kg'— not read from shipping method dataShipBy.phphardcodes'weight.kg'in the Converter call:The Shipping Method form has a
weight_unitselect field (kg,g,lbs) that is saved to thedataJSON column, but ShipBy never reads it. If a merchant selects grams as the unit, tiers would be entered in grams (e.g., min 500g) but the cart weight is always converted to kg (e.g., 0.5), making0.5 >= 500always false.Similarly,
ManageShippingRates.phphardcodes'kg'in the field suffix and helper text:The selected
weight_unitis never used to display the correct unit or to convert weights accordingly.2. Float truncation on save — decimals are lost
ManageShippingRates.phpsaves weight tiers with:Since
min_quantityis an integer column, an admin entering2.5kg gets(int) 2.5 = 2stored. The 0.5 kg is silently lost. Tier boundaries become inaccurate.3. Float
$tiervalue causes truncation and precision bugsPricingManager::qty()declarespublic function qty(int $qty). At runtime,$tierfor weight mode is a float (e.g.,2.5). PHP truncates this to2when passed toqty(). While ShipBy applies its own filter with the original$tier, the truncated value in PricingManager can affect base price selection.Additionally, IEEE 754 float arithmetic means
0.1 + 0.2 != 0.3, which can cause intermittent comparison failures in$tier >= $price->min_quantity.Suggested Fix
Restore the ×100 storage scale (consistent with
cart_totalmode), makeweight_unitconfigurable and used throughout, and use(int) round()to eliminate float precision issues:2.5→ stored as250(×100, integer, no truncation)(int) round(2.5 * 100) = 250(integer comparison, no float bugs)÷100on hydrate → shows2.5again$data['weight_unit'] ?? 'kg'and convert all weights to that unit