Создание заказа
Заказ создаётся одним запросом:
- метод:
POST - ресурс:
/v3/billing/api/order/order - тело запроса - объект CreateOrderRequest
- тело ответа - объект OrderResponse
- Возможные ошибки
Пример запроса и ответа
HTTP
POST /v3/billing/api/order/order
Authorization: Bearer b37c4c689295904ed21eee5d9a48d42e
Content-Type: application/json
User-Agent: MyApp 1.0
Accept: application/json
Тело запроса:
{
"merchantId": "ffffffff-ffff-ffff-ffff-ffffffffffff",
"merchantOrderId": "m-1608560079",
"amount": 366.00,
"successUrl": "https://shop.example.com/order/4412?result=success",
"failUrl": "https://shop.example.com/order/4412?result=fail",
"returnUrl": "https://shop.example.com/order/4412?result=return",
"vatAmount": 66.00,
"basketItems": [
{
"sku": "5fe0adcfa7fb4",
"name": "Бронирование номера",
"measure": "шт.",
"measureCode": "796",
"grossWeight": 0,
"netWeight": 0,
"quantity": 3,
"amount": 122.00,
"amountWoVat": 100.00,
"totalAmount": 366.00,
"totalVatAmount": 66.00,
"vatCode": "RUS_VAT22",
"type": "service",
"paymentType": "full_prepayment"
}
],
"metaData": {
"@type": "LodgingReservation",
"reservationId": "abc456",
"reservationStatus": "https://schema.org/ReservationConfirmed",
"underName": {
"@type": "Person",
"name": "John Smith"
},
"reservationFor": {
"@type": "LodgingBusiness",
"name": "Hilton San Francisco Union Square",
"address": {
"@type": "PostalAddress",
"streetAddress": "333 O'Farrell St",
"addressLocality": "San Francisco",
"addressRegion": "CA",
"postalCode": "94102",
"addressCountry": "US"
},
"telephone": "415-771-1400"
},
"checkinTime": "2026-08-12T16:00:00-08:00",
"checkoutTime": "2026-08-14T11:00:00-08:00"
},
"expirationDate": "2026-12-22T00:00:00+00:00",
"languageId": "ru",
"currencyId": "RUB",
"description": "Оплата номера в отеле",
"customer": {
"type": "legal",
"name": "ООО «Ромашка»",
"phone": "79001112233",
"email": "buh@example.invbox.ru",
"vatNumber": "7701234560",
"taxRegistrationReasonCode": "770101001",
"registrationAddress": "190000, Санкт-Петербург, Невский пр. 147, офис 321"
}
}
CURL
curl -L -X POST 'https://api.invoicebox.ru/v3/billing/api/order/order' \
-H 'Authorization: Bearer b37c4c689295904ed21eee5d9a48d42e' \
-H 'Content-Type: application/json' \
-H 'User-Agent: MyApp 1.0' \
-H 'Accept: application/json' \
-d '{
"merchantId": "ffffffff-ffff-ffff-ffff-ffffffffffff",
"merchantOrderId": "m-1608560079",
"amount": 366.00,
"vatAmount": 66.00,
"currencyId": "RUB",
"languageId": "ru",
"description": "Оплата номера в отеле",
"expirationDate": "2026-12-22T00:00:00+00:00",
"successUrl": "https://shop.example.com/order/4412?result=success",
"failUrl": "https://shop.example.com/order/4412?result=fail",
"returnUrl": "https://shop.example.com/order/4412?result=return",
"basketItems": [
{
"sku": "5fe0adcfa7fb4",
"name": "Бронирование номера",
"measure": "шт.",
"measureCode": "796",
"grossWeight": 0,
"netWeight": 0,
"quantity": 3,
"amount": 122.00,
"amountWoVat": 100.00,
"totalAmount": 366.00,
"totalVatAmount": 66.00,
"vatCode": "RUS_VAT22",
"type": "service",
"paymentType": "full_prepayment"
}
],
"customer": {
"type": "legal",
"name": "ООО «Ромашка»",
"phone": "79001112233",
"email": "buh@example.invbox.ru",
"vatNumber": "7701234560",
"taxRegistrationReasonCode": "770101001"
}
}'
Ответ (по схеме):
{
"data": {
"id": "01771534-1a57-f184-dee3-ebeb91dded75",
"description": "string",
"currencyId": "RUB",
"amount": 100.5,
"vatAmount": 100.5,
"basketItems": [
{
"sku": "string",
"name": "string",
"groupName": "string",
"measure": "string",
"measureCode": "string",
"originCountry": "string",
"originCountryCode": "string",
"grossWeight": 100.5,
"netWeight": 100.5,
"quantity": 100.5,
"amount": 100.5,
"amountWoVat": 100.5,
"totalAmount": 100.5,
"totalVatAmount": 100.5,
"vatCode": "RUS_VAT22",
"type": "string",
"gtdNumber": "string",
"paymentType": "string",
"excise": 100.5,
"metaData": {},
"tnved": "string",
"rnpt": "string",
"gtin": "string",
"category": "string",
"categoryType": "string",
"serviceDate": "2026-08-03T12:00:00+03:00"
}
],
"merchantId": "01771534-1a57-f184-dee3-ebeb91dded75",
"status": "string",
"shipmentStatus": "string",
"subtype": "string",
"processingStatus": "string",
"createdAt": "2026-08-03T12:00:00+03:00",
"merchantOrderId": "01771534-1a57-f184-dee3-ebeb91dded75",
"merchantOrderIdVisible": "string",
"expirationDate": "2026-08-03T12:00:00+03:00",
"metaData": {},
"customerData": {},
"notificationUrl": "string",
"orderSetting": {
"roundPolicy": "string",
"disableValidate": false
},
"internalProperty": {
"guarantee": false,
"hold": false,
"rtp": false,
"agent": false,
"rtpExternal": false
},
"tags": {},
"shopId": 1,
"sourceId": "l3",
"paidAt": "2026-08-03T12:00:00+03:00",
"parentId": "01771534-1a57-f184-dee3-ebeb91dded75",
"returnUrl": "string",
"successUrl": "string",
"failUrl": "string",
"paymentUrl": "string",
"paymentUrlType": "redirect",
"paymentPageUrl": "string",
"customer": {
"type": "string",
"name": "string",
"phone": "string",
"email": "string",
"vatNumber": "string",
"countryId": "01771534-1a57-f184-dee3-ebeb91dded75",
"registrationAddress": "string",
"taxRegistrationReasonCode": "string"
},
"languageId": "01771534-1a57-f184-dee3-ebeb91dded75",
"contactId": 1,
"contactCounterpartyId": 1,
"processable": false,
"userAccountId": "01771534-1a57-f184-dee3-ebeb91dded75",
"invoiceSetting": {
"customerLocked": false,
"customerLockedFields": {},
"customerHiddenFields": {},
"paymentMethodIdLocked": false,
"paymentMethodId": 1,
"savePaymentData": false,
"clientId": "01771534-1a57-f184-dee3-ebeb91dded75",
"paymentToken": "string",
"paymentMethodCode": "string",
"paymentMethodAutosubmit": false,
"bankDetailId": 1
},
"orderContainerId": "01771534-1a57-f184-dee3-ebeb91dded75",
"paymentInfo": {},
"holdTill": "2026-08-03T12:00:00+03:00",
"additionalOrderId": "01771534-1a57-f184-dee3-ebeb91dded75",
"shipmentTotalAmount": "string",
"shipmentTotalVatAmount": "string"
},
"metaData": {
"totalCount": 1,
"pageSize": 1,
"page": 1
},
"extendedData": [
{
"type": "string",
"data": {}
}
]
}
Адреса возврата в примере — адреса вашего магазина: shop.example.com стоит вместо них, чтобы пример
нельзя было принять за настоящий сайт. В консоли «Выполнить» портал подставляет свои
демонстрационные экраны — их можно открыть и посмотреть, что увидит покупатель:
успешная оплата, ошибка оплаты,
возврат без оплаты. Туда же портал подставляет свой адрес уведомлений,
поэтому оплату демо-заказа видно в песочнице на быстром старте.
Как считаются суммы
Половина отказов при создании заказа — это расхождение сумм. Правило простое:
amountзаказа равен суммеtotalAmountвсех позиций корзины;vatAmountзаказа равен суммеtotalVatAmountвсех позиций;- в позиции
amount— цена одной единицы,totalAmount— за всё количество (quantity × amount); amountWoVat— цена единицы без НДС; сумма без НДС и сумма НДС вместе даютtotalAmount;- строгость сверки задаёт
orderSetting.roundPolicy(по умолчаниюnone).
Если суммы не сходятся, API отвечает ошибкой wrong_total_vat_amount — расшифровка и остальные коды
в справочнике ошибок.
Ставка НДС передаётся кодом в поле vatCode позиции: с 2026 года основная ставка — 22 %
(RUS_VAT22 — НДС в цене, RUS_VAT22_ADDED — НДС сверху). Полный перечень — в
справочнике ставок.
Фискальные поля позиции
Чек формирует онлайн-касса, а данные для него берутся из позиций корзины. Реквизиты чека, которые в 54-ФЗ называются тегами, передаются обычными полями:
| Реквизит чека | Поле позиции | Справочник |
|---|---|---|
| Признак предмета расчёта (тег 1212) | type | значения |
| Признак способа расчёта (тег 1214) | paymentType | значения в таблице BasketItem |
| Ставка НДС | vatCode | ставки НДС |
| Единица измерения | measureCode | ОКЕИ |
Как устроена фискализация и когда чек попадает покупателю — онлайн-касса.
Пока заказ не оплачен
Созданный заказ не высечен в камне:
- сумму и состав можно изменить — новый заказ создавать не нужно;
- ненужный заказ отменяют;
- когда наступает время из
expirationDate, заказ сам переходит в статусexpired: оплатить по прежней ссылке нельзя, нужен новый заказ. О переходе магазин узнаёт из уведомления о смене статуса — обработайте этот статус, иначе заказ останется в вашей системе «ждущим оплаты» навсегда.
Повтор после сбоя и таймаута
Сеть обрывается на самом неудачном месте: запрос ушёл, ответ не вернулся, и магазин не знает, создан ли заказ.
Сразу о главном: по умолчанию уникальность merchantOrderId не проверяется. Номер можно передать
любой, в том числе уже использованный, — и на тот же номер создастся второй заказ. Значит слепой
повтор после таймаута приводит к двум счетам на одну покупку, а идемпотентности у создания заказа нет
ни полной, ни частичной.
Порядок действий, который от этого защищает:
-
Сохраните
merchantOrderIdдо отправки запроса и не меняйте его при повторах — он строится из номера покупки в вашей системе, а не из случайного числа. -
Если ответ не пришёл (таймаут, обрыв,
5xx), не отправляйте создание заново. Сначала спросите, есть ли заказ — созданный заказ виден в выборке сразу, поэтому пустой ответ означает, что заказа нет, и ждать не нужно:GET /v3/filter/api/order/order?merchantOrderId=O-12345 -
Заказ нашёлся — сверьте
merchantId, сумму и состав с тем, что отправляли, и берите из негоidиpaymentUrl. -
По номеру нашлось несколько заказов — остановите автоматическую обработку и разберитесь вручную. Выбирать «последний по
createdAt» нельзя: скорее всего дубль создал предыдущий неудачный повтор, и лишние счёта нужно отменить, а не тихо выбрать один из них. -
Заказ не нашёлся — повторите создание с тем же номером.
-
Новый номер генерируйте только для новой покупки. Новый номер после неудачи — это второй заказ на ту же покупку: покупатель увидит два счёта, а магазин — двойную выручку в отчётах.
Внимание
Порядок «проверил — создал» защищает от одиночного таймаута, но не от гонки: два воркера, обрабатывающие одну покупку одновременно, оба увидят пустую выборку и оба создадут заказ. Если создание может запускаться параллельно (очередь с повторами, несколько инстансов), нужен замок на вашей стороне — уникальный индекс по номеру покупки в вашей базе или распределённая блокировка на время «проверка + создание». Найденные дубли разбираются человеком: лишний счёт отменяется, а не выбирается «последний по дате».
Важно
Проверку уникальности можно включить на стороне Инвойсбокс: это настройка магазина, тумблер
«Проверять уникальность номера заказа» на вкладке магазина в
личном кабинете (Мои продажи → Магазины → Общая информация).
С включённой проверкой
повторный номер вернёт ошибку merchant_order_id_duplicate — это страховка от второго заказа, а не
замена порядку выше: ответ первого запроса повтор всё равно не вернёт, id и paymentUrl придётся
получить выборкой. Для боевого запуска считайте включение проверки обязательным пунктом, а не
советом: это единственная серверная защита от гонки двух воркеров — он есть и в
чеклисте запуска. Проверить, что настройка включена, просто: повторный POST с уже
использованным номером должен вернуть merchant_order_id_duplicate.
Что можно повторять
| Что случилось | Чтение (GET) | Создание, возврат, отмена |
|---|---|---|
| Таймаут или обрыв связи | повторить сразу | результат неизвестен: сначала выборка по merchantOrderId, повтор — только если операции нет |
5xx | повторить с задержкой | то же: запрос мог дойти и выполниться |
429 — превышен лимит частоты | повторить с растущей задержкой | повторить с растущей задержкой: до операции запрос не дошёл |
Прочие 4xx | не повторять | не повторять: причина в данных запроса, а не в связи |
Задержку увеличивайте от попытки к попытке (например, 1, 2, 4, 8 секунд) и добавляйте случайный
разброс, чтобы повторы нескольких процессов не сошлись в одну секунду. Заголовка Retry-After в
ответах нет — паузу выбирает магазин.
Возврат и отмена
Правило то же, меняется только запрос, которым вы проверяете результат.
Возврат. У каждого возврата свой merchantOrderId — по нему и ищите:
GET /v3/filter/api/order/refund-order?merchantOrderId=R-12345
Нашёлся — возврат создан, работайте с ним. Не нашёлся — повторяйте создание с тем же номером. Новый номер после неудачи означает второй возврат по тому же заказу.
Отмена заказа. Прочитайте заказ и посмотрите статус: canceled — отмена прошла, повторять
нечего. Заказ в статусе created — отмену можно повторить безопасно: у неоплаченного заказа отмена
не имеет денежного эффекта.
Осторожно с остальными статусами: денежный эффект отмены зависит от состояния заказа, а между
вашим чтением и повтором оно может измениться. У заказа, оплаченного гарантийным инструментом, отмена
запускает настоящий возврат гарантийного платежа — см. семантику отмены.
Поэтому для completed, hold и любого неожиданного статуса автоматический повтор запрещён:
остановите обработку и передайте заказ человеку. Автомат повторяет только то, чей эффект одинаков при
любом исходе гонки.
CreateOrderRequest
| Свойство | Тип | Описание | Пример значения |
|---|---|---|---|
| description * | string(1000) | Описание заказа | Оплата номера в отеле |
| merchantId * | string(36) | Идентификатор магазина. В примерах на этой странице стоит демо-магазин — свой возьмите в личном кабинете, вкладка «Интеграция (API)» | ffffffff-ffff-ffff-ffff-ffffffffffff |
| merchantOrderId * | string(100) | Идентификатор заказа в учётной системе магазина, должен быть уникальным | O-12345 |
| merchantOrderIdVisible | string(100) | Номер заказа, отображаемый на платежной странице. Если не заполнено, показывается значение из merchantOrderId | 111TN22-33 |
| amount * | float | Сумма заказа, ограничений нет | 19658.45 |
| vatAmount * | float | Сумма НДС | 156.56 |
| currencyId * | string(3) enum | Код валюты заказа в соответствии с ISO 4217 | RUB, USD,EUR, GBP |
| languageId | string(2) enum | Язык интерфейса платежной страницы | ru, en |
| expirationDate * | datetime | Срок действия заказа | 2026-12-22T00:00:00+00:00 |
| basketItems * | array of BasketItem | Корзина заказа | |
| metaData | object | Дополнительные данные заказа | |
| customer * | Customer | Информация о заказчике | |
| notificationUrl | string(1000) | URL для отправки уведомлений об изменениях статуса заказа, по умолчанию используется URL из настроек магазина | |
| successUrl | string(1000) | Ссылка для перехода на сайт Магазина в случае успешной оплаты | https://shop.example.com/order/4412?result=success |
| failUrl | string(1000) | Ссылка для перехода на сайт Магазина в случае ошибки оплаты | https://shop.example.com/order/4412?result=fail |
| returnUrl | string(1000) | Ссылка для возврата на сайт Магазина (кнопка «вернуться в магазин» на платёжной странице) | https://shop.example.com/order/4412?result=return |
| invoiceSetting | InvoiceSetting | Дополнительные настройки параметров оплаты | |
| orderSetting | OrderSetting | Дополнительные настройки параметров заказа | |
| parentId | string(36) | Идентификатор базового заказа, применимо для создания корректирующих заказов | 01771534-196a-1105-839a-82422289d6d9 |
| orderContainerId | string(36) | Идентификатор основного заказа, применимо для добавления заказа к уже существующему счёту | 01771534-196a-1105-839a-82422289d6d9 |
| shopId | string(36) | Идентификатор связанного магазина/маркетплейса | 06581534-196a-1105-839a-82422289d6d8 |
| userAccountId | string(36) | Идентификатор программы лояльности | 06581534-196a-1105-839a-82422289d6d7 |
| processable | bool | Признак процессингового заказа: true — расчёты проходят в биллинге Инвойсбокс (по умолчанию), false — непроцессинговый заказ: оплату принимает магазин, а Инвойсбокс формирует документы | true, false |
| subtype | string(36) | Подтип заказа, возможные значения order - обычный заказ (по умолчанию). hold - заказ с холдированием | order, hold |
OrderResponse
Повторяет свойства объекта CreateOrderRequest с дополнительными свойствами:
| Свойство | Тип | Описание | Пример значения |
|---|---|---|---|
| id * | string(36) | Идентификатор заказа в системе Инвойсбокс | 01771534-1a57-f184-dee3-ebeb91dded75 |
| paymentUrl * | string(1000) | Ссылка для перехода на платёжный шлюз для оплаты заказа | |
| createdAt * | datetime | Дата создания заказа | 2026-12-22T00:00:00+00:00 |
| status * | string(50) enum | Статус заказа: created — ожидает оплаты, completed — успешная оплата, hold — средства захолдированы | completed, hold |
| paidAt | datetime | Дата оплаты заказа (если оплачен) | 2026-12-22T00:00:00+00:00 |
| paymentInfo | PaymentInfo | Информация об оплате и плательщике | {"maskedPan": "220024**0954", "expiration": "202601", "paymentSystem": "MIR", "cardholderName": "CARDHOLDER NAME"} |
| holdTill | datetime | Дата и время списания холдированных средств (для заказов с подтипом hold) | 2026-08-03T12:00:00+03:00 |
Важно
В зависимости от сценария использования API, ссылка для перехода на платёжный шлюз (paymentUrl) может быть получена для переадресации пользователя в браузере или же закодирована в QR-коде для дальнейшего его сканирования камерой или приложением Инвойсбокс.
Customer
| Свойство | Тип | Описание | Пример значения |
|---|---|---|---|
| type | string(10) enum | Тип заказчика | legal - юр. лицо, private - физ лицо |
| name | string(500) | Наименование или имя | ООО «Ромашка» |
| phone | string(100) | Номер телефона | 79001112233 |
| string(100) | Электронная почта | buh@example.invbox.ru | |
| vatNumber | string(20) | ИНН | 7701234560 |
| taxRegistrationReasonCode | string(9) | КПП | 770101001 |
| registrationAddress | string(1000) | Юр. адрес, включая почтовый индекс | 190000, Санкт-Петербург, Невский пр. 147, офис 321 |
Примеры выше собраны на плательщика-организацию: type = legal, ИНН в vatNumber, КПП в
taxRegistrationReasonCode. По этим реквизитам покупателю уходят закрывающие документы. У ИП КПП нет —
поле остаётся пустым. Если платит физлицо, укажите type = private: ИНН, КПП и юридический адрес
тогда не нужны, а из документов формируется только фискальный чек.
BasketItem
Корзина заказа. Пожалуйста, внимательно ознакомьтесь с требованиями по заполнению наименования номенклатуры.
| Свойство | Тип | Описание |
|---|---|---|
| sku * | string(36) | Артикул, например: 5fe0adcfa7fb4 |
| name * | string(300) | Наименование, например Бронирование номера |
| type * | string(10) | Идентификатор типа позиции, в соответствии со справочником или service - Услуга, commodity - Товар |
| groupName | string(500) | Наименование группы позиций заказа, используется для формирования отчетных документов |
| measure * | string(10) | Единица измерения словом, например шт.. Достаточно передать одно из двух полей — единицу или код: второе сервер заполнит сам по справочнику ОКЕИ |
| measureCode * | string(4) | Код единицы измерения по ОКЕИ, например 796. Парное поле к measure: см. строку выше |
| originCountry | string(20) | Страна происхождения товара, например, Россия |
| originCountryCode | string(4) | Код страны происхождения, например, Россия 643 |
| grossWeight | float | Вес брутто, например 125.45 |
| netWeight | float | Вес нетто, например 125.45 |
| quantity * | float | Количество, например 3 |
| amount * | float | Стоимость единицы, например 100.55, ограничений нет |
| amountWoVat * | float | Стоимость единицы без учета НДС |
| totalAmount * | float | Стоимость всех единиц с НДС, например123.55 |
| totalVatAmount * | float | Итого сумма НДС, например 23 |
| excise | float | Сумма акциза, например, 10.00 |
| gtdNumber | string(23) | Номер грузовой транспортной накладной |
| vatCode * | string(20) enum | Код ставки НДС Инвойсбокс, см. справочник - ставки НДС |
| serviceDate | date | Дата оказания услуги, если тип позиции = Услуга, например, 2023-11-16 |
| paymentType * | string(20) enum | Признак способа расчёта (тег 1214): full_prepayment, prepayment, advance, full_payment. В контракте поле помечено необязательным: если его не передать, сервер подставит значение из настроек магазина. Передавайте явно — так состав чека не зависит от настройки, которую можно поменять в кабинете |
| categoryType | string(20) enum | Справочник категории товаров: merchantHonestSignMap (Справочник категорий магазина) или honestSign (Честный знак) |
| category | string(20) enum | Товарная группа в справочнике категорий товаров магазина или системы Честный знак: milk, water и т.д. |
| metaData | object | Дополнительные данные элемента корзины |
InvoiceSetting
| Свойство | Тип | Описание | Пример значения |
|---|---|---|---|
| customerLocked | bool | Запретить изменение реквизитов плательщика (всех) | true - запретить изменения, по умолчанию false |
| customerLockedFields | array | Набор полей из Customer, которые требуется запретить для редактирования на платежной странице | ['type', 'name', 'phone', 'email', 'vatNumber', registrationAddress'] |
| paymentMethodIdLocked | bool | Запретить изменение способа оплаты (платёжного инструмента) | true - запретить изменения, по умолчанию false |
| paymentMethodId | int | Идентификатор предвыбранного способа оплаты (платёжного инструмента) | 123 |
| paymentMethodCode | string | Код предвыбранного способа оплаты. Публично доступны invoice — по счёту, invoice-rtp — счёт через Запрос о платеже, acquiring — карта, sbp — СБП. Остальные способы выдаются по запросу под конкретную интеграцию | invoice, invoice-rtp, acquiring, sbp |
| paymentMethodAutosubmit | bool | Флаг, отвечающий за автоматическое перенаправление покупателя на страницу оплаты в платежной системе, указанной в paymentMethodCode, без необходимости выбирать способ оплаты на платежной странице Инвойсбокс. Для корректной работы опции, требуется полное заполнение Customer | true |
| deliveryAddress | string | Полный адрес доставки товара | Московская область, Московская область, городской округ Химки, Химки, Вашутинское шоссе, 6 |
| savePaymentData | bool | Необходимо сохранить данные карты пользователя, для использования в рекуррентных платежах | true, false |
| clientId | string | Идентификатор клиента в системе магазина | 123, c-102322 |
OrderSetting
| Свойство | Тип | Описание |
|---|---|---|
| roundPolicy | string(20) enum | Как сверяется итоговая сумма заказа с суммой позиций. По умолчанию none. Значения: strict — строгое соответствие; none — допустимо, когда quantity × amount ≤ totalAmount; floor — округление к меньшему целому; ceil — к большему; halfUp — 0,50 к большему; halfDown — 0,50 к меньшему |
PaymentInfo
| Свойство | Тип | Описание |
|---|---|---|
| paymentToken | string | Платёжный токен |
| cardholderName | string | Имя держателя карты |
| expiration | string | Срок действия карты |
| maskedPan | string | Номер карты в замаскированом виде |
| paymentSystem | string | Название платёжной системы карты |