Базы данных

System Design: платёжная система

Wise (TransferWise) обрабатывает $12 миллиардов международных переводов в месяц. Каждый перевод проходит через несколько банков в разных странах. Если деньги 'зависли' между системами - клиент в панике. Double-entry бухгалтерия + reconciliation позволяет Wise найти каждую потерянную копейку в течение минут.

  • **Stripe**: idempotency keys обязательны для всех Payment Intents API вызовов - это защищает от retry storms
  • **Brex**: event sourcing на Kafka - баланс корпоративных карт это sum всех ledger events, не хранимое поле
  • **Wise**: near-real-time reconciliation каждые 15 минут с банками-партнёрами в 80+ странах

Double-entry бухгалтерия в БД

Double-entry accounting - каждая финансовая транзакция записывается минимум дважды: дебет одного счёта = кредит другого. Сумма всех дебетов всегда равна сумме всех кредитов. Это математический инвариант, который позволяет обнаружить любую ошибку или потерю данных. Stripe, Brex, Wise используют double-entry как фундамент.

Никогда нельзя хранить деньги в FLOAT или DOUBLE - IEEE 754 float не может точно представить большинство десятичных дробей. 0.1 + 0.2 = 0.30000000000000004 в float. Используйте BIGINT (в центах/копейках) или DECIMAL(19, 4) - это стандарт в финтехе.

Почему нельзя хранить денежные суммы в столбце типа FLOAT?

Idempotency keys

Платёжные запросы могут быть отправлены повторно: таймаут клиента, retry при сетевой ошибке, дублирующий webhook. Idempotency key - уникальный клиентский ключ, который гарантирует что повторный запрос с тем же ключом даёт тот же результат без повторной обработки. Stripe требует Idempotency-Key заголовок для всех мутирующих API вызовов.

Stripe хранит idempotency keys 24 часа. Wise (TransferWise) использует 2-phase commit с idempotency для cross-border платежей через несколько банков. PayPal внедрил idempotency после инцидента 2013 года, когда retry шторм создал двойные списания на $170M.

Клиент отправил запрос на перевод $50 и получил таймаут (нет ответа). Что произойдёт при повторном запросе с тем же idempotency key?

Reconciliation: сверка данных

Reconciliation - процесс сверки внутренних записей с внешними системами (банк, платёжный провайдер, карточные сети). Даже с idempotency и транзакциями возможно расхождение: банк принял платёж, но ответ потерян; провайдер выполнил возврат без уведомления. Reconciliation обнаруживает и устраняет несоответствия.

Wise проводит reconciliation в режиме near-real-time (каждые 15 минут) для критических коридоров. Stripe reconciles 24/7 с Visa/Mastercard через ISO 8583 файлы. Typical reconciliation variance (discrepancy rate) у финтех компаний: <0.001% от объёма - за этим следят как за SLA.

Reconciliation показал транзакцию в банке, которой нет во внутренней системе. Что это означает?

Audit Trail и неизменяемость

Audit trail - полная неизменяемая история всех изменений в финансовой системе. Финансовые регуляторы (PCI DSS, SOX) требуют хранить историю транзакций минимум 7 лет. Данные не должны изменяться - только добавляться. Event sourcing - архитектурный паттерн где состояние системы = replay всех событий.

Amazon DynamoDB Streams и AWS Kinesis Data Firehose часто используются для capture изменений в operational БД и записи в immutable audit store в S3. Brex (финтех) использует event sourcing на Kafka: все изменения счетов - события, баланс = sum всех событий.

Для хранения транзакций достаточно одной таблицы с текущим балансом и обновлять его при каждой операции

Double-entry ledger с immutable events гарантирует что каждая копейка учтена и никогда не потеряна; баланс - это лишь производное от истории событий

Если обновлять только баланс - при любом сбое теряется информация о том что именно произошло. Можно восстановить итог, но не причину. Double-entry + event sourcing позволяет reconstruct любое состояние на любой момент времени и доказать его регулятору.

Почему в audit trail таблице нельзя делать UPDATE или DELETE строк?

Итоги

  • **Double-entry** - каждая транзакция = deb + cred, sum всегда = 0, математический инвариант целостности
  • **Idempotency keys** - клиентский UUID предотвращает двойную обработку retry запросов; ответ кэшируется на стороне сервера
  • **Audit trail** - append-only ledger с row-level security; баланс = производная от истории, не хранимое поле

Связанные темы

Платёжные системы опираются на несколько критических концепций:

  • ACID транзакции — Double-entry требует atomic commit двух ledger записей - без ACID один из двух INSERT может упасть
  • Репликация — Платёжные БД используют synchronous replication - нельзя потерять подтверждённую транзакцию при failover
  • Мониторинг и алертинг — Reconciliation variance >0.001% должен вызывать алерт немедленно - это SLA критической системы

Вопросы для размышления

  • Как правильно обработать частичный возврат (refund) через double-entry? Сколько ledger entries создаётся для возврата $30 из $100 покупки?
  • Система обнаружила reconciliation mismatch: у нас $1000 платежей, в банке $999.99. Алгоритм действий для расследования?
  • Идемпотентность - достаточное условие для безопасности платежей или нужны дополнительные гарантии? Что не защищает idempotency key?

Связанные уроки

  • sd-30-payments
System Design: платёжная система

0

1

Войти