REST API — v1

API пополнения игр и ваучеров для реселлеров

Один REST API для пополнения игр, игровых счетов и кодов ваучеров: читайте каталог, проверяйте игрока, создавайте заказ и забирайте результат вебхуком или запросом статуса.

Это host-to-host (H2H) API пополнений, на котором работает SHOP2TOPUP. Обычный JSON API поверх HTTPS с Bearer-аутентификацией, ключами идемпотентности на UUID, опубликованными лимитами запросов и стабильными машиночитаемыми кодами ошибок — магазин, витрина или бот могут продавать цифровую игровую валюту программно, без клиентской библиотеки. Через этот же интерфейс прошло 3 000 000+ доставленных заказов за 5+ лет непрерывной работы.

Ищете цены, способы оплаты и описание самого партнёрского аккаунта? Прочитайте обзор реселлерской программы SHOP2TOPUP.

Первый вызов — ещё до всяких подписаний

Каждый эндпоинт — это обычный HTTPS-запрос с одним заголовком. Нечего устанавливать, нечего добавлять в зависимости и нечего дописывать в сборку. Вставьте вызов ниже, подставьте свой ключ — и живой ответ каталога уже в вашем терминале.

Запрос — cURL
curl -s "https://shop2topup.com/api/endpoints/v1/catalog/categories?bigCategoryId=1" \
  -H "Authorization: Bearer YOUR_KEY_ID.YOUR_KEY_SECRET"
Ответ — 200 OK
{
  "success": true,
  "categories": [
    {
      "id": 12,
      "name": "Free Fire",
      "description": "Garena Free Fire diamonds",
      "big_category_id": 1,
      "big_category_name": "Mobile Games"
    }
  ]
}

Базовый URL

https://shop2topup.com/api/endpoints/v1

Все задокументированные пути указаны относительно этого базового URL. Ответы приходят в JSON и всегда содержат булево поле success, поэтому одна ветка в клиенте отделяет успешный сценарий от всего остального.

Конверт ошибки

Ошибки сохраняют одинаковую форму при любом статусе. Ветвитесь по error.code — он часть контракта; никогда не ветвитесь по error.message, который написан для людей и может быть переформулирован в любой момент.

Ответ с ошибкой
{
  "success": false,
  "error": {
    "code": "PRICE_INCREASED",
    "message": "Price has increased beyond expected",
    "details": {
      "expected_unit_price": "0.950000",
      "current_unit_price": "0.980000"
    }
  }
}

Вся поверхность API на одном экране

Одиннадцать эндпоинтов покрывают всю интеграцию: прочитать каталог, узнать цену товара, проверить игрока, оформить заказ и потом свести его. Каждая строка ведёт прямо в полный справочник — со всеми параметрами, примером запроса и примером ответа.

МетодПутьЧто делаетСправочник
GET/accountGet Account InfoОткрыть справочник
GET/catalog/big-categoriesList Big CategoriesОткрыть справочник
GET/catalog/categoriesList CategoriesОткрыть справочник
GET/catalog/subcategoriesList Subcategories (Products)Открыть справочник
GET/catalog/subcategory/:itemId/priceGet Item PriceОткрыть справочник
GET/catalog/category/:categoryId/requirementsGet Category RequirementsОткрыть справочник
POST/player/validateValidate PlayerОткрыть справочник
POST/orders/createCreate OrderОткрыть справочник
GET/orders/:orderIdGet Order StatusОткрыть справочник
POST/orders/batchBatch Get OrdersОткрыть справочник
GET/ordersList OrdersОткрыть справочник

Плюс вебхуки статуса заказа — они полностью описаны ниже на этой странице.

Аутентификация и контроль доступа

Аутентификация — это один HTTP-заголовок. Каждый запрос несёт заголовок Authorization с Bearer-учёткой из идентификатора ключа и секрета, выпущенных на странице API Access в панели реселлера. Идентификатор ключа определяет аккаунт, секрет доказывает, что он ваш, и проверяется на сервере HMAC-проверкой.

