Платёжное ядро
Независимое от фреймворка TS-ядро платежей для банков Литвы — защищённые webhook и суммы.
Платёжное ядро
Задача
Логику оплаты переписывали под каждый продукт и путали с его фреймворком и базой — именно там и прячутся платёжные баги.
Что делали
Чистое TypeScript-ядро с доменными правилами оплаты без импортов фреймворка или БД; каждый литовский банк или PSP это тонкий провайдер-адаптер, а ядро усилено там, где деньги реально ломаются: проверки подписи и свежести webhook, защита от переполнения суммы и лишних знаков, валидация redirect-целей и потоки ручной проверки, когда ссылка или сумма из callback не совпадают.
Результат
Новый продукт подключает оплату, дописывая адаптеры вокруг одного протестированного, проверенного на безопасность ядра, а не реализуя банковские потоки и состояния ошибок заново.
Dev-story статья
Платёжное ядро: как создавался проект
Возврат из банка через редирект браузера означает лишь то, что пользователь вернулся на сайт — но не то, что деньги ушли, и всё же множество интеграций помечают заказ оплаченным именно по этому сигналу. Это ядро построено на обратном допущении: редирект ничего не доказывает, а единственное надёжное состояние приходит из подписанного колбэка или серверной перепроверки статуса. Всё время напряжение создавало то, что каждый банковский протокол, карточный агрегатор и API кошелька подписывают, кодируют и сообщают суммы по-разному, поэтому укрепление должно было быть общей логикой, а не копипастом под каждого провайдера.
Разделы
05
Модули
05
Стек
TypeScript + Bank integrations
Почему появился проект
Логику оплаты переписывали под каждый продукт и путали с его фреймворком и базой — именно там и прячутся платёжные баги.
Возврат из банка через редирект браузера означает лишь то, что пользователь вернулся на сайт — но не то, что деньги ушли, и всё же множество интеграций помечают заказ оплаченным именно по этому сигналу. Это ядро построено на обратном допущении: редирект ничего не доказывает, а единственное надёжное состояние приходит из подписанного колбэка или серверной перепроверки статуса. Всё время напряжение создавало то, что каждый банковский протокол, карточный агрегатор и API кошелька подписывают, кодируют и сообщают суммы по-разному, поэтому укрепление должно было быть общей логикой, а не копипастом под каждого провайдера.
Что было создано
Чистое TypeScript-ядро с доменными правилами оплаты без импортов фреймворка или БД; каждый литовский банк или PSP это тонкий провайдер-адаптер, а ядро усилено там, где деньги реально ломаются: проверки подписи и свежести webhook, защита от переполнения суммы и лишних знаков, валидация redirect-целей и потоки ручной проверки, когда ссылка или сумма из callback не совпадают.
Это пакет на TypeScript, содержащий только правила платёжной предметной области — подпись запросов, валидацию колбэков, нормализацию статусов, проверки суммы и валюты, обработку повторных колбэков и безопасные для аудита нормализованные события. Ядро не импортирует никакого фреймворка, HTTP-рантайма или клиента базы данных: оно принимает простую структуру PaymentHttpRequest, так что один и тот же код провайдеров работает из нашего реактивного бэкенда, Node-воркеров или тестов. Реализовано несколько реальных литовских банков и провайдеров; банки без официальных мерчант-материалов поставляются как строгие скелеты, которые бросают ошибку, а не гадают.
Основные модули и путь пользователя
Проверка подписанных колбэков и вебхуков по каждому протоколу (RSA-SHA512/SHA1/SHA256, отсоединённый JWS, hex HMAC-SHA256) по точным сырым байтам запроса, а возвратные редиректы моделируются как provesPayment:false, чтобы поддельный URL успеха никогда не мог завершить заказ
Защита от повтора через опциональные окна свежести: недатированные колбэки или те, чья метка времени/JWS iat выходит за допуск, отклоняются как недоверенные; по умолчанию выключено, чтобы законно запоздавшие колбэки всё же доходили
Денежные проверки как общие хелперы: decimalAmountToCents, возвращающий undefined за пределами Number.MAX_SAFE_INTEGER, и отклонение сумм с более чем двумя знаками после запятой вместо тихого округления 12.999 до 1300 центов
Маршрутизация на ручную проверку при расхождении: когда ссылка, сумма или валюта вебхука не совпадают с авторитетным заказом, полученным с сервера, событие уходит в requires_manual_review как сигнал вмешательства, а не слепо доверяет одной из сторон
Независимое от фреймворка ядро плюс тонкие адаптеры, со строгими скелетами провайдеров, которые бросают явные ошибки «требуется реализация», пока не появятся реальные эндпоинты, поля, подписи и сертификаты
Архитектура и технологические решения
Сделано на TypeScript, Bank integrations, Crypto.
Чистый TypeScript с подключаемыми адаптерами Web Crypto и Node crypto, без импортов фреймворка в точке входа ядра, и CI-гейт, удерживающий 100% покрытие по операторам, ветвям, функциям и строкам в рантайм-коде.
Результат и выводы
Новый продукт подключает оплату, дописывая адаптеры вокруг одного протестированного, проверенного на безопасность ядра, а не реализуя банковские потоки и состояния ошибок заново.
Один и тот же код провайдеров работает без изменений на разных бэкендах и в тестах, а каждый путь, завершающий платёж, ломается безопасно — переполненная сумма, повторный колбэк или несовпадающая ссылка выдают отсутствие платежа или флаг проверки, а не неверный результат.
Связанные статьи
Читать дальше
Связанные истории проектов
Эти проекты близки по техническим или продуктовым решениям и показывают, как тот же принцип работает в другом контексте.
Dev-storyCMS
Динамический headless-CMS на webedge-db — типы контента, медиа, роли и публичный read-API, питающий наши сайты и статьи.
Dev-storyПубличный сайт WebEdge
Наш lt/en/ru сайт на Astro, контент из WebEdge CMS.
Dev-storyВоркфлоу Pi на локальной модели
Расширение Pi: локальная модель делает работу, GPT-5.6 только проверяет план и ревью.
Есть похожая идея?
Обсудить проект