Guide

Best Practices

Дедуп webhook-ов, server-side подтверждение, обработка 429, мониторинг и security checklist.

Дедуплицируйте webhook'и

Webhook может прийти больше одного раза (мы не отвечаем за то, что ваш сервер на ретрае ответил > 15 секунд, и мы решили перепослать). Поле id события стабильно между ретраями. Храните набор обработанных id хотя бы за последние сутки и игнорируйте дубли.

// Postgres-подход: уникальный ключ на event.id, ON CONFLICT DO NOTHING.
INSERT INTO balancedpay_events (event_id, kind, payload, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (event_id) DO NOTHING;

// Если SQL вернул 0 строк — это дубль, не делаем побочные эффекты.

Подтверждайте оплату сервер-сайдом, не клиент-сайдом

После оплаты мы вернём покупателя на return_url с ?payment_id=…&status=succeeded. Эти параметры может подменить любой пользователь руками. Перед тем как «поздравить с покупкой», подтвердите статус webhook'ом или явным GET /payments/{id} с проверкой status === 'succeeded'.

Idempotency-Key — по одному на бизнес-операцию

Не генерируйте новый ключ на каждый ретрай. Иначе ретрай создаст второй платёж. Привязывайте ключ к бизнес-сущности: например, order:{id}:create для создания платежа, order:{id}:refund:50000 для возврата конкретной суммы. Один и тот же ключ + то же тело → 200 OK с уже созданным объектом.

Не храните карточные данные

Мы не отдаём номер карты, CVV или срок действия — только маски (customer_card_mask: 411111******1111) и бренд. Это PCI DSS-требование. Не пытайтесь распарсить полный PAN из ответа — его там нет и не будет.

Обрабатывайте 429 — это норма

Лимит мерчанта на public-API настраивается в админке balancedpay. При превышении возвращаем 429 rate_limited и Retry-After в секундах. Не игнорируйте этот заголовок: ретрай раньше ничего не даст. Все наши SDK кидают BalancedpayError с code: 'rate_limited' — ловите его и спите.

Test-режим — это test, а не production-debug

В test-режиме платежи проходят через симулятор. Не считайте, что «у меня всё работает на test, в live тоже работает» — банк-эквайер на live добавляет реальные кейсы: 3DS-фейлы, недостаточно средств, заблокированная карта, fraud-блок. Прогоните test-кейсы из раздела «Тестирование» — там сценарии, которые в live действительно случаются.

Мониторьте ключевые метрики

  • Доля payment.failed по failure_code. Скачок card_declined — повод переключиться на резервный коннектор.
  • Время от payment.created до payment.succeeded. P95 > 30 сек — что-то с банком.
  • Доля 429 rate_limited в ответах. Если >0% — пора договориться с админами balancedpay о повышении лимита.
  • Возраст последнего успешного webhook'а. Если за последний час ничего не пришло, а оплаты идут — проверьте webhook-endpoint и retry-логику в ЛК.

Security checklist

  • API-ключи — в секретах CI/CD или secret manager. Никогда в git и никогда в front-end бандле.
  • Webhook-endpoint — только HTTPS на production. HTTP-endpoint мы разрешим (для локалки), но ваши данные пойдут открытым текстом.
  • Включите HMAC-подпись запроса (флаг «Требовать подпись» на магазине) — второй фактор поверх Bearer.
  • Allow-list IP — обязателен для боевых ключей. Список IP вашего бэкенда, ничего лишнего.
  • Ротируйте webhook-секрет раз в полгода (в ЛК — кнопка «Ротация»). Старый перестаёт работать сразу, новый показываем один раз.
  • Логируйте X-Freefin-Event-Id на каждый принятый webhook — это даёт вам корреляцию с нашим логом доставок.