Секрет показывается один раз, в момент создания, и потом его нельзя получить снова. Положите его в тот менеджер секретов, который уже есть в вашем стеке, прокидывайте переменной окружения и держите вне системы контроля версий. Потеряли — не восстанавливаете, а ротируете.

Нет ни редиректа OAuth, ни сессионной куки, ни refresh-токена, и планировать нечего. Для автоматизированных интеграций это важно: cron-задача или воркер очереди могут годами держать одну и ту же учётку без пути обновления, и нет счётчика истечения, из-за которого приходится вставать в три часа ночи.

К ключу можно прикрепить список разрешённых IP. Как только он непустой, запросы с любых других адресов отклоняются с IP_NOT_ALLOWED и HTTP 403 ещё до выполнения бизнес-логики, поэтому утёкшая учётка бесполезна вне ваших серверов. Ротация — это деплой, а не миграция: создайте новый ключ, выкатите, удалите старый.

Ключи принадлежат одному аккаунту реселлера и тратят кошелёк этого аккаунта, поэтому никогда не отправляйте секрет в браузер, в мобильную сборку или в публичный репозиторий. Вызывайте API со своего бэкенда, а фронтенд пусть общается с вашим бэкендом. Всё описанное здесь рассчитано на вызывающую сторону server-to-server.

Заголовок запроса
Authorization: Bearer <keyId>.<secret>

Проверка ключа одним вызовом

GET /account — это health-check для учётки. Он сразу отвечает на три вопроса: проходит ли ключ аутентификацию, включён ли аккаунт и хватает ли на кошельке средств на то, что вы собираетесь заказать. Вызывайте его при старте и перед любым пакетным прогоном.

Открыть справочник по аутентификации и аккаунту

Полная интеграция от начала до конца

Рабочая интеграция — это четыре шага. Примеры ниже проходят их все на живом API: на cURL, Node и PHP. Подставьте свою учётку — и они выполнятся как есть.

  1. 1

    Прочитайте каталог

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

  2. 2

    Проверьте игрока

    Сопоставьте идентификатор с игровым именем и регионом до того, как пойдут деньги. Именно здесь ловятся опечатки и ошибки с чужим регионом.

  3. 3

    Создайте заказ

    Сгенерируйте UUID, сохраните его и отправьте как order_id. Это ваш ключ идемпотентности и единственный безопасный способ повторить запрос на создание.

  4. 4

    Подтвердите доставку

    Дождитесь вебхука со статусом заказа, а для всего, что осталось в pending после вашего окна ожидания, используйте чтение статуса.

cURL — полный сценарийПоказать код
export S2T_KEY="YOUR_KEY_ID.YOUR_KEY_SECRET"
export S2T_BASE="https://shop2topup.com/api/endpoints/v1"

# 1. Find the item you want to sell.
curl -s "$S2T_BASE/catalog/subcategories?categoryId=12" \
  -H "Authorization: Bearer $S2T_KEY"

# 2. Read the exact price you will be charged for it.
curl -s "$S2T_BASE/catalog/subcategory/999/price" \
  -H "Authorization: Bearer $S2T_KEY"

# 3. Confirm the player exists BEFORE any money moves.
curl -s -X POST "$S2T_BASE/player/validate" \
  -H "Authorization: Bearer $S2T_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sub_category_id": 999, "player_id": "123456789", "server": "Asia"}'

# 4. Create the order. order_id is YOUR uuid and YOUR idempotency key.
curl -s -X POST "$S2T_BASE/orders/create" \
  -H "Authorization: Bearer $S2T_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "01912345-6789-7abc-8def-0123456789ab",
    "sub_category_id": 999,
    "quantity": 1,
    "requirements": { "player_id": "123456789", "server": "Asia" },
    "expected_unit_price": "0.950000"
  }'

