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

Пополнение мобильной связи

Пополнение мобильной связи зачисляет деньги прямо на предоплаченный номер телефона. Ваш покупатель получает на свою линию минуты или интернет. Никакого кода передавать не нужно. Оплата идёт с вашего кошелька CardV, так же как и за другие заказы.

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

#Как это работает

Text
GET  /recharge/countries            страны, где доступно пополнение
GET  /recharge/operators?country=US  операторы, типы пополнения и суммы
POST /recharge/quote                ваша цена и quote_token, действует 300 с
POST /recharge/orders               оформить заказ с оплатой из кошелька
GET  /recharge/orders/{order_id}    проверять статус или ждать вебхук
  • Все пути начинаются с /api/v1. Передавайте те же заголовки, что и в любом запросе.
  • Оба запроса POST нужно подписать. Подписывайте их так же, как POST/orders, но со своим путём, например /api/v1/recharge/quote.
  • Заказы на пополнение хранятся отдельно от заказов подарочных карт. Читайте их через методы /recharge.
  • Доступно только прямое пополнение. PIN-товаров (код, который покупатель вводит сам) нет.

#Страны

GET/api/v1/recharge/countries показывает страны, где пополнение доступно сейчас.

JSON
{
  "count": 2,
  "results": [
    {"code": "MX", "name": "Mexico", "currency_codes": ["MXN"], "operator_count": 4, "offer_count": 37},
    {"code": "US", "name": "United States", "currency_codes": ["USD"], "operator_count": 6, "offer_count": 52}
  ]
}
  • code — код страны по ISO 3166-1 alpha-2. В следующих запросах передавайте его как country.
  • currency_codes — местные валюты, в которых продают операторы этой страны.
  • Список меняется, когда операторы добавляются или становятся недоступны. Загружайте его раз в несколько часов.

#Операторы

GET/api/v1/recharge/operators?country=US показывает операторов одной страны. Добавьте search=att, чтобы отфильтровать по названию оператора.

JSON
{
  "count": 1,
  "results": [
    {
      "operator_key": "us-att",
      "name": "AT&T",
      "country": "US",
      "country_name": "United States",
      "logo_url": "https://b2b.cardv.net/api/v1/catalog-assets/3f5c...a1.png",
      "subtypes": ["airtime", "data"],
      "amount_model": "range",
      "currency_codes": ["USD"],
      "amounts": [
        {"min": "5.0000", "max": "100.0000", "currency": "USD", "subtype": "airtime", "label": "5.0000-100.0000 USD"},
        {"min": "15.0000", "max": "15.0000", "currency": "USD", "subtype": "data", "label": "15.0000 USD"}
      ],
      "offer_count": 3
    }
  ]
}
ПолеЧто значит
operator_keyID, который вы передаёте в запрос цены и заказ, например us-att. Храните его как строку.
subtypesЧто можно купить: airtime (баланс для звонков), data или bundle (звонки и интернет).
amount_modelfixed, если все суммы заданы заранее, range, если хотя бы одна сумма — диапазон.
amounts[]Варианты суммы. Если min равно max, сумма фиксированная. Иначе подходит любая сумма между ними.
amounts[].currencyМестная валюта этого варианта. Передавайте её как local_currency.
logo_urlКартинка на серверах CardV или "".
  • Суммы указаны в местной валюте: столько получит телефонная линия.
  • Неизвестный или неправильно записанный country вернёт HTTP 400.

#Запрос цены

POST/api/v1/recharge/quote возвращает вашу цену за одно пополнение. Этот запрос нужно подписать.

JSON
{
  "country": "US",
  "operator_key": "us-att",
  "amount": "10.00",
  "local_currency": "USD",
  "subtype": "airtime"
}
ПолеОбязательноЧто значит
countryДаКод страны из списка стран.
operator_keyДаИз списка операторов.
amountДаСумма в местной валюте, строкой. Для fixed — одно из указанных значений. Для range — от min до max.
local_currencyЖелательноКод ISO 4217 для amount из amounts[].currency. Передавайте его, если у оператора несколько валют.
subtypeНетairtime (по умолчанию), data или bundle.

Ответ:

JSON
{
  "country": "US",
  "country_name": "United States",
  "operator": {"operator_key": "us-att", "name": "AT&T", "logo_url": ""},
  "subtype": "airtime",
  "local_amount": "10.0000",
  "local_currency": "USD",
  "merchant_price": "9.6200",
  "merchant_currency": "USD",
  "expires_at": "2026-09-30T08:20:30.123456+00:00",
  "quote_token": "eyJ2ZXJzaW9uIjox...:1uXyZa:8c1f..."
}
  • merchant_price — сколько спишется с вашего кошелька, в валюте merchant_currency.
  • quote_token фиксирует эту цену на 300 секунд, до expires_at. Передайте его в заказе без изменений.
  • Токен привязан к вашему аккаунту и к этим стране, оператору, типу и сумме.
  • Перед заказом проверьте, что local_currency — та валюта, которую вы ожидали.
  • Запрос цены не резервирует деньги. Новую цену можно запросить в любой момент.

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

POST/api/v1/recharge/orders пополняет телефон и списывает деньги с кошелька. Этот запрос нужно подписать.

JSON
{
  "external_order_id": "SHOP-RC-20260930-0001",
  "country": "US",
  "operator_key": "us-att",
  "amount": "10.00",
  "local_currency": "USD",
  "subtype": "airtime",
  "account": "12125550100",
  "quote_token": "eyJ2ZXJzaW9uIjox...:1uXyZa:8c1f..."
}
ПолеОбязательноЧто значит
external_order_idДаВаш номер заказа. Уникален среди всех ваших заказов, включая заказы подарочных карт.
country, operator_key, amount, local_currency, subtypeДаТе же значения, что вы передали в запрос цены.
accountДаНомер телефона для пополнения: только цифры, с кодом страны, без + и пробелов.
quote_tokenДаИз ответа на запрос цены, пока он не истёк.

Примеры номеров: 12125550100 (США), 525512345678 (Мексика). Убедитесь, что номер принадлежит выбранному оператору. Пополнение на неверный номер отменить нельзя.

CardV проверяет цену, сразу списывает merchant_price с вашего кошелька и запускает пополнение в фоне. Новый заказ возвращает HTTP 201:

JSON
{
  "idempotent_replay": false,
  "order": {
    "order_id": "O-00005678",
    "external_order_id": "SHOP-RC-20260930-0001",
    "status": "accepted",
    "status_title": "Recharge accepted",
    "poll_after_seconds": 12,
    "account": "12***00",
    "local_amount": "10.0000",
    "local_currency": "USD",
    "merchant_price": "9.6200",
    "merchant_currency": "USD",
    "...": "more fields"
  }
}
  • Сохраните order.order_id.
  • Номер телефона в ответе всегда скрыт, целиком он не возвращается.

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

external_order_id защищает от двойного пополнения.

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

«То же пополнение» — это те же страна, оператор, тип, сумма и номер телефона. Повторный заказ распознаётся ещё до проверки цены, поэтому даже с истёкшим quote_token вернётся первый заказ.

  • После тайм-аута, ответа 5xx или обрыва соединения отправьте то же тело с тем же номером заказа. Подпишите его заново с новыми временем и nonce.
  • Никогда не придумывайте новый номер заказа из-за того, что ответ потерялся. Так телефон может пополниться дважды.

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

GET/api/v1/recharge/orders/{order_id} возвращает один заказ. Можно использовать номер заказа CardV (O-00005678).

JSON
{
  "order_id": "O-00005678",
  "external_order_id": "SHOP-RC-20260930-0001",
  "status": "processing",
  "order_status": "processing",
  "status_title": "Recharge processing",
  "status_message": "The recharge request is being processed. Delivery is not confirmed yet.",
  "next_step": "Keep this order open and wait for confirmation before placing another recharge.",
  "poll_after_seconds": 12,
  "country": "US",
  "operator": {"operator_key": "us-att", "name": "AT&T", "logo_url": ""},
  "subtype": "airtime",
  "account": "12***00",
  "local_amount": "10.0000",
  "local_currency": "USD",
  "merchant_price": "9.6200",
  "merchant_currency": "USD",
  "attempts": [{"attempt_no": 1, "status": "processing", "error_message": "", "submitted_at": "2026-09-30T08:16:02.511201Z", "completed_at": null}],
  "created_at": "2026-09-30T08:16:01.004211Z",
  "updated_at": "2026-09-30T08:16:02.611978Z",
  "...": "more fields"
}
  • Неизвестный номер заказа или заказ другого аккаунта вернёт HTTP 404.
  • status_title, status_message и next_step — текст на английском, его можно показывать вашим сотрудникам.
  • poll_after_seconds — сколько ждать до следующей проверки. 0 означает, что заказ завершён.

