Перейти к содержимому

Интернет-заказ из внешней системы

Задача: интернет-магазин или маркетплейс оформляет заказ у себя и должен провести его в BILLZ как продажу конкретного магазина — с покупателем, скидкой и оплатой.

Слово «заказ» в BILLZ занято.

Формулировка задачи Как это называется в BILLZ Методы
Покупатель заказал товар на сайте Продажа (Order, чек продажи) /v2/order, /v2/order-product, /v2/order-payment
Товар заказан у поставщика Заказ поставщику (SupplierOrder) /v2/supplier-order, /v2/supplier-order/add-item

Для интернет-заказа нужна первая строка.

GET /v1/shop, /v1/company-payment-type справочники, один раз
│
GET /v1/client?phone_number=… найти клиента
POST /v1/client создать, если не нашёлся
│
POST /v2/order {shop_id, cashbox_id} создать продажу ──> order_id
POST /v2/order-product/:order_id добавить товар (по одному)
PUT /v2/order-customer-new/:order_id привязать клиента
POST /v2/order-manual-discount/:order_id скидка
POST /v1/recalculate-order-bill/:order_id сверить сумму
POST /v2/order-payment/:order_id завершить продажу
│
GET /v2/order/:id детали проведённой продажи

Создание продажи и методы, меняющие состав чека, — асинхронные: требуется заголовок Billz-Response-Channel: HTTP, иначе вместо результата приходит идентификатор события. Пересчёт и оплата — синхронные, им заголовок не нужен.

Окно терминала
curl -X GET 'https://api-admin.billz.ai/v1/client?phone_number=%2B998900000000&limit=10&page=1' \
-H 'Authorization: Bearer ТОКЕН'

Параметры: phone_number, search, chat_id, limit, page. Ответ — {count, clients[]}. Подробная карточка по найденному ID — GET /v1/customer/:id (у неё другой, более широкий набор полей, чем у элемента clients[]).

search ищет подстроку по имени, фамилии, отчеству, телефонам, external_id, chat_id, дате рождения, коду и названию карты лояльности. Несколько слов через пробел — «И». Спецсимволы вырезаются: +998901234567 и 998901234567 равнозначны. phone_number ищет по подстроке, chat_id — по точному совпадению.

Окно терминала
curl -X POST 'https://api-admin.billz.ai/v1/client' \
-H 'Authorization: Bearer ТОКЕН' \
-H 'Content-Type: application/json' \
-d '{
"first_name": "Иван",
"last_name": "Петров",
"phone_number": "+998900000000",
"gender": 1,
"date_of_birth": "1990-05-14"
}'

Ответ — { "id": "..." }: UUID клиента, который сохраняется во внешней системе. gender: 1 — мужской, 2 — женский, прочие значения записываются как пустое.

Продажа создаётся асинхронным методом POST /v2/order. Канал ответа задаётся заголовком Billz-Response-Channel: HTTP или одноимённым query-параметром — как в примере ниже.

Окно терминала
curl -X POST 'https://api-admin.billz.ai/v2/order?Billz-Response-Channel=HTTP' \
-H 'Authorization: Bearer ТОКЕН' \
-H 'Content-Type: application/json' \
-d '{
"shop_id": "c1056fcc-539b-47ea-8bc4-d9e337c433f0",
"cashbox_id": "9b4a7f8c-a8ef-4870-8a1b-89869fdd4141"
}'
{
"session_id": "fd8d352a-6299-4489-8a6e-a5c037f3f65b",
"status_code": 200,
"id": "38c3920a-ffdd-4233-a823-8af3266f5f4f",
"error": { "code": "", "message": "" },
"data": {
"id": "38c3920a-ffdd-4233-a823-8af3266f5f4f",
"order_number": "5632631379",
"order_type": "SALE"
},
"correlation_id": "3674f28e-7b96-4683-87c3-80892698f421"
}

UUID продажи — поле id (то же значение в data.id), он подставляется во все следующие шаги. order_number — короткий номер продажи, который видит кассир в интерфейсе.

Магазин выбирается полем shop_id, касса — полем cashbox_id. Список магазинов — GET /v1/shop.

По одному товарному наименованию на запрос.

Окно терминала
curl -X POST 'https://api-admin.billz.ai/v2/order-product/ID_ПРОДАЖИ' \
-H 'Authorization: Bearer ТОКЕН' \
-H 'Content-Type: application/json' \
-H 'Billz-Response-Channel: HTTP' \
-d '{
"product_id": "2ce40c63-4527-4898-a975-7c30c255fd50",
"sold_measurement_value": 2,
"used_wholesale_price": false
}'

Это upsert с абсолютным количеством: повторный вызов с тем же product_id заменяет количество. sold_measurement_value: 0 удаляет позицию из чека.

Окно терминала
curl -X PUT 'https://api-admin.billz.ai/v2/order-customer-new/ID_ПРОДАЖИ' \
-H 'Authorization: Bearer ТОКЕН' \
-H 'Content-Type: application/json' \
-H 'Billz-Response-Channel: HTTP' \
-d '{ "customer_id": "ID_КЛИЕНТА", "check_auth_code": false }'

Ответ приходит в конверте: объект лежит в поле data.

Окно терминала
curl -X POST 'https://api-admin.billz.ai/v2/order-manual-discount/ID_ПРОДАЖИ' \
-H 'Authorization: Bearer ТОКЕН' \
-H 'Content-Type: application/json' \
-H 'Billz-Response-Channel: HTTP' \
-d '{ "discount_unit": "PERCENTAGE", "discount_value": 10 }'

discount_unit — PERCENTAGE или CURRENCY. Необязательный product_id применяет скидку к одной позиции; без него — на всю корзину.

