Integration of 1C-Bitrix with POS terminals

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.online module 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]
  1. An order in Bitrix transitions to the "Awaiting in-store payment" status
  2. The cashier opens the order on the terminal (or it is pushed automatically)
  3. The customer pays at the POS
  4. Evotor records the transaction and sends a webhook to Bitrix
  5. 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:

  1. POS with a built-in fiscal drive (FN) (Evotor, MTS Kassa) — the receipt is printed directly at the terminal upon payment
  2. POS without FN + online cash register — the terminal records the payment, Bitrix sends the data to a cloud cash register (Atol Online, OFD.ru)
  3. 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