Skip to content

Weight-based shipping rate tiers #2514

Description

@zalito12

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions