Обзор #

На BIN провайдера W карту можно выпустить с буфером: это небольшой резерв,
который PayCA держит рядом с картой на ваше имя. Собственные списания провайдера
по карте — комиссии за отказы, комиссии за трансграничные операции — оплачиваются
из буфера и не уменьшают баланс карты, который видит ваш пользователь.

Когда буфер включён для вашего клиента, карты на этих BIN выпускаются через
/v2/cards, и все карты, выпущенные так, обслуживаются тоже через /v2/cards.
Вызовы /v1/cards, которые двигают деньги или меняют состояние карты, отвечают
400 с card_buffer_requires_v2 в details: выпуск на таком BIN, а также
пополнение, вывод, заморозка, разморозка, закрытие и история по буферной карте.
Буфер для клиента включает ваш менеджер PayCA; включён ли он, покажет запрос
capability ниже.

Эти вызовы остаются в v1 и работают как раньше:

Вызов Назначение
POST /v1/users, POST /v1/users/{id}/cardholder Создать пользователя и держателя, на которого выпускается карта.
GET /v1/cards/{id}/sensitive Номер карты, CVC и срок действия.
POST /v1/webhooks Регистрация адресов вебхуков.
GET /v2/accounts Ваши счета, балансы и BIN.

Все суммы в /v2/cards — строки в USD, не больше двух знаков после точки.
Передавайте "50.00" или "50", но не число. В ответах нули в конце
отбрасываются ("18", "2.8"), поэтому разбирайте их как десятичные числа, а не по формату.


Перед началом #

  1. Ключи. Передавайте x-client-id и x-client-secret в каждом запросе.
    Если ключи верные, GET /v2/accounts отвечает 200.
  2. Счёт. Возьмите из GET /v2/accounts id счёта, к которому подключён BIN.
    В примерах это <account-id>: деньги на карту берутся с него и возвращаются
    на него.
  3. Держатель. Каждая карта выпускается на одобренного держателя
    (pass_audit). Как его создать, зависит от BIN — см.
    Provider W Cardholder Integration. На
    B2B-BIN это один вызов с именем, фамилией и email пользователя.
  4. Вебхуки. Подключите через POST /v1/webhooks такие типы:
Тип Что приходит
card_buffer Каждое изменение статуса ваших операций /v2/cards. Так вы узнаёте, что карта выпущена.
card События по картам, в том числе коды 3DS. Без него пользователи не смогут подтверждать онлайн-платежи.
cardholder Изменения статуса держателей.

Как делится платёж #

У каждого BIN своя политика буфера. Берите цифры из API, не зашивайте их в код:
они есть в каждой котировке и в GET /v2/cards/{id}.

Поле Значение
target_amount Размер полного буфера.
freeze_threshold_amount 20% от полного буфера. Когда буфер падает до этой суммы или ниже, либо какое-то списание осталось неоплаченным, карта замораживается автоматически.
resume_threshold_amount 50% от полного буфера. Замороженная карта размораживается сама, когда буфер снова не меньше этой суммы и неоплаченных списаний нет.

Выпуск. funding_amount — всё, что спишется с вашего счёта. Сумма делится так:

funding_amount = operation_fee (комиссия за выпуск) + buffer_refill (полный буфер) + card_credit

При буфере $14 и комиссии за выпуск $3 сумма "50.00" даёт на карте $33.00.
Минимальная сумма приходит в minimum_funding_amount (в этом примере $18.00:
минимум карты $1, буфер и комиссия).

Пополнение. В буфер идёт не больше 50% пополнения: сначала на погашение
списаний, которые буфер не покрыл (uncovered_repayment), потом на доливку буфера
до полного (buffer_refill). Остальное — card_credit. В полный буфер ничего
не идёт.

Когда буфера не хватает. Списание больше буфера обнуляет его, остаток
записывается в uncovered_fee_amount, карта замораживается. Следующее пополнение
гасит этот остаток из своей доли 50%.

Комиссии вашего тарифа (за выпуск, ежемесячная, за разморозку, за вывод)
списываются как раньше. Комиссия за выпуск показана в котировке как
operation_fee.


Выпуск карты #

Шаг 1 — Проверить доступность #

