Integration of the cashback system with 1C 1C-Bitrix

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 the Cashback System with 1C in 1C-Bitrix

A retail chain operates in two environments: offline sales via 1C:Retail and an online store on 1C-Bitrix. A customer makes a purchase in a physical store — the cashback accrual must appear in their personal account on the website within minutes. Conversely, cashback redeemed online must be reflected on the next in-store visit. Without balance synchronization between systems, the loyalty program effectively operates as two separate programs, one per channel.

Bidirectional Synchronization Architecture

Choosing the master system is the first architectural question. Options:

Option A: 1C as master. 1C-Bitrix stores only a cached balance; all operations are written to 1C; 1C-Bitrix regularly fetches the current balance. Easier to maintain consistency, but online redemption is impossible when 1C is unavailable.

Option B: 1C-Bitrix as master. 1C fetches the balance from 1C-Bitrix via API. Operations from 1C are sent to a 1C-Bitrix queue. Vulnerability — an offline register cannot redeem cashback when the network is unavailable.

Option C: synchronous exchange with a queue. Each system writes operations to its own queue; a background agent synchronizes them. The most reliable, but requires a conflict resolution mechanism for simultaneous operations.

For most projects, Option A with balance caching on the 1C-Bitrix side is appropriate.

API on the 1C-Bitrix Side

Create a REST endpoint in 1C-Bitrix to receive and send operations from 1C:

// /local/api/cashback/v1/
// Routing via urlrewrite.php or a dedicated file

class CashbackApiController
{
    /**
     * GET /local/api/cashback/v1/balance?user_phone=79001234567
     * Used by 1C to check the balance at the register
     */
    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
     * 1C sends operations (accrual/deduction for offline purchases)
     */
    public function addTransaction(): void
    {
        $this->requireApiKey();
        $body = json_decode(file_get_contents('php://input'), true);

        $this->validateTransaction($body); // type, amount, external_id, user_phone

        // Idempotency: external_id is unique on the 1C side
        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'], // 1C document ID
                '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);
        }
    }
}

External ID and Idempotency

The EXTERNAL_ID field in the transaction table is the unique identifier of the document in 1C. 1C generates it as {OperationType}_{DocumentNumber}_{Date}. When the same document is sent again (network failure, request retry), 1C-Bitrix responds with already_exists without re-crediting — this protects against duplicate balances.

Handler on the 1C Side

In 1C:Retail or 1C:Trade Management, an external data processor or extension is created that:

  1. When processing a receipt for accrual — sends a POST to the 1C-Bitrix API
  2. When redeeming cashback at the register — first requests the balance (GET /balance), then POSTs an operation of type debit
  3. When voiding a receipt — sends a release operation (cancellation of deduction) or a negative accrual

Example HTTP request from 1C (built-in HTTP client):

Запрос = Новый HTTPЗапрос("/local/api/cashback/v1/transactions");
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.Заголовки.Вставить("X-API-Key", Константы.КешбекAPIКлюч.Получить());
Запрос.УстановитьТелоИзСтроки(ЗаписатьJSON(ТелоЗапроса));
Ответ = Соединение.ОтправитьДляОбработки(Запрос);

Synchronization During Offline Register Operation

The register may operate without network connectivity. In this case, operations accumulate in the local 1C database and are sent as a batch when the connection is restored. The 1C-Bitrix API accepts an array of transactions via POST /transactions/batch. Each transaction is processed independently; the response contains an array with the result for each (success/error/duplicate).

Conflict scenario: the user redeemed 500 online while the register was offline. The offline register attempted to deduct another 300, but the balance was 500. When synchronizing, 1C-Bitrix will detect that after the first deduction the balance is 0 and will reject the offline operation with insufficient_balance. 1C must handle this case: void the discount or request an additional payment.

User Lookup

Customer identification offline — by phone number. Lookup in 1C-Bitrix:

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'];
    }

    // Search in additional field 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;
}

Phone numbers are stored in various formats — normalization to 11 digits (without +, leading 7 or 8) is mandatory on input.

Displaying Offline Operations in the Personal Account

Transactions with SOURCE = '1c_retail' are displayed in the history with the label "In-store purchase" instead of a link to an online order. The DESCRIPTION field receives the store address or register number from 1C — this is shown to the user.

Synchronization Monitoring

The local_cashback_sync_log table records all incoming requests from 1C: timestamp, external_id, response status. If no operations are received from a specific store within N hours — a trigger notifies the administrator (possibly a processing failure on the 1C side).

Metric Target
Time for an offline operation to appear in 1C-Bitrix < 5 minutes with online register
Delay for batch synchronization after offline period < 10 minutes after connection is restored
Transaction duplication 0 (idempotency via external_id)

Scope of Work

  • REST API on the 1C-Bitrix side: balance, transactions, batch
  • Transaction tables with EXTERNAL_ID and SOURCE
  • Idempotency logic, conflict handling for insufficient balance
  • Phone normalization, user lookup
  • 1C external data processor (coordinated with the 1C developer)
  • Synchronization monitoring, failure alerts

Timeline: 4–6 weeks with a 1C developer on the project. 6–10 weeks if the 1C handler needs to be developed from scratch.