Снаружи финтех выглядит безупречно: есть подробная документация, надёжные провайдеры, а платёжные интеграции будто можно внедрять «на автопилоте». Но стоит заглянуть внутрь, и вы попадаете в «семь кругов финтеха», где любая мелочь превращается в драму, отнимающую выходные и нервы всей команды.

В этой статье разберем два эпизода из практики: от технических тонкостей до ситуаций на стыке бизнеса и разработки. Поговорим о том, почему слепое доверие документации часто оборачивается болью и как проактивный подход помогает компании сэкономить деньги, а разработчикам — сохранить нервы и сон.

Маргарита Моногарова

Altenar

Меня зовут Маргарита Моногарова, я — тимлид команды интеграций международного разработчика ПО для спортивной аналитики Altenar. Уже восемь лет я занимаюсь бэкенд-разработкой, четыре года руковожу командами. Так уж вышло, что с самого начала карьеры мне стабильно доставались интеграции — и особенно платёжные. За это время я накопила немало «боевых» историй и стала настоящим параноиком в вопросах надёжности и стабильности систем. Сейчас делюсь этим опытом, чтобы другим командам не приходилось наступать на те же грабли.

А для тех, кто ещё ни разу не сталкивался с интеграциями, начнём с простого: что это вообще такое, зачем нужны и почему для них создают отдельные команды.

Что такое интеграция

Ни один бизнес не делает всё сам. И чтобы расти, компании подключают то, что уже создано другими.

Интеграция — это связка между вашим продуктом и внешним сервисом.

Например, нет смысла строить с нуля сложнейшую платёжную систему, если можно подключить готового провайдера. Необязательно самим проверять паспорта и документы клиентов — для этого есть сервисы, типа Know Your Customer (KYC). То же самое с почтовыми шлюзами: вместо своего — готовый API.

Я работаю в компании, которая активно использует сторонние решения и делает высоконагруженные сервисы для букмекерских контор. Сегодня у нас более 600 сотрудников, присутствие в 50+ странах и свыше 10 лет опыта на рынке. За это время накопилось больше 60 интеграций.

Руководителем команды я стала 2,5 года назад. Разумеется, не все интеграции создавались под моим руководством — многие появились задолго до меня. Но я всё равно их знаю. И не потому, что специально изучила каждую с нуля, а потому что до сих пор регулярно сталкиваюсь с проблемами, которые всплывают даже спустя 4-5 лет после релиза.

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

Но радость была преждевременной. С одной-двумя интеграциями справиться ещё можно, но когда их больше шестидесяти — это совсем другая реальность. Рассказать о каждой здесь невозможно. Поэтому я сосредоточусь на платёжных интеграциях — тех, с которыми мы сталкиваемся чаще всего.

Так же чтобы история звучала живее, главным персонажем в моём рассказе будет  разработчик Валера. Он работает в стартапе и делает свою первую платёжную интеграцию.

Эпизод 1. Первая платёжная интеграция Валеры

У Валеры были ожидания. Он думал, что изучит документацию, реализует интеграцию, внесёт правки после QA, выкатит всё на прод и вообще навсегда забудет о задаче.

…но вместо этого он впал в состояние, близкое к депрессии.

Чтобы понять, как так получилось, вернёмся в день, когда интеграция прошла тестирование QA и была доставлена в прод. Валера радостный пошёл домой и лёг спать. Ему снился прекрасный сон о том, как он заработал кучу денег и отдыхает где-то на Мальдивах. Пока не разбудил звонок продакта:

Валера, естественно, в панике. «Как так? Мы же всё проверили! Я всё покрыл тестами и расписал для QA миллион сценариев!» — помчался в офис, открыл логи и увидел:

  • Сообщение об успешном платеже было отправлено провайдером.

  • Сообщение об успешном платеже было принято интеграцией.

Так и должно быть. Но спустя 10 секунд:

  • Сообщение об успешном платеже снова отправлено провайдером.

  • Сообщение об успешном платеже снова принято интеграцией.

Проблема в том, что провайдер при сбое может многократно отправлять одно и то же сообщение. А значит, у интеграции должен быть механизм, умеющий это обрабатывать.

