Payments & checkout
Делаю платёжные сценарии и оформление заказа, которые принимают деньги корректно: wallet-чекаут, Stripe, банковские переводы — идемпотентно, на вебхуках, со сверкой. В продакшене у ChatFood и alfii.
Платежи, которые я делал
Платежи — это место, где баг перестаёт быть косметическим и начинает стоить реальных денег. Я строил и интегрировал их в боевых продуктах, а не в песочнице.
В ChatFood — платформе order-and-pay, которую купил Deliverect, — я сделал чекаут через Apple Pay на фронтенде Vue 3: весь путь от каталога к корзине и до одного касания в кошельке. На бэкенде я участвовал в фиче payment links на Laravel — сценарии, где продавец отправляет клиенту ссылку и получает оплату без полноценной витрины.
В alfii, HR- и payroll-SaaS, я интегрировал Stripe для оплаты картами и Dapi для прямых банковских переводов. Я провёл анализ, выстроил рабочий процесс и связал оба способа с фронтендом на React. Попутно я перевёл слой данных с Redux-Saga на RTK Query, и это сильно упростило понимание потоков платежей и состояния денег.

Деньги без ошибок: целые копейки, никогда не float
Самое важное правило в платежах — самое скучное: никогда не храните деньги как число с плавающей точкой. 0.1 + 0.2 в float — это не 0.3, и как только эта ошибка округления попадает в баланс, она накапливается до выплат, которые не сходятся.
В alfii я столкнулся ровно с этим — баг в обработке денег из-за арифметики с плавающей точкой. Решение было перевести каждое денежное значение в целые минимальные единицы: центы, филсы, тийины. Вы храните 1099, а не 10.99, и приводите к десятичному виду только на самом краю — когда показываете число человеку. Математика остаётся точной, итоги сходятся, а целый класс багов просто исчезает.
Это не обсуждается ни в одном из моих проектов. Деньги — это целое количество наименьших единиц своей валюты, и точка.
Wallet-чекаут (Apple Pay)
Чекаут, который я сделал для ChatFood, был wallet-first, и причина — конверсия. Каждое поле, которое вы просите клиента заполнить на телефоне, — это место, где он отваливается. Apple Pay сворачивает имя, карту, адрес плательщика и доставку в одно подтверждённое касание — биометрия, без клавиатуры.
Для заказа еды, где клиент голоден и сидит с телефона, это важнее почти всего остального на странице. Инженерная задача — показать панель кошелька в нужный момент, чисто передать платёжный токен на бэкенд и обработать случаи, когда кошелёк недоступен, не ломая сценарий.
Wallet-чекаут ещё и сокращает PCI-scope: данные карты не касаются ваших серверов. Это выигрыш и по безопасности, и по комплаенсу одновременно.
Банковские переводы (Dapi)
Не каждый платёж должен идти через карту. Для зарплат и крупных B2B-сумм банковские переводы дешевле и часто именно то, что нужно клиенту. В alfii я интегрировал Dapi, чтобы переводить деньги напрямую со счёта на счёт.
Банковские рельсы ведут себя иначе, чем карты: они асинхронны, проводятся по своему расписанию, и источник истины — вебхук, а не ответ API, который вы получили в момент запуска перевода. Сделать это хорошо — значит проектировать с учётом этой задержки с самого начала: pending-состояния, которые UI может честно показать, и конечный автомат, который помечает деньги как полученные только тогда, когда рельса это подтвердила.
Как я подхожу к интеграции платежей
Каждая интеграция платежей, которую я делаю, стоит на нескольких жёстких правилах, выученных на боевых проектах.
Ключи идемпотентности на каждом вызове, двигающем деньги. Сети повторяют запросы, пользователи кликают дважды, мобильные соединения рвутся посреди запроса. Ключ идемпотентности означает, что одно и то же намерение, обработанное дважды, всё равно списывает один раз. Без него вы получаете двойные списания и злых клиентов.
Источник истины — вебхуки, а не ответ API. Авторизация по карте может пройти, а захват средств — упасть; перевод может быть принят, а потом отменён. Вебхук провайдера — это реальный поток событий. Я веду состояние платежа по вебхукам, а синхронный ответ считаю подсказкой, а не фактом.
Сверка — часть разработки, а не то, что потом. В любой момент я хочу мочь ответить: совпадает ли то, что мы считаем собранным, с тем, что провайдер считает проведённым? Если две книги нельзя сравнить, о потерянных деньгах вы узнаёте с опозданием в недели.
Минимизируйте PCI-scope. Я опираюсь на hosted fields, wallet-сценарии и хостинговый чекаут провайдера, чтобы сырые данные карт не попадали на серверы, за которые я отвечаю. Меньше scope — меньше поверхности для аудита и меньше рисков.
Если вы строите коммерс-продукт, которому нужно принимать деньги, это та основа, с которой я начинаю.
Почему я выбираю это
Деньги — целое число, никогда не float
Я храню каждую сумму в наименьшей единице валюты — центы, филсы, тийины — чтобы итоги оставались точными и сходились. В alfii я починил реальный денежный баг, перейдя на целые копейки. Float-арифметика — это то, как ошибки округления просачиваются в балансы.
Идемпотентно и на вебхуках
Каждый вызов, двигающий деньги, несёт ключ идемпотентности, так что повтор или двойной клик списывают один раз, а не дважды. Состояние платежа ведётся по вебхукам провайдера — реальному потоку событий, — а не по синхронному ответу, который может соврать.
Wallet-first ради конверсии
Apple Pay и Google Pay сворачивают весь чекаут в одно касание с биометрией, что заметно лучше конвертирует на мобильных. Именно для этого я сделал wallet-чекаут в ChatFood — меньше введённых полей означает меньше потерянных клиентов.
Меньше PCI-scope, меньше рисков
Я опираюсь на hosted fields, wallet-сценарии и хостинговый чекаут провайдера, чтобы сырые данные карт не касались моих серверов. Меньший PCI-scope — это меньшая поверхность для аудита, меньше ответственности и более безопасный продукт по умолчанию.
Сделано на этом
Частые вопросы
Apple Pay или обычный чекаут картой?
В идеале и то, и другое — предложите кошелёк и оставьте форму карты как запасной вариант. Apple Pay (и Google Pay) конвертируют лучше на мобильных, потому что убирают ввод: одно касание с биометрией заменяет имя, номер карты и адрес. Они ещё и держат данные карты вне ваших серверов, что сокращает PCI-scope. Именно такой wallet-first чекаут я сделал на фронтенде ChatFood на Vue 3.
Как вы храните денежные значения?
Как целые числа в наименьшей единице валюты — центы, филсы, тийины — никогда как float. `10.99` становится `1099`, и я привожу к десятичному виду только при показе человеку. В alfii я починил реальный баг обработки денег, перейдя на целые копейки. Float-арифметика вносит ошибки округления, которые ломают сверку; целые минимальные единицы делают математику точной.
Stripe или локальный платёжный шлюз?
Зависит от того, куда идут деньги. Stripe отлично подходит для карт и из коробки даёт идемпотентность, вебхуки и хостинговый чекаут — я интегрировал его в alfii. Но для зарплат, крупных B2B-сумм или рынков, где карты дороги, прямые банковские переводы часто лучше; для этого я использовал Dapi в alfii. Я выбираю рельсу под задачу, а не по умолчанию.
Как вы обрабатываете неудавшиеся или дублирующиеся списания?
Ключами идемпотентности и состоянием на вебхуках. Каждый запрос, двигающий деньги, несёт ключ идемпотентности, так что повтор или двойной клик обрабатывают одно намерение один раз, а не списывают дважды. Состояние списания ведётся по вебхукам провайдера — реальному потоку событий, — а не по синхронному ответу API, который может соврать. Неудавшиеся списания дают честные pending- и failed-состояния в UI, а не тихие ошибки.
Можно ли добавить платежи в существующее приложение?
Да — обычно так и бывает. В alfii я добавил Stripe и Dapi в уже работающее React-приложение и сначала провёл полный анализ и проектирование процесса. Работа — это чистая интеграция с вашим слоем данных, проектирование конечного автомата денег и связка вебхуков и сверки, а не перестройка продукта вокруг чекаута.
Как вы защищаете платёжные данные?
Держа их вне моих серверов везде, где это возможно. Я использую hosted fields, wallet-чекаут (Apple Pay) и хостинговые сценарии провайдера, чтобы сырые номера карт не касались инфраструктуры, которой управляю я, — это минимизирует PCI-scope. Состояние денег живёт в целых минимальных единицах, каждый вызов, двигающий деньги, идемпотентен, а вебхуки и сверка гарантируют, что книги всегда сходятся.

