Корзина в Битрикс: разбор на примере пустого заказа
Пользователь добавляет товар, видит его в корзине, а после обновления страницы корзина пустеет. Разбираем цепочку Sale, FUSER и сессии на примере 1С-Битрикс и показываем, где искать причину.

Пользователь добавляет товар в корзину, получает успешный ответ AJAX, а после обновления страницы видит пустой список. В 1С-Битрикс такая ошибка обычно возникает не в кнопке «Добавить», а на стыке сессии, FUSER, сайта и сохранения сущности корзины. Ниже разберём этот сценарий на версии 1С-Битрикс: Управление сайтом 24.x и PHP 8.1+, покажем цепочку обработки, диагностический код и места, которые ломаются первыми.
Что именно хранит корзина в Битрикс
Корзина в D7 — это не один массив в сессии. Она связана с анонимным или авторизованным покупателем через идентификатор FUSER. Для гостя FUSER создаётся отдельно, а после авторизации корзины могут объединяться по правилам магазина.
Упрощённая цепочка выглядит так:
- Браузер отправляет запрос с cookie и сессионными данными.
- Битрикс определяет пользователя корзины — FUSER.
- Метод Sale загружает корзину этого FUSER для конкретного сайта.
- В корзине находятся позиции BasketItem: товар, количество, цена, скидка и служебные поля.
- При оформлении заказа позиции связываются с Order и проходят повторный пересчёт.
Если на шаге 2 получен другой FUSER, пользователь увидит другую корзину. Если на шаге 3 передан не тот SITE_ID, результат тоже будет выглядеть как пустая корзина, хотя записи в базе могут существовать.
FUSER — внутренний идентификатор покупателя корзины в 1С-Битрикс. Это не ID пользователя из таблицы пользователей: гость может иметь FUSER, даже если не зарегистрирован.
Главный вопрос при пустой корзине — не «добавился ли товар», а «тем же ли FUSER и для того ли сайта вы загружаете корзину после добавления».
Разбор сценария: товар добавился, но исчез после обновления
Представим интернет-магазин на домене shop.example. Кнопка отправляет AJAX-запрос на свой обработчик. Обработчик добавляет товар, возвращает JSON с успехом, но следующий запрос к странице каталога создаёт новый контекст корзины.
Шаг 1. Проверить, какой FUSER видит запрос
Начните не с базы данных, а с серверного лога. Важно записать FUSER, SITE_ID, идентификатор товара и результат сохранения. В диагностическом фрагменте ниже нет записи в корзину: он только читает текущую корзину.
<?php
use Bitrix\Main\Loader;
use Bitrix\Sale\Basket;
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
if (!Loader::includeModule('sale')) {
throw new RuntimeException('Модуль sale не подключён');
}
$siteId = SITE_ID;
$fuserId = (int) \CSaleBasket::GetBasketUserID();
$basket = Basket::loadItemsForFUser($fuserId, $siteId);
$items = [];
foreach ($basket as $basketItem) {
$items[] = [
'product_id' => $basketItem->getProductId(),
'quantity' => $basketItem->getQuantity(),
'price' => $basketItem->getPrice(),
'currency' => $basketItem->getCurrency(),
];
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'site_id' => $siteId,
'fuser_id' => $fuserId,
'items' => $items,
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
Проверка считается успешной, если после добавления товара повторный запрос возвращает тот же FUSER и содержит позицию с ожидаемым ID. Если FUSER меняется, ищите проблему в cookie, домене, протоколе или преждевременном завершении сессии.
Шаг 2. Не создавать FUSER вручную в каждом запросе
Типичная ошибка — обработчик добавления получает FUSER одним способом, а компонент корзины загружает его другим. Например, AJAX-запрос работает на одном домене, а страница открыта на другом поддомене. Браузер не передаёт прежнюю cookie, и Битрикс создаёт новый идентификатор.
Проверьте в браузере:
- совпадают ли домен и протокол страницы и AJAX-обработчика;
- не меняется ли путь cookie между запросами;
- не отправляется ли запрос без cookie из-за настроек клиента;
- не очищает ли прокси или CDN заголовки
Set-Cookie; - не выполняется ли добавление на одном сайте, а чтение на другом.
Для запроса из браузера важен не только PHP-код. Если фронтенд использует отдельный поддомен, проверьте фактические заголовки запроса и ответа во вкладке Network. Серверный лог с одним FUSER на добавлении и другим FUSER на чтении почти сразу подтверждает проблему контекста.
Шаг 3. Сохранить изменения и проверить результат
В D7 изменение объекта корзины само по себе не означает, что оно записано в базу. После создания или изменения позиции вызывайте сохранение корзины и проверяйте Result.
<?php
use Bitrix\Main\Loader;
use Bitrix\Sale\Basket;
if (!Loader::includeModule('sale')) {
throw new RuntimeException('Модуль sale не подключён');
}
$productId = 123;
$siteId = SITE_ID;
$fuserId = (int) \CSaleBasket::GetBasketUserID();
$basket = Basket::loadItemsForFUser($fuserId, $siteId);
$basketItem = $basket->createItem('catalog', $productId);
$basketItem->setFields([
'QUANTITY' => 1,
'LID' => $siteId,
]);
$saveResult = $basket->save();
if (!$saveResult->isSuccess()) {
throw new RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}
// После save() перечитываем корзину тем же FUSER и SITE_ID.
$reloadedBasket = Basket::loadItemsForFUser($fuserId, $siteId);
Этот фрагмент показывает саму механику D7, но не заменяет расчёт цены. В рабочем магазине стоимость должна приходить из каталога и обработчика цен, а не приниматься из браузера. Перед добавлением убедитесь, что для товара настроены цена, валюта и доступный провайдер каталога.
Где ломается первым
| Симптом | Вероятная причина | Что проверить |
|---|---|---|
| После AJAX всё хорошо, после F5 пусто | Разные cookie или FUSER | FUSER в логах двух запросов, домен и протокол |
| В админке позиция есть, на сайте её нет | Корзина загружается для другого SITE_ID | Значение SITE_ID и сайт, на котором создана позиция |
| Позиция создаётся, но заказ не оформляется | Нет корректной цены, валюты или данных товара | Ошибки результата save и настройки каталога |
| У гостя корзина работает, после входа исчезает | Не разобран перенос или объединение корзины при авторизации | FUSER до и после входа, обработчики авторизации |
| Количество возвращается к прежнему | Параллельные запросы или повторное чтение старого состояния | Порядок запросов, ответы AJAX и момент save() |
Цена ошибки с SITE_ID
В многосайтовой установке один и тот же товар может участвовать в разных витринах, а корзина загружается с привязкой к сайту. Нельзя считать, что «товар уже есть в базе» автоматически делает его видимым в любой корзине.
Зафиксируйте в логе связку fuser_id + site_id + product_id. По отдельности эти значения мало что дают. Вместе они позволяют сравнить запрос добавления, запрос обновления мини-корзины и запрос оформления.
Цена ошибки с ценой из браузера
Поле PRICE, пришедшее из JavaScript, нельзя считать доверенным. Пользователь может изменить его до отправки. Сервер должен сам определить товар, цену, валюту, скидки и доступное количество, а затем снова проверить их при создании заказа.
Отдельная тема — инвалидация корзины и пересчёт скидок. Здесь рассматриваем только сохранение позиции и восстановление правильного контекста; правила скидок требуют отдельной проверки на конкретной конфигурации магазина.
Что измерять перед исправлением
Без замеров разработчик часто исправляет не причину, а последний видимый симптом. Для одного проблемного сценария соберите последовательность:
- Открытие каталога: cookie, домен, SITE_ID.
- Запрос добавления: FUSER, ID товара, HTTP-код и тело ответа.
- Ответ обработчика: результат
save()и список ошибок. - Обновление мини-корзины: FUSER и количество позиций.
- Переход к оформлению: тот же FUSER, цена и доступность товара.
Сравнивать нужно один и тот же браузер, одну сессию и один товар. Если после отключения CDN проблема исчезает, это не доказывает вину CDN, но даёт направление: проверяйте кеширование HTML, заголовки cookie и маршрутизацию AJAX.
Граничные случаи
Гость и авторизованный пользователь могут иметь разные состояния корзины. После входа не рассчитывайте на автоматическое объединение без проверки конкретной версии и настроек магазина.
Второй случай — несколько витрин в одной установке. Передача константы или жёстко заданного идентификатора сайта в общем обработчике может незаметно направить запрос в другую корзину.
Третий — параллельные запросы. Двойной клик по кнопке или повтор AJAX после тайм-аута способен создать две позиции либо перезаписать количество. На фронтенде блокируйте повторную отправку до ответа, а на сервере не доверяйте только состоянию интерфейса.
Четвёртый — кеш. Кешированная страница может показывать старый счётчик, хотя серверная корзина уже изменилась. Для диагностики сравнивайте данные API и содержимое HTML, а не только цифру в шапке.
Что делать дальше
Сначала добавьте временное логирование FUSER, SITE_ID и результата save() в три точки: добавление, обновление корзины и оформление заказа. Затем воспроизведите проблему в гостевой сессии и после авторизации, отдельно на каждой витрине.
После исправления оставьте в мониторинге долю ошибок сохранения корзины, ответы AJAX с HTTP-кодами 4xx и 5xx, а также случаи, когда мини-корзина и страница корзины возвращают разное количество товаров. Это даст ранний сигнал, что проблема вернулась после обновления шаблона, PHP или конфигурации веб-сервера.
Разбор научной статьи: Сложный текст — понятным языком, с терминами
Спросить за 12 ₽Источники: Официальная документация 1С-Битрикс: класс Sale\Basket, Официальная документация 1С-Битрикс: D7, модуль sale
Частые вопросы
Почему корзина Битрикс пустая после обновления страницы?
Можно ли хранить корзину только в PHP-сессии?
Почему товар есть в базе, но не показывается в корзине?
Что будет, если пользователь войдёт в аккаунт после добавления товара?
Почему save() возвращает ошибку при добавлении товара?
Что проверять, если код из примера не работает на другой версии Битрикс?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.