Валера обратился к более опытным коллегам, и те сказали ему всего одно слово — идемпотентность.

Многие знакомы с этим понятием, но для тех, кто не сталкивался, приведу простой пример. Представьте, вы нажимаете на кнопку «Открыть дверь» на брелоке от машины. Нажали один раз — дверь открылась. Нажали второй раз — результат тот же: дверь открыта. Это идемпотентно.

А вот если при первом нажатии одной и той же кнопки дверь открылась, при втором закрылась, а при третьем открылся багажник — это уже не идемпотентно, потому что каждый раз вы получаете разный результат.

Валера оказался умным парнем, он быстренько прочитал об этом и добавил проверку идентификатора платежа и его статуса при каждом сообщении от провайдера. Теперь при повторных запросах приложение корректно отвечало 200 OK, но по факту ничего не делало. То есть ключом идемпотентности в его случае стал именно идентификатор платежа и его статус.

Понимаю, что для многих это очевидно, но эта проблема до сих пор актуальна. Например, недавно мы столкнулись с ситуацией: из-за таймаута первый запрос на вывод средств не вернул ответа и система отправила его повторно. В итоге провайдер обработал оба запроса, несмотря на то, что ключ идемпотентности был передан.

Немножко заглянем внутрь.

Крайне рекомендую не делать ключи идемпотентности на базе уникального индекса в БД. Это звучит красиво, но на практике превращается в боль. Очень легко забыть, что этот уникальный индекс — ключ идемпотентности. И как только вы начинаете расширяться — к примеру, добавляете партиционирование по месяцам (для MySql для этого включаете в уникальный индекс колонку created_at), — вы теряете идемпотентность, даже не заметив.

На самом деле, тема ключей идемпотентности куда сложнее, чем кажется на первый взгляд.

Вернёмся к Валере. На этот раз он пошёл домой чуть менее радостный, но всё же лёг спать. Снился ему снова прекрасный сон — теперь уже про отпуск в Турции. Пока его снова не разбудил звонок продакта:

Валера спросонья вообще ничего не понял: «Как так? Я же только вчера это починил!» Подумал, что, наверное, продакт что-то перепутал. Но как ответственный разработчик он собрался, приехал в офис, открыл логи — и что же он увидел:

  • Сообщение об успешном платеже было отправлено провайдером.

  • Сообщение об успешном платеже было принято интеграцией.

Всё верно. Но в тот же момент, на другой ноде:

  • Сообщение об успешном платеже было отправлено провайдером.

  • Сообщение об успешном платеже было принято интеграцией.

Казалось бы, Валера добавил ключи идемпотентности, но почему-то это не помогло. Дальше разберёмся, как можно было найти источник проблемы.

Ответ кроется в распределённой природе системы. Чтобы разобраться, важно быть знакомым с тремя понятиями:

  • Race condition

Приложения, которые писал Валера, распределённые и работают в нескольких дата-центрах, поэтому одно и то же событие может быть получено одновременно на нескольких нодах. В итоге две операции могут выполняться параллельно.

  • Транзакции

С транзакциями Валера был знаком и даже использовал их в своём решении. Но, как видно, этого оказалось недостаточно. Транзакции сами по себе не спасли ситуацию.

  • Уровни изолированности транзакций

Гораздо глубже лежит тема уровней изолированности транзакций. И вот о ней Валера не знал. Как не знал и то, что Postgres, который он использует, по умолчанию обладает уровнем Read Committed и не блокирует чтение при записи.

В результате первый процесс, обновляющий сумму и статус, завершил работу уже после того, как второй процесс начал своё выполнение. Поэтому ключ идемпотентности просто не помог.

Есть несколько способов справиться с подобными ситуациями. Например, использовать: 

  • Распределённые блокировки

Самый популярный подход — мьютексы. Это механизм синхронизации, который не даёт нескольким потокам одновременно менять одни и те же данные. Обычно для этого используют внешнюю точку координации, например Redis.

Я всегда вспоминаю аналогию с аквапарком. Представьте, что все посетители хотят скатиться с горки и лезут без очереди. Итог — хаос, кто-то падает в бассейн, кто-то мимо. Мьютекс — это инструктор наверху, который пускает по одному: как только один человек закончил спуск, пускает следующего.

  • Шардирование

