Документация

Заказы: создание, отслеживание и все возможные финалы

Один эндпоинт создаёт заказы, два читают их обратно. Интересен здесь не сам вызов, а набор состояний, в которые заказ может прийти, и то, под какие из них вам придётся писать код.

Создание заказа

POST /b2b/orders
X-Api-Key: gc_live_…
X-Idempotency-Key: 7f3c1a9e-2b44-4d1a-9d4e-1c0f9a2b3c4d
Content-Type: application/json
{
"items": [
{ "productId": 34521, "quantity": 1, "deliveryData": { "gameUserId": "5123456789" } }
],
"externalOrderId": "MP-98765",
"callbackUrl": "https://api.yourshop.com/webhooks/gamecore"
}
ПолеОбязательноеПравила
itemsдаОт 1 до 200 записей. Пустой массив — 400.
items[].productIdдаЧисловой id из каталога.
items[].quantityнетПо умолчанию 1; целые единицы, от 1 до 10000.
items[].deliveryDataдаОтображение строка-в-строку. Если товару поля не нужны, передавайте {} — сам ключ не опциональный.
externalOrderIdнетВаша собственная ссылка. Возвращается на обоих читающих эндпоинтах и в вебхуке — но не в ответе на создание, где приходят только коды. Сопоставление сохраняйте у себя в момент создания.
callbackUrlнетКуда придёт вебхук. Проверяется при создании.

X-Idempotency-Key обязателен: без него придёт 400. Эта проверка — первое действие хендлера, а значит, она идёт после аутентификации вашего ключа и после валидации тела по схеме: вызов без ключа получит 401, а кривое тело — 422, что бы ни было в заголовке. Поля цены нет: цену мы считаем в этот момент, по текущему курсу и вашему тарифу за объём — он применяется здесь и нигде в каталоге.

Всё, что происходит дальше, — одна транзакция базы: списание с баланса, запись платежа и заказы коммитятся вместе или не коммитятся вовсе. Состояния «деньги ушли, а заказа нет» не существует.

Один запрос — несколько заказов

{
"success": true,
"data": {
"paymentCode": "P-AB12CD",
"totalAmount": 2129.0,
"orders": [
{ "code": "ash-XY7K3M", "gameId": "pubg-mobile", "gameName": "PUBG Mobile", "total": 1064.5, "itemCount": 1 },
{ "code": "ash-QW4N8P", "gameId": "free-fire", "gameName": "Free Fire", "total": 1064.5, "itemCount": 1 }
]
}
}

Позиции группируются по игре поставщика и по самому поставщику — а не по игре в том виде, в каком вы видите её в каталоге, — и каждая группа становится отдельным заказом со своим кодом и своим жизненным циклом. Поэтому корзина из трёх игр даст минимум три заказа, которые выполняются и падают независимо друг от друга, а одна каталожная игра может дать больше одного заказа, если её SKU приходят из нескольких источников. Никогда не считайте, что один запрос — это один заказ, даже для одной игры.

Два следствия, которые лучше заложить сразу: в вашей модели данных нужна связь «один ваш заказ — много наших», а по массиву orders нужно итерироваться даже там, где вы ждёте одну запись.

Код заказа — это короткий префикс вашего аккаунта плюс шесть символов из алфавита без O, 0, 1, I и L: ash-XY7K3M. Его безопасно диктовать голосом. Сохраняйте каждый код — дальше всё делается через него.

Чем оплачивается заказ

Заказы оплачиваются с вашего B2B-баланса в одном из двух режимов, согласованных при подключении:

  • Предоплата — списание должно уместиться в баланс.
  • Овердрафт — баланс может уйти в минус в пределах кредитного лимита.

Если не умещается, ничего не создаётся, а вызов возвращает 402 с причиной: Insufficient balance или Exceeds credit limit (available: …) с остатком. Это бизнес-условия, а не ошибки в запросе: повторить тот же вызов с тем же идемпотентным ключом после пополнения — ровно правильный ход.

Тот же статус носит третье сообщение, означающее совсем другое. Concurrent balance modification говорит, что ваш баланс изменило что-то ещё — обычно другой ваш же параллельный вызов создания заказа — пока этот коммитился; транзакция откатилась, поэтому деньги не двигались и заказа нет. Пополнять нечего: сделайте короткую паузу и повторите тот же самый запрос с тем же ключом. Если создавать заказы по аккаунту по одному, такое встречается редко. Полная таблица повторов — на странице Идемпотентность и ошибки.

Статусы заказа

СтатусЧто означаетТерминальный
pendingСоздан, ещё не оплачен. Живёт миллисекунды внутри транзакции — обычно вы его не увидите.нет
processingОплачен и передан поставщику. Нормальный ответ сразу после создания.нет
completedВсе позиции заказа доставлены.да
failedЗаказ не выполнился — включая случай, когда выполнилась только часть позиций.да

Ещё два значения, cancelled и refunded, существуют в самой колонке, но через этот API недостижимы: эндпоинта отмены нет, инициировать возврат партнёр не может. Обрабатывать их на всякий случай можно, но строить на их ожидании логику не нужно.

Статусы позиций

Статус позиции отдельный от статуса заказа, и словарь у него шире:

Статус позицииЧто означает
pendingПринята, ещё не разрешена. Это же вы видите, пока позиция ждёт очереди повторов.
queuedОтложена в нашу очередь повторов, потому что поставщик был недоступен.
awaiting_codeОжидает код, приходящий вне канала API.
awaiting_screenshotОжидает ручного подтверждения.
completedДоставлена. Именно здесь заполняется cdKeys, если у товара вообще есть ключи.
failedНе доставлена. Причину объясняют errorCode и errorMessage.

