API
СмИТ Биллинг 1.6 предоставляет несколько типов API для интеграции с внешними системами: платёжными шлюзами, личными кабинетами, 1С, системами мониторинга и другими сервисами.
https://your-billing-server.example.comАвторизация: Cookie-сессия (Django) или SOAP-хэш
Формат данных: JSON (REST), XML (SOAP)
Обзор API
| Тип | Протокол | URL | Аутентификация |
|---|---|---|---|
| REST API v2 | JSON over HTTP | /rest_api/v2/<model>/ | Cookie (session) |
| REST API v1 | JSON/XML | /rest_api/ | Cookie (session) |
| SOAP API | XML (Spyne) | /api/, /api/fiscal/ | SOAP-хэш |
| Mobile API | JSON over HTTP | /mobile-api/v1/* | JWT (Bearer token) |
| System API | JSON | /system_api/ | IP whitelist + пароль |
| Web Admin API | HTML/JSON | /admin/* | Cookie + CSRF |
Аутентификация
Содержание раздела
Логин через Web (получение сессии)
Двухшаговый процесс: получение CSRF-токена, затем POST с логином и паролем.
# Шаг 1: получить CSRF-токен
curl -s -c cookies.txt "https://billing.example.com/admin/" > /dev/null
# Шаг 2: логин
CSRF=$(grep csrftoken cookies.txt | awk '{print $NF}')
curl -s -c cookies.txt -b cookies.txt \
-X POST "https://billing.example.com/admin/" \
-d "username=admin&password=YOUR_PASSWORD&csrfmiddlewaretoken=$CSRF" \
-H "Referer: https://billing.example.com/admin/" \
-o /dev/null -w "%{http_code}"
# 302 = успешный вход
API-логин (JSON)
Альтернативный способ — получение сессионного хэша через GET-параметры:
curl -s "https://billing.example.com/admin/?api=1&username=admin&password=YOUR_PASSWORD&format=json"
# Ответ: {"hash": "session_hash_hex"}
# 401 = неверные учётные данные
Логаут
# Web-логаут
curl -s -b cookies.txt "https://billing.example.com/admin/logout"
# API-логаут
curl -s -b cookies.txt -X POST "https://billing.example.com/rest_api/logout"
# Ответ: {"status": "ok"}
REST API v2
Универсальный CRUD-интерфейс для работы с любой моделью биллинга. Поддерживает фильтрацию, сортировку, пагинацию и выбор полей.
Список записей
# Получить список абонентов (первые 100)
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/?limit=100&offset=0"
Ответ:
{
"count": 5554,
"next": "/rest_api/v2/Abonents/?limit=100&offset=100",
"previous": null,
"results": [
{
"id": 1,
"name": "Иванов Иван Иванович",
"contract_number": "12345",
"email": "ivanov@example.com",
"enabled": true,
...
},
...
]
}
Фильтрация
# Абоненты с тарифом ID=131, только активные
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/?tarif=131&enabled=true"
# Поиск по имени (частичное совпадение)
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/?name__icontains=иванов"
Сортировка
# Сортировка по имени (А→Я)
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/?ordering=name"
# Обратная сортировка по балансу (от большего к меньшему)
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/?ordering=-account__ostatok"
Выбор полей
# Вернуть только id, name и contract_number
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/?fields=id,name,contract_number"
Получение одной записи
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/Abonents/5552/"
Создание записи (POST)
curl -s -b cookies.txt -X POST \
"https://billing.example.com/rest_api/v2/Abonents/" \
-H "Content-Type: application/json" \
-d '{
"name": "Новый абонент",
"contract_number": "99999",
"email": "new@example.com",
"tarif": 131
}'
# Ответ: 201 Created + JSON объекта
Обновление записи (PUT)
curl -s -b cookies.txt -X PUT \
"https://billing.example.com/rest_api/v2/Abonents/5552/" \
-H "Content-Type: application/json" \
-d '{"email": "updated@example.com"}'
# Ответ: 200 OK + обновлённый JSON
Удаление записи (DELETE)
curl -s -b cookies.txt -X DELETE \
"https://billing.example.com/rest_api/v2/Abonents/5552/"
# Ответ: 204 No Content
Доступные модели
| Модель | Описание |
|---|---|
Abonents | Абоненты |
Tarif | Тарифные планы |
Usluga | Услуги |
Users | Учётные записи (логин/пароль/IP) |
Nas | Серверы доступа (NAS/BRAS) |
Switch | Коммутаторы |
Homes | Адреса (дерево: город→улица→дом) |
IpPull | IP-пулы |
FinanceOperations | Финансовые операции |
AdminAccounts | Лицевые счета |
Hdsk | Заявки HelpDesk |
Cards | Карты оплаты |
Обещанный платёж
Специализированный эндпоинт для управления обещанными платежами абонентов.
GET — Статус обещанного платежа
curl -s -b cookies.txt \
"https://billing.example.com/rest_api/v2/promise_pay/5552/"
Ответ:
{
"abonent_id": 5552,
"abonent_name": "Иванов Иван Иванович",
"promise_pay": "90.00",
"promise_date_end": "2026-03-15T12:00:00",
"active_promises": [
{
"id": 3,
"usluga_id": -4,
"type": "prepay",
"limit": "90.00",
"end_time": "2026-03-15T12:00:00",
"comment": "Обещанный платёж по предоплате до ...",
"create_date": "2026-03-12T12:00:00"
}
]
}
POST — Добавить обещанный платёж
# С параметрами по умолчанию (сумма и срок из настроек)
curl -s -b cookies.txt -X POST \
"https://billing.example.com/rest_api/v2/promise_pay/5552/" \
-H "Content-Type: application/json" \
-d '{}'
# С явными параметрами
curl -s -b cookies.txt -X POST \
"https://billing.example.com/rest_api/v2/promise_pay/5552/" \
-H "Content-Type: application/json" \
-d '{"limit": 500, "end_time": "2026-03-31", "postpay": false}'
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
limit | number | 90 | Сумма обещанного платежа (руб.) |
end_time | string | +3 дня | Дата окончания (YYYY-MM-DD или ISO 8601) |
postpay | bool | false | true = постоплата, false = предоплата |
Ответ (201 Created):
{
"status": "ok",
"promise_id": 3,
"abonent_id": 5552,
"usluga_id": -4,
"type": "prepay",
"limit": "90",
"end_time": "2026-03-15T12:00:00"
}
DELETE — Удалить обещанный платёж
# Удалить предоплату (по умолчанию)
curl -s -b cookies.txt -X DELETE \
"https://billing.example.com/rest_api/v2/promise_pay/5552/"
# Удалить постоплату
curl -s -b cookies.txt -X DELETE \
"https://billing.example.com/rest_api/v2/promise_pay/5552/?postpay=1"
Ответ (200):
{"status": "ok", "abonent_id": 5552, "message": "Promise pay deleted"}
Usluga:ID
-4 (предоплата): сумма 90 руб., срок 3 дняID
-3 (постоплата): сумма 90 руб., срок 3 дня
Платёжные API (ЮKassa и Wallet One)
Биллинг поддерживает две платёжные системы: ЮKassa (REST API v3 и HTTP-протокол «старой» Yandex.Kassa) и Wallet One (W1). Все платежи проходят через единый сервис lk/services/payment.py.
POST /lk/payments/webhook/ — автоматически определяет систему по полям запроса (WMI_MERCHANT_ID → W1, action=checkOrder|paymentAviso → ЮKassa HTTP, Content-Type: application/json → ЮKassa REST v3).Единый инициатор:
GET/POST /lk/payments/pay/ — создаёт платёж в активной системе (LK_PAYMENT_SYSTEM = yookassa | w1).Mobile:
POST /mobile-api/v1/finance/pay — возвращает redirect_url (REST v3) или form_post (HTTP-протокол).Деньги в БД: хранятся как
amount × 1010 (поле DB_MONEY_KOEF).
Полный список endpoint
| Метод | URL | Назначение | Аутентификация |
|---|---|---|---|
| Инициация платежа | |||
| GET | /lk/payments/pay/?amount=<N> | Страница оплаты (редирект/форма) | Cookie (LK-сессия) |
| POST | /lk/payments/pay/ | Создание платежа (amount, опц. system) | Cookie + CSRF |
| POST | /mobile-api/v1/finance/pay | Создание платежа (JSON) — возвращает redirect_url или form_post | JWT Bearer |
| Webhook (единая точка для всех ПС) | |||
| POST | /lk/payments/webhook/ | ЮKassa REST v3 (JSON) — event: payment.succeeded | Подпись metadata (shop) |
| POST | /lk/payments/webhook/ | ЮKassa HTTP — action=checkOrder | MD5 (shopPassword) |
| POST | /lk/payments/webhook/ | ЮKassa HTTP — action=paymentAviso | MD5 (shopPassword) |
| POST | /lk/payments/webhook/ | Wallet One — WMI_MERCHANT_ID+WMI_SIGNATURE | base64(md5) (W1 secret) |
| Возврат клиента после оплаты | |||
| GET | /lk/payments/result/ | Success/Fail landing для LK | Cookie (опционально) |
| GET | /lk/payments/ | История платежей абонента | Cookie |
| Админ-настройки | |||
| GET/POST | /admin/settings/payment/ | Форма настроек ЮKassa/W1, кнопка «Проверить подключение» | Admin cookie + CSRF |
| Внешние API (сервер биллинга → платёжная система) | |||
| POST | https://api.yookassa.ru/v3/payments | Создание платежа в ЮKassa REST v3 | Basic(shopId:secretKey) + Idempotence-Key |
| POST | https://yoomoney.ru/eshop.xml | Форма ЮKassa HTTP-протокол (браузер) | — |
| POST | https://wl.walletone.com/checkout/checkout/Index | Форма Wallet One (браузер) | WMI_SIGNATURE |
process_webhook(request) в lk/services/payment.py определяет источник по полям запроса:
WMI_MERCHANT_IDв POST → Wallet Oneaction ∈ {checkOrder, paymentAviso}→ ЮKassa HTTP-протоколContent-Type: application/json→ ЮKassa REST v3
Схема прохождения платежа
- Абонент вводит сумму на
/lk/payments/pay/или в мобильном приложении. - Сервис рассчитывает сумму к оплате =
amount × (1 + commission_rate). По умолчанию комиссия0.045(4.5 %), настраивается вLK_PAYMENT_COMMISSION. - Для ЮKassa REST / W1 — редирект на форму оплаты; для ЮKassa HTTP — форма
POSTнаyoomoney.ru/eshop.xml. - После успешной оплаты платёжная система вызывает
/lk/payments/webhook/. - Webhook проверяет подпись, извлекает
credit_amount(из metadata / кэша / расчёта), вызывает_credit_abonent():account.ostatok += credit_amount × 1010и создаёт запись вFinanceOperations.
Инициация платежа
# LK — через форму (редирект для REST, или автосабмит form_post)
GET /lk/payments/pay/?amount=500
POST /lk/payments/pay/ # amount=500&system=yookassa|w1
# Mobile API
curl -X POST "https://billing.example.com/mobile-api/v1/finance/pay" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"amount": 500, "return_url": "https://app/return"}'
Ответ Mobile API (ЮKassa REST v3):
{
"status": "ok",
"redirect_url": "https://yoomoney.ru/checkout/payments/v2/contract?orderId=..."
}
Ответ Mobile API (HTTP-протокол — авто-сабмит формы на клиенте):
{
"status": "ok",
"form_post": {
"action": "https://yoomoney.ru/eshop.xml",
"fields": {"shopId": "267304", "scid": "2487841", "sum": "522.50", ...}
}
}
ЮKassa — REST API v3
Используется, если LK_YOOKASSA_SECRET_KEY начинается с live_ / test_ или длиннее 30 символов. Документация: yookassa.ru/developers/api.
Создание платежа (сервер → ЮKassa)
POST https://api.yookassa.ru/v3/payments
Authorization: Basic base64(shopId:secretKey)
Idempotence-Key: <uuid4>
Content-Type: application/json
{
"amount": { "value": "522.50", "currency": "RUB" },
"confirmation": { "type": "redirect", "return_url": "https://app/return" },
"capture": true,
"description": "Пополнение баланса. Договор: 0114",
"metadata": {
"abonent_id": 2712,
"credit_amount": "500.00",
"commission": "22.50"
}
}
Ответ содержит confirmation.confirmation_url, куда нужно направить абонента.
Webhook (ЮKassa → биллинг)
POST /lk/payments/webhook/
Content-Type: application/json
{
"event": "payment.succeeded",
"object": {
"id": "2b3f3a7b-000f-5000-9000-1a8a0b7e1c43",
"status": "succeeded",
"amount": { "value": "522.50", "currency": "RUB" },
"metadata": {
"abonent_id": "2712",
"credit_amount": "500.00",
"commission": "22.50"
}
}
}
Биллинг обрабатывает только event = payment.succeeded. Остальные события (payment.canceled, refund.succeeded) принимаются с HTTP 200, но действий не выполняется. Сумма к зачислению берётся из metadata.credit_amount; если её нет — вычисляется как paid_amount / (1 + commission_rate).
ЮKassa — HTTP-протокол (Yandex.Kassa legacy)
Используется, если настроен только shopId + scid + shopPassword (без API-ключа). Подробнее: yoomoney.ru — HTTP-протокол.
Форма оплаты (браузер → ЮKassa)
POST https://yoomoney.ru/eshop.xml
Content-Type: application/x-www-form-urlencoded
shopId=267304
&scid=2487841
&sum=522.50
&customerNumber=2712
&orderNumber=2712-a4f90b12
&shopSuccessURL=https://billing.example.com/lk/payments/success/
&shopFailURL=https://billing.example.com/lk/payments/fail/
&cps_email=user@example.com
&cps_phone=+79991234567
&paymentType=
Webhook checkOrder (проверка заказа)
POST /lk/payments/webhook/
Content-Type: application/x-www-form-urlencoded
action=checkOrder
&orderSumAmount=522.50
&orderSumCurrencyPaycash=643
&orderSumBankPaycash=1001
&shopId=267304
&invoiceId=2190485900123
&customerNumber=2712
&md5=<UPPERCASE_MD5>
MD5 считается по строке, склеенной через ;:
md5 = UPPER(md5(
action + ";" +
orderSumAmount + ";" +
orderSumCurrencyPaycash + ";" +
orderSumBankPaycash + ";" +
shopId + ";" +
invoiceId + ";" +
customerNumber + ";" +
shopPassword
))
Ответ на checkOrder:
<?xml version="1.0" encoding="UTF-8"?>
<checkOrderResponse performedDatetime="2026-04-12T10:00:00"
code="0" invoiceId="2190485900123" shopId="267304"/>
Webhook paymentAviso (подтверждение оплаты)
Аналогичный запрос с action=paymentAviso. После проверки MD5 биллинг зачисляет credit_amount = orderSumAmount / (1 + commission_rate) на customerNumber. Ответ:
<?xml version="1.0" encoding="UTF-8"?>
<paymentAvisoResponse performedDatetime="2026-04-12T10:00:00"
code="0" invoiceId="2190485900123" shopId="267304"/>
| code | Значение |
|---|---|
0 | Успех — продолжить |
1 | MD5 не совпал — остановить обработку |
100 | Зачисление не удалось — ЮKassa повторит запрос |
Wallet One (W1)
Платёжная система walletone.com. URL формы: https://wl.walletone.com/checkout/checkout/Index.
Форма оплаты (браузер → W1)
POST https://wl.walletone.com/checkout/checkout/Index
Content-Type: application/x-www-form-urlencoded
WMI_MERCHANT_ID=123456
&WMI_PAYMENT_AMOUNT=522.50
&WMI_CURRENCY_ID=643
&WMI_DESCRIPTION=BASE64:0J/QvtC/0L7Qu9C90LXQvdC40LUg...
&WMI_PAYMENT_NO=2712-a4f90b12
&WMI_SUCCESS_URL=https://billing.example.com/lk/payments/success/
&WMI_FAIL_URL=https://billing.example.com/lk/payments/fail/
&WMI_CUSTOMER_EMAIL=user@example.com
&WMI_SIGNATURE=<base64_md5>
Подпись W1: берутся все поля, начинающиеся с WMI_ (кроме WMI_SIGNATURE), сортируются по ключу, значения конкатенируются (list-значения сортируются и склеиваются), в конец добавляется secret_key. Результат: base64(md5_raw(values + secret_key)).
Webhook (W1 → биллинг)
POST /lk/payments/webhook/
Content-Type: application/x-www-form-urlencoded
WMI_MERCHANT_ID=123456
&WMI_PAYMENT_AMOUNT=522.50
&WMI_PAYMENT_NO=2712-a4f90b12
&WMI_ORDER_STATE=Accepted
&WMI_SIGNATURE=<base64_md5>
Биллинг зачисляет сумму только при WMI_ORDER_STATE = Accepted. Ответы (plain text, без XML):
| Ответ | Смысл |
|---|---|
WMI_RESULT=OK | Успешно обработано (W1 не повторит) |
WMI_RESULT=RETRY&WMI_DESCRIPTION=... | Ошибка — W1 повторит попытку |
Сумма зачисления (credit_amount) берётся из кеша (w1_order:{WMI_PAYMENT_NO}, TTL 24 ч), куда была записана при создании платежа. Если кеш пуст — вычисляется как paid_amount / (1 + commission_rate).
Настройки (.env или БД через админ-панель)
| Ключ | Описание |
|---|---|
LK_PAYMENT_SYSTEM | Активная система: yookassa или w1 |
LK_PAYMENT_COMMISSION | Комиссия (по умолчанию 0.045) |
LK_YOOKASSA_SHOP_ID | shopId (напр. 267304) |
LK_YOOKASSA_SECRET_KEY | REST-ключ (live_* / test_*) или shopPassword для HTTP-протокола |
LK_YOOKASSA_SCID | scid для HTTP-протокола (напр. 2487841) |
LK_YOOKASSA_SUCCESS_URL / ..._FAIL_URL | Страницы возврата |
LK_W1_MERCHANT_ID | ID магазина в W1 |
LK_W1_SECRET_KEY | Секретный ключ для подписи |
LK_W1_CURRENCY | ISO 4217 числовой код (643 = RUB) |
LK_W1_SUCCESS_URL / ..._FAIL_URL | Страницы возврата |
UI настроек: /admin/settings/payment/ — кнопки «Проверить подключение» для обеих систем.
Запись в FinanceOperations
После успешного зачисления создаётся запись в FinanceOperations:
| Поле | Значение |
|---|---|
op_type_id | 23 — ЮKassa / ЮKassa-HTTP; 24 — W1 (тип «Оплата через платёжные системы») |
op_summa | credit_amount × 1010 (положительное число) |
abonent_id | ID абонента из metadata / customerNumber / WMI_PAYMENT_NO |
descr | «Онлайн-оплата (YooKassa/W1). Оплачено: X.XX, комиссия: Y.YY» |
op_date | timezone.now() |
Параллельно account.ostatok увеличивается на credit_amount × 1010. Операция выполняется в транзакции (@transaction.atomic).
SOAP API
Содержание раздела
SOAP-интерфейс на базе фреймворка Spyne. Используется для интеграции с платёжными системами, 1С и кассовым ПО.
WSDL
# Получить WSDL-описание сервисов
curl -s "https://billing.example.com/api/?wsdl"
curl -s "https://billing.example.com/api/fiscal/?wsdl"
Эндпоинты
| URL | Сервис | Методы |
|---|---|---|
/api/ | UserService | get_user_hash |
/api/v2/ | UserService | get_user_hash |
/api/1c/ | UserService | get_user_hash |
/api/userside/ | UserService | get_user_hash |
/api/collector/ | UserService | get_user_hash |
/api/cabinet/ | UserService | get_user_hash |
/api/fiscal/ | FiscalService | pay_usr_act2, pay_usr_act_import |
get_user_hash — Аутентификация
curl -s -X POST "https://billing.example.com/api/" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tns="billing.users">
<soapenv:Body>
<tns:get_user_hash>
<tns:username>admin</tns:username>
<tns:hash>sha1_hex_hash</tns:hash>
</tns:get_user_hash>
</soapenv:Body>
</soapenv:Envelope>'
| Параметр | Тип | Описание |
|---|---|---|
username | String | Имя пользователя |
hash | String | SHA1(MD5(password) + salt) |
Ответ: {hash: "32-byte-hex-session-token"} — токен для SSO, хранится 1 час.
pay_usr_act2 — Проведение платежа
curl -s -X POST "https://billing.example.com/api/fiscal/" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tns="rpc.view.fiscal">
<soapenv:Body>
<tns:pay_usr_act2>
<tns:hash_key>session_token</tns:hash_key>
<tns:ACT>PAY</tns:ACT>
<tns:CONTRACT_NUMBER>12345</tns:CONTRACT_NUMBER>
<tns:SUM_IN>100.00</tns:SUM_IN>
<tns:PAY_OPERATOR>Sberbank</tns:PAY_OPERATOR>
<tns:COMMENT_IN>Оплата услуг</tns:COMMENT_IN>
</tns:pay_usr_act2>
</soapenv:Body>
</soapenv:Envelope>'
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
hash_key | String | Да | Сессионный токен (из get_user_hash) |
ACT | String | Да | Тип операции: PAY, CHECK |
CONTRACT_NUMBER | String | Да | Номер договора абонента |
SUM_IN | String | Да | Сумма платежа |
PAY_OPERATOR | String | Да | Название оператора/платёжной системы |
COMMENT_IN | String | Нет | Комментарий к платежу |
PAY_ID_STR | String | Нет | Внешний ID платежа |
Web API (управление абонентами)
Содержание раздела
Эндпоинты для управления абонентами через HTTP POST. Требуют авторизацию (cookie) и CSRF-токен. Возвращают HTTP 302 (redirect) при успехе.
Блокировка абонента
CSRF=$(grep csrftoken cookies.txt | awk '{print $NF}')
curl -s -b cookies.txt -X POST \
"https://billing.example.com/admin/abonents/block/5552/" \
-d "csrfmiddlewaretoken=$CSRF" \
-H "Referer: https://billing.example.com/admin/Abonents/5552/" \
-o /dev/null -w "%{http_code}"
# 302 = успех
Разблокировка абонента
curl -s -b cookies.txt -X POST \
"https://billing.example.com/admin/abonents/unblock/5552/" \
-d "csrfmiddlewaretoken=$CSRF" \
-H "Referer: https://billing.example.com/admin/Abonents/5552/"
Отключение / Восстановление
# Отключить абонента
curl -s -b cookies.txt -X POST \
"https://billing.example.com/admin/abonents/reconnect/5552/" \
-d "csrfmiddlewaretoken=$CSRF" \
-H "Referer: https://billing.example.com/admin/Abonents/5552/"
# Восстановить удалённого абонента
curl -s -b cookies.txt -X POST \
"https://billing.example.com/admin/Abonents/restore/5552/" \
-d "csrfmiddlewaretoken=$CSRF" \
-H "Referer: https://billing.example.com/admin/Abonents/5552/"
СОРМ API
Endpoint'ы для интеграции внешних систем (мониторинг, скрипты pre-deploy) с механизмом проверки готовности СОРМ-данных. Полное описание раздела — на странице Интеграция с СОРМ3 → СОРМ-метаданные.
GET /admin/equipment/sorm_list/validate/ — pre-flight проверка
Проверяет полноту СОРМ-данных перед запуском выгрузки. Возвращает JSON с двумя секциями:
meta_issues— проблемы метаданныхUserAttributesdata_issues— отсутствие значений у активных абонентов для обязательных полей
Аутентификация: Django session cookie (@login_required). Любой залогиненный пользователь.
Запрос
curl -s -b cookies.txt \
"https://billing.example.com/admin/equipment/sorm_list/validate/" \
-H "Accept: application/json"
Ответ (200 OK)
{
"ok": true,
"has_problems": true,
"meta_issues": [
{
"kind": "missing_field_code",
"attribute_id": 13,
"attribute_name": "Паспорт №",
"message": "СОРМ-атрибут без sorm_field_code"
},
{
"kind": "duplicate_field_code",
"report_type": "ABONENT_LEGAL",
"sorm_field_code": "director",
"attribute_ids": [10, -219000],
"attribute_names": ["Директор", "Директор"],
"message": "В отчёте ABONENT_LEGAL sorm_field_code «director» используется у 2 реквизитов: Директор, Директор"
}
],
"data_issues": [
{
"report_type": "ABONENT_LEGAL",
"field_code": "inn",
"attribute_name": "ИНН",
"attribute_id": 4,
"missing": 241
}
]
}
Поля meta_issues
| kind | Описание |
|---|---|
missing_field_code | Атрибут с is_sorm=True, но sorm_field_code пустой. Builder его пропустит — данные потеряются в выгрузке |
invalid_field_code | field_code не соответствует регэкспу ^[a-z][a-z0-9_]*$ (пробелы, кириллица, тире и т.д.) |
duplicate_field_code | Два разных атрибута имеют один и тот же field_code в одном отчёте. Builder выберет один через LIMIT 1 непредсказуемо |
exception | Внутренняя ошибка builder при попытке валидации. Поле message содержит текст исключения |
Поля data_issues
| Поле | Тип | Описание |
|---|---|---|
report_type | string | Тип СОРМ-отчёта где обнаружены пропуски: ABONENT_LEGAL, ABONENT, ABONENT_IDENT и т.д. |
field_code | string | Техническое имя поля (sorm_field_code) |
attribute_name | string | Имя реквизита из UserAttributes.NAME |
attribute_id | int | PK атрибута |
missing | int | Сколько активных абонентов без заполненного значения этого поля |
ABONENT_LEGAL) проверяются только у юр.лиц (company=True); физ-поля (passport_series_number, birth_date) — только у физических лиц. Это исключает ложные срабатывания.
Использование в мониторинге
Endpoint удобен для интеграции с Zabbix / Grafana / собственными скриптами cron. Пример Bash-обёртки которая вернёт ненулевой exit code если есть проблемы:
#!/bin/bash
# sorm_check.sh — алертит если есть проблемы метаданных или критические пропуски
RESPONSE=$(curl -s -b /etc/sorm/cookies.txt \
"https://billing.example.com/admin/equipment/sorm_list/validate/")
META=$(echo "$RESPONSE" | jq '.meta_issues | length')
DATA=$(echo "$RESPONSE" | jq '.data_issues | length')
if [ "$META" -gt 0 ]; then
echo "CRITICAL: $META проблем метаданных"
exit 2
fi
if [ "$DATA" -gt 0 ]; then
echo "WARNING: $DATA полей с пропусками"
exit 1
fi
echo "OK: все СОРМ-данные готовы"
exit 0
CRUD UserAttributes с СОРМ-метаданными
Endpoint /admin/dictionary/user_attributes_crud/[id]/ поддерживает GET/POST/DELETE и принимает 4 поля СОРМ-метаданных. Используется UI /admin/dictionary/UserAttributes/.
GET — получение метаданных одного атрибута
curl -s -b cookies.txt \
"https://billing.example.com/admin/dictionary/user_attributes_crud/4/"
# Ответ:
{
"ok": true,
"id": 4,
"name": "ИНН",
"type_id": 1,
"is_sorm": true,
"sorm_field_code": "inn",
"sorm_report_types": "ABONENT,ABONENT_LEGAL",
"sorm_required": true,
"sorm_alt_names": "",
"use_count": 280,
...
}
POST — создать или обновить атрибут
curl -s -b cookies.txt -X POST \
"https://billing.example.com/admin/dictionary/user_attributes_crud/4/" \
-H "Content-Type: application/json" \
-H "X-CSRFToken: $CSRF" \
-d '{
"name": "ИНН",
"type_id": 1,
"is_sorm": true,
"sorm_field_code": "inn",
"sorm_report_types": "ABONENT,ABONENT_LEGAL",
"sorm_required": true,
"sorm_alt_names": ""
}'
Защита от случайного переименования
Если попытаться переименовать СОРМ-атрибут (изменить name), сервер возвращает HTTP 400 с флагом sorm_rename_blocked: true:
{
"ok": false,
"error": "«ИНН» — реквизит из СОРМ-отчётности. Переименование сломает SQL-запросы в billing/views/sorm.py (`WHERE NAME=...`). Подтвердите force_sorm_rename=true если уверены.",
"sorm_rename_blocked": true
}
Чтобы всё-таки переименовать — повторить запрос с дополнительным полем "force_sorm_rename": true в JSON.
Валидация sorm_field_code
- Регэксп:
^[a-z][a-z0-9_]*$— латинские буквы/цифры/_, первый символ — буква. - Уникальность
(report_type, field_code)— нельзя 2 атрибута с одинаковымinnв одном отчёте. Backend ищет конфликты по каждому типу изsorm_report_types. - При нарушении — HTTP 400 с понятным текстом ошибки в поле
error.
SormReportBuilder — Python API
Класс billing.services.sorm_sql.SormReportBuilder — программный API для генерации SQL и валидации метаданных. Используется внутри billing.views.sorm и Celery-задачи run_report_export, но доступен для скриптов и shell-сессий.
from billing.services.sorm_sql import (
SormReportBuilder,
validate_sorm_data,
)
# Сгенерировать SQL для отчёта ABONENT_LEGAL
sql = SormReportBuilder('ABONENT_LEGAL').build_sql()
# Получить порядок колонок CSV
codes = SormReportBuilder('ABONENT_LEGAL').get_field_codes()
# ['inn', 'kpp', 'ogrn', 'legal_address', ...]
# Список поддерживаемых типов отчётов
SormReportBuilder.supported_report_types()
# ['ABONENT_LEGAL'] # на только этот тип имеет builder-шаблон
# Валидация метаданных (без обращения к БД абонентов)
issues = SormReportBuilder.validate_meta()
# [{'kind': 'missing_field_code', 'attribute_id': 13, ...}, ...]
# Валидация заполнения данных у активных абонентов
data_issues = validate_sorm_data(['ABONENT_LEGAL'])
# [{'report_type': 'ABONENT_LEGAL', 'field_code': 'inn', 'missing': 241}, ...]
Исключения: SormReportBuilderError поднимается если тип отчёта не поддержан, либо нет ни одного СОРМ-атрибута с sorm_field_code для этого типа, либо у атрибута пустой field_code. В коде billing/views/sorm.py используется graceful fallback на захардкоженный legacy SQL — если builder упадёт, выгрузка продолжит работать.
AJAX-эндпоинты
Содержание раздела
JSON-эндпоинты для асинхронных запросов из веб-интерфейса. Требуют авторизацию (cookie).
Получить IP из пула
# Из любого свободного пула
curl -s -b cookies.txt "https://billing.example.com/admin/ajax/ippull_get/"
# Из конкретного пула (ID=1)
curl -s -b cookies.txt "https://billing.example.com/admin/ajax/ippull_get/1/"
Ответ: {"msg": "ok", "ip": "10.0.0.5", "mask": "255.255.255.0"}
Информация о MAC-адресе
curl -s -b cookies.txt \
"https://billing.example.com/admin/ajax/user_getinfo/?user_id=123&cmd=GetMac"
Ответ: {"msg": "ok", "mac": "AA:BB:CC:DD:EE:FF"}
Порты коммутатора
curl -s -b cookies.txt \
"https://billing.example.com/admin/ajax/user_get_switch_port_list/?user_id=123&switch_id=1"
Ответ: [{"pk": 1, "name": "Port 1", "selected": false}, ...]
Утилиты
| Метод | URL | Описание |
|---|---|---|
| GET | /admin/Abonents/get_bills/<filename> | Скачать файл счёта |
| GET | /admin/Abonents/download_operations/ | Скачать отчёт по операциям |
| GET | /admin/getpdf/<abonent_id>/<item_id>/ | Генерация PDF-документа |
| GET | /admin/Users/resolve_dns/<ip>/ | Обратный DNS-запрос |
Mobile API
Содержание раздела
REST API для мобильного приложения абонента. Аутентификация через JWT-токены (без cookies/CSRF). Все суммы возвращаются в рублях (деление на 1010 выполняется на стороне сервера).
/mobile-api/v1/Авторизация: JWT Bearer token (access = 15 мин, refresh = 30 дней)
Rate limit: 30 запросов/мин на пользователя
Стек: Django REST Framework + djangorestframework-simplejwt
Помимо защищённых endpoints ниже, есть 3 публичных для конфига приложения при старте:
GET /mobile-api/v1/branding,
GET /mobile-api/v1/features,
GET /mobile-api/v1/version/check.
Кэшируются на стороне клиента 5 мин (Cache-Control). Подробное описание и сценарии использования —
в разделе ЛК и мобильные → Public Mobile API.
Аутентификация
# Логин — получить JWT-токены
curl -s -X POST "https://billing.example.com/mobile-api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"contract": "0828", "password": "mypassword"}'
Ответ:
{
"access": "eyJhbGciOiJIUzI1NiIs...",
"refresh": "eyJhbGciOiJIUzI1NiIs..."
}
Поле contract принимает номер договора (с ведущим нулём и без) или логин из таблицы Users.
# Обновление access-токена
curl -s -X POST "https://billing.example.com/mobile-api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refresh": "eyJhbGciOiJIUzI1NiIs..."}'
Аккаунт
# Статус абонента (баланс, тариф, блокировки)
curl -s "https://billing.example.com/mobile-api/v1/account/status" \
-H "Authorization: Bearer ACCESS_TOKEN"
Ответ:
{
"abonent_id": 2712,
"name": "Богданова Татьяна Петровна",
"contract_number": "0114",
"balance": "28.29",
"tariff_name": "2025_SmIT30new",
"tariff_id": 139,
"speed_mbit": null,
"monthly_cost": "0.00",
"is_blocked": false,
"block_reason": "",
"has_promise_pay": false,
"promise_pay_end": null,
"balance_until_date": null,
"address": "Волгоград",
"email": "user@example.com",
"sms": "+79001234567",
"notification": "Уважаемые абоненты! Плановые работы 20.03.",
"last_payment": {"amount": "500.00", "date": "19.03.2026"}
}
# Список доступных тарифов
curl -s "https://billing.example.com/mobile-api/v1/account/tariffs" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Смена тарифа
curl -s -X POST "https://billing.example.com/mobile-api/v1/account/tariff" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tariff_id": 140}'
# Смена пароля
curl -s -X POST "https://billing.example.com/mobile-api/v1/account/change_password" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"current_password": "old", "new_password": "new123", "confirm_password": "new123"}'
# Добровольная блокировка — статус
curl -s "https://billing.example.com/mobile-api/v1/account/voluntary_block" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Добровольная блокировка — включить/выключить
curl -s -X POST "https://billing.example.com/mobile-api/v1/account/voluntary_block" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enable": true}'
Финансы
# История платежей (пагинация + фильтры)
curl -s "https://billing.example.com/mobile-api/v1/finance/history?period=month&page=1&per_page=25" \
-H "Authorization: Bearer ACCESS_TOKEN"
# История начислений (только отрицательные суммы)
curl -s "https://billing.example.com/mobile-api/v1/finance/charges" \
-H "Authorization: Bearer ACCESS_TOKEN"
Параметры фильтрации /finance/history:
| Параметр | Описание |
|---|---|
period | month, 3month, year |
from, to | Даты в формате YYYY-MM-DD |
page | Номер страницы (по умолчанию 1) |
per_page | Записей на странице (макс. 100, по умолчанию 25) |
# Обещанный платёж — статус
curl -s "https://billing.example.com/mobile-api/v1/finance/promise_pay" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Обещанный платёж — активировать
curl -s -X POST "https://billing.example.com/mobile-api/v1/finance/promise_pay" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Обещанный платёж — отменить
curl -s -X DELETE "https://billing.example.com/mobile-api/v1/finance/promise_pay" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Создать платёж (YooKassa)
curl -s -X POST "https://billing.example.com/mobile-api/v1/finance/pay" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"amount": "500.00", "system": "yookassa"}'
# Ответ (REST API v3): {"redirect_url": "https://yookassa.ru/checkout/..."}
# Ответ (HTTP-протокол): {"form_post": true, "action": "https://yoomoney.ru/eshop.xml", "fields": {...}}
Поддержка (FreeScout)
# Список тикетов
curl -s "https://billing.example.com/mobile-api/v1/support/tickets" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Создать тикет (POST на тот же URL)
curl -s -X POST "https://billing.example.com/mobile-api/v1/support/tickets" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject": "Нет интернета", "body": "Не работает с утра"}'
# Детали тикета + сообщения
curl -s "https://billing.example.com/mobile-api/v1/support/tickets/59808" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Ответ на тикет
curl -s -X POST "https://billing.example.com/mobile-api/v1/support/tickets/59808" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body": "Спасибо, проблема решена"}'
Услуги
# Список активных услуг
curl -s "https://billing.example.com/mobile-api/v1/services/list" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Вкл/выкл услугу
curl -s -X POST "https://billing.example.com/mobile-api/v1/services/toggle" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_id": 123, "enable": false}'
Push-уведомления
# Зарегистрировать FCM-токен
curl -s -X POST "https://billing.example.com/mobile-api/v1/push/register" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"token": "fcm_token_here", "platform": "android"}'
Сводная таблица эндпоинтов
| Метод | Путь | Описание |
|---|---|---|
| POST | /auth/login | Логин → JWT-токены |
| POST | /auth/refresh | Обновление access-токена |
| GET | /account/status | Баланс, тариф, блокировки |
| GET | /account/tariffs | Доступные тарифы |
| POST | /account/tariff | Смена тарифа |
| POST | /account/change_password | Смена пароля |
| GET/POST | /account/voluntary_block | Добровольная блокировка |
| GET | /finance/history | История платежей |
| GET | /finance/charges | История начислений |
| GET/POST/DELETE | /finance/promise_pay | Обещанный платёж |
| POST | /finance/pay | Создание платежа |
| GET/POST | /support/tickets | Список + создание тикетов |
| GET/POST | /support/tickets/<id> | Детали тикета + ответ |
| GET | /services/list | Список услуг |
| POST | /services/toggle | Вкл/выкл услуги |
| POST | /push/register | Регистрация FCM-токена |
Коды ошибок
| HTTP-код | Описание |
|---|---|
200 | Успех |
201 | Создано (POST) |
204 | Удалено (DELETE) |
302 | Редирект (Web API — успех) |
400 | Неверный запрос (ошибка валидации) |
401 | Не авторизован |
403 | Доступ запрещён (CSRF, READ_ONLY_MODE) |
404 | Объект не найден |
429 | Превышен лимит запросов (Mobile API: 30/мин) |
500 | Внутренняя ошибка сервера |
503 | Сервис временно недоступен (платёжная система) |
VpnConst.READ_ONLY_MODE), все POST/PUT/DELETE запросы вернут 403 Forbidden.