Разработчикам/Интеграция

Каталог и заказы

Здесь по шагам описан весь процесс покупки: проверить баланс, найти товар, узнать цену, оформить заказ и получить коды.

См. также: Аутентификация · Общие правила · Вебхуки

#Аккаунт и баланс

#Аккаунт

GET/api/v1/account показывает данные вашей компании и то, включён ли доступ к API.

JSON
{
  "merchant_id": "M00000001",
  "name": "Acme Shop",
  "legal_name": "Acme Shop Ltd",
  "tier": "standard",
  "billing_email": "[email protected]",
  "status": "active",
  "kyb_status": "approved",
  "api_access_enabled": true,
  "default_currency": "USD"
}
  • default_currency — валюта вашего кошелька. Все цены, которые вы платите, указаны в ней.
  • api_access_enabled становится true, когда CardV одобрит вашу компанию.

#Баланс

GET/api/v1/balance показывает, сколько денег вы можете потратить.

JSON
{
  "currency": "USD",
  "balance": "1520.4000",
  "reserved_amount": "0.0000",
  "available_balance": "1520.4000",
  "low_balance_threshold": "200.0000",
  "low_balance_notified_at": null,
  "is_active": true
}
  • available_balance — сумма, которую можно потратить прямо сейчас. Это balance минус reserved_amount.
  • Заказ на сумму больше available_balance отклоняется, деньги не списываются.
  • low_balance_threshold — порог, при котором приходит письмо о низком балансе. Он задаётся в кабинете.
  • Пополнить кошелёк можно в кабинете.

#Товары (SKU)

SKU — это конкретный товар, который можно купить, например «Steam Wallet 10 USD». Его ID выглядит так: S000456. Цену запрашивают и заказ оформляют именно по SKU.

GET/api/v1/skus возвращает список товаров, которые вам доступны. Пример:

HTTP
GET /api/v1/skus?search=steam&region=US&limit=50&offset=0

Фильтры (все необязательные):

ФильтрПримерЧто ищет
searchsteamID товара, название или бренд
brandSteamНазвание бренда (регистр не важен)
regionUSКод или название страны
verticalgift_cardТоварное направление
product_typepin_codeСпособ выдачи

Постраничная выдача: передайте limit (по умолчанию 100, максимум 500) и offset. Увеличивайте offset и запрашивайте снова, пока offset не дойдёт до count.

JSON
{
  "count": 7,
  "limit": 1,
  "results": [
    {
      "sku_id": "S000456",
      "product_id": "P000123",
      "name": "Steam Wallet 10 USD",
      "product_name": "Steam Wallet US",
      "brand": "Steam",
      "region": "US",
      "vertical": "gift_card",
      "product_type": "pin_code",
      "denomination_type": "fixed",
      "denomination_value": "10.0000",
      "face_currency": "USD",
      "merchant_price": "9.2500",
      "settlement_currency": "USD",
      "availability": "available",
      "min_quantity": 1,
      "max_quantity": 100,
      "required_input_schema": [],
      "...": "more fields"
    }
  ],
  "filter_options": {"brands": [], "regions": [], "verticals": []}
}

GET/api/v1/skus/{sku_id} возвращает один товар с теми же полями.

Самые полезные поля:

ПолеЧто значит
sku_idID, по которому запрашивают цену и оформляют заказ.
merchant_priceВаша цена за единицу в валюте settlement_currency.
availabilityavailable или unavailable. Заказывайте только available.
denomination_typefixed или range. См. фиксированный номинал и диапазон.
face_currencyВалюта, указанная на карте. Может отличаться от валюты кошелька.
min_quantity, max_quantityСколько единиц может быть в одной строке заказа.
product_typepin_code (вы получаете код) или direct_charge (мы пополняем аккаунт).
required_input_schemaДанные, которые нужно передать для прямого пополнения.
brand_logo_url, image_urlКартинки на серверах CardV или "".
description, redemption_instructions, termsТексты, которые можно показать покупателям.

Советы:

  • Вы видите только активные товары, открытые для вашего аккаунта. Для остальных придёт 404.
  • Обновляйте список товаров каждые 5–15 минут. Прямо перед заказом всегда запрашивайте цену.
  • filter_options перечисляет бренды, регионы и направления, по которым можно фильтровать.

#Фиксированный номинал и диапазон

У большинства товаров фиксированный номинал, например 10 USD. У некоторых номинал задаётся диапазоном: покупатель сам выбирает сумму, например от 5 до 500 USD.

ТипПри запросе ценыПри заказе
fixedПередайте quantityНе передавайте amount
rangeПередайте quantity и amountПередайте amount

Для товара с диапазоном amount должен быть между min_face_value и max_face_value. Сумма указывается в face_currency.

#Прямое пополнение