Любой незнакомый статус позиции трактуйте как «ещё в работе». Код, который ветвится только на completed и failed, будет вечно ждать на awaiting_code, и это ожидание невидимо до жалобы покупателя.

Чтение одного заказа

GET /b2b/orders/ash-XY7K3M
X-Api-Key: gc_live_…
{
"success": true,
"data": {
"code": "ash-XY7K3M",
"externalOrderId": "MP-98765",
"gameId": "pubg-mobile",
"gameName": "PUBG Mobile",
"status": "completed",
"totalAmount": 1064.5,
"createdAt": "2026-08-18T18:30:58.114Z",
"completedAt": "2026-08-18T18:31:15.432Z",
"items": [
{
"id": 991204,
"productName": "PUBG Mobile 660 UC",
"amount": 1,
"price": 1064.5,
"status": "completed",
"cdKeys": [],
"errorCode": null,
"errorMessage": null,
"completedAt": "2026-08-18T18:31:15.432Z"
}
]
}
}

Неизвестный код отдаёт 404 с телом { "success": false, "error": "Order not found" } — и ровно то же самое отдаёт код чужого партнёра: поиск идёт в пределах вашего аккаунта.

cdKeys возвращается только для позиций в статусе completed. В любом другом статусе там [], что означает «ещё нет», а не «не будет». Для товара id_only он останется [] навсегда, и это правильное финальное состояние.

Список заказов

GET /b2b/orders?status=failed&page=1&limit=100
ПараметрПо умолчаниюПримечания
page1Нумерация с единицы.
limit20Ограничен сверху 100.
statusТочное совпадение со статусом заказа. Неизвестное значение вернёт пустую страницу, а не ошибку.

Сортировка фиксированная: сначала новые по времени создания. В ответе есть pagination с page, limit и total, а каждая строка — это заказ без позиций; за позициями идите в запрос одного заказа.

Этот эндпоинт нужен для сверки, а не для доставки. Он правильный инструмент для «покажи всё, что упало за последний час», и неправильный — чтобы узнать, выполнился ли один конкретный заказ.

Частичная доставка

Заказ из нескольких позиций может закончиться так, что часть доставлена, а часть нет. Что происходит дальше — самая неочевидная часть этого API, поэтому проговорим её по пунктам:

  • Статус заказа становится failed, а не «частично выполнен». Частичного статуса не существует. Доставленные позиции при этом остаются доставленными и лежат в ответе со status: "completed".
  • Вебхук не отправляется. Вебхуки эмитятся, когда выполнились все позиции или когда провалились все. Смешанный исход не эмитит ничего: если вы полагаетесь только на вебхуки, частичный заказ пройдёт молча.
  • Проваленные строки автоматически возвращаются на баланс. Стоимость доставленных строк — нет: товар у вас, деньги за него списаны.

Практическое правило: сверка не опциональна. Опрашивайте заказы, которые через несколько минут всё ещё в processing, и всегда читайте массив позиций, а не только статус заказа: failed на уровне заказа вполне может означать, что покупатель получил бо́льшую часть корзины.

Когда заказ не двигается

Две разные ситуации выглядят снаружи одинаково, и разрешается сама собой только одна.

Поставщик недоступен. Позиция уходит в очередь повторов и повторяется по лестнице примерно 1 минута, 5 минут, 15 минут, 1 час и 6 часов, до десяти попыток. Если очередь сдаётся, позиция помечается failed, заказ разрешается, а деньги возвращаются на ваш баланс. Всё это время вы видите только processing.

Поставщик принял заказ и не разрешает его. Мы опрашиваем его примерно раз в минуту столько, сколько потребуется. Часовой таймаут, который сам провалил бы такой заказ, отсутствует: заказ остаётся в processing неограниченно долго, и деньги автоматически не возвращаются.

Поэтому заказ, висящий в processing часами, не нужно продолжать опрашивать. Заведите на него алерт — для автоматических типов доставки порог в час разумен — и напишите нам с кодом заказа. Ручные типы доставки законно идут дольше; порог для них задайте отдельно.

Поллинг или вебхуки

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

Рабочая схема:

  • Указывайте callbackUrl в каждом заказе и реагируйте на событие, когда оно пришло.
  • Раз в несколько минут проходите по своим ещё открытым заказам старше пары минут и перечитывайте каждый.
  • Не опрашивайте один заказ в плотном цикле. Его статус не может обновиться быстрее нашего собственного опроса поставщика — примерно раз в минуту, — а бюджет у вас 500 запросов в минуту на всю интеграцию.
  • Сделайте оба пути идемпотентными друг относительно друга. Один и тот же заказ регулярно будет разрешаться дважды — вебхуком и сверкой, — и оба раза это должно быть безопасно.

Перед запуском

  • В вашей модели один ваш заказ связан со многими нашими по полю code.
  • failed на уровне заказа разбирается по позициям до того, как вы что-то скажете покупателю.
  • Частичные заказы находит ваша сверка, потому что вебхук о них не сообщит.
  • Незнакомые статусы позиций считаются «в работе».
  • cdKeys: [] у выполненной позиции id_only — это успешный путь, а не ошибочный.
  • Заказ, застрявший в processing дольше вашего порога, поднимает алерт, а не новый цикл опроса.
  • 402 по балансу повторяется после пополнения тем же идемпотентным ключом, а не новым.

Обновлено 18 августа 2026 г.

Смотрите также: Поставка по API · Цены и тарифы

Нужен ключ API?

Напишите, какие игры и регионы вы продаёте, — выдадим ключ и пройдём интеграцию вместе с вами. На вопросы по интеграции отвечают те же люди, которые ведут API.