curl -s -G "$PAYCA_BASE_URL/v2/cards/issue/capability" \
  --data-urlencode "source_account_id=<account-id>" \
  --data-urlencode "bin=49372410" \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET"
{
  "model": "separate_buffer_v1",
  "required": true,
  "eligible": true,
  "minimum_funding_amount": "18"
}

required: true значит, что для вашего клиента этот BIN выпускается через
/v2/cards. При eligible: false приходит reason_code:

reason_code Значение
not_enabled Буфер не включён для вашего клиента или для этого BIN.
bin_not_attached BIN не подключён к этому счёту.
bin_disabled BIN выключен на этом счёте.
client_not_ready, program_not_ready, parent_not_ready, delivery_fee_policy_unresolved Не завершена настройка на нашей стороне. Напишите в поддержку.

Этот запрос необязателен: котировка проверяет то же самое.

Шаг 2 — Получить котировку #

curl -s -X POST "$PAYCA_BASE_URL/v2/cards/issue/quote" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{
    "source_account_id": "<account-id>",
    "bin": "49372410",
    "cardholder_id": "<cardholder-id>",
    "funding_amount": "50.00",
    "external_user_id": "usr-001"
  }'
Поле Обязательное Назначение
source_account_id Да Счёт, с которого берутся деньги.
bin Да BIN, на котором выпускается карта.
funding_amount Да Вся сумма, вместе с комиссией и буфером.
cardholder_id Передавайте всегда Одобренный держатель, на которого выпускается карта. Без него карта не привязана ни к одному держателю, и лимит карт на держателя к ней не применяется.
external_user_id Нет Ваш идентификатор для карты.

Ответ (сокращённо):

{
  "quote_id": "8b2d6f0e-6c1a-4b7e-9d43-2f1c0a9e5b11",
  "kind": "issue",
  "bin": "49372410",
  "currency": "USD",
  "expires_at": "2026-10-29T10:01:00Z",
  "funding_amount": "50",
  "operation_fee": "3",
  "buffer_refill": "14",
  "card_credit": "33",
  "total_debit": "50",
  "buffer_after": "14",
  "freeze_threshold_amount": "2.8",
  "resume_threshold_amount": "7",
  "minimum_funding_amount": "18"
}

Пока ничего не списано. Держатель проверяется на этом шаге: неодобренный,
чужой, зарегистрированный на другой BIN или уже с максимумом карт держатель
отклоняется здесь.

Котировка действует одну минуту (expires_at). Подтвердите её сразу или
запросите новую.

Шаг 3 — Подтвердить котировку #

curl -s -X POST "$PAYCA_BASE_URL/v2/cards" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{
    "quote_id": "8b2d6f0e-6c1a-4b7e-9d43-2f1c0a9e5b11",
    "idempotency_key": "issue-usr-001-0001"
  }'

Ответ — 202 с операцией. total_debit теперь удержан на вашем счёте,
карта выпускается асинхронно.

{
  "operation_id": "3f9a1c52-0d7e-4f6b-a1e8-6c2b9d4e7a30",
  "kind": "issue",
  "status": "funds_held",
  "source_account_id": "<account-id>",
  "funding_amount": "50",
  "quoted_card_credit": "33",
  "quoted_operation_fee": "3",
  "maximum_source_debit": "50",
  "idempotency_key": "issue-usr-001-0001"
}

idempotency_key (1–200 символов) делает запрос безопасным для повтора: тот же
ключ с той же котировкой возвращает ту же операцию. Ключ, уже использованный для
другого запроса, отклоняется с idempotency_conflict.

Шаг 4 — Дождаться результата #

Следите за операцией по вебхуку card_buffer или запрашивайте её:

curl -s "$PAYCA_BASE_URL/v2/cards/operations/<operation-id>" \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET"
status Значение
funds_held Принято, деньги удержаны на вашем счёте.
provider_submission_prepared, provider_pending Отправлено эмитенту, ждём ответа.
needs_reconciliation Ответ эмитента сверяется на нашей стороне. Ждите, не повторяйте запрос с новым ключом.
succeeded Готово. У выпуска заполнен card_id, поля actual_* показывают, что проведено.
rejected Отказ, причина в error_code. При отказе в выпуске удержанные деньги возвращаются на счёт.
compensated Операция отменена, деньги возвращены.

