Интеграция кэшбэк-системы с 1С 1С-Битрикс
Сеть магазинов работает в двух средах: офлайн-продажи через 1С:Розница и интернет-магазин на 1С-Битрикс. Клиент совершает покупку в физическом магазине — начисление кэшбэка должно появиться в личном кабинете на сайте в течение нескольких минут. И наоборот: списание кэшбэка онлайн должно учитываться при следующем визите в магазин. Без синхронизации балансов между системами программа лояльности фактически работает раздельно для каждого канала.
Архитектура двусторонней синхронизации
Выбор мастер-системы — первый архитектурный вопрос. Варианты:
Вариант А: 1С — мастер. Битрикс хранит только кешированный баланс, все операции записываются в 1С, Битрикс регулярно получает актуальный баланс. Проще поддерживать консистентность, но при недоступности 1С онлайн-списание невозможно.
Вариант Б: Битрикс — мастер. 1С получает баланс из Битрикс через API. Операции из 1С отправляются в Битрикс-очередь. Уязвимость — офлайн-касса не может списать кэшбэк при недоступности сети.
Вариант В: синхронный обмен с очередью. Каждая система записывает операции в свою очередь, фоновый агент синхронизирует. Самый надёжный, но требует механизма разрешения конфликтов при одновременных операциях.
Для большинства проектов подходит Вариант А с кешированием баланса на стороне Битрикс.
API на стороне Битрикс
Создаём REST-endpoint в Битрикс для получения и отправки операций из 1С:
// /local/api/cashback/v1/
// Маршрутизация через urlrewrite.php или отдельный файл
class CashbackApiController
{
/**
* GET /local/api/cashback/v1/balance?user_phone=79001234567
* Используется 1С для проверки баланса на кассе
*/
public function getBalance(): void
{
$this->requireApiKey();
$phone = $_GET['user_phone'] ?? '';
$userId = $this->getUserIdByPhone($phone);
if (!$userId) {
$this->respond(['error' => 'user_not_found'], 404);
return;
}
$balance = CashbackBalanceTable::getBalance($userId);
$this->respond([
'user_id' => $userId,
'balance' => $balance,
'updated_at' => CashbackBalanceTable::getLastUpdated($userId),
]);
}
/**
* POST /local/api/cashback/v1/transactions
* 1С отправляет операции (начисление/списание при офлайн-покупке)
*/
public function addTransaction(): void
{
$this->requireApiKey();
$body = json_decode(file_get_contents('php://input'), true);
$this->validateTransaction($body); // type, amount, external_id, user_phone
// Идемпотентность: external_id уникален на стороне 1С
if (CashbackTransactionTable::existsByExternalId($body['external_id'])) {
$this->respond(['status' => 'already_exists', 'idempotent' => true]);
return;
}
$userId = $this->getUserIdByPhone($body['user_phone']);
\Bitrix\Main\Application::getConnection()->startTransaction();
try {
CashbackTransactionTable::add([
'USER_ID' => $userId,
'TYPE' => $body['type'], // accrual|debit
'AMOUNT' => $body['amount'],
'DESCRIPTION' => $body['description'] ?? '',
'EXTERNAL_ID' => $body['external_id'], // ID документа в 1С
'SOURCE' => '1c_retail',
'CREATED_AT' => new \Bitrix\Main\Type\DateTime($body['created_at']),
]);
if ($body['type'] === 'accrual') {
CashbackBalanceTable::credit($userId, $body['amount']);
} else {
CashbackBalanceTable::debit($userId, $body['amount']);
}
\Bitrix\Main\Application::getConnection()->commitTransaction();
$this->respond(['status' => 'ok']);
} catch (\Exception $e) {
\Bitrix\Main\Application::getConnection()->rollbackTransaction();
$this->respond(['error' => $e->getMessage()], 500);
}
}
}
Внешний ID и идемпотентность
Поле EXTERNAL_ID в таблице транзакций — уникальный идентификатор документа в 1С. 1С формирует его как {ТипОперации}_{НомерДокумента}_{Дата}. При повторной отправке одного и того же документа (сетевой сбой, повтор запроса) Битрикс отвечает already_exists без повторного зачисления — это защищает от дублей баланса.
Обработчик на стороне 1С
В 1С:Розница или 1С:Управление торговлей создаётся внешняя обработка или расширение, которое:
- При проведении чека начисления — отправляет POST на Битрикс API
- При списании кэшбэка на кассе — предварительно запрашивает баланс (
GET /balance), затем POST с операциейdebit - При отмене чека — отправляет операцию
release(отмена списания) или отрицательное начисление
Пример HTTP-запроса из 1С (встроенный HTTP-клиент):
Запрос = Новый HTTPЗапрос("/local/api/cashback/v1/transactions");
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.Заголовки.Вставить("X-API-Key", Константы.КешбекAPIКлюч.Получить());
Запрос.УстановитьТелоИзСтроки(ЗаписатьJSON(ТелоЗапроса));
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Синхронизация при офлайн-работе кассы
Касса может работать без сети. В этом случае операции накапливаются в локальной базе 1С и отправляются пакетом при восстановлении соединения. Битрикс API принимает массив транзакций через POST /transactions/batch. Каждая транзакция обрабатывается независимо; в ответе — массив с результатом для каждой (успех/ошибка/дубль).
Конфликт: пользователь онлайн списал 500 рублей, пока касса работала офлайн. Офлайн-касса пыталась списать ещё 300, но баланс был 500. При синхронизации Битрикс обнаружит, что после первого списания остаток = 0, и отклонит офлайн-операцию с insufficient_balance. 1С должна обработать этот случай: аннулировать скидку или запросить доплату.
Поиск пользователя
Идентификация покупателя в офлайн — по номеру телефона. Поиск в Битрикс:
private function getUserIdByPhone(string $phone): ?int
{
$phone = preg_replace('/\D/', '', $phone);
$result = \Bitrix\Main\UserTable::getList([
'filter' => ['PERSONAL_PHONE' => $phone],
'select' => ['ID'],
'limit' => 1,
]);
if ($row = $result->fetch()) {
return (int)$row['ID'];
}
// Поиск по дополнительным полям UF_PHONE_VERIFIED
$result = \Bitrix\Main\UserTable::getList([
'filter' => ['UF_PHONE_VERIFIED' => $phone],
'select' => ['ID'],
'limit' => 1,
]);
return ($row = $result->fetch()) ? (int)$row['ID'] : null;
}
Телефоны хранятся в разных форматах — нормализация до 11 цифр (без +, ведущая 7 или 8) обязательна на входе.
Отображение офлайн-операций в личном кабинете
Транзакции с SOURCE = '1c_retail' отображаются в истории с меткой «Покупка в магазине» вместо ссылки на онлайн-заказ. В DESCRIPTION 1С передаёт адрес магазина или номер кассы — это показывается пользователю.
Мониторинг синхронизации
Таблица local_cashback_sync_log фиксирует все входящие запросы от 1С: время, external_id, статус ответа. Если в течение N часов не было операций от конкретного магазина — триггер уведомляет администратора (возможно, сбой в обработке на стороне 1С).
| Показатель | Ориентир |
|---|---|
| Время попадания офлайн-операции в Битрикс | < 5 минут при онлайн-работе кассы |
| Задержка при пакетной синхронизации после офлайн | < 10 минут от восстановления связи |
| Дублирование транзакций | 0 (идемпотентность через external_id) |
Состав работ
- REST API на стороне Битрикс: balance, transactions, batch
- Таблицы транзакций с
EXTERNAL_IDиSOURCE - Логика идемпотентности, обработка конфликтов при недостаточном балансе
- Нормализация телефонов, поиск пользователей
- Внешняя обработка 1С (согласовывается с программистом 1С)
- Мониторинг синхронизации, оповещения при сбоях
Сроки: 4–6 недель при наличии программиста 1С на проекте. 6–10 недель если требуется разработка обработчика 1С с нуля.







