Перейти к содержимому

Работа с корзиной из кода - добавление, количество, пересчёт

Добавляем товар в корзину, меняем количество и считаем итог со скидками, не сломав корзину покупателя.

Решение

Загружаем корзину текущего покупателя:

use Bitrix\Main\Context;
use Bitrix\Sale;
$siteId = Context::getCurrent()->getSite();
$basket = Sale\Basket::loadItemsForFUser(Sale\Fuser::getId(), $siteId);
// код сайта обязателен: на мультисайте у каждой витрины своя корзина
// корзина гостя привязана к куке, корзина вошедшего - к его записи

Метод берёт корзину фиксированного покупателя: у гостя её определяет кука, у авторизованного - его запись. Для покупки в один клик берут пустую корзину через Sale\Basket::create, иначе строки уедут из корзины в заказ.

Добавляем товар:

$item = $basket->createItem('catalog', $productId);
$item->setFields([
'QUANTITY' => 2,
'CURRENCY' => $currency,
'LID' => $siteId,
'PRODUCT_PROVIDER_CLASS' => '\CCatalogProductProvider', // цену подтянет каталог
]);
$basket->save();

Провайдер обязателен: без него позиция сохранится с нулевой ценой и пустым названием. Повторное добавление того же товара не создаёт вторую строку, а увеличивает количество существующей.

Меняем количество у позиции:

$item = $basket->getExistsItem('catalog', $productId);
if ($item) {
$item->setField('QUANTITY', 5);
$result = $basket->save();
if (!$result->isSuccess()) {
echo implode('; ', $result->getErrorMessages()), "\n"; // например, нет на складе
}
}

Сохранение возвращает результат с ошибками. Самая частая - превышение доступного количества: корзина проверяет остаток и не даёт положить больше, чем есть.

Считаем итог со скидками:

$order = Sale\Order::create($siteId, $userId);
$order->setPersonTypeId($personTypeId);
$order->setBasket($basket);
$order->doFinalAction(true); // применяет правила корзины и купоны
echo "итого: ", $order->getPrice(), "\n";

Сумма по позициям без этого шага даст цену без скидок: правила корзины применяются к заказу, а не к самой корзине.

Пересчёт стоит делать один раз и в конце, а не после каждой правки позиции. Каждый вызов проходит по всем активным правилам и по всему составу корзины, и на десятке правил это уже заметно. В типовом сценарии порядок такой: собрали корзину, положили её в заказ, посчитали итог, показали покупателю.

Отдельно про доступность. Метод canBuy у позиции отвечает, можно ли её купить прямо сейчас: он учитывает активность товара, остаток и права группы. Проверять его стоит до оформления, а не полагаться на то, что заказ откажет сам. Покупатель, дошедший до формы и получивший отказ, чаще уходит, чем исправляет корзину.

Перечитываем цены и доступность перед показом:

$basket->refreshData([
'PRICE', // цена могла измениться с момента добавления
'COUPONS', // купон мог истечь
'QUANTITY', // остаток мог уменьшиться
]);
$basket->save();

Метод обновляет позиции по данным каталога и возвращает результат с ошибками по тем, что стали недоступны.

Корзина живёт долго: строки хранятся до очистки агентом, и товар в ней может успеть подорожать, кончиться или стать неактивным. Поэтому цену и доступность перечитывают при каждом показе корзины, а не берут из того, что было записано при добавлении.

Типичные проблемы

Товар добавился с нулевой ценой.

Не указан PRODUCT_PROVIDER_CLASS. Корзина не знает, откуда брать цену, и сохраняет позицию пустой. Своя цена ставится флагом CUSTOM_PRICE.

Из корзины покупателя пропали товары после оформления.

Заказ собран из живой корзины. При сохранении её строки получили ORDER_ID и перешли в заказ. Для покупки в один клик берут новую корзину.

Количество не меняется, создаётся вторая строка.

Позиция ищется не тем способом: getExistsItem сопоставляет по модулю и идентификатору товара, а перебор по массиву легко промахивается.

Сохранение молча ничего не делает.

Результат save не проверяется. Ошибки вроде нехватки остатка возвращаются в объекте результата, а не выбрасываются исключением.

Частые вопросы

Как убрать всплывающее окно «Товар добавлен в корзину»?

Это поведение шаблона компонента каталога, а не самой корзины. Параметр отвечает за режим добавления: перезагрузка страницы, всплывающее окно или тихое добавление через AJAX.

Можно ли класть в корзину не товар каталога?

Да, первый аргумент createItem - имя модуля-поставщика. Для своих сущностей пишут свой провайдер, который умеет отдавать цену, название и доступное количество.

Как учесть коэффициент единицы измерения?

Коэффициент хранится у товара и умножается на количество при расчёте. В корзине лежит количество в базовых единицах, а витрина показывает пересчитанное значение.

Почему корзина пуста в консольном скрипте?

Фиксированный покупатель определяется по куке или сессии, которых в консоли нет. Идентификатор передают явно, а не рассчитывают на Fuser::getId.

Смежное

Первоисточник