Integrating 1C-Bitrix with POS Terminals
An online store accepts payment online, while at pickup points payment goes through a POS terminal. Orders from the online store reach 1C, but the payment status on the terminal and in Bitrix diverge: the cashier sees "paid", the manager in the CRM sees "awaiting payment". On top of that, Federal Law No. 54-FZ requires a fiscal receipt to be issued for POS payments. The integration solves three tasks simultaneously: synchronizing payment status, issuing receipts, and updating stock levels.
POS Hardware Options and Protocols
Working with POS in Bitrix depends on the specific hardware and its API:
-
Evotor — REST API, a ready-made module is available on the Bitrix Marketplace (
evotor.kassa) -
Atol Online — REST API for fiscalization, integration via the
atol.onlinemodule or custom implementation - MTS Kassa, Lightbox — REST API, custom integration
- Ingenico, VeriFone — no public HTTP API, integration via middleware (1C or a separate service)
For built-in POS systems with a REST-level API, integration is done directly from Bitrix. For terminals without a direct API — via an intermediary, typically 1C:Retail or a separate Windows service.
Integration Scheme via Evotor
Evotor is the most common case for Russian retail. The scheme:
[Bitrix] <---> [Evotor API] <---> [Evotor Smart Terminal]
- An order in Bitrix transitions to the "Awaiting in-store payment" status
- The cashier opens the order on the terminal (or it is pushed automatically)
- The customer pays at the POS
- Evotor records the transaction and sends a webhook to Bitrix
- Bitrix moves the order to "Paid" and creates a payment record
Payment System Handler in Bitrix
POS integration is implemented as a custom payment system:
// /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
{
// Called when POS payment is initiated
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();
// Send the order to Evotor
$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;
}
// Save transaction_id for subsequent verification
$payment->setField('PS_INVOICE_ID', $response['transaction_id']);
return $result;
}
// Webhook: Evotor reports a successful payment
public function processRequest(
\Bitrix\Sale\Payment $payment,
Request $request
): ServiceResult {
$result = new ServiceResult();
$data = json_decode($request->getInput(), true);
// Verify the Evotor signature
$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% VAT
];
}
return $items;
}
}
Fiscalization: Federal Law No. 54-FZ
A fiscal receipt is mandatory for POS payments. Three options:
- POS with a built-in fiscal drive (FN) (Evotor, MTS Kassa) — the receipt is printed directly at the terminal upon payment
- POS without FN + online cash register — the terminal records the payment, Bitrix sends the data to a cloud cash register (Atol Online, OFD.ru)
- Integration via 1C — 1C:Retail handles fiscalization, Bitrix receives the status via the data exchange
If option 2 is used, Bitrix needs a handler for the OnSaleOrderPaid event:
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale', 'OnSaleOrderPaid',
function (\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
$payment = $event->getParameter('PAYMENT');
// Check that this is a POS payment (not online)
$paySystemId = $payment->getPaymentSystemId();
if ($paySystemId !== POS_PAYSYSTEM_ID) {
return;
}
// Send to Atol Online
AtolOnlineService::sendReceipt($order, $payment, 'sell');
}
);
Stock Level Synchronization After a POS Sale
When a product is sold through a POS terminal from the store floor (not against an online order), the stock level must also be deducted in Bitrix:
// Deduct stock at the warehouse on POS sale
\Bitrix\Catalog\StoreProductTable::decreaseProductQuantity(
$productId,
$storeId,
$quantity
);
// Recalculate available quantity in the catalog
CCatalogProduct::getProductData($productId, ['QUANTITY' => true]);
In practice, stock synchronization for POS sales is the most painful part: the terminal may work offline, transactions accumulate and arrive in batches. An operation queue and idempotent processing are required: if the same transaction arrives twice (webhook retry), stock must not be deducted again.
Webhook Handler Idempotency
// Check whether this transaction_id has already been processed
$existing = \Bitrix\Sale\PaySystem\Manager::getList([
'filter' => ['PS_INVOICE_ID' => $transactionId],
'select' => ['ID'],
])->fetch();
if ($existing) {
// Already processed — return 200 without repeating the action
return (new ServiceResult())->setOperationType(ServiceResult::MONEY_COMING);
}
Development Timeline
| Phase | Content | Duration |
|---|---|---|
| POS hardware and API analysis | Documentation, terminal test stand | 1–2 days |
| Payment handler development | Module in /local/, webhook endpoint | 3–5 days |
| Fiscalization (if not built-in) | Integration with Atol Online or equivalent | 2–3 days |
| Stock synchronization | Deduction via StoreProductTable | 1–2 days |
| Testing on a real terminal | Payment, cancellation, receipt, stock | 2–3 days |







