Платежи и YooKassa
Пополнение баланса проходит через YooKassa. Сценарий:
- Создание платежа → получение
confirmation_url. - Перенаправление пользователя на
confirmation_url. - YooKassa присылает подписанный вебхук об успехе → баланс зачисляется атомарно.
Создание платежа
curl -b cookies.txt -X POST https://api.runne.run/v1/payments \
-H 'Content-Type: application/json' \
-d '{"workspace_id":"0191…","amount_rubles":"500.00","description":"Top up my-app"}'Тело запроса:
| Поле | Тип | Обязательно | Примечания |
|---|---|---|---|
workspace_id | string (UUID) | да | должен принадлежать сессии |
amount_rubles | string | да | ^\d+(\.\d{1,2})?$, минимум 100.00 |
description | string | нет | макс. 500 символов |
Ответ (HTTP 201):
{
"payment_id": "0191…",
"confirmation_url": "https://yoomoney.ru/checkout/payments/v2/contract?orderId=…",
"amount_rubles": "500.00",
"tokens_credited": "500.00",
"status": "pending"
}tokens_credited равен amount_rubles по фиксированному курсу beta (1 токен = 1 ₽). Минимальная сумма — 100.00 ₽.
Перенаправление и оплата
Отправьте пользователя на confirmation_url. По завершении YooKassa возвращает на страницу успеха кабинета и присылает вебхук.
Вебхук
POST /webhooks/yookassa — единственный канал уведомлений. Аутентификация — через заголовок X-YooKassa-Signature:
X-YooKassa-Signature = HMAC-SHA256(raw_body, YOOKASSA_WEBHOOK_SECRET)Сравнение выполняется за постоянное время. Отсутствующая или неверная подпись возвращает 401, и событие игнорируется.
События
| Событие | Эффект |
|---|---|
payment.succeeded | Зачисляет баланс (атомарно), затем отправляет чек 54-ФЗ |
payment.canceled | Помечает платёж как cancelled (только если ещё pending) |
receipt.succeeded | Фиксирует номера фискального документа/накопителя |
receipt.canceled | Помечает чек как failed |
Идемпотентность
Вебхук защищён от повторной доставки тремя уровнями:
- Проверка статуса — уже
succeededплатёж пропускается. - Условный
UPDATE … WHERE status='pending'внутри транзакции — побеждает только один параллельный обработчик. - Уникальный индекс на
transactions.payment_id— строка зачисления может существовать только один раз.
Повторный вебхук поэтому никогда не зачислит воркспейс дважды. Сумма дополнительно сверяется со строкой платежа; несовпадение аудируется и игнорируется.
Чеки (54-ФЗ)
Чеки выпускаются только для клиентов-физлиц, как того требует 54-ФЗ:
- Чек создаётся после успешного платежа и отправляется через YooKassa со стабильным ключом идемпотентности (идентификатор платежа).
vat_codeравен1(без НДС) в beta.- Отправка чека — best-effort: сбой отправки аудируется и повторяется cron-задачей, но не роняет вебхук.
Юридическим лицам вместо чека выдаётся акт/счёт (в beta ещё не реализовано).
Отмена
Если пользователь бросает оплату, YooKassa присылает payment.canceled; платёж переходит в cancelled, баланс не зачисляется.
Список платежей
curl -b cookies.txt 'https://api.runne.run/v1/payments?workspace_id=0191…'{
"payments": [
{ "payment_id": "0191…", "amount_rubles": "500.00", "tokens_credited": "500.00", "status": "succeeded", "created_at": "…" }
]
}workspace_id — обязательный query-параметр (HTTP 400, если отсутствует). Ответ ограничен только воркспейсом вызывающего.
Связанное
- Воркспейсы и баланс — как проходит зачисление.
- Ошибки — путь
402 insufficient_balance. - Архитектура — атомарные финансовые операции.