<b>Refund — это не кнопка, а отдельная ветка бизнес-логики с минами</b>
Возврат ломает наивную схему «оплата прошла — деньги ушли — всем хорошо». У вас появляется своя модель состояния: pending refund, partial refund, full refund, failed refund, reversed refund. Если это не хранить явно, начинаются фантомные зачисления, дубль-возвраты и бухгалтерия, которая смотрит на вас как на последнего крипто-мистика.
Правила, без которых вы сами себе устроите инцидент:
• возврат должен быть идемпотентным, иначе ретрай превратится в двойной отстрел денег;
• храните связку payment_id/refund_id/attempt_id, а не один сиротский external_id;
• частичный refund не должен обнулять исходный платёж везде сразу;
• если провайдер шлёт вебхук позже вашего API-ответа — это нормально, а не баг космоса.
Отдельная боль — расхождение между «инициировали возврат» и «деньги реально ушли». Между ними может быть холд, сеттлмент, ручная модерация провайдера или тупо упавший webhook consumer. Поэтому бизнес-статус и банковский статус — разные звери. Склеите их в один флаг, и получите лотерею с отрицательным UX.
Идемпотентность или смерть. Возвраты надо проектировать как финальный сценарий с очередями, ретраями, аудит-логом и reconciliation-job. Иначе однажды ваш мерчант забанен без объяснения причин — просто потому, что система возвратов решила жить своей жизнью.
Интеграция платежных решений
@payment_integration_ops_arb
<b>Refund — это не кнопка, а отдельная ветка бизнес-логики с минами</b>
Этот пост опубликован в Telegram-канале Интеграция платежных решений. Подписаться можно по ссылке: @payment_integration_ops_arb.