Другой вариант — разделение данных на логические части. Например, если бы у нас были шарды по пользователю, то конфликтов просто не возникло бы: разные процессы работали бы со своими сегментами данных.

Конечно, есть и другие подходы — очереди, саги и прочие механизмы. Но сейчас не будем углубляться.

Вернёмся к Валере. Он имплементировал мютексы на Redis, выкатил на прод и пошёл спать со спокойной душой. Ему всё ещё снился хороший сон. Правда, денег заработал чуть меньше, поэтому отдыхал уже в Ялте. И вот снова звонок от продакта:

Валера был, конечно, не рад, но теперь хотя бы проблема другая. Он помчался в офис и открыл логи. Запросов нет. Пришлось лезть в логи к провайдеру, и там всё прояснилось:

  • Сообщение об успешном платеже было отправлено провайдером

  • Сообщение потерялось

  • Приложение об этом не в курсе

Что в этом случае стоит знать, чтобы исправить ситуацию.

Валера использовал актуальную схему взаимодействия с провайдером, так называемую схему на коллбэках. В этом случае приложение обращается к провайдеру за ссылкой на оплату, получает её, а дальше сам провайдер сообщает приложению всё, что происходит с платежом — оплата в процессе, оплата прошла и так далее.

Схема удобная: приложение получает информацию именно тогда, когда необходимо. Но у этого подхода есть минус — если что-то пошло не так, мы, как принимающая сторона, об этом можем просто не узнать.

В этом случае неплохо было бы дополнить менее популярной схемой, которая есть не у всех провайдеров, но ранее интеграции чаще встречались именно на ней. Это схема на самостоятельном опросе статусов.

Приложение регулярно ходит к провайдеру с вопросом: «А что с моей оплатой? А теперь? А сейчас?». Минус очевиден: мы нагружаем провайдера, а если делаем это редко, данные обновляются с задержкой, словно в замедленной съёмке. Клиентам такой опыт, конечно, не нравится.

Но если объединить обе схемы, получается отличный вариант. Мы используем коллбэки как основной механизм, а если за установленный тайминг (например, 10 минут) от провайдера не приходит никаких изменений статусов, инициируем опрос сами: «А ты не забыл про меня? Может, что-то изменилось?». Такой подход выглядит лучше.

Здесь также полезно вспомнить и предыдущие моменты, где мы говорили о мьютексах и способах работы с распределённой системой. Даже в этой схеме возможна ситуация, когда два процесса будут пытаться выполнить одну ту же операцию. Например, одновременно придёт коллбэк, и мы запросим информацию о платеже. На благо Валера уже учёл опыт с мьютексами и идемпотентностью — и сумел избежать повторных ошибок.

Но домой снова пошёл тревожным. Во сне ему мерещилось, что денег он так и не заработал и в отпуск его никто не отпустит. И снова звонок продакта:

Валера уже на нервах. Быстро оделся, помчался в офис, открыл логи. Что же пошло не так?

  • Сообщение о платеже в процессе обработки было отправлено провайдером. 

  • Сообщение об успешном платеже было отправлено провайдером. 

Пока на стороне провайдера всё выглядит корректно. Но вот что на стороне приложения:

  • Сообщение об успешном платеже было получено приложением.

  • Сообщение о платеже в процессе обработки было получено приложением.

Проблема в том, что статусная модель Валеры нарушилась. Он даже не думал, что такое возможно, но оказалось, что провайдер не гарантирует последовательность статусов. Их нужно отслеживать самостоятельно. Для многих платёжных интеграций простой и рабочий вариант — использовать механизм допустимых переходов. Когда есть чёткая схема, статусы идут только вперёд, и никаких проблем не возникает.

Но у подхода есть ограничения. Если менять статусы «вбок» (например: сначала платёж active, потом suspended, потом снова active), допустимые переходы уже не спасают. Нужно договариваться с провайдером и уточнять, как различать запросы: какой первый, какой второй. Но, к сожалению, универсального решения нет.

