Вес заказа WooCommerce складывается из веса каждой позиции, умноженного на количество, и дальше его читают тарифы доставки и плагины служб вроде СДЭК. Сам по себе магазин ничего не округляет и не добавляет за упаковку, поэтому цифра, которую получает служба доставки, почти всегда расходится с реальным весом посылки. Ниже я показываю, где лежит вес, как получить сумму для корзины и для готового заказа, как добавить вес упаковки и как использовать результат в расчёте доставки.
Где WooCommerce хранит вес товара и вариаций
Вес задаётся в карточке товара, на вкладке «Доставка» блока «Данные товара». Рядом лежат длина, ширина, высота и класс доставки. Для простого товара это одно поле. Для вариативного вес можно указать у каждой вариации отдельно, а можно оставить пустым: тогда вариация берёт вес родительского товара. Из-за этого в каталоге с сотнями вариаций легко получить ситуацию, когда у родителя стоит 0,3 кг, а реальные вариации весят по килограмму, и никто этого не замечает до первой жалобы от службы доставки.
В коде вес читается методом get_weight() у объекта товара. Метод возвращает строку, причём пустую, если вес не заполнен. Это важно: приведение пустой строки к числу даёт ноль, и заказ из десяти позиций без веса спокойно уходит в доставку как «нулевой».
| Место | Что там лежит | Как достать |
|---|---|---|
| Карточка простого товара | Вес в единице магазина | $product->get_weight() |
| Вариация | Свой вес или вес родителя, если поле пустое | $variation->get_weight() |
| Корзина | Сумма по позициям с учётом количества | WC()->cart->get_cart_contents_weight() |
| Готовый заказ | Отдельного поля общего веса нет, есть позиции | цикл по $order->get_items() |
Последняя строка таблицы это то, на чём спотыкаются чаще всего. У корзины готовый метод суммы есть, а у объекта WC_Order я такого метода не встречал, поэтому вес заказа приходится считать самому или сохранять в мету в момент оформления.
Единицы измерения: как не перепутать граммы и килограммы
Единица веса задана для всего магазина одна, в настройках товаров. Значение лежит в опции woocommerce_weight_unit, допустимы kg, g, lbs и oz. Если магазин настроен в граммах, а служба принимает килограммы (или наоборот), нужна конвертация. Для неё в WooCommerce есть функция wc_get_weight(): она принимает число, целевую единицу и, необязательно, исходную. Если исходную не указать, берётся единица магазина.
// вес корзины в килограммах, независимо от настройки магазина
$cart_kg = wc_get_weight( WC()->cart->get_cart_contents_weight(), 'kg' );
// вес в граммах для API, которое ждёт целое число
$cart_g = (int) round( wc_get_weight( WC()->cart->get_cart_contents_weight(), 'g' ) );
Я всегда конвертирую на выходе, а не меняю единицу магазина под конкретную службу. Единица магазина влияет на то, как вес показывается покупателю и как его вводит менеджер в админке, и переключать её ради интеграции значит заново перебивать все карточки.
Как посчитать общий вес корзины и готового заказа
Вес корзины
Пока заказа нет, всё считает корзина. Метод get_cart_contents_weight() перебирает позиции, берёт вес товара или вариации, умножает на количество и суммирует. Виртуальные товары без веса в сумму ничего не добавляют, что нас устраивает. Вызывать метод можно в хуках расчёта доставки и на странице чекаута.
Вес уже созданного заказа
Для заказа, который лежит в базе (например, чтобы отправить его в службу доставки из хука смены статуса), я считаю вес циклом по позициям. Обращаться к таблицам напрямую не стоит: на современных магазинах заказы живут в отдельных таблицах HPOS, поэтому работать надо через объект WC_Order.
function kd_order_weight_kg( WC_Order $order ) {
$total = 0;
foreach ( $order->get_items() as $item ) {
$product = $item->get_product();
if ( ! $product ) {
continue; // товар удалён после оформления
}
$total += (float) $product->get_weight() * $item->get_quantity();
}
return wc_get_weight( $total, 'kg' );
}
Проверка if ( ! $product ) нужна не для красоты. Если товар удалили из каталога, get_product() вернёт false, и без проверки получится фатальная ошибка в момент, когда менеджер нажимает кнопку в старом заказе.
Есть и вторая особенность. Считая вес по текущим карточкам, вы получаете вес на сегодня, а не на момент покупки. Если в марте товар весил 400 г, а в июне вы поправили карточку на 450 г, пересчёт старого заказа даст другую цифру. Поэтому вес лучше зафиксировать в мете при оформлении:
add_action( 'woocommerce_checkout_create_order', function ( $order, $data ) {
$kg = wc_get_weight( WC()->cart->get_cart_contents_weight(), 'kg' );
$order->update_meta_data( '_kd_total_weight_kg', round( $kg, 3 ) );
}, 10, 2 );
Хук woocommerce_checkout_create_order получает объект заказа и данные формы, а сохранение произойдёт вместе с самим заказом, отдельный save() не нужен. Дальше вес читается через $order->get_meta( '_kd_total_weight_kg' ).
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
Вес упаковки и запас на погрешность
Служба доставки тарифицирует посылку целиком, а не только товар. Коробка, наполнитель и скотч добавляют вес, и для тяжёлых и мелких заказов эта добавка заметна. Базового поля «вес упаковки» в карточке нет, поэтому я добавляю его константой или опцией в той же функции расчёта. Типичная схема у меня такая: фиксированный вес коробки плюс небольшой процент на наполнитель.
function kd_package_weight_kg( $goods_kg ) {
$box_kg = 0.25; // пустая коробка, взвесьте свою
$filler = $goods_kg * 0.05; // наполнитель, подберите по факту
return round( $goods_kg + $box_kg + $filler, 3 );
}
Числа в примере мои рабочие заготовки, а не нормативы: коробку и наполнитель надо взвесить на своём складе. Разумно сверить расчётный вес с реальным на первых 20-30 отправках и поправить коэффициенты. Если расхождения идут в одну сторону (расчёт всегда меньше), служба будет присылать доплаты, и вы это увидите по первому же отчёту.
Как передать вес в расчёт доставки WooCommerce
Что умеют штатные методы
В WooCommerce три встроенных метода: Flat Rate, Free Shipping и Local Pickup. В формуле стоимости Flat Rate доступны подстановки [qty], [cost] и [fee], а общего веса там нет. Поэтому для тарифов по весу есть два пути: разнести товары по классам доставки (подходит, когда весовых групп две-три) или подключить плагин конкретной службы, который сам читает вес. Для тарифов по весу, которые вы определяете сами, остаётся фильтр.
Фильтр woocommerce_package_rates
Хук woocommerce_package_rates получает массив уже рассчитанных тарифов и пакет доставки, и возвращает массив, который увидит покупатель. На нём удобно делать правила по весу. Например, убрать бесплатную доставку для тяжёлых заказов и добавить надбавку за вес сверх порога:
add_filter( 'woocommerce_package_rates', function ( $rates, $package ) {
$kg = wc_get_weight( WC()->cart->get_cart_contents_weight(), 'kg' );
foreach ( $rates as $rate_id => $rate ) {
if ( $kg > 30 && 'free_shipping' === $rate->get_method_id() ) {
unset( $rates[ $rate_id ] );
}
if ( $kg > 10 && 'flat_rate' === $rate->get_method_id() ) {
$rate->set_cost( $rate->get_cost() + ( $kg - 10 ) * 20 );
}
}
return $rates;
}, 10, 2 );
Пороги 30 и 10 кг и ставка 20 ₽ за каждый лишний килограмм здесь условные, подставьте свои. Метод get_method_id() возвращает тип метода (flat_rate, free_shipping), а ключ массива содержит ещё и идентификатор экземпляра в зоне. Если сравнивать ключ целиком, правило сработает только в одной зоне.
Отдельное предупреждение про кэш. WooCommerce кэширует рассчитанные тарифы для пакета, и хэш пакета зависит от содержимого корзины и адреса. Вес меняется вместе с содержимым, так что обычно всё обновляется само, но если вы берёте в расчёт что-то внешнее (вес упаковки из опции, курс), после изменения этих значений тарифы в тестовой корзине надо перепроверять, а не доверять первой же цифре.
Вес заказа для СДЭК и других служб доставки
Когда нужна доставка СДЭК, я использую официальный плагин CDEKDelivery с wordpress.org, а не пишу интеграцию с нуля. Он берёт вес из карточек товаров, поэтому главная работа на моей стороне лежит в данных: у каждого товара и вариации должен стоять реальный вес. Перед запуском я прохожу каталог и ищу пустые значения, а также нули и подозрительно круглые цифры вроде «1» у товаров, которые явно легче или тяжелее. Как плагин поведёт себя с пустым весом на вашей версии, смотрите в его настройках и в тестовом заказе, не полагаясь на мои слова.
Для собственных интеграций (передача заказа в службу по API, выгрузка в таблицу склада, уведомление в Telegram) схема другая. Вес берётся из зафиксированной меты _kd_total_weight_kg, конвертируется под формат получателя и уходит в запросе. Для службы, которая ждёт граммы целым числом, я округляю и отдельно проверяю, что результат не меньше одного грамма: нулевой вес многие API отклоняют. Если обмен идёт по событиям, например при переходе заказа в статус «В обработке», подвешиваюсь к хуку woocommerce_order_status_processing, он получает id заказа и сам объект.
Перед передачей я также сверяю, не выросла ли цифра после упаковки. Покупателю в чекауте показывается вес товаров, а службе уходит вес с упаковкой, и это две разные величины, которые не нужно смешивать в одной переменной.
Если магазин ещё только планируется и вес, габариты и тарифы нужно заложить в архитектуру сразу, проще заказать разработку интернет-магазина под ключ от 80 000 ₽, чем потом переписывать каталог и чекаут под доставку.
Типичные ошибки при расчёте веса
- Пустой вес у вариаций. Вариация наследует вес родителя, и если родитель заполнен «для порядка», все варианты считаются одинаково тяжёлыми.
- Единицы не те. Вес 0,5 внесён как «500» при единице kg. Заказ из двух позиций получается весом в тонну, и служба доставки отказывается его считать.
- Вес не умножается на количество. В самописном коде суммируют
get_weight()без количества, это встречается чаще, чем хотелось бы. - Пересчёт старых заказов по новым карточкам. Решается фиксацией веса в мете при оформлении.
- Виртуальные и скачиваемые товары. Их вес должен быть пустым. Если в карточке остался вес, он попадёт в сумму.
- Блочный чекаут. На магазинах, запущенных после версии 8.3, корзина и чекаут по умолчанию блочные, а не шорткодовые. Хуки расчёта доставки при этом работают, но код, который правит поля через
woocommerce_checkout_fields, на блочном чекауте может не сработать, и сначала нужно выяснить, какой чекаут стоит.
Для проверки я завожу в тестовой среде три заказа: один лёгкий, один на границе порога и один сильно тяжёлый, с вариативным товаром в каждом. Если вес на странице корзины, в тарифе и в мете заказа совпадает во всех трёх, схема собрана верно.
WooCommerce и плагин CDEKDelivery регулярно обновляются, поэтому перед внедрением я сверяюсь с актуальной документацией.
Частые вопросы
Есть ли в WooCommerce готовый метод для общего веса заказа?
Для корзины есть: WC()->cart->get_cart_contents_weight(). Для уже созданного заказа отдельного метода я не использую, считаю циклом по позициям или читаю вес, сохранённый в мету при оформлении.
Что будет, если у товара не указан вес?
Метод get_weight() вернёт пустую строку, и в сумму такая позиция даст ноль. Заказ уйдёт в доставку легче, чем есть на самом деле. Поэтому перед запуском нужно пройти каталог и заполнить вес у всех физических товаров.
Как учесть вес упаковки?
Отдельного поля в карточке для этого нет. Я добавляю вес коробки константой и процент на наполнитель в функции расчёта, а коэффициенты уточняю по первым реальным отправкам.
Можно ли сделать тарифы по весу без кода?
Частично. Если весовых групп мало, помогают классы доставки в Flat Rate. Для плавной шкалы по килограммам нужен либо плагин службы доставки, который сам считает по весу, либо фильтр woocommerce_package_rates, как в примере выше.