Классическая ситуация: правим цены в админке, 1С выгрузила свежие остатки, а на витрине покупатель видит старую цену и запас, которого уже нет. Добавляет в корзину - а там сюрприз.
Штатный способ обновления - перезагрузить страницу. Но в 2026 году перезагружать страницу ради изменения пары цифр - это как гвозди микроскопом забивать.
Решение - Bitrix Push & Pull (модуль pull). Я сам недавно внедрил на проекте, где 1С гоняет цены каждые 15 минут. Проект для b2b, товары списком, что-то типа дашборда для закупщиков - постоянно держат страницу открытой что бы мониторить цены и остатки. Раньше люди видели актуальное только после F5, теперь - сразу.
Как это работает
СЕРВЕР (PHP) --[Event::add]--> PUSH-СЕРВЕР (Node.js, порт 8893) --[WebSocket]--> БРАУЗЕР (JS)
- При изменении товара (цена/остаток) PHP отправляет событие через
\Bitrix\Pull\Event::add() - Push-сервер ретранслирует его всем подписанным клиентам по WebSocket
- JS в браузере ловит событие и обновляет DOM
Никаких AJAX-опросов (ajax pulling) каждые 5 секунд. Только реальные изменения по факту.
Серверная часть: отправляем события
Подключаем модули и пишем функции отправки:
use Bitrix\Main\Loader;
use Bitrix\Pull\Event;
Loader::includeModule('pull');
Loader::includeModule('catalog');
/**
* Отправка события об изменении цены товара
*/
function sendPriceUpdateEvent(int $productId, float $newPrice, string $currency = 'RUB'): void
{
Event::add(
'catalog_price_update',
'onPriceChanged',
[
'productId' => $productId,
'price' => $newPrice,
'currency' => $currency,
'timestamp' => time(),
]
);
}
/**
* Отправка события об изменении остатка
*/
function sendStockUpdateEvent(int $productId, int $newQuantity): void
{
Event::add(
'catalog_stock_update',
'onStockChanged',
[
'productId' => $productId,
'quantity' => $newQuantity,
'timestamp' => time(),
]
);
}
Дальше вешаем эти функции на D7-модели каталога. Добавляем в init.php:
use Bitrix\Main\EventManager;
$eventManager = EventManager::getInstance();
// При изменении цены через Bitrix\Catalog\Model\Price
$eventManager->addEventHandler('catalog', 'Bitrix\Catalog\Model\Price::OnAfterUpdate', function($event) {
$id = $event->getParameter('id');
$data = $event->getParameter('fields');
if ($id > 0) {
sendPriceUpdateEvent(
(int)$data['PRODUCT_ID'],
(float)$data['PRICE'],
(string)$data['CURRENCY']
);
}
});
// При изменении товара (остатки, доступность)
$eventManager->addEventHandler('catalog', 'Bitrix\Catalog\Model\Product::OnAfterUpdate', function($event) {
$id = $event->getParameter('id');
$data = $event->getParameter('fields');
if ($id > 0 && array_key_exists('QUANTITY', $data)) {
sendStockUpdateEvent((int)$id, (int)$data['QUANTITY']);
}
});
Клиентская часть: ловим в браузере
В шаблоне каталога или в script.js компонента подписываемся на каналы:
BX.ready(function() {
// Подписка на изменение цен
BX.PULL.subscribe({
moduleId: 'catalog_price_update',
command: 'onPriceChanged',
callback: function(params) {
var priceElement = document.querySelector(
'[data-product-id="' + params.productId + '"] .product-price'
);
if (priceElement) {
priceElement.textContent = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: params.currency || 'RUB'
}).format(params.price);
// Подсветка изменения
priceElement.classList.add('price-updated');
setTimeout(function() {
priceElement.classList.remove('price-updated');
}, 2000);
}
}
});
// Подписка на изменение остатков
BX.PULL.subscribe({
moduleId: 'catalog_stock_update',
command: 'onStockChanged',
callback: function(params) {
var stockElement = document.querySelector(
'[data-product-id="' + params.productId + '"] .product-stock'
);
if (stockElement) {
stockElement.textContent = params.quantity > 0
? 'В наличии: ' + params.quantity + ' шт.'
: 'Нет в наличии';
stockElement.className = 'product-stock ' +
(params.quantity > 0 ? 'in-stock' : 'out-of-stock');
}
}
});
});
Чтобы JS находил элементы, в шаблоне компонента добавляем data-атрибуты:
<div class="product-card" data-product-id="<?= $arItem['ID'] ?>">
<span class="product-price"><?= $arItem['PRICE'] ?> руб.</span>
<span class="product-stock in-stock">В наличии: <?= $arItem['QUANTITY'] ?> шт.</span>
</div>
И немного CSS для анимации:
.price-updated {
animation: priceFlash 0.6s ease-in-out 3;
}
@keyframes priceFlash {
0%, 100% { background-color: transparent; }
50% { background-color: #fff3cd; }
}
.product-stock.in-stock {
color: #28a745;
}
.product-stock.out-of-stock {
color: #dc3545;
}
Продвинутый сценарий: перерисовка карточки целиком
Если нужно обновить не только цену, а всю карточку (картинка, описание, свойства) - делаем AJAX-запрос при получении Pull-события:
BX.PULL.subscribe({
moduleId: 'catalog_price_update',
command: 'onPriceChanged',
callback: function(params) {
BX.ajax.runComponentAction('bitrix:catalog', 'getProductData', {
mode: 'class',
data: { productId: params.productId }
}).then(function(response) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (card) {
card.querySelector('.product-price').textContent = response.data.price + ' руб.';
card.querySelector('.product-stock').textContent =
'В наличии: ' + response.data.quantity + ' шт.';
}
});
}
});
Проверка что Push-сервер работает
Без запущенного Push-сервера события будут писаться в базу, но до клиента не дойдут. Поднять его можно двумя способами:
- Штатное веб-окружение Битрикс - в нём Push-сервер уже предустановлен и настроен, нужно только проверить что запущен.
- Вручную на любом сервере - как описано в заметке про установку CRM Битрикс на Ubuntu, там подробно разобрана настройка Push-сервера.
Базовая проверка:
# Проверить статус
systemctl status push-server
# Запустить если надо
/opt/bitrix/php/bin/php /bitrix/modules/pull/push_server/push-server.php start
# Проверить порт
netstat -tlnp | grep 8893
Из PHP можно проверить доступность:
function isPushServerAvailable(): bool
{
$config = \Bitrix\Pull\Config::get();
return !empty($config['server']);
}
Важные моменты реализации
- Без запущенного Push-сервера ничего не работает. Event::add() сохранит событие в БД, но WebSocket-соединения нет - доставки не будет.
- Композитный кеш (Composite). Если сайт на композите - обновление DOM через JS может конфликтовать с кешированным HTML. Цены и остатки лучше вынести в динамические области.
- Доставка не гарантируется. При нестабильном соединении пакет может не дойти. Для критичных операций (оформление заказа) всегда делайте серверную проверку.
- Pull не создаёт подписки автоматом. Каталог регистрируется в Pull через событие onGetDependentModule, но подписки на каналы нужно писать руками.
Куда что класть: карта файлов для штатного каталога
Пути ниже - для стандартного компонента bitrix:catalog с шаблоном .default. У себя замените название шаблона и компонента на свои.
- Функции
sendPriceUpdateEvent(),sendStockUpdateEvent(),isPushServerAvailable()- в/bitrix/php_interface/init.phpили/local/php_interface/init.php. Добавить в конец файла. Либо вынести в отдельный/local/php_interface/pull_events.phpи подключить черезrequire_onceв init.php - так код чище. - Регистрация обработчиков событий через EventManager - туда же, в
init.php. Добавить после функций, порядок не важен. - JS-код подписки на Pull - в
/bitrix/templates/.default/components/bitrix/catalog/.default/script.js. Заменить.defaultна название своего шаблона и компонента. Либо разместить вtemplate.phpвнутри<script>-тега. - data-атрибут
data-product-id- в/bitrix/templates/.default/components/bitrix/catalog/.default/template.phpилиcomponent_epilog.php. Добавить к контейнеру товара:<div class="product-card" data-product-id="<?= $arItem['ID'] ?>"> - CSS для анимации
price-updated- в/bitrix/templates/.default/components/bitrix/catalog/.default/style.css. Можно добавить и в общийtemplate_styles.cssшаблона - тоже сработает.
Для других компонентов вывода (catalog.section, catalog.element, news.list и т.д.): принцип тот же - JS в script.js компонента, data-атрибуты в template.php, стили в style.css.
Проверка после установки:
- Открыть страницу каталога на витрине
- В консоли браузера (F12) проверить что
BX.PULLопределён - значит модуль подключен - В админке изменить цену любого товара
- На витрине цена должна моргнуть жёлтым и обновиться без перезагрузки
- Если не работает - проверить запущен ли Push-сервер (см. раздел выше)
Собрать обновление цен и остатков в реальном времени на Bitrix Push & Pull - час работы, если всё уже настроено. Задача не сложная, но результат даёт вау-эффект: цены меняются на глазах у покупателя, никаких F5, никаких просроченных остатков в корзине.
Где это можно использовать и зачем
- Акций и распродаж (цена меняется автоматически по правилам корзины)
- 1С-обмена (выгрузили новые остатки - они сразу на витрине)
- Мультискладского учёта (резервирование на одном складе видно на всех)
- Аукционов и динамического ценообразования
Модуль pull есть в любой редакции Битрикс начиная с "Малый бизнес". Единственное что Push-сервер нужно поднимать отдельно. Но если сайт уже использует живую ленту или мессенджер - он уже работает, просто подпишитесь на свои каналы.