Итак, Валера реализовал механизм допустимых переходов, но идти после этого домой, уже не захотел. Просто сидел и изучал свою интеграцию, думая, где ещё что-то может пойти не так. Поздно вечером его буквально выгнали из офиса. Заснул Валера с трудом, и на этот раз ему снился не отпуск, а продакт. И, как водится, снова звонок:

Валера, конечно, «в восторге» от происходящего, но встал и поехал на работу. Как так получилось? 

Валера оказался слишком ответственным. В документации провайдера он прочитал: срок жизни ссылки на оплату — 15 минут. Поэтому все платежи в статусе open, которым было больше 20 минут, он автоматически отменял. Логично, казалось бы — ссылка на оплату уже не актуальна, но только через 10 минут после отмены пришло:

  • Сообщение об успешном платеже было отправлено провайдером

  • Сообщение об успешном платеже НЕ было принято приложением

Помните, мы реализовали корректную схему обработки статусов? Здесь она подвела. А Валера понял, что перестарался. Исправить оказалось просто: он удалил свою реализацию с отменой платежа. Платежи оставались в открытом статусе, и проблема была решена.

Валера за время этой истории прошёл почти все стадии принятия.

Отрицание. Он не мог поверить, что с его интеграцией что-то не так. Он ведь очень старался!

Гнев. Злился на провайдера: «Почему он не указал эту информацию? Почему я должен сам до этого додумываться?»

Торг. Уговаривал себя: «Сегодня я баг исправлю — и точно посплю нормально. Больше меня никто не разбудит».

Депрессия. В ней он и оказался. Но, чтобы выйти дальше к принятию, Валере нужно было осознать пару вещей.

Стоит ли верить документации? Казалось бы, конечно, да: это же протокол. Но правильный ответ — нет. Документации слепо доверять нельзя. Даже если поля описаны, они могут оказаться неактуальными, иметь другие типы или просто не соответствовать реальности. Провайдеры не описывают массу ситуаций, с которыми придётся столкнуться. Поэтому важно советоваться: с коллегами, сообществом, кем угодно, лишь бы получить опыт из первых рук.

Стоит ли верить в стабильность провайдера? Хотелось бы, но и здесь правильный ответ — нет. Каким бы надёжным и крупным ни был провайдер, всё, что может пойти не так, рано или поздно пойдёт не так.

В итоге Валера дошёл до состояния «не верю никому». Что-то переписал сам, начал хранить логи в персистентном хранилище — и это оказалось правильным решением. Со временем пришло и принятие: он понял, что неплох как разработчик. Просто никто не подсказал, что такие ситуации вообще возможны.

Если эту статью читают представители платёжных провайдеров, у меня есть к вам есть просьба. Попробуйте поставить себя на место разработчика, у которого нет команды и наставника. Подумайте, что стоит добавить в документацию, чтобы помочь такому человеку не наломать дров. Никто не ждёт, что вы опишете всё пошагово, но хотя бы пару базовых понятий точно не повредят.

Что ж, с первым эпизодом мы закончили, переходим ко второму.

Эпизод 2. Первая переписанная платёжная интеграция Валеры

Чёрный лебедь — это событие, которое изначально кажется редким и труднопрогнозируемым, но постфактум часто оказывается вполне логичным, исходя из сложившейся ситуации.

Нассим Талеб, известный математик и экономист

Чёрные лебеди прятались до 1697 года, человечество вообще не знало об их существовании, а теперь они кажутся обыденностью. 

Прошло полгода. Валера жил спокойно… пока бизнес не решил выйти на рынок Японии. Сначала Валера был в шоке, но пошёл смотреть документацию провайдера. Там всё выглядело просто: поддержка разных валют, возможность запросить курс. Но стоило копнуть глубже, как оказалось, что у японской иены нет копеек или центов!

Валера уже начал разбираться, как это решить, когда его отвлекла бухгалтерия. Бухгалтер спрашивал, почему отчёт в системе не совпадает с отчётом провайдера. Валера даже представить не мог, что пошло не так, но не успел начать разгребать проблему, когда его перебил продакт: клиенты нажимают кнопку «Вывести 10 000 рублей», а деньги списываются дважды, трижды и так далее. И таких случаев — не один. Валера понял: из компании утекли деньги. Пришлось срочно переключаться на этот баг, но снова прибежал продакт с криками: «Провайдер прекратил обслуживание на неопределённый срок! Резервного канала оплаты нет!»

