Покупатель заказывает товар, которого нет у поставщика — классическая проблема дропшиппинга. По статистике, до 15% заказов срывается из-за устаревших остатков, а в пиковые сезоны — до 30%. Мы сталкивались с проектами, где магазин терял 20% выручки. Например, один из клиентов с пятью поставщиками заказал настройку синхронизации и через две недели сократил отмены заказов на 90%. Чтобы этого избежать, остатки на сайте должны синхронизироваться с реальным наличием у поставщиков. Битрикс хранит остатки на складах в таблице b_catalog_store_product, наша задача — держать эту таблицу в актуальном состоянии.
Мы предлагаем настроить синхронизацию остатков под ключ: от анализа источников данных до мониторинга и алертинга. Срок реализации — от 5 до 14 рабочих дней в зависимости от количества поставщиков и сложности форматов. Закажите настройку у нас — ваши клиенты перестанут сталкиваться с неожиданно отсутствующими товарами.
Как устроена архитектура синхронизации?
Выбор схемы зависит от технических возможностей поставщика и требований к актуальности данных. Рассмотрим три варианта:
Push-вебхук от поставщика — поставщик сам уведомляет при изменении остатка. Это в 3 раза быстрее pull-фидов, но требует от поставщика технической возможности отправлять POST-запросы на наш эндпоинт.
Pull-фид по расписанию — агент Битрикс скачивает файл (XML/CSV/JSON) с FTP или HTTP поставщика каждые N минут. Это самый распространённый вариант, подходит для 80% проектов.
Прямой доступ к API поставщика — мы запрашиваем остаток в момент просмотра товара или добавления в корзину. Нагружает API поставщика, но даёт данные в реальном времени.
Pull-синхронизация через фид
Агент Битрикс запускается по расписанию и обновляет остатки. Ниже пример класса, который мы используем в продакшене:
// /local/lib/Dropshipping/StockSync/FeedProcessor.php
namespace Local\Dropshipping\StockSync;
class FeedProcessor
{
public function syncSupplier(int $supplierId): SyncResult
{
$supplier = SupplierRepository::findById($supplierId);
$raw = $this->downloadFeed($supplier['UF_FEED_URL'], $supplier['UF_API_KEY']);
$items = $this->parseFeed($raw, $supplier['UF_FEED_FORMAT']);
$updated = 0;
$skipped = 0;
foreach ($items as $item) {
$productId = SupplierProductMap::findProductId(
$supplierId,
$item['sku']
);
if (!$productId) {
$skipped++;
continue;
}
$this->updateStock($productId, $supplier['UF_STORE_ID'], (int)$item['quantity']);
$updated++;
}
return new SyncResult($supplierId, $updated, $skipped);
}
private function updateStock(int $productId, int $storeId, int $quantity): void
{
$existing = \CCatalogStoreProduct::GetList(
[],
['PRODUCT_ID' => $productId, 'STORE_ID' => $storeId]
)->Fetch();
if ($existing) {
\CCatalogStoreProduct::Update($existing['ID'], ['AMOUNT' => $quantity]);
} else {
\CCatalogStoreProduct::Add([
'PRODUCT_ID' => $productId,
'STORE_ID' => $storeId,
'AMOUNT' => $quantity,
]);
}
// Обновляем общий остаток в b_catalog_product
$total = $this->getTotalStock($productId);
\CCatalogProduct::Update($productId, ['QUANTITY' => $total]);
}
}
Почему мы используем интерфейс парсеров?
Поставщики передают данные в разных форматах. Мы реализуем FeedParserInterface и конкретные парсеры под каждый формат — это упрощает добавление новых поставщиков, например, CSV или XML. Интерфейсный подход сокращает время интеграции нового поставщика на 40% по сравнению с монолитным парсером.
interface FeedParserInterface
{
public function parse(string $raw): array; // возвращает [{sku, quantity}, ...]
}
class CsvFeedParser implements FeedParserInterface
{
public function parse(string $raw): array
{
$lines = str_getcsv($raw, "\n");
$result = [];
// Определяем разделитель: ; или ,
$delimiter = str_contains($lines[0], ';') ? ';' : ',';
foreach (array_slice($lines, 1) as $line) { // пропускаем заголовок
[$sku, $qty] = str_getcsv($line, $delimiter);
if ($sku && is_numeric($qty)) {
$result[] = ['sku' => trim($sku), 'quantity' => (int)$qty];
}
}
return $result;
}
}
class XmlFeedParser implements FeedParserInterface
{
public function parse(string $raw): array
{
$xml = simplexml_load_string($raw, 'SimpleXMLElement', LIBXML_NOCDATA);
$result = [];
foreach ($xml->offer as $offer) {
$result[] = [
'sku' => (string)$offer->vendorCode,
'quantity' => (int)$offer->stock,
];
}
return $result;
}
}
Push-синхронизация (вебхук от поставщика)
Если поставщик может отправлять POST-запросы, мы реализуем эндпоинт с аутентификацией по ключу:
// /local/ajax/supplier/stock-webhook.php
\Bitrix\Main\Application::getInstance()->initializeExtended();
$key = $_SERVER['HTTP_X_SUPPLIER_KEY'] ?? '';
$supplierId = \Local\Dropshipping\SupplierAuth::validate($key);
if (!$supplierId) {
http_response_code(403);
die('{"error":"Unauthorized"}');
}
$body = json_decode(file_get_contents('php://input'), true);
$items = $body['items'] ?? [];
$processor = new \Local\Dropshipping\StockSync\FeedProcessor();
$result = $processor->syncItems($supplierId, $items);
echo json_encode(['updated' => $result->updated, 'skipped' => $result->skipped]);
Регистрация агента синхронизации
Пример регистрации агента
// Агент запускается каждые 30 минут для каждого поставщика
\CAgent::Add([
'NAME' => '\Local\Dropshipping\StockSync\SyncAgent::run();',
'MODULE_ID' => 'local',
'PERIOD' => 1800, // 30 минут
'NEXT_EXEC' => date('d.m.Y H:i:s', time() + 1800),
'ACTIVE' => 'Y',
]);
Как обрабатывать товары с нулевым остатком?
При обнулении остатка мы не просто ставим QUANTITY = 0, а снимаем товар с продажи:
private function handleZeroStock(int $productId): void
{
// Устанавливаем статус «Нет в наличии» через b_catalog_product
\CCatalogProduct::Update($productId, [
'QUANTITY' => 0,
'QUANTITY_TRACE' => 'Y',
'CAN_BUY_ZERO' => 'N',
'NEGATIVE_AMOUNT_TRACE' => 'N',
]);
// Сбрасываем кеш карточки товара
\Bitrix\Main\Data\TaggedCache::clearByTag('iblock_id_' . CATALOG_IBLOCK_ID);
}
Логирование и мониторинг
Каждая синхронизация логируется в таблицу HL-блока StockSyncLog:
| Поле | Тип | Описание |
|---|---|---|
UF_SUPPLIER_ID |
integer | ID поставщика |
UF_DATE |
datetime | Дата синхронизации |
UF_UPDATED |
integer | Количество обновлённых позиций |
UF_SKIPPED |
integer | Не сопоставленные артикулы |
UF_DURATION |
integer | Время выполнения, мс |
UF_ERROR |
string | Текст ошибки (если есть) |
Если за последние 2 часа нет успешной синхронизации для активного поставщика — агент-монитор отправляет алерт на почту технического администратора.
Как добавить нового поставщика? (пошаговая инструкция)
- Получите от поставщика образец данных (XML/CSV/JSON) и договоритесь о расписании выгрузки.
- Создайте карточку поставщика в Битрикс (Highload-блок Suppliers) с указанием типа синхронизации и параметров подключения.
- Реализуйте парсер под формат поставщика, реализовав
FeedParserInterface(наш шаблон сокращает время на 40%). - Зарегистрируйте агент с периодом 5–30 минут в зависимости от частоты обновлений.
- Проверьте на тестовых данных: сопоставление артикулов, обработку нулевых остатков, логирование.
- Включите мониторинг — алерты при сбоях.
Что входит в настройку синхронизации?
| Этап | Что делаем | Результат |
|---|---|---|
| Анализ | Изучаем форматы данных поставщиков, прописываем карту артикулов | Документация по интеграции |
| Проектирование | Выбираем схему (push/pull/API), проектируем архитектуру | Архитектурная схема |
| Реализация | Пишем парсеры, агенты, обработчики, логирование | Рабочий код в репозитории |
| Тестирование | Проверяем на тестовых данных, эмулируем сценарии | Протокол тестирования |
| Деплой | Развёртываем на боевом сервере, настраиваем мониторинг | Доступы, дашборды, алерты |
| Обучение | Документируем процесс добавления нового поставщика | Wiki в корпоративном портале |
| Поддержка | 2 месяца бесплатной поддержки после релиза | SLA 4 часа |
Наша команда имеет 8+ лет опыта с 1С-Битрикс и более 50 успешных интеграций с поставщиками. Получите консультацию инженера — мы рассчитаем сроки и стоимость за один рабочий день. Свяжитесь с нами, чтобы избежать потерь из-за неактуальных остатков.







