Інтеграція 1С-Бітрікс з POS-терміналами
Інтернет-магазин приймає оплату онлайн, а в точках самовивозу — через POS-термінал. Замовлення з онлайну потрапляють у 1С, але статус оплати на терміналі і в Бітрікс розходяться: касир бачить «оплачено», менеджер у CRM — «очікує оплати». Плюс 54-ФЗ вимагає, щоб при оплаті через POS видавався фіскальний чек. Інтеграція вирішує три завдання одночасно: синхронізацію статусу оплати, видачу чека і оновлення залишків.
Варіанти POS-обладнання та протоколи
Робота з POS у Бітрікс залежить від конкретного обладнання та його API:
-
Еватор — REST API, є готовий модуль на Marketplace Бітрікс (
evotor.kassa) -
Атол Онлайн — REST API для фіскалізації, інтеграція через модуль
atol.onlineабо кастомно - МТС Каса, Лайтбокс — REST API, кастомна інтеграція
- Ingenico, VeriFone — немає публічного HTTP API, інтеграція через middleware (1С або окремий сервіс)
Для вбудованих POS з API на рівні REST — інтеграція напряму з Бітрікс. Для терміналів без прямого API — через посередника, зазвичай 1С:Роздрібна торгівля або окремий Windows-сервіс.
Схема інтеграції через Еватор
Еватор — найбільш типовий випадок для роздрібної торгівлі. Схема:
[Бітрікс] <---> [Еватор API] <---> [Смарт-термінал Еватор]
- Замовлення в Бітрікс переходить у статус «Очікує оплати в магазині»
- Касир відкриває замовлення на терміналі (або воно передається автоматично)
- Покупець оплачує на POS
- Еватор фіксує транзакцію, надсилає webhook у Бітрікс
- Бітрікс переводить замовлення в «Оплачено», створює запис про оплату
Обробник платіжної системи в Бітрікс
Інтеграція з POS реалізується як кастомна платіжна система:
// /local/modules/custom.pos/install/handlers/pos.php
namespace Custom\Pos\Handler;
use Bitrix\Sale\PaySystem\ServiceHandler;
use Bitrix\Sale\PaySystem\ServiceResult;
use Bitrix\Main\Request;
use Bitrix\Sale\Order;
class PosPaymentHandler extends ServiceHandler
{
// Викликається при ініціації оплати через POS
public function initiatePay(
\Bitrix\Sale\Payment $payment,
Request $request = null
): ServiceResult {
$result = new ServiceResult();
$order = $payment->getOrder();
$orderId = $order->getId();
$amount = $payment->getSum();
$currency = $payment->getCurrencyCode();
// Передаємо замовлення в Еватор
$evotor = new EvoktorApiClient($this->service->getField('EVOTOR_TOKEN'));
$response = $evotor->createReceipt([
'order_id' => $orderId,
'total' => $amount,
'currency' => $currency,
'items' => $this->buildReceiptItems($order),
]);
if (!$response['success']) {
$result->addError(new \Bitrix\Main\Error($response['error']));
return $result;
}
// Зберігаємо transaction_id для подальшої перевірки
$payment->setField('PS_INVOICE_ID', $response['transaction_id']);
return $result;
}
// Webhook: Еватор повідомляє про успішну оплату
public function processRequest(
\Bitrix\Sale\Payment $payment,
Request $request
): ServiceResult {
$result = new ServiceResult();
$data = json_decode($request->getInput(), true);
// Верифікуємо підпис Еватора
$signature = $request->getHeader('X-Evotor-Signature');
if (!$this->verifySignature($data, $signature)) {
$result->addError(new \Bitrix\Main\Error('Invalid signature'));
return $result;
}
if ($data['status'] === 'PAID') {
$result->setOperationType(ServiceResult::MONEY_COMING);
$result->setPaid('Y');
}
return $result;
}
private function buildReceiptItems(Order $order): array
{
$items = [];
/** @var \Bitrix\Sale\Basket $basket */
$basket = $order->getBasket();
foreach ($basket as $basketItem) {
$items[] = [
'name' => $basketItem->getField('NAME'),
'quantity' => $basketItem->getQuantity(),
'price' => $basketItem->getPrice(),
'vat' => 'VAT20', // ПДВ 20%
];
}
return $items;
}
}
Фіскалізація: 54-ФЗ
При оплаті через POS обов'язковий касовий чек. Три варіанти:
- POS із вбудованим ФН (Еватор, МТС Каса) — чек друкується прямо на терміналі при оплаті
- POS без ФН + онлайн-каса — термінал фіксує факт оплати, Бітрікс надсилає дані до хмарної каси (Атол Онлайн, OFD.ru)
- Інтеграція через 1С — 1С:Роздрібна торгівля фіскалізує, Бітрікс отримує статус через обмін
Якщо використовується варіант 2, у Бітрікс потрібен обробник події OnSaleOrderPaid:
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale', 'OnSaleOrderPaid',
function (\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
$payment = $event->getParameter('PAYMENT');
// Перевіряємо, що це POS-оплата (не онлайн)
$paySystemId = $payment->getPaymentSystemId();
if ($paySystemId !== POS_PAYSYSTEM_ID) {
return;
}
// Надсилаємо в Атол Онлайн
AtolOnlineService::sendReceipt($order, $payment, 'sell');
}
);
Синхронізація залишків після POS-продажу
Коли через POS-термінал продається товар із магазину (не під онлайн-замовлення), залишки потрібно списати і в Бітрікс:
// Списання залишків на складі при POS-продажу
\Bitrix\Catalog\StoreProductTable::decreaseProductQuantity(
$productId,
$storeId,
$quantity
);
// Перерахунок доступної кількості в каталозі
CCatalogProduct::getProductData($productId, ['QUANTITY' => true]);
На практиці синхронізація залишків при POS-продажах — найболючіша частина: термінал може працювати офлайн, транзакції накопичуються і надходять пакетом. Потрібна черга операцій та ідемпотентна обробка: якщо одна і та ж транзакція прийшла двічі (повтор webhook), не списувати залишки повторно.
Ідемпотентність webhook-обробника
// Перевіряємо, чи не оброблено вже цей transaction_id
$existing = \Bitrix\Sale\PaySystem\Manager::getList([
'filter' => ['PS_INVOICE_ID' => $transactionId],
'select' => ['ID'],
])->fetch();
if ($existing) {
// Вже оброблено — повертаємо 200 без повторної дії
return (new ServiceResult())->setOperationType(ServiceResult::MONEY_COMING);
}
Терміни розробки
| Етап | Зміст | Термін |
|---|---|---|
| Аналіз POS-обладнання та API | Документація, тест-стенд терміналу | 1–2 дні |
| Розробка платіжного обробника | Модуль у /local/, webhook-ендпоінт | 3–5 днів |
| Фіскалізація (якщо немає вбудованої) | Інтеграція з Атол Онлайн або аналогом | 2–3 дні |
| Синхронізація залишків | Списання через StoreProductTable | 1–2 дні |
| Тестування на реальному терміналі | Оплата, скасування, чек, залишки | 2–3 дні |