succeeded, rejected и compensated — конечные статусы.

Шаг 5 — Получить карту #

curl -s "$PAYCA_BASE_URL/v2/cards/<card-id>" \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET"

В ответе карта (card) и её буфер (buffer) рядом: buffer.balance,
target_amount, uncovered_fee_amount, оба порога и флаги, что карта принимает
сейчас: can_topup, can_auto_resume, can_manual_unfreeze. model равен
separate_buffer_v1 у буферной карты и legacy_on_card у карты, выпущенной до
включения буфера.

Номер карты, CVC и срок действия — в GET /v1/cards/{card-id}/sensitive.


Пополнение карты #

Те же два шага, что при выпуске: котировка, затем подтверждение.

curl -s -X POST "$PAYCA_BASE_URL/v2/cards/<card-id>/topup/quote" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{ "source_account_id": "<account-id>", "funding_amount": "100.00" }'

curl -s -X POST "$PAYCA_BASE_URL/v2/cards/<card-id>/topup" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{ "quote_id": "<quote-id>", "idempotency_key": "topup-0001" }'

Котировка показывает разбивку: uncovered_repayment, buffer_refill,
card_credit, буфер до и после (buffer_before, buffer_after).

  • Заморожена из-за буфера. Карту, замороженную из-за нехватки буфера, можно
    пополнить. Пополнение должно быть достаточным, чтобы из своей доли 50% погасить
    неоплаченное и вернуть буфер до resume_threshold_amount; иначе котировка
    назовёт минимум. Карта разморозится сама, когда пополнение пройдёт.
  • Заморожена вами. Карту с причиной manual или insufficient_funds
    пополнить нельзя, сначала разморозьте её.
  • Занята. По карте идёт одна операция за раз. Вторая отклоняется с
    operation_in_progress, пока первая не завершится.
  • Устаревшая котировка. Если буфер изменился между котировкой и
    подтверждением, подтверждение отклоняется с quote_stale; запросите новую.

Вывод с карты #

Переводит деньги с карты обратно на счёт, с которого она пополнялась. Буфер не
затрагивается.

curl -s -X POST "$PAYCA_BASE_URL/v2/cards/<card-id>/withdraw" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{ "source_account_id": "<account-id>", "amount": "20.00", "idempotency_key": "withdraw-0001" }'

source_account_id должен быть счётом, с которого выпущена карта. Комиссия за
вывод по вашему тарифу, если она есть, вычитается из суммы
(quoted_operation_fee); сумма, которая её не покрывает, отклоняется.


Заморозка и разморозка #

curl -s -X POST "$PAYCA_BASE_URL/v2/cards/<card-id>/freeze" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{ "idempotency_key": "freeze-0001", "reason": "manual" }'

POST /v2/cards/<card-id>/unfreeze принимает то же тело. У карты может быть
несколько причин заморозки сразу, каждая снимается отдельно:

reason Кто ставит Как снять
manual (по умолчанию) Вы unfreeze с manual. Бесплатно.
insufficient_funds Вы, после отказа по недостатку средств unfreeze с insufficient_funds. Списывается комиссия за разморозку по вашему тарифу (buffer.unfreeze_fee), когда эмитент подтвердит разморозку.
заморозка буфера PayCA, автоматически Пополнение, которое восстанавливает буфер. Через unfreeze не снимается.

Разморозка, после которой карта осталась бы замороженной по другой причине,
отклоняется: сначала снимите ту причину. Заранее это видно по
can_manual_unfreeze в GET /v2/cards/{id}.


Закрытие карты #

curl -s -X DELETE "$PAYCA_BASE_URL/v2/cards/<card-id>" \
  -H 'Content-Type: application/json' \
  -H "x-client-id: $PAYCA_CLIENT_ID" \
  -H "x-client-secret: $PAYCA_CLIENT_SECRET" \
  -d '{ "idempotency_key": "close-0001" }'

Закрытие подтверждает эмитент, это может занять около 30 дней. До этого
операция остаётся в процессе (provider_pending).

  • Баланс карты возвращается на ваш счёт, когда эмитент подтвердит закрытие.
  • Буфер. Списания могут прийти и после закрытия карты, поэтому буфер держится
    60 дней после запроса на закрытие и оплачивает их. Остаток затем
    возвращается на ваш счёт, а вебхук card_buffer по операции закрытия приходит
    ещё раз с событием close_hold_released.

