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:
- After a successful
POST, saveORANGEDATA_DOC_IDandORANGEDATA_STATUS = pendingtob_sale_order_props. - The
OrangeDataCheckAgentagent runs every minute, selects orders withORANGEDATA_STATUS = pending, and pollsGET /api/v2/documents/{id}. - When the status changes to
done— save the fiscal data (fn,fd,fpd, receipt link) and setORANGEDATA_STATUS = done. - The fiscal data is sent to the buyer by email via the
SALE_NEW_ORDERevent 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 |







