router->group(function ($router) { require_once __DIR__ . '/Http/shipping-api.php'; }); (new ScriptHandler())->register(); } public function handleItemsChanges($data) { $scope = Arr::get($data, 'scope'); $ignores = [ 'apply_coupons', 'discounts_recalculated', 'remove_coupon', 'payment_method_fee_recalculate', ]; if (in_array($scope, $ignores)) { return; } $cart = Arr::get($data, 'cart'); if (!$cart->requireShipping()) { return; } $fillData = $this->getRecalculatedCartDataArr([ 'cart_data' => $cart->cart_data, 'checkout_data' => $cart->checkout_data ], $cart); $cart->fill($fillData); $cart->save(); } public function maybeRecalculateShippingCharges($fillData, $data) { $cart = $data['cart']; if (!$cart->requireShipping()) { return $fillData; } $watchingKeys = [ 'shipping_country', 'shipping_state', 'shipping_method_id' ]; $changes = Arr::get($data, 'changes', []); if (!array_intersect(array_keys($changes), $watchingKeys)) { // No changes detected return $fillData; } Arr::set($fillData, 'hook_changes.shipping', true); $fillData = $this->getRecalculatedCartDataArr($fillData, $cart); return $fillData; } private function getRecalculatedCartDataArr($fillData, $cart) { $shippingCountry = Arr::get($fillData, 'checkout_data.form_data.shipping_country', null); $shippingState = Arr::get($fillData, 'checkout_data.form_data.shipping_state', null); // Checkout renderer resolves shipping methods with a billing fallback when no // shipping address is set (ship-to-different unchecked). Resolve the same way // here, otherwise this chain zeroes the method/charges the renderer just offered. if (!$shippingCountry) { $shippingCountry = Arr::get($fillData, 'checkout_data.form_data.billing_country', null); $shippingState = Arr::get($fillData, 'checkout_data.form_data.billing_state', null); } $availableShippingMethods = AddressHelper::getShippingMethods($shippingCountry, $shippingState); if (!$availableShippingMethods || is_wp_error($availableShippingMethods)) { Arr::set($fillData, 'checkout_data.shipping_data.shipping_charge', 0); Arr::set($fillData, 'checkout_data.shipping_data.shipping_method_id', null); $cartData = Arr::get($fillData, 'cart_data', []); foreach ($cartData as &$cartItem) { $cartItem['shipping_charge'] = 0; $cartItem['itemwise_shipping_charge'] = 0; } unset($cartItem); Arr::set($fillData, 'cart_data', $cartData); return $fillData; } $prevShippingMethodId = Arr::get($fillData, 'checkout_data.shipping_data.shipping_method_id', null); $shippingMethod = Arr::first($availableShippingMethods); if ($prevShippingMethodId) { foreach ($availableShippingMethods as $method) { if ($method->id == $prevShippingMethodId) { $shippingMethod = $method; break; } } } // Use profile-aware calculation if any cart items have shipping classes $cartData = Arr::get($fillData, 'cart_data', []); $hasShippingClasses = $this->cartHasShippingClasses($cartData); if ($hasShippingClasses && $shippingCountry) { $calculations = CartHelper::calculateShippingByProfile( $shippingMethod->id, $cartData, $shippingCountry, $shippingState, 'items' ); } else { $calculations = CartHelper::calculateShippingMethodCharge($shippingMethod, $cartData, 'items'); } $newCharge = Arr::get($calculations, 'shipping_amount', 0); $items = Arr::get($calculations, 'items', []); Arr::set($fillData, 'cart_data', $items); Arr::set($fillData, 'checkout_data.shipping_data.shipping_charge', $newCharge); Arr::set($fillData, 'checkout_data.shipping_data.shipping_method_id', $shippingMethod->id); return $fillData; } private function cartHasShippingClasses($cartData) { if (empty($cartData)) { return false; } $postIds = array_unique(array_column($cartData, 'post_id')); if (empty($postIds)) { return false; } $products = \FluentCart\App\Models\Product::query() ->whereIn('ID', $postIds) ->with(['detail']) ->get(); foreach ($products as $product) { if ($product->detail && !empty($product->detail->other_info['shipping_class'])) { return true; } } return false; } }