# 5. Read the order back until it leaves "pending".
curl -s "$S2T_BASE/orders/01912345-6789-7abc-8def-0123456789ab" \
  -H "Authorization: Bearer $S2T_KEY"
Node.js — полный сценарийПоказать код
// Node 18+ — no dependencies, global fetch and global crypto.
const BASE = 'https://shop2topup.com/api/endpoints/v1';
const KEY = process.env.S2T_KEY; // "<keyId>.<secret>", server-side only.

async function call(path, init = {}) {
  const res = await fetch(BASE + path, {
    ...init,
    headers: {
      Authorization: `Bearer ${KEY}`,
      'Content-Type': 'application/json',
      ...(init.headers || {}),
    },
  });

  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') || 5);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(path, init);
  }

  const body = await res.json();
  if (!res.ok || body.success === false) {
    throw Object.assign(new Error(body?.error?.code || 'HTTP_' + res.status), {
      code: body?.error?.code,
      status: res.status,
    });
  }
  return body;
}

async function sell({ itemId, categoryId, playerId, server }) {
  // 1 + 2. Catalog and current price.
  const { subcategories } = await call(
    `/catalog/subcategories?categoryId=${categoryId}`,
  );
  const item = subcategories.find((s) => s.item_id === itemId);
  const { price } = await call(`/catalog/subcategory/${itemId}/price`);

  // 3. Player check before charging.
  const { player } = await call('/player/validate', {
    method: 'POST',
    body: JSON.stringify({
      sub_category_id: itemId,
      player_id: playerId,
      server,
    }),
  });

  // 4. Create the order. Persist orderId BEFORE the call so a crash mid-flight
  //    can be replayed with the same uuid instead of double-charging.
  const orderId = crypto.randomUUID();
  await saveIntent(orderId, itemId, playerId);

  const { order } = await call('/orders/create', {
    method: 'POST',
    body: JSON.stringify({
      order_id: orderId,
      sub_category_id: itemId,
      quantity: 1,
      requirements: { player_id: playerId, server },
      expected_unit_price: price.unit_price,
    }),
  });

  // 5. Confirm. A webhook usually beats the poll; the poll is the safety net.
  let status = order.status;
  while (status === 'pending') {
    await new Promise((r) => setTimeout(r, 20000));
    status = (await call(`/orders/${orderId}`)).order.status;
  }

  return { orderId, status, itemName: item?.name, playerName: player.player_name };
}
PHP — полный сценарийПоказать код
<?php
// PHP 8 — plain cURL, no SDK required.
const S2T_BASE = 'https://shop2topup.com/api/endpoints/v1';

function s2t(string $path, ?array $body = null): array
{
    $ch = curl_init(S2T_BASE . $path);
    $headers = [
        'Authorization: Bearer ' . getenv('S2T_KEY'),
        'Content-Type: application/json',
    ];

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_TIMEOUT        => 30,
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }

    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $data = json_decode($raw, true) ?: [];

    if ($status >= 400 || ($data['success'] ?? false) === false) {
        throw new RuntimeException($data['error']['code'] ?? 'HTTP_' . $status);
    }

    return $data;
}

// 1 + 2. Catalog, then the exact price for the item.
$catalog = s2t('/catalog/subcategories?categoryId=12');
$price   = s2t('/catalog/subcategory/999/price');

// 3. Player check before charging.
$player = s2t('/player/validate', [
    'sub_category_id' => 999,
    'player_id'       => '123456789',
    'server'          => 'Asia',
]);

// 4. Create the order with your own uuid as the idempotency key.
$orderId = sprintf(
    '%04x%04x-%04x-7%03x-%04x-%04x%04x%04x',
    random_int(0, 0xffff), random_int(0, 0xffff),
    random_int(0, 0xffff), random_int(0, 0x0fff),
    random_int(0x8000, 0xbfff),
    random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff)
);