Если вам кажется, что такой сценарий маловероятен, напомню пару историй. В 2010 году из-за DDoS-атаки на сервера ПС «Ассист» бронирование авиабилетов на сайте Аэрофлота не работало целую неделю. Но таких проблем хватает и сегодня. Например, 2023 год, система «Золотая Корона», плюс-минус та же ситуация.

Рефлексия

В итоге: провайдер не работает, баги чинить не надо, потому что чинить-то уже нечего. Поэтому Валера сел рефлексировать: что пошло не так и что можно было сделать иначе и начал разматывать весь комок проблем с самого конца.

Чужая зона ответственности

Провайдер обещал красивый SLA в 99,9%, но Валера понимал: такие цифры в реальности редко выполняются. А если что-то сломается, отвечать всё равно придётся ему. Он даже не подумал обсудить этот риск с бизнесом. Возможно, бизнес бы не согласился, но хотя бы тогда у Валеры была бы моральная подстраховка: «Я же предупреждал». А без этого остаётся только молча разбирать последствия.

«Подумаем об этом завтра»

Свою первую интеграцию Валера делал только под одного провайдера. Мысль о мультипровайдерной схеме он отложил «на потом». В итоге, когда бизнесу понадобился ещё один провайдер, внедрение растянулось надолго: слишком много общей логики оказалось жёстко завязано на старый код.

Отсутствие подстраховки

Валера вспомнил и о баге с многократными списаниями. Здесь можно было заложить защиту с самого начала. Никто ведь не запрещал ввести базовые ограничения:

  • дневные лимиты,

  • верификацию при больших суммах,

  • алерты на аномальные пики транзакций.

Да, часть мер нужно согласовывать с бизнесом, но многие из них можно внедрить и без долгих обсуждений. Если бы такие антифрод-правила были реализованы с самого начала, Валера сам бы заметил дорогую ошибку и исправил её до того, как прибежал продакт. Но, к сожалению, этого не произошло.

Прорефлексировав над проблемой утечки денег из-за позднего обнаружения бага, Валера вернулся к вопросу от бухгалтерии и иенам.

Тут его ждал сюрпиз, оказывается, он накосячил в при проектировании баз данных. Думал: дело простое, ничего сложного, и не заморачивался. Вы, наверное, знаете это состояние, когда уставший сидишь над миграцией для новой таблицы и думаешь: «Да нормально всё, справлюсь». А потом оказывается, что выбранный тип данных не подходит.

Вот и Валера не задумался, не прогуглил, не посоветовался с коллегами, а оказалось, что использовать float для работы с деньгами — плохая идея.

Напомню: программирование — это не всегда математика. Дело не в языке программирования, Валера использовал вполне нормальный. А в том, как компьютер работает с данными. Использовать типы с десятичной арифметикой, где числа произвольной длины хранятся без потери точности, было бы гораздо лучшим решением. Либо хранить суммы в минимальных единицах (например, в центах). Тогда бы решился и вопрос с бухгалтерией, и с японскими валютами без копеек.

Вместо этого из-за float возникли ошибки округления:

0.1 + 0.2 = 0.30000000000000004

В итоге мы получили набор проблем:

  • нет поддержки мультипровайдеров,

  • нет лимитов,

  • везде используется float,

  • не предусмотрена работа с валютами без копеек.

Бизнес собрался вместе с Валерой думать, что делать. Выход оказался один — полностью переписывать первую интеграцию, а заодно добавлять ещё одну. Валера справился, благо успел до этого сходить в отпуск. Но отпуск мало помог — цену он заплатил большую и снова оказался на грани выгорания.

Советы от Валеры

Другим разработчикам, особенно в стартапах, чтобы высыпаться

Документации нельзя слепо верить

Документация — это протокол, но вовсе не факт, что он будет соблюдён. Лучше лишний раз посоветуйтесь с коллегами или спросите у сообщества, что может пойти не так.

Не рассчитывайте на стабильность провайдера