Некоторые товары пополняют аккаунт вашего покупателя напрямую, например игровой аккаунт. Для этого CardV нужны данные аккаунта. Какие именно — указано в товаре:

JSON
"required_input_schema": [
  {"key": "player_id", "label": "Player ID", "required": true},
  {"key": "server", "label": "Server", "required": false}
]

Передайте значения в поле inputs строки заказа, используя каждый key:

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • Поле обязательно, если у него не указано "required": false.
  • Если обязательного значения нет, заказ отклоняется с ошибкой items.
  • Это персональные данные вашего покупателя. Защищайте их (см. Безопасность).

#Запрос цены

Запрос цены показывает текущую цену за нужное количество.

HTTP
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00
JSON
{
  "sku_id": "S000456",
  "settlement_currency": "USD",
  "merchant_price": "9.2500",
  "quantity": 2,
  "total_price": "18.5000",
  "min_quantity": 1,
  "max_quantity": 100,
  "availability": "available"
}
  • Запрос цены не закрепляет цену. Цены могут измениться в любой момент.
  • Чтобы подстраховаться, при заказе передайте merchant_price в поле expected_unit_price. Если цена изменилась, CardV отклонит заказ и ничего не спишет.
  • Количество или сумма вне допустимого диапазона вернёт HTTP 400 с ошибкой quantity или amount.

#Оформление заказа

POST/api/v1/orders покупает один или несколько товаров и оплачивает их из кошелька. Этот запрос нужно подписать.

JSON
{
  "external_order_id": "SHOP-20260929-10001",
  "items": [
    {"sku_id": "S000456", "quantity": 2, "expected_unit_price": "9.2500"},
    {"sku_id": "S000789", "amount": "25.00", "expected_unit_price": "23.7500"},
    {
      "sku_id": "S000900",
      "expected_unit_price": "4.9000",
      "inputs": {"player_id": "123456789"}
    }
  ]
}
ПолеОбязательноЧто значит
external_order_idДаВаш номер заказа, от 1 до 120 символов. Должен быть уникальным.
itemsДаОдна или несколько строк заказа.
items[].sku_idДаТовар, который вы покупаете.
items[].quantityНетСколько штук. По умолчанию 1.
items[].amountДля товаров с диапазономНоминал, который вы покупаете.
items[].expected_unit_priceЖелательноmerchant_price из запроса цены. Передавайте всегда.
items[].inputsДля прямого пополненияДанные аккаунта для прямого пополнения.

Приняв заказ, CardV сразу списывает с кошелька всю сумму. Затем CardV в фоновом режиме начинает выдачу кодов.

Ответ приходит с HTTP 201:

JSON
{
  "idempotent_replay": false,
  "order": {
    "order_id": "O-00001234",
    "external_order_id": "SHOP-20260929-10001",
    "status": "accepted",
    "total_amount": "46.2000",
    "...": "more fields"
  }
}

Сохраните order.order_id. Кодов в этом ответе нет никогда — их нужно получить позже (см. Получение заказа).

#Отклонённые заказы

Отклонённый заказ возвращает HTTP 400, деньги не списываются. Причину подскажет ключ ошибки:

КлючПричинаЧто делать
itemsИзменилась цена, товар недоступен, неверная сумма или не хватает данныхЗапросите цену заново, исправьте и отправьте снова
balanceВ кошельке не хватает денегПополните кошелёк в кабинете
riskПревышен лимит на размер заказа или дневной лимитСвяжитесь с CardV
external_order_idВаш номер заказа уже занят другим заказомСм. Безопасный повтор

Пример ошибки из-за изменения цены:

JSON
{
  "items": "SKU S000456 price changed from 9.2500 to 9.4100 USD; refresh quote and confirm again."
}

Тариф вашего аккаунта определяет лимиты на один заказ, на сумму покупок за день и на число заказов за день. Дневные лимиты сбрасываются в 00:00 UTC. Узнать свои лимиты можно у CardV.

#Безопасный повтор

Ваш номер заказа (external_order_id) защищает от двойной покупки. Если вы отправите тот же заказ с тем же номером ещё раз, CardV не спишет деньги повторно, а вернёт уже существующий заказ.

Что вы отправляетеЧто получаете
Новый номер заказаHTTP 201. Новый заказ. Деньги списываются с кошелька.
Тот же номер и тот же заказHTTP 200 и "idempotent_replay": true. Существующий заказ. Без списания.
Тот же номер, но другой заказHTTP 400 с ошибкой external_order_id. Ничего не происходит.

«Тот же заказ» — это те же строки в том же порядке, с теми же товарами, количеством, суммой и данными inputs. Если вы передаёте expected_unit_price, он должен совпадать с ценой из первого заказа.

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

#Как повторять без риска