Ручная скидка запускает пересчёт корзины заново: оплата вызывается после асинхронного ответа на скидку.

Окно терминала
curl -X POST 'https://api-admin.billz.ai/v1/recalculate-order-bill/ID_ПРОДАЖИ' \
-H 'Authorization: Bearer ТОКЕН'

Метод синхронный, возвращает актуальный чек с позициями, скидками и итогом.

Для отложенной продажи метод отказывает.

Окно терминала
curl -X POST 'https://api-admin.billz.ai/v2/order-payment/ID_ПРОДАЖИ' \
-H 'Authorization: Bearer ТОКЕН' \
-H 'Content-Type: application/json' \
-d '{
"payments": [
{ "company_payment_type_id": "c6e84d9b-...", "paid_amount": 900000, "returned_amount": 0 }
],
"comment": "Заказ №12345 с сайта",
"without_cashback": false
}'
Поле Смысл
payments[] список оплат; несколько элементов — оплата разными способами
payments[].company_payment_type_id тип оплаты из GET /v1/company-payment-type
payments[].paid_amount внесённая сумма
payments[].returned_amount сдача
payments[].gift_card_id целое число; нужно для оплаты подарочной картой
with_cashback сумма (не флаг), погашаемая бонусами клиента
without_cashback true — не начислять кешбэк за эту продажу
sold_at дата и время продажи в формате YYYY-MM-DD HH:MM:SS — для проведения задним числом
comment номер заказа внешней системы; пустая строка не затирает прежний
skip_ofd снимает проверки ОФД перед оплатой, см. ниже

Метод синхронный. В ответ приходит {"order_type": "SALE", "should_print_cheque": true} со статусом 200.

Ошибка приходит с HTTP 400, а номер причины лежит внутри data:

{
"status_code": 400,
"error": { "code": "", "message": "status: 400 - errors: Bad request - causes: ..." },
"data": { "error_code": 20084, "message": "payment is possible only for calculated order" }
}

Причина определяется по data.error_code: поле error.code в этом ответе пустое.

Код Сообщение Что означает
20009 cashbox shop id and order shop id are different текущая касса пользователя из другого магазина
20010 user’s current cashbox id is empty в магазине продажи нет открытой кассы
20011 cashbox has not enough money в кассе не хватает денег на возврат
20015 order does not have items в чеке нет позиций
20016 not enough product не хватает остатка; доступное количество — в data.data
20017 order is deleted продажа удалена
20020 wrong payment amount сумма оплат не сходится; в data — total_price и claimed_amount
20047 not enough funds in customer balance не хватает бонусов под with_cashback
20063 order not sent to epos чек не отправлен в ОФД (снимается флагом skip_ofd)
20082 cannot make payment with closed cashbox касса закрыта
20084 payment is possible only for calculated order корзина не пересчитана, см. шаг 3

claimed_amount — сумма paid_amount минус returned_amount по всем элементам, плюс with_cashback, плюс долг; сверяется с итогом чека.

ID возвращается на шаге 2. Полные детали проведённой продажи — позиции, оплаты, скидки, клиент:

Окно терминала
curl -X GET 'https://api-admin.billz.ai/v2/order/ID_ПРОДАЖИ' \
-H 'Authorization: Bearer ТОКЕН'

Полем shop_id в теле POST /v2/order; там же задаётся касса — cashbox_id. Список магазинов — GET /v1/shop.

Можно ли оформить продажу при нехватке остатка

Заголовок раздела «Можно ли оформить продажу при нехватке остатка»

Это управляется настройкой товара «разрешить отрицательный остаток». Если она выключена, добавление товара сверх остатка не пройдёт; если включена — остаток уйдёт в минус.

Ошибка — HTTP 400, data.error_code = 20016, data.message = not enough product; доступное количество приходит в data.data.

Предварительная проверка остатка — GET /v2/products → shop_measurement_values[].active_measurement_value по нужному shop_id. Между проверкой и оплатой товар может быть продан офлайн: отказ на оплате также обрабатывается.

Готовой накладной или печатной формы по продаже публичный API не отдаёт.

Что нужно Чем закрывается
Своя печатная форма GET /v2/order/:id — позиции, цены, скидки, оплаты, магазин, клиент; вёрстка на стороне внешней системы
Выгрузка для бухгалтерии отчёты /v1/general-report-table, /v1/transaction-report-table

UUID продажи (id из конверта ответа шага 2). order_number предназначен для переписки с поддержкой.

  1. Связь «номер заказа внешней системы → UUID продажи BILLZ» хранится на стороне внешней системы; UUID записывается сразу после шага 2, до добавления товаров.
  2. Перед созданием продажи проверяется, нет ли уже записи по этому заказу.
  3. Номер заказа внешней системы передаётся в comment при оплате: дубль виден в интерфейсе BILLZ и в отчётах.

Разбор 403 — в решении проблем, полный перечень доступного ключу — в справочнике.

Выпуск ключа открывает базовый набор методов; чувствительные методы открывают отдельно, настройкой прав ключа.

Шаг Метод Тип
Список магазинов GET /v1/shop sync
Типы оплат GET /v1/company-payment-type sync
Поиск клиента GET /v1/client sync
Карточка клиента GET /v1/customer/:id sync
Создать клиента POST /v1/client sync
Изменить клиента PUT /v1/client/:id sync
Создать продажу POST /v2/order async
Добавить товар POST /v2/order-product/:order_id async
Привязать клиента PUT /v2/order-customer-new/:order_id async
Скидка POST /v2/order-manual-discount/:order_id async
Сверить сумму POST /v1/recalculate-order-bill/:order_id sync
Завершить продажу POST /v2/order-payment/:order_id sync
Детали продажи GET /v2/order/:id sync