#Список заказов на пополнение

GET/api/v1/recharge/orders показывает ваши заказы на пополнение, сначала самые новые.

HTTP
GET /api/v1/recharge/orders?status=processing&limit=50&offset=0
JSON
{"count": 3, "limit": 50, "results": ["..."]}
  • Фильтры: status и search (номер заказа CardV, ваш номер заказа или название оператора).
  • По умолчанию limit равен 20, максимум — 100. Большие значения уменьшаются до 100.
  • Отрицательный или нечисловой limit или offset вернёт HTTP 400.

#Статус

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► manual_review ──► succeeded или refunded
                  │
                  └──► failed ──► refunded   (деньги вернулись в кошелёк)
СтатусЗавершён?Что делать
acceptedНетЖдать. Деньги списаны, пополнение ещё не началось.
processingНетЖдать. Это может занять несколько минут. Не оформляйте заказ повторно.
manual_reviewНетCardV уточняет результат у оператора. Ждать.
succeededДаТелефон пополнен. Сообщите покупателю.
failedЕщё нетПополнение не прошло. Дождитесь refunded.
refundedДаДеньги вернулись в кошелёк. Можно оформить новый заказ.
  • Проверяйте через poll_after_seconds, затем реже: 30 с, 60 с, а дальше каждые 5 минут. Не выходите за лимит запросов.
  • Если статус вам незнаком, считайте, что заказ ещё не завершён.
  • Пока заказ не завершён, не отправляйте на тот же номер новое пополнение с новым номером заказа. Если первое тоже пройдёт, телефон пополнится дважды.

#Вебхуки и возвраты

Заказы на пополнение присылают те же вебхуки, что и другие заказы: order.succeeded, order.failed и order.refunded. В вебхуке есть номер заказа CardV, ваш номер заказа и пустой список items. Получив вебхук, прочитайте заказ через GET/api/v1/recharge/orders/{order_id}.

Возвраты происходят автоматически. Когда оператор подтверждает сбой, CardV возвращает весь merchant_price в ваш кошелёк, и заказ переходит в статус refunded. Возврат виден на странице операций в кабинете. Успешное пополнение нельзя вернуть или отменить.

#Ошибки

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

КлючГдеЧто делать
detailЗапрос цены, заказСтрана, оператор, тип или сумма недоступны. Сверьтесь со списком операторов.
amountЗапрос цены, заказНе число, ноль или вне диапазона.
local_currencyЗапрос цены, заказНе трёхбуквенный код ISO 4217.
accountЗаказНе указан номер телефона.
quote_tokenЗаказНет, истёк, изменён или не совпадает. Посмотрите code и запросите цену заново.
balanceЗаказПополните кошелёк в кабинете.
riskЗаказВы упёрлись в лимит заказов. Свяжитесь с CardV.
external_order_idЗаказУже использован для другого заказа. См. Безопасный повтор.
walletЗаказНет активного кошелька. Свяжитесь с CardV.

Ошибки quote_token приходят с полем code:

JSON
{"code": "quote_expired", "quote_token": "This quote expired. Refresh the price and confirm again."}
codeЧто значит
quote_requiredquote_token не передан.
quote_expiredПрошло больше 300 секунд. Запросите цену заново.
quote_invalidИзменён или выдан для другого пополнения. Запросите цену заново.
price_changedВаша цена изменилась после запроса. Запросите цену заново и подтвердите новую.

HTTP 403 означает проблему с учётными данными, подписью, IP или одобрением. См. Аутентификация.

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