Деньги: баланс, котировки, резервы
Как устроен баланс, как считается цена операции и когда деньги списываются.
Формат сумм
Все суммы — десятичные строки в USD: "50", "57.0464", "0.2". Хвостовые нули не печатаются. Точность — до 4 знаков после точки.
Не превращайте суммы во float
0.1 + 0.2 во float — это 0.30000000000000004. Храните и считайте суммы десятичным типом: decimal.Decimal в Python, BigDecimal в Java, numeric в Postgres, bcmath в PHP; в JavaScript — библиотека вроде decimal.js или целые десятитысячные доли.
Баланс
У аккаунта один предоплаченный счёт в USD. Пополняется он по договорённости с менеджером, через API — нет.
{ "currency": "USD", "balance": "1250.5", "hold": "57.0464", "available": "1193.4536" }| Поле | Смысл |
|---|---|
balance | Учтённые деньги |
hold | Резерв под операции в процессе |
available | balance − hold. Новая операция принимается, только если available её покрывает |
balance может стать отрицательным: комиссия за транзакцию по карте (tx_fee_ok, tx_fee_declined) списывается после того, как карта уже заплатила мерчанту, и отказать в ней нельзя. Выпущенные карты при этом работают, новые операции — нет, пока вы не пополните счёт.
Жизнь резерва
POST /cardsилиPOST /cards/{id}/topup→ сумма операции (hold_amount) уходит вhold.- Операция
succeeded→ резерв списывается сbalance. - Операция
failed→ резерв снимается целиком,balanceне меняется. - Операция
needs_review→ резерв остаётся, пока мы не разберёмся.
Закрытие карты синхронное: комиссия закрытия списывается в том же вызове, только если карта действительно закрылась.
Тариф
{
"id": "3f6c2a1e-8b4d-4e2a-9c71-5d0e8f1a2b34",
"issue_fee": "5",
"topup_fee_pct": "3",
"topup_fee_const": "0.5",
"close_fee": "1",
"close_refund_fee_pct": "0",
"tx_fee_ok": "0.2",
"tx_fee_declined": "0.5",
"min_topup": "10",
"max_topup": "5000"
}| Поле | Когда берётся |
|---|---|
issue_fee | Один раз при выпуске |
topup_fee_pct, topup_fee_const | С каждого зачисления на карту, включая начальное при выпуске |
close_fee | При закрытии карты |
close_refund_fee_pct | Процент, удерживаемый с остатка закрытой карты. 0 — остаток возвращается целиком |
tx_fee_ok, tx_fee_declined | За каждую одобренную / отклонённую транзакцию по карте |
min_topup, max_topup | Границы суммы зачисления на карту. Нет поля — нет границы |
Цена фиксируется тарифом, на котором карта выпущена: пополнение всегда считается по нему.
Как считается цена
Пусть p = topup_fee_pct / 100, c = topup_fee_const.
amount_type: credit (по умолчанию) — вы задаёте, сколько придёт на карту:
debit = credit / (1 − p) + c округление вверх до 0.0001amount_type: debit — вы задаёте бюджет, мы считаем, сколько придёт на карту:
credit = (debit − c) × (1 − p) округление вниз до 0.0001При выпуске к debit добавляется issue_fee. Округление всегда в нашу пользу и не больше чем на 0.0001 USD — котировка никогда не меньше реального списания.
Пример
Тариф выше, выпуск с 50 USD на карте:
50 / 0.97 = 51.54639175…
+ 0.5 = 52.04639175… → 52.0464 (пополнение)
+ 5 = 57.0464 (выпуск)
topup_fee = 52.0464 − 50 = 2.0464Проверить расчёт до операции можно через POST /quote — побочных эффектов нет:
BODY='{"kind":"issue","tariff_id":"3f6c2a1e-8b4d-4e2a-9c71-5d0e8f1a2b34","amount":"50"}'
TS=$(date +%s)
SIG=$(printf '%s\n%s\n%s\n%s' "$TS" "POST" "/api/partner/v1/quote" "$BODY" \
| openssl dgst -sha256 -hmac "$PP_SECRET" -hex | awk '{print $NF}')
curl -sS -X POST "https://api.plativputi.com/api/partner/v1/quote" \
-H "X-Key-Id: $PP_KEY_ID" -H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
-H "Content-Type: application/json" -d "$BODY"const quote = await pp("POST", "/quote", {
kind: "issue",
tariff_id: "3f6c2a1e-8b4d-4e2a-9c71-5d0e8f1a2b34",
amount: "50",
});quote = pp("POST", "/quote", body={
"kind": "issue",
"tariff_id": "3f6c2a1e-8b4d-4e2a-9c71-5d0e8f1a2b34",
"amount": "50",
})$quote = pp('POST', '/quote', [
'kind' => 'issue',
'tariff_id' => '3f6c2a1e-8b4d-4e2a-9c71-5d0e8f1a2b34',
'amount' => '50',
]);{ "debit": "57.0464", "credit": "50", "issue_fee": "5", "close_fee": "0", "topup_fee": "2.0464" }Пополнение на 100 USD с amount_type: debit: (100 − 0.5) × 0.97 = 96.515 придёт на карту.
Возврат остатка при закрытии
При закрытии остаток карты в том же вызове возвращается на ваш баланс за вычетом close_refund_fee_pct:
"refund": { "status": "refunded", "residual": "37.4", "fee": "0", "amount": "37.4" }refund.status | Значит |
|---|---|
refunded | amount зачислен на баланс |
zero | Карта была пустой |
unknown | Баланс карты прочитать не удалось. Ничего пока не зачислено, остаток урегулируем вручную |
Лимиты аккаунта
Кроме границ тарифа у аккаунта могут быть свои лимиты: максимум на одну операцию (422 amount_above_limit) и число выпусков в сутки (429 daily_issue_limit). Их задаёт менеджер.