Мы часто верим, что провайдер будет работать всегда. Но на его стороне может очень многое пойти не так. Поменяются справочники, и если вас не предупредили — узнаете об этом только когда систему завалят алерты. Провайдер может банально «вырубиться». Думайте о том, что может сломаться, и старайтесь предусмотреть это заранее.

Больше логов

С платёжными провайдерами логи — это святая святых. Разбираться придётся много, дотошно, а иногда и с вопросами материальной ответственности. Пример: деньги списались дважды, потому что не было ключа идемпотентности. Чтобы в таких ситуациях выяснить, кто виноват, нужно разбираться с провайдерами, имея детальные логи.

Проектируйте «на берегу»

Совет спорный, потому что никто не любит овер-инжиниринг. Но если бизнес уже начал работать с интеграцией, велика вероятность, что на ней всё не закончится: появятся новые страны, новые города, новые провайдеры. Лучше это сразу закладывать и хотя бы какие-то моменты общей логики вынести отдельно.

Будьте проактивны

Формально зона ответственности разработчика ограничена. Но именно нам потом вставать по ночам и фиксить. Поэтому иногда лучше предупредить бизнес: сказать, что SLA в 99,9% звучит красиво, но в реальности всё может быть иначе. Возможно, вас не послушают. Но так вы хотя бы сохраните себе нервы и добавите несколько часов сна.

Если у вас возникли вопросы по теме материалов, их можно задать в личных сообщениях в Telegram.

Скрытый текст

А если вы хотите больше узнать о работе с высоконагруженными системами, пообщаться с ведущими специалистами IT-сферы и обменяться опытом с коллегами, не пропустите конференцию HighLoad++ 2025! Мероприятие пройдет 6-7 ноября в Москве. Принять участие можно как онлайн, так и оффлайн.

