Refund — это не «минус платеж», а отдельный протокол с собственными граблями
Если вы строите возвраты как зеркалку платежа, поздравляю: у вас уже есть баг, который всплывёт на сверке. Refund живёт своей жизнью: у него другие статусы, другие сроки, другая связь с холдами и сеттлментом. И да, «всё просто, нажали кнопку» — это сказка для лендинга, не для продакшена.
Нормальная схема должна держать: — связь refund_id с исходным payment_id; — идемпотентность на создание возврата, иначе получите дубль и потом будете объяснять бухгалтерии, почему денег стало больше; — отдельный state machine, потому что «pending», «succeeded» и «failed» для возврата не совпадают с оплатой. Документация — это ложь, логи — истина.
Самый мерзкий косяк — частичный возврат. Бизнес хочет вернуть «чуть-чуть», а платёжка, эквайер и ваш внутренний ledger начинают играть в испорченный телефон. Если не зафиксировать остаток доступного к возврату и не блокировать гонки, два саппорта нажмут кнопку одновременно — и получите перерасход лимита, спорные суммы и ручную инвентаризацию 🧨
Отдельно проверяйте вебхуки: возврат может прилететь раньше, чем UI успеет обновить статус, а иногда вообще не прилетит без ретраев и reconciliation job. Ваш мерчант забанен без объяснения причин? Нет, просто вы сломали жизненный цикл возврата и пытаетесь лечить это cron’ом.
Держите refunds как отдельный домен, а не как хвост у payment flow. Идемпотентность, блокировки, сверка и нормальный ledger — иначе это не финтех, а костыль на костыле и финтехом погоняет.
Интеграция платежных решений
@payment_integration_ops_arb
Refund — это не «минус платеж», а отдельный протокол с собственными граблями
Этот пост опубликован в Telegram-канале Интеграция платежных решений. Подписаться можно по ссылке: @payment_integration_ops_arb.