Если вы не получили понятного ответа, просто отправьте тот же заказ ещё раз.

Text
POST /orders с номером заказа R
 ├─ 201 или 200 → сохраните order_id. Готово.
 ├─ 400 items / balance / risk → заказ не создан.
 │      Устраните причину и отправьте снова. R можно оставить.
 ├─ 400 external_order_id → R уже занят другим заказом. Остановитесь и разберитесь.
 ├─ 403 ошибка подписи → подпишите заново и отправьте то же тело.
 ├─ 429 → дождитесь Retry-After, подпишите заново, отправьте то же тело.
 └─ тайм-аут, 5xx или обрыв соединения
        → отправьте то же тело с тем же R.
          Придёт 201 (первая попытка не дошла) или 200 (дошла).

Правила:

  • Никогда не придумывайте новый номер заказа из-за того, что ответ потерялся. Если первый запрос всё-таки дошёл, с новым номером вы купите всё дважды.
  • Для каждой повторной отправки нужны новые время, nonce и подпись. Тело остаётся прежним.

#Получение заказа

GET/api/v1/orders/{order_id} возвращает заказ, его статус и коды.

JSON
{
  "order_id": "O-00001234",
  "external_order_id": "SHOP-20260929-10001",
  "status": "succeeded",
  "currency": "USD",
  "total_amount": "18.5000",
  "created_at": "2026-09-29T08:15:30.123456Z",
  "updated_at": "2026-09-29T08:15:41.004211Z",
  "items": [
    {
      "sku_id": "S000456",
      "product_name": "Steam Wallet US",
      "quantity": 2,
      "unit_price": "9.2500",
      "total_price": "18.5000",
      "delivery_count": 2,
      "deliveries": [{"...": "see Codes below"}]
    }
  ],
  "...": "more fields"
}
  • total_amount — сумма, списанная при приёме заказа.
  • items[].unit_price — цена, закреплённая за этим заказом.
  • В items[].deliveries лежат коды целиком. Обращайтесь с ответом как с секретом.
  • invoice_url и delivery_file_url — пути к счёту и к кодам в формате CSV. Оба работают только в кабинете. С API-ключом они вернут HTTP 403.
  • Ещё в ответе есть id (старый номер, не используйте его), events (история только для показа) и ход выдачи по каждой строке. Всё это можно не учитывать.
  • Для неизвестного номера заказа придёт HTTP 404.

#Статус заказа и коды

#Статус

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (часть строк выдана, часть нет)
                  │
                  └──► failed ──► refunded   (деньги вернулись в кошелёк)
СтатусЗавершён?Что делать
acceptedНетЖдать. Деньги списаны, выдача ещё не началась.
processingНетЖдать. Не оформляйте заказ повторно.
succeededДаПолучите коды и передайте их покупателю.
partially_succeededДаВыдайте то, что пришло. За остальное деньги вернутся позже.
failedЕщё нетДождитесь refunded. Статус failed — это ещё не возврат денег.
refundedДаДеньги вернулись в кошелёк.

Если вы не используете вебхуки, опрашивайте так: через 5 секунд, затем через 10 с, 30 с, 60 с, а дальше каждые 5 минут. Не выходите за лимит запросов. Большинство заказов завершается за секунды. Некоторым нужна ручная проверка, и она может занять несколько часов.

#Коды

Каждая выданная единица — отдельный объект в items[].deliveries:

JSON
{
  "status": "stored",
  "delivery_type": "card_pin",
  "display_fields": [
    {"key": "card_number", "label": "Card number", "value": "X1234", "kind": "secret"},
    {"key": "pin_code", "label": "PIN", "value": "9876", "kind": "secret"}
  ],
  "redeem_url": "",
  "expiry_date": "2027-09-29",
  "instructions": "Redeem at ...",
  "is_masked": false
}
  • Покажите покупателю display_fields: у каждого есть label (название) и value (значение). Также покажите redeem_url, expiry_date и instructions, если они не пустые.
  • kind равен secret для кодов и PIN и reference — для справочных данных, например серийных номеров.
  • delivery_type показывает, что именно вы получили: code, card_pin, link, code_link или qr. Могут появиться новые типы, поэтому всегда показывайте данные из display_fields.
  • Для выдачи типа link сам redeem_url и есть код. Держите его в секрете.
  • Никогда не передавайте покупателю единицу со status равным voided.
  • У товаров с прямым пополнением выдачи обычно нет. succeeded значит, что аккаунт пополнен.
  • Копии данных, например card_number и pin_code, приходят также отдельными полями. Они могут быть пустыми.
  • Кодов в вебхуках никогда нет. Получив вебхук, запросите заказ.

Вопросы по интеграции? Напишите на [email protected], указав ваш Merchant ID и ID заказа или запроса.