Комментарии (12)


  1. sshmakov
    29.10.2025 09:54

    И как только вы начинаете расширяться — к примеру, добавляете партиционирование по месяцам (для MySql для этого включаете в уникальный индекс колонку created_at), — вы теряете идемпотентность, даже не заметив.

    Не стоит путать уникальный индекс, первичный ключ и ключ партиционирования

    Но в целом подборка кейсов и их решений достойная, спасибо


  1. sshmakov
    29.10.2025 09:54

    Гораздо глубже лежит тема уровней изолированности транзакций. И вот о ней Валера не знал. Как не знал и то, что Postgres, который он использует, по умолчанию обладает уровнем Read Committed и не блокирует чтение при записи.

    В результате первый процесс, обновляющий сумму и статус, завершил работу уже после того, как второй процесс начал своё выполнение. Поэтому ключ идемпотентности просто не помог.

    Для этого сначала запись блокируется оператором SELECT FOR UPDATE SKIP LOCKED или NOWAIT, а уж потом все остальное.


    1. dph
      29.10.2025 09:54

      А зачем SKIP LOCKED? Тут же как раз нужна блокировка.
      SKIP LOCKED сделан для очередей, а не для попыток установить лок.


      1. sshmakov
        29.10.2025 09:54

        Конструкция FOR UPDATE ставит лок, а SKIP LOCKED нужен для того, чтобы не тратить время на те записи, где лок уже стоит


        1. dph
          29.10.2025 09:54

          Это понятно.
          Но тут же нет задачи "поставить блокировку на первую (первые) незаблокированную запись", а SKIP LOCKED предполагается именно для такой схемы, для реализации очередей с несколькими воркерами.
          Если не нужно ждать блокировки, то лучше уж NOWAIT?


          1. sshmakov
            29.10.2025 09:54

            Так я и предлагаю изменить подход - ставить лок на платеж в самом начале обработки, чтобы никто его не забрал.

            Если не нужно ждать блокировки, то лучше уж NOWAIT?

            Зависит от сценария обработки, и так, и так можно сделать.


            1. dph
              29.10.2025 09:54

              А чем простой select for update не проходит? Он же поставит блокировку, если ее нет.
              Впрочем, мне вообще не понятно, зачем при обработке платежа нужны подобные локи. Тут скорее проблема вообще всего процесса платежа, не нужны там ни мутексы, ни блокировки на БД.


  1. svetayet
    29.10.2025 09:54

    отличная статья. прям спасибо!


  1. dph
    29.10.2025 09:54

    А почему задачу по платежной интеграции поручили джуну без опыта? И при этом его решение даже миддл не посмотрел?
    И почему вообще нет стандартного шаблона для платежных интеграций? Обычно он появляется после третьей попытки...


  1. dph
    29.10.2025 09:54

    Ну и очень, очень много вопросов к архитектуре.
    Не совсем понятно, почему "наличие нескольких ДЦ" приводит к "одновременному приходу одного и того же запроса". Вообще-то вполне можно решить эту проблему (и множеством разных способов).
    Валера так и не смог отличить "идемпотентность" от "безопасной логики". Идемпотентность - это характеристика запроса. А в данном случае правильно говорить не об идемпотентности, а о корректном процессе обработки изменений статуса платежа. Но, судя по всему, эта задача так и не была решена до конца (и даже не была корректно поставлена).
    И подобных вопросов очень много (

    Написание платежных шлюзов - достаточно простая задача, но да, желательно, чтобы ее (хотя бы первый раз) реализовывал сеньор с хоть каким-то опытом в финтехе. Иначе будет так, как написано (и это далеко не худшее из того, что может быть, мне доводилось встречать и гораздо более неприятные истории при эквайринговых интеграциях)


  1. ilih
    29.10.2025 09:54

    Из личного опыта интеграции Сбербанка:
    Интеграция сделана, базовое тестирование прошло, но на расширенной проверке обнаружилось, что для некоторых заказов банк выдает ошибку.
    Сообщение об ошибке никакой полезной информации не содержало.
    Поддержка банка на сообщения с проблемными заказами отвечала, что проблемы на вашей стороне.
    Удалось найти заказ, четко воспроизводящий проблему - все параметры одинаковые, кроме номера заказа и типа оплаты: кредит и "рассрочка" (кредит без переплаты / кредит за счет скидки магазина). Кредит обрабатывается нормально, рассрочка выдает ошибку.
    Улика найдена и... поддержка даёт прекрасный ответ: "так и используйте кредит, а не рассрочку".
    Пришлось объяснять что это разные вещи, и заменить одном другим не выйдет.
    Но для решения проблемы этого оказалось мало, понадобилось создать специальную страницу, чтобы специалисты банка могли отправлять тестовые заказы со стороны магазина с просмотром отправляемых запросов.
    Через какое-то время от них пришел ответ: вы отправляете некорректный заказ, вот данные вашего заказа. На что показали им логи, в которых отправляем совсем другие данные.

    Но из присланных данных заказа стало понятно, в чем проблема:

    1. API для заказа требует передавать не только цены по каждому товару и доп. услугам, но и отдельно сумму всего заказа

    2. при оплате "в рассрочку" (кредит без переплаты) в системе банка цены и сумма уменьшаются на величину кредита с округлением до копеек (то есть формируется новый платеж, в котором цены пересчитаны по формуле new_price = round(price * (1 - rate)))

    3. после округления указанная сумма заказа и сумма по товарам и услугам иногда совпадают, а иногда не совпадают, в случае несовпадения система банка выдавала ошибку

    Получилась прекрасная иллюстрация к вопросу "Для чего программистам нужна математика?".


  1. fixikus
    29.10.2025 09:54

    Например, нет смысла строить с нуля сложнейшую платёжную систему, если можно подключить готового провайдера. Необязательно самим проверять паспорта и документы клиентов — для этого есть сервисы, типа Know Your Customer (KYC). То же самое с почтовыми шлюзами: вместо своего — готовый API.

    Современный мир показывает, что в этом смысл есть - делать все свое: во первых, можно просто лишиться функционала, потому что какой то дядька решил санкции наложить, либо платить за услуги с каждым днем все накладнее, либо сервис нарушает законодательство, либо.., либо.., либо.. Тем более функцинал этих сервисов давно реализован и обкатан, хваленые llm должны с этим справиться. Все проблемы, которые вы подняли, были описаны и систематизированы лет 15 назад и по идее вы с ними сталкиваться на проде не должны.