$order = s2t('/orders/create', [
    'order_id'            => $orderId,
    'sub_category_id'     => 999,
    'quantity'            => 1,
    'requirements'        => ['player_id' => '123456789', 'server' => 'Asia'],
    'expected_unit_price' => $price['price']['unit_price'],
]);

// 5. Read it back until it settles.
do {
    sleep(20);
    $latest = s2t('/orders/' . $orderId);
} while ($latest['order']['status'] === 'pending');

echo $latest['order']['status'];

Две детали стоит скопировать дословно: сохраняйте UUID заказа до вызова создания, а не после, и перечитывайте цену, а не доверяйте закэшированному числу. Эти две привычки убирают почти все двойные списания и почти все отказы PRICE_INCREASED.

Вебхуки: колбэки со статусом заказа

Зарегистрируйте один HTTPS-эндпоинт — и платформа будет присылать на него результаты заказов по мере их появления, чтобы витрина перестала опрашивать и начала реагировать. Колбэк подписан, поэтому вы можете доказать, что тело пришло от нас, прежде чем что-то с ним делать.

События

СобытиеКогда срабатывает
order.completedThe order finished and everything it owed the buyer was delivered.
order.failedThe order failed and no sub-transaction is left pending or processing.
order.refundedThe order was fully refunded back to your wallet.
webhook.testYou triggered a signed test delivery from the panel to check your handler.

Заголовки доставки

ЗаголовокЗначение
Content-Typeapplication/json
X-Shop2Topup-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body
X-Shop2Topup-EventEvent name, e.g. order.completed
User-Agentshop2topup-webhook/1.0

Тело колбэка

У каждого колбэка одни и те же три поля верхнего уровня — event, timestamp и data, — а data повторяет объект заказа, который возвращает эндпоинт статуса. Коды ваучеров есть только у выполненного заказа, player_name может быть null, а sub_transaction_summary появляется, когда заказ был разбит на единицы.

Тело POST — order.completed
{
  "event": "order.completed",
  "timestamp": "2026-03-11T14:30:00.000Z",
  "data": {
    "order_id": "01912345-6789-7abc-8def-0123456789ab",
    "status": "completed",
    "player_id": "123456789",
    "player_name": "ProGamer99",
    "subcategory_name": "FF 100 Diamonds",
    "quantity": 1,
    "charged_amount": "0.950000",
    "currency": "USD",
    "created_at": "2026-03-11T14:28:00.000Z",
    "completed_at": "2026-03-11T14:30:00.000Z",
    "vouchers": [
      {
        "code": "XXXX-YYYY-ZZZZ",
        "serial_number": "SN-9928371",
        "expiry_date": "2027-03-11"
      }
    ],
    "sub_transaction_summary": {
      "total": 1,
      "completed": 1,
      "failed": 0,
      "pending": 0,
      "processing": 0,
      "retrying": 0,
      "refunded": 0
    }
  }
}

Проверка подписи

Каждая доставка несёт X-Shop2Topup-Signature вида sha256= и далее hex HMAC-SHA256 от сырого тела запроса на вашем webhook-секрете. Проверяйте по сырым байтам, до любого разбора JSON, и сравнивайте за константное время. Обработчик, который сначала парсит, а потом проверяет, можно обмануть пересобранным телом.

Node.js — проверка и подтверждение
// Express — verify the signature against the RAW body, not the parsed object.
const crypto = require('crypto');

app.post(
  '/shop2topup/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.get('X-Shop2Topup-Signature') || '';
    const expected =
      'sha256=' +
      crypto
        .createHmac('sha256', process.env.S2T_WEBHOOK_SECRET)
        .update(req.body)
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).end();
    }

    const payload = JSON.parse(req.body.toString('utf8'));

    // Answer fast, then do the work. The delivery times out after 10 seconds.
    res.status(200).end();
    enqueueSettlement(payload.data.order_id, payload.event);
  },
);

Доставка и повторы

