Integration with OrangeData for 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
    1173
  • 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
    745
  • 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

OrangeData Integration for 1C-Bitrix

OrangeData is a cloud fiscal recorder that operates on a "cash register as a service" model. The store does not have a physical cash register: OrangeData owns and maintains the hardware, and you receive an API for sending receipts. This is convenient until you start getting into the details: X.509 certificate authentication, asynchronous fiscalisation, and a specific receipt attribute schema under Federal Law No. 54-FZ. There is no standard module in Bitrix — everything is implemented via a custom payment system handler.

How the OrangeData API Works

OrangeData uses a REST API with mutual TLS authentication. It is not simply a token in the header — you need a client X.509 certificate issued at registration. Every request to the API is signed with the private key from that certificate.

Main endpoints:

  • POST /api/v2/documents/ — submit a document (income receipt, refund, correction)
  • GET /api/v2/documents/{id} — retrieve the fiscalisation result by identifier

Important: fiscalisation is asynchronous. After a POST you receive 202 Accepted, not a ready receipt. The actual fiscal attribute appears after a few seconds or minutes — you need to poll the GET endpoint or wait for a callback.

Receipt Submission Request Structure

$document = [
    'id' => uniqid('', true), // unique document identifier
    'inn' => $inn,
    'group' => 'Main',
    'key' => $signatureKeyName, // key name from the OrangeData personal account
    'content' => [
        'type' => 1, // 1 - income, 2 - refund
        'positions' => $this->buildPositions($payment),
        'checkClose' => [
            'payments' => [
                [
                    'type' => $this->getPaymentType($payment), // 1-cash, 2-non-cash
                    'amount' => $payment->getSum(),
                ],
            ],
            'taxationSystem' => 0, // 0-General, 1-Simplified income, 2-Simplified income-expense
        ],
        'customerContact' => $this->getCustomerContact($order),
    ],
];

The positions field is an array of receipt line items. Each position contains quantity, price, tax (VAT rate code), text (name), paymentMethodType, and paymentSubjectType. The last two fields are a requirement of Federal Law No. 54-FZ since 2019: you must explicitly state what is being sold (goods, service, work) and by what method (prepayment, full settlement).

private function buildPositions(\Bitrix\Sale\Payment $payment): array
{
    $order = $payment->getOrder();
    $basket = $order->getBasket();
    $positions = [];

    foreach ($basket as $item) {
        $positions[] = [
            'quantity'           => $item->getQuantity(),
            'price'              => $item->getPrice(),
            'tax'                => $this->mapVatRate($item->getField('VAT_RATE')),
            'text'               => $item->getField('NAME'),
            'paymentMethodType'  => 4, // 4 - full settlement
            'paymentSubjectType' => 1, // 1 - goods
        ];
    }

    // Delivery as a separate line item
    if ($order->getDeliveryPrice() > 0) {
        $positions[] = [
            'quantity'           => 1,
            'price'              => $order->getDeliveryPrice(),
            'tax'                => 6, // no VAT
            'text'               => 'Delivery',
            'paymentMethodType'  => 4,
            'paymentSubjectType' => 4, // 4 - service
        ];
    }

    return $positions;
}

TLS Authentication: PHP Configuration

Sending requests with a client certificate via cURL:

private function apiRequest(string $method, string $path, array $data = []): array
{
    $ch = curl_init('https://api.orangedata.ru:12003' . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_POSTFIELDS     => json_encode($data),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_SSLCERT        => $this->certPath,       // path to .crt
        CURLOPT_SSLKEY         => $this->keyPath,        // path to .key
        CURLOPT_CAINFO         => $this->caPath,         // OrangeData root certificate
        CURLOPT_SSL_VERIFYPEER => true,
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode === 202) {
        return ['status' => 'queued'];
    }

    return json_decode($response, true);
}

Certificates and keys are stored outside the webroot — for example, in /local/certs/orangedata/. Paths are passed via payment system handler settings. Never place .key files inside public_html.

Asynchronous Fiscalisation: Polling via Bitrix Agents

After submitting a document, you need to wait for the fiscal attribute. Implementation via an agent:

  1. After a successful POST, save ORANGEDATA_DOC_ID and ORANGEDATA_STATUS = pending to b_sale_order_props.
  2. The OrangeDataCheckAgent agent runs every minute, selects orders with ORANGEDATA_STATUS = pending, and polls GET /api/v2/documents/{id}.
  3. When the status changes to done — save the fiscal data (fn, fd, fpd, receipt link) and set ORANGEDATA_STATUS = done.
  4. The fiscal data is sent to the buyer by email via the SALE_NEW_ORDER event or a separate letter.
public static function run(): string
{
    $pendingOrders = self::getPendingOrders();
    foreach ($pendingOrders as $orderId => $docId) {
        $result = self::checkDocumentStatus($docId);
        if (($result['status'] ?? '') === 'done') {
            self::saveFiscalData($orderId, $result);
        }
    }
    return __CLASS__ . '::run();';
}

Refunds

For a refund, a document with type: 2 (income refund) is submitted. The amount and line items must match the original (for a full refund) or contain only the returned items (for a partial refund). OrangeData does not verify the relationship with the original document by ID — this is your responsibility.

A refund is triggered from the OnSaleOrderRefund event handler or from the admin panel during manual refund processing.

Test Environment

OrangeData provides a test API: https://apip.orangedata.ru:12003. Test certificates are issued separately. In test mode, fiscalisation goes through a virtual cash register; there are no real fiscal drives. Switching between environments is done via the handler parameter USE_TEST_API.

Timeline

Scope Duration
Basic integration (income + refund) 4–5 days
+ Asynchronous polling + email with receipt +2 days
+ Partial refunds + VAT rate mapping +1 day