Работа с корзиной из кода - добавление, количество, пересчёт
Добавляем товар в корзину, меняем количество и считаем итог со скидками, не сломав корзину покупателя.
Решение
Загружаем корзину текущего покупателя:
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.
Смежное
-
Корзина - оглавление подтемы
-
Скидка на отдельную позицию в корзине - правила и купоны
-
Каталог и продажи - корзина, заказ и коллекции
-
Добавление в корзину: из списка, из кода, с нужными свойствами - как позиция попадает в корзину
-
Свой расчёт в оформлении: цена позиции и доставка - своя цена позиции и провайдер продукта
-
Единицы измерения, вес и габариты товара - откуда берётся кратность количества
-
События корзины: подарки, ограничения, пересчёт - как вмешаться в корзину событиями
-
Брошенные корзины: поиск, письмо, чистка старых - что делать с корзиной без заказа
-
Корзина гостя и вход покупателя: привязка, слияние, чистка - к чему привязана корзина и что с ней при входе
-
Дополнительные услуги в заказе: упаковка, сборка, страховка - позиция корзины, которая не товар