Колбэк отправляется один раз, и у вашего эндпоинта есть десять секунд на ответ. Автоматических повторов и экспоненциальной задержки за этим нет, и это сделано намеренно: авторитетная запись — эндпоинт статуса, поэтому сверка принадлежит опросу, которым управляете вы, а не расписанию повторов, которого вы не видите. Возвращайте 200 сразу, а работу выполняйте в своей очереди.

Идемпотентные обработчики

Стройте обработчик вокруг order_id и делайте повтор колбэка холостым. Даже при одной попытке доставки ваша собственная инфраструктура иногда отдаст одно и то же сообщение двум воркерам, а заказ, который зачислил клиенту дважды, — гораздо худший баг, чем заказ, зачисленный с опозданием.

Регистрация и секреты

Регистрация URL колбэка, отправка подписанной тестовой доставки и ротация секрета подписи выполняются в панели реселлера под вашей учётной записью, а не через публичный API. HTTPS обязателен, приватные адреса не принимаются.

Открыть API access в панели реселлера

Лимиты запросов и обработка ошибок

Лимиты опубликованы, считаются на аккаунт и применяются по скользящему окну. Самый широкий запас — у работы с каталогом; эндпоинты статуса намеренно узкие, потому что задуманы как запасной вариант для сверки, а не как цикл опроса.