История #

Вызов Что возвращает
GET /v2/cards/{id}/transactions Записи по карте, её буферу и комиссиям.
GET /v2/accounts/{id}/transactions Историю счёта вместе с операциями буфера.

Оба принимают limit (1–100, по умолчанию 50) и cursor; чтобы получить
следующую страницу, передайте next_cursor предыдущей. Когда на счёте появились
операции буфера, GET /v1/accounts/{id}/transactions для него отвечает
card_buffer_history_requires_v2.


Вебхуки #

Тело вебхука card_buffer — это сама операция, тот же объект, что в
GET /v2/cards/operations/{operation_id}. Он отправляется при каждом изменении
статуса, поэтому по одной операции приходит несколько вебхуков. Убирайте дубли по
паре operation_id + status и проверяйте заголовок x-signature, как для любого
вебхука PayCA.

Перед обработкой смотрите kind: кроме ваших операций (issue, topup,
withdraw, freeze, unfreeze, close) могут приходить операции, которые вы
не запускали, например комиссия, оплаченная из буфера.


Ошибки #

Ошибка приходит как {"code": "...", "details": "..."}.

HTTP code Значение Что делать
409 quote_expired Котировке больше минуты. Запросите новую.
409 quote_stale Котировка больше не соответствует карте или счёту. Запросите новую.
409 idempotency_conflict Ключ уже использован для другого запроса. Для нового запроса нужен новый ключ.
409 operation_in_progress По карте уже идёт операция или карта закрывается. Дождитесь её завершения.
409 buffer_state_changed Состояние заморозки изменилось до выполнения запроса. Запросите карту заново.
403 feature_not_enabled Для вашего клиента не включены буфер, пополнения или политика буфера на BIN. Обратитесь к менеджеру.
400 card_buffer_ineligible Запрос нельзя выполнить в таком виде: сумма ниже минимума, карта заморожена или закрывается, счёт не владеет картой. В details причина, а для слишком маленькой суммы — минимум. Исправьте запрос по details.
400 ErrFailedPrecondition с card_buffer_requires_v2 Вызов v1 для BIN или карты с буфером. Используйте вызов /v2/cards.
400 ErrFailedPrecondition с card_buffer_history_requires_v2 GET /v1/accounts/{id}/transactions по счёту с операциями буфера. Используйте GET /v2/accounts/{id}/transactions.
400 buffer_model_requires_v2 Вызов пополнения v2 для карты без буфера. Для этой карты используйте вызов v1.
400 ErrInvalidArg с card_buffer_ineligible BIN не поддерживает буфер. Выберите BIN с буфером.
400 ErrFailedPrecondition с kyc required: ... Держателя нет, он не одобрен или зарегистрирован не на этот BIN. См. Provider W Cardholder Integration.
400 ErrFailedPrecondition с cardholder card limit reached: ... У держателя максимум активных карт. Закройте одну из его карт или возьмите другого держателя.
400 ErrInsufficientFunds На счёте не хватает денег на котировку. Пополните счёт.
400 ErrInvalidArg Неверное тело: сумма не положительная строка с двумя знаками, или переданы обе суммы, или ни одной. Исправьте запрос.

Справочник вызовов #

Метод Вызов Описание
GET /v2/cards/issue/capability Выпускается ли BIN с буфером для этого счёта, и минимальная сумма.
POST /v2/cards/issue/quote Котировка выпуска.
POST /v2/cards Подтверждение котировки выпуска.
GET /v2/cards/{id} Карта и буфер.
POST /v2/cards/{id}/topup/quote Котировка пополнения.
POST /v2/cards/{id}/topup Подтверждение котировки пополнения.
POST /v2/cards/{id}/withdraw Вывод с карты на её счёт.
POST /v2/cards/{id}/freeze Заморозка с причиной.
POST /v2/cards/{id}/unfreeze Снятие причины заморозки.
DELETE /v2/cards/{id} Закрытие карты.
GET /v2/cards/operations/{operation_id} Операция.
GET /v2/cards/{id}/transactions История карты.
GET /v2/accounts/{id}/transactions История счёта.