Інтеграція з АТОЛ Онлайн для 1С-Бітрікс
Вимога 54-ФЗ про онлайн-касу поширюється на більшість інтернет-магазинів: кожна безготівкова оплата від фізичної особи повинна супроводжуватися фіскальним чеком, переданим до ОФД. АТОЛ Онлайн — один із найбільших провайдерів хмарної фіскалізації в Росії. Інтеграція з 1С-Бітрікс технічно нетривіальна: потрібно правильно сформувати позиції чека, коректно вказати ставки ПДВ, ознаки предмета та способу розрахунку, обробити асинхронний callback від АТОЛ і врахувати кейси з поверненнями.
Схема роботи АТОЛ Онлайн
АТОЛ Онлайн — не фізична каса, а хмарний сервіс: ваші дані про покупку надходять до API АТОЛ, АТОЛ передає їх на віртуальну касу, яка формує фіскальний документ і надсилає його до ОФД (оператор фіскальних даних), а покупцеві надходить електронний чек на email або телефон.
Схема взаємодії:
- Покупець оплатив замовлення → у callback-обробнику вашого еквайрингу платіж підтверджено
- Магазин надсилає запит до АТОЛ API (
sell— чек приходу) - АТОЛ повертає
uuidзадачі (асинхронно) - Через кілька секунд АТОЛ надсилає webhook з результатом фіскалізації
- Магазин зберігає фіскальний документ (номер ФД, ФП) і позначає чек як «пробитий»
Реєстрація в АТОЛ і отримання credentials
Перед інтеграцією потрібні:
- Договір з АТОЛ і зареєстрована віртуальна каса
- login і password — для отримання токена API
- group_code — код групи кас
- inn — ІПН вашої організації (для чека)
- payment_address — адреса розрахунків (URL сайту)
Отримання токена
class AtolOnlineClient
{
private const API_URL = 'https://online.atol.ru/possystem/v5/';
private string $token = '';
public function auth(string $login, string $password): void
{
$response = $this->request('getToken', [
'login' => $login,
'pass' => $password,
], false);
if (empty($response['token'])) {
throw new \RuntimeException('АТОЛ: помилка авторизації — ' . ($response['error']['text'] ?? 'невідомо'));
}
$this->token = $response['token'];
// Кешуємо токен на 24 години (термін дії — 24 години)
\Bitrix\Main\Data\Cache::createInstance()->set('atol_token', $this->token, 86000);
}
private function request(string $endpoint, array $data, bool $withToken = true): array
{
$headers = ['Content-Type: application/json; charset=utf-8'];
if ($withToken) {
$headers[] = 'Token: ' . $this->token;
}
$ch = curl_init(self::API_URL . $endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data, JSON_UNESCAPED_UNICODE),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
return $result ?? [];
}
}
Формування чека приходу
public function sell(string $groupCode, \Bitrix\Sale\Order $order, string $callbackUrl): array
{
$basket = $order->getBasket();
$payment = $order->getPaymentCollection()->current();
$buyer = $order->getPropertyCollection();
$items = [];
foreach ($basket->getOrderableItems() as $item) {
$items[] = [
'name' => mb_substr($item->getField('NAME'), 0, 128), // макс 128 символів
'price' => round($item->getBasePrice(), 2),
'quantity' => $item->getQuantity(),
'sum' => round($item->getFinalPrice(), 2),
'measurement_unit' => 'шт',
'payment_method' => 'full_payment', // повна оплата
'payment_object' => 'commodity', // товар
'vat' => ['type' => 'none'], // без ПДВ; або 'vat10', 'vat20'
];
}
// Доставка як окрема позиція (обов'язково!)
$deliveryPrice = $order->getDeliveryPrice();
if ($deliveryPrice > 0) {
$items[] = [
'name' => 'Доставка',
'price' => round($deliveryPrice, 2),
'quantity' => 1,
'sum' => round($deliveryPrice, 2),
'measurement_unit' => 'посл',
'payment_method' => 'full_payment',
'payment_object' => 'service',
'vat' => ['type' => 'none'],
];
}
$receiptPayments = [];
// Визначаємо тип оплати для чека
$paySystemCode = $payment->getPaymentSystemId();
if (isCashPayment($paySystemCode)) {
$receiptPayments[] = ['type' => 0, 'sum' => $order->getPrice()]; // готівка
} else {
$receiptPayments[] = ['type' => 1, 'sum' => $order->getPrice()]; // безготівкові
}
$payload = [
'external_id' => 'BX_' . $order->getId() . '_' . time(),
'receipt' => [
'client' => [
'email' => $buyer->getItemByOrderPropertyCode('EMAIL')?->getValue(),
'phone' => $buyer->getItemByOrderPropertyCode('PHONE')?->getValue(),
],
'company' => [
'email' => COMPANY_EMAIL,
'inn' => COMPANY_INN,
'payment_address' => SITE_URL,
'sno' => 'usn_income', // система оподаткування
],
'items' => $items,
'payments' => $receiptPayments,
'total' => round($order->getPrice(), 2),
],
'service' => [
'callback_url' => $callbackUrl,
],
'timestamp' => date('d.m.Y H:i:s'),
];
return $this->request($groupCode . '/sell', $payload);
}
Типові помилки при формуванні чека
Розбіжність суми. receipt.total повинен точно збігатися із сумою payments[].sum. Дробові копійки при округленні — часта причина помилки invalid_total_amount.
Ознаки предмета розрахунку. payment_object = commodity для товарів, service для послуг (доставка, монтаж), payment для авансів. Неправильна ознака — адміністративна відповідальність.
Ознака способу розрахунку. payment_method = full_payment для повної оплати, prepayment для авансу, advance для передоплати. При двостадійному еквайрингу (холд + capture) перший чек з advance, другий — з full_payment.
Довжина найменування. АТОЛ обмежує name 128 символами. Довгі назви потрібно обрізати.
Обробка callback від АТОЛ
АТОЛ асинхронно надсилає POST на callback_url з результатом:
// /bitrix/tools/atol_callback.php
$body = file_get_contents('php://input');
$data = json_decode($body, true);
// Перевіряємо UUID задачі
$uuid = $data['uuid'] ?? '';
$status = $data['status'] ?? '';
$payload = $data['payload'] ?? [];
// Знаходимо замовлення за uuid
$atolTask = AtolTaskTable::getList([
'filter' => ['UUID' => $uuid],
])->fetch();
if (!$atolTask) {
http_response_code(200);
exit; // невідома задача — ігноруємо
}
if ($status === 'done' && !empty($payload['fiscal_document_number'])) {
// Чек успішно пробитий
AtolTaskTable::update($atolTask['ID'], [
'STATUS' => 'DONE',
'FISCAL_DOCUMENT_NUMBER' => $payload['fiscal_document_number'],
'FISCAL_DOCUMENT_ATTRIBUTE' => $payload['fiscal_document_attribute'],
'FNS_SITE' => $payload['fns_site'] ?? '',
'FN_NUMBER' => $payload['fn_number'] ?? '',
'SHIFT_NUMBER' => $payload['shift_number'] ?? '',
'RECEIPT_DATETIME' => $payload['receipt_datetime'] ?? '',
]);
} elseif ($status === 'fail') {
AtolTaskTable::update($atolTask['ID'], [
'STATUS' => 'FAIL',
'ERROR' => $data['error']['text'] ?? 'Невідома помилка',
'ERROR_CODE'=> $data['error']['code'] ?? 0,
]);
// Алерт адміністратору — чек не пробитий
notifyAdmin('Помилка АТОЛ', 'Замовлення ' . $atolTask['ORDER_ID'] . ': ' . $data['error']['text']);
}
http_response_code(200);
echo 'OK';
Чек повернення
При оформленні повернення покупцеві потрібно пробити чек повернення (sell_refund):
public function sellRefund(string $groupCode, \Bitrix\Sale\Order $order, float $refundAmount, array $refundItems): array
{
$payload = [
'external_id' => 'BX_REFUND_' . $order->getId() . '_' . time(),
'receipt' => [
'client' => ['email' => $buyerEmail],
'company' => ['inn' => COMPANY_INN, 'payment_address' => SITE_URL, 'sno' => 'usn_income'],
'items' => $refundItems, // позиції з повернення
'payments' => [['type' => 1, 'sum' => $refundAmount]],
'total' => $refundAmount,
],
'service' => ['callback_url' => ATOL_CALLBACK_URL],
'timestamp' => date('d.m.Y H:i:s'),
];
return $this->request($groupCode . '/sell_refund', $payload);
}
Інтеграція з обробником платіжної системи
Виклик АТОЛ вбудовується в подію підтвердження оплати:
// В події OnSalePaymentEntitySaved або в обробнику callback еквайрингу
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale', 'OnSalePaymentEntitySaved',
function(\Bitrix\Main\Event $event) {
$payment = $event->getParameter('ENTITY');
if ($payment->isPaid() && !$payment->isSystem()) {
$order = $payment->getOrder();
// Запускаємо фіскалізацію
$atol = new AtolOnlineService();
$atol->fiscalize($order);
}
}
);
Терміни
| Завдання | Термін |
|---|---|
| Базова інтеграція: чек приходу + callback | 2–3 дні |
| Чек повернення + коректні ознаки предмета розрахунку | 1–2 дні |
| Моніторинг: таблиця задач, алерти при помилках | 1 день |
| Тестування на тестовому середовищі АТОЛ | 1 день |
| Перемикання на бойове середовище та приймання | 0.5 дня |