МаршрутЗапросовОкноСчитается на
GET /catalog/* (all five)8060sper account
POST /orders/create12060sper account
GET /orders/:orderId360sper account + order id
POST /orders/batch260sper account
GET /orders260sper account
GET /account6060sper account
POST /player/validatededicated limiterper account

Каждый ответ несёт X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset, поэтому корректному клиенту не приходится гадать, где он находится. Следите за счётчиком remaining и притормаживайте до того, как вас ограничат, а не после.

Ограниченный ответ — 429
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 80
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1765000000

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 45 seconds.",
    "details": {
      "limit": 80,
      "remaining": 0,
      "retry_after": 45,
      "window_seconds": 60
    }
  }
}

Ошибки, с которыми вы столкнётесь первыми, и их решения

Отказы всегда возвращают стабильный код рядом с HTTP-статусом. Здесь собраны те, на которые новая интеграция натыкается в первую же неделю, и к каждому приложено изменение, которое его снимает.

КодHTTPЧто означаетКак исправить
INVALID_API_KEY401Invalid API keyThe provided API key does not match any active key in the system.Verify your key ID and secret are correct. Regenerate the key from the API Access panel if needed.
IP_NOT_ALLOWED403IP address not in allowlistThe request originated from an IP address not in your API key allowlist.Add your server IP to the allowlist in the API Access panel, or remove IP restrictions.
MISSING_REQUIRED_FIELD400Missing required fieldA required field is missing from the request body.Check the endpoint documentation for required fields.
INVALID_UUID_FORMAT400Invalid UUID formatThe order_id is not a valid UUID format.Use any valid UUID format (v1, v4, v7, etc). Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
PLAYER_NOT_FOUND400Player not foundThe player ID could not be found in the game system.Verify the player ID is correct. Check if a zone_id is required for the game.
REGION_MISMATCH400Region mismatchThe player belongs to a different region than expected.Use the correct zone_id or genshin_zone for the player region.
INSUFFICIENT_BALANCE400Insufficient wallet balanceYour wallet does not have enough funds to complete this order.Top up your wallet from the Reload page before placing the order.
OUT_OF_STOCK400Product is out of stockThe requested product is currently unavailable.Try again later or choose a different product.
DUPLICATE_ORDER409Duplicate order IDAn order with this UUID already exists. This is an idempotency protection.Generate a new UUID for a new order. If retrying, use the same UUID to get the existing order.
PRICE_INCREASED400Price has increased beyond expectedThe current unit price is higher than the expected_unit_price you provided.Fetch the latest price with GET /catalog/subcategory/:id/price and update expected_unit_price.
RATE_LIMIT_EXCEEDED429Rate limit exceededYou have exceeded the rate limit for this endpoint. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. When exceeded, the 429 response body contains: limit, remaining (0), retry_after (seconds to wait), and window_seconds. A Retry-After header is also set.Wait for the number of seconds indicated by retry_after or the Retry-After header before retrying. Monitor X-RateLimit-Remaining headers to avoid hitting limits.
SERVICE_UNAVAILABLE503Service temporarily unavailableThe service is temporarily down for maintenance or overloaded.Wait and retry after a few minutes.
Полный справочник кодов ошибок — все коды (32) и способы их устранения

Ответы на вопросы по интеграции

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

Как реселлерский API аутентифицирует запросы?

Одним HTTP-заголовком: Authorization: Bearer <keyId>.<secret>. Пара ключей выпускается на странице API Access в панели реселлера, секрет показывается один раз при создании, и сервер проверяет его HMAC-проверкой на каждом вызове. Нет ни редиректа OAuth, ни сессионной куки, ни токена, который нужно обновлять, — один и тот же заголовок работает и для первого запроса, и для всех последующих.

На какой базовый URL и версию должен ходить мой клиент?

На один базовый URL, и он боевой: отдельного песочного хоста, на котором сначала пишут, а потом переключаются, здесь нет. Направьте клиент на базовый URL v1, показанный выше, отправляйте Authorization: Bearer <keyId>.<secret> в каждом запросе и начните с GET /account, чтобы убедиться в работоспособности ключа и формате заголовка. POST /orders/create — единственный вызов, который двигает деньги: оставьте его напоследок, запускайте на самом мелком номинале и отправляйте вместе с ним expected_unit_price, пока клиент ещё обкатывается.

Какое поле является ключом идемпотентности в POST /orders/create?

Поле order_id — UUID, который вы генерируете у себя и отправляете в теле создания. Повторное создание с уже существующим order_id получает HTTP 409 и код DUPLICATE_ORDER вместо второго заказа, поэтому повтор запроса не превращается во второе списание; исходный заказ читается через GET /orders/:orderId. Сгенерируйте и сохраните UUID до первой попытки: UUID, созданный после таймаута, — это новый ключ, а не ключ повтора, и сервер не склеит два order_id, которыми вы имели в виду одно и то же.

Запрос завершился таймаутом. Заказ создан?

Выясните это чтением статуса, а не догадками. Вызовите GET /orders/:orderId с тем же UUID, что отправляли: если заказ вернулся, таймаут случился уже после его приёма и повторять запрос не нужно. Если пришёл ORDER_NOT_FOUND, ничего не списано и запрос на создание с тем же UUID можно безопасно повторить.

Как проверить подпись у вебхук-колбэка?

Пересчитайте HMAC по сырому телу запроса и сравните с заголовком X-Shop2Topup-Signature. В заголовке лежит sha256=<hex>: посчитайте HMAC-SHA256 по тем самым байтам, которые пришли, своим секретом вебхука, добавьте префикс sha256= и сравнивайте за константное время — но никогда не по повторно сериализованному JSON-объекту, потому что разбор и обратная сборка меняют байты, и совпадения не будет никогда. Несовпадение отклоняйте с 401, на корректную доставку отвечайте 200 в пределах десяти секунд, а сверку выполняйте потом в своей очереди.

Что происходит при превышении лимита запросов?

Вы получаете HTTP 429 с кодом RATE_LIMIT_EXCEEDED и заголовком Retry-After. В теле ответа то же число повторяется в retry_after, плюс приходят limit, remaining и window_seconds. Подождите ровно столько секунд и повторите один раз; не повторяйте сразу и не размазывайте один и тот же вызов по нескольким ключам — счётчик привязан к аккаунту, а не к соединению.

Какие поля возвращает POST /player/validate?

Поле player_name, а также zone_id и region, если игра их отдаёт, — внутри объекта data в ответе. Создание заказа само разрешает игрока и проверяет регион, а попытка из чужого региона отклоняется кодом REGION_MISMATCH (HTTP 400) до какого-либо списания с кошелька, так что баланс защищает не этот вызов. Вызывайте его потому, что прочитанный player_name — единственный практичный способ показать покупателю аккаунт, который он собирается пополнить, и поймать опечатку в идентификаторе до подтверждения: имя всегда приходит из игры, а не из того, что ввёл покупатель.

Как превратить ответ requirements в тело заказа?

Разложите его по field_name: каждая запись, которую возвращает GET /catalog/category/:categoryId/requirements, становится одним свойством объекта requirements, отправляемого в POST /orders/create. В каждой записи есть data_type — форма знает, что рисовать, а сервер знает, к какому типу приводить; записи single_select и multi_select дополнительно везут список допустимых значений, и предлагать нужно именно его, а не свободный ввод. Кешируйте список по категории вместо чтения на каждый заказ и перечитывайте его, когда создание возвращает MISSING_REQUIRED_FIELD.

Как клиенту обрабатывать ответ PRICE_INCREASED?

Перечитать цену, принять решение и отправить снова — но не повторять то же тело. PRICE_INCREASED (HTTP 400) означает, что текущая цена за единицу выше отправленного вами expected_unit_price, поэтому создание отклонено и ничего не списано; в details ошибки лежат и expected_unit_price, и current_unit_price, а слепой повтор со старым числом будет отклоняться каждый раз. Прочитайте GET /catalog/subcategory/:itemId/price, решите, устраивает ли вас новое число, и отправьте создание снова с обновлённым expected_unit_price и тем же order_id — отклонённая попытка его не израсходовала. Если expected_unit_price не отправлять, защита выключается совсем, а автоматической интеграции это почти никогда не нужно.

Как частично доставленный заказ выглядит в ответе?

Как статус partial, а разбивка лежит в sub_transaction_summary. Этот объект считает единицы по состояниям — completed, refunded, failed, pending, processing, retrying, — а sub_transactions отдаёт строку за каждым счётчиком, так что ваша логика сверки читает числа, а не догадывается по одному слову. Считайте pending и processing нетерминальными и продолжайте ждать; completed, partial, failed и refunded — терминальные. Колбэки order.completed, order.failed и order.refunded несут ту же сводку, поэтому payload с проверенной подписью закрывает заказ без повторного чтения.

Что получит запрос с адреса вне списка разрешённых?

HTTP 403 с кодом IP_NOT_ALLOWED — ответ приходит до любой бизнес-логики. Проверка работает, пока список разрешённых IP у ключа непустой, поэтому утёкший ключ бесполезен где-либо, кроме ваших собственных исходящих адресов. Ветвитесь по error.code, а не по тексту сообщения, и внезапный IP_NOT_ALLOWED в проде читайте как смену исходящего адреса — новый NAT-шлюз или новая подсеть воркеров, — а не как сломанный ключ. Оставляйте список пустым только если исходящий адрес действительно нестабилен.

Сколько времени занимает выполнение заказа?

Считайте выполнение асинхронным, а не мгновенным. POST /orders/create отвечает, как только заказ принят и деньги списаны с кошелька, — обычно со статусом pending; дальше исполнение идёт на отдельных воркерах, а терминальное состояние приходит к вам вебхуком или при следующем чтении статуса. Не блокируйте клиентский HTTP-запрос ожиданием финального состояния: примите заказ, ответьте покупателю и завершите расчёт вне основного потока.

Создайте аккаунт, сгенерируйте ключ, запускайтесь

Между регистрацией и первым вызовом API нет ни очереди на одобрение, ни периода ожидания. Создайте аккаунт реселлера, сгенерируйте ключ на странице API Access, пополните кошелёк — и первый заказ может уйти в тот же день.

Вы не разработчик этого проекта? Коммерческая сторона описана на странице реселлерской программы SHOP2TOPUP.

API пополнения игр и ваучеров для реселлеров — REST API