Перейти к содержимому
Интеграция

Деньги остаются в ваших системах. Арифметика — в наших.

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

Граница храненияКарта системы, разделённая одной жирной линией. Со стороны оператора: его аккаунт Stripe, его аккаунт NOWPayments, его банк, его кошелёк и его собственный бэкенд. Со стороны платформы: реестр, прогоны, команды, заявки и исходящая очередь. Границу пересекают четыре линии, несущие команду, квитанцию, инструкцию и событие. Ни одна из них не несёт денег, а реестр и прогоны не пересекают её вовсе.ОператорXEXКомандаКвитанцияИнструкцияСобытиеГраница храненияАккаунт StripeАккаунт NOWPaymentsБанкКошелёкВаш бэкендКнигаПрогоныКомандыЗаявкиИсходящиеНи одна линия здесь не несёт денег. XEX передаёт инструкции; деньги двигает оператор.Книга и расчёты ничего не пересекают
Границу пересекают четыре вещи: расписка, команда, поручение и событие. Ни одна из них не деньги.
Контракт

Опубликованная спецификация и два клиента, написанных по ней.

Документ OpenAPI в репозитории, клиент на TypeScript и клиент на Dart. Ваш инженер прочитает всю поверхность API прежде, чем кто-то назначит созвон.

OpenAPI

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

api/openapi.yaml

Клиент на TypeScript

Для ваших веб- и Node-сервисов. Типы взяты из того же документа.

sdk/typescript

Клиент на Dart

Для приложения на Flutter: у программы, обращённой к участникам, оно обычно есть, а самописный второй HTTP-слой — как раз то место, где эти две стороны расходятся.

sdk/dart

Исходящие

События, в доставке которых можно быть уверенным.

Одиннадцать типов событий, ставящихся в очередь в той же транзакции, что и породившее их изменение состояния.

  1. 01

    Транзакционный outbox, а не POST по принципу «отправил и забыл»

    Событие пишется в той транзакции, которая его вызвала, и рассылается потом из очереди с арендой. Нет окна, в котором вашей системе сообщили о том, что книга затем откатила, и нет окна, в котором книга зафиксировала, а никому не сообщили.

    internal/outbox

  2. 02

    Подписано, с ротируемым идентификатором ключа

    Каждая доставка несёт подпись HMAC-SHA256 с идентификатором ключа и меткой времени. Ротация перекрывается: старый ключ действителен, пока вы переключаетесь, поэтому смена секрета — не простой, который надо планировать.

    internal/outbox/sign.go

  3. 03

    События расчётов идут первыми

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

    internal/events

  4. 04

    Детерминированные id, поэтому повторный прогон ничего не меняет

    Повторный прогон обработчика ставит в очередь тот же id события, а outbox отбрасывает дубликат. Это доставка «хотя бы один раз», которую действительно можно повторять, а не та, с которой надо быть осторожным.

    internal/events · internal/outbox

  5. 05

    Проверка исходящего трафика на ваш URL

    Адрес назначения задаёт оператор, и одно это делает его SSRF-вектором по конструкции. Диспетчер проверяет URL до отправки запроса и блокирует те адреса, которые вы и ожидаете от него заблокировать.

    internal/outbox/ssrf.go

  6. 06

    Отступ, парковка и сверщик

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

    internal/outbox · internal/reconciler

Входящие

Команды, выполняемые ровно один раз, по идентификаторам ваших записей.

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

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

Контракт ошибок явный, потому что «повторять или нет» — это тот вопрос, на который клиентской интеграции действительно надо отвечать. Возвращённая ошибка освобождает расписку, и вы можете повторить. Отказ с кодом 4xx окончателен и кэшируется: команда отклонена по существу, и повтор отклонят снова.

internal/commands · internal/command

Деньги наружу

Платите так, как платите уже сейчас.

Платформа резервирует ровно нужные строки книги и выдаёт поручение. Исполняет платёж ваша сторона.

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

internal/claims · internal/httpapi/payouts.go

Ваш бренд

Ваш собственный домен, подтверждённый до подключения.

Заявите имя хоста, опубликуйте TXT-запись, которую сгенерирует платформа, и подключение произойдёт, как только запись начнёт разрешаться.

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

internal/domains

Дальше

Возьмите песочницу и направьте её на стенд.

Бесплатно на любом тарифе, с той же поверхностью API, теми же событиями и теми же подписями, что и в бою.