samples/dotnet/inproc/confirmation в репозитории: одно приложение, которое
одновременно и издатель Veriqa, и бэкенд, который к нему обращается. Ещё два устроены так же:
samples/dotnet/inproc/step-up подтверждает разрушительное действие и проверяет, что подтвердил
владелец аккаунта (expected_identities), а samples/dotnet/inproc/agent-approval заставляет вызов
инструмента ИИ-агента ждать одобрения человека.
Как это идёт
- Бэкенд получает токен грантом Client Credentials.
- Создаёт транзакцию:
POST /api/transaction/confirmationс объявленным action type и значениями слотов его сообщения. - Показывает пользователю
channel_entryиз ответа — картинку QR (qr) на экране или ссылку (url) на том же устройстве. - Пользователь открывает канал, читает текстовку и подтверждает или отклоняет.
- Бэкенд опрашивает
GET /api/transaction/{id}/result, пока исход не перестанет бытьpending. - По желанию обменивает подтверждённую транзакцию на
id_tokenтого, кто подтвердил.
1. Настройте клиента
Всё живёт в записи клиента — приложения, которое вызывает API:Где объявляется action type
Action type — это и есть вид сообщения (kind), и объявляется он в записи клиента:Veriqa:OpenIddict:Clients:[n]:MessageTemplates:{action type}:Contract и …:Templates. Групп ByType
и ByAction для этого добавлять не нужно.
Читается и уровень Tenant, если ваш хост зарегистрировал для него читателя; из коробки такого нет.
Что пользователь читает по завершении
Конец транзакции описывают две настройки уровня хоста, и рабочие примеры выше выставляют обе:DisplayIntent задаёт, где появляется квитанция исхода. Продукт везёт ReplacePrompt — квитанция
встаёт на место сообщения с вопросом, — и подтверждение как раз тот случай, когда нужно другое
значение: вопрос несёт формулировку действия, и подмена оставляет переписку с исходом, но без
записи о том, на что он отвечал. NewMessage сохраняет оба сообщения. Это намерение, а не обещание:
Telegram и MAX его исполняют, WhatsApp всегда шлёт новое сообщение. Там, где платформа позволяет
убрать кнопки у отправленного сообщения, не переписывая его текст, NewMessage снимает их тем же
ходом, которым доставляет квитанцию: вопрос сохраняет формулировку как запись о том, на что отвечал
исход, и под завершённой транзакцией не остаётся ничего, что приглашало бы нажать ещё раз. Где такой
операции у платформы нет, кнопка может уцелеть — повторное нажатие приносит лишь ещё одну квитанцию,
потому что терминальную транзакцию не переиграть.
Виды квитанций — собственные сообщения продукта, поэтому живут в секции хоста, а не в записи
клиента: правило про типы действий выше на них не распространяется. Под ByType:confirmation
продукт не везёт ничего, поэтому отвечает широкое объявление {kind} и переформулировка работает
как написана. {app} сервер заполняет ClientId, как и выше; последний шаг каждой лестницы не
пользуется слотами, потому что ни один слот квитанции не гарантирован — даже помеченный
Guaranteed.
Контракт
Contract:Slots — массив. Каждый слот:
{app} подтверждения сервер заполняет ClientId клиента, а не его DisplayName. Чтобы в текстовке было
читаемое имя, заведите свой слот.
Шаблоны
Templates — лестница, от самой полной текстовки к минимальной. Показывается первая ступень, у которой
заполнены все слоты, поэтому необязательный payee аккуратно выпадает, если его не передали.
Последняя ступень может опираться только на слоты, у которых значение есть всегда, — слот
вызывающей стороны с Required: true или серверный слот с Guaranteed: true. Лестница, чья последняя
ступень опирается на необязательный слот, отбрасывается целиком, и транзакции нечего показать.
Мессенджеры показывают простой текст; ступень может быть и структурой { "Plain": "…", "Html": "…" } —
см. Veriqa:MessageTemplates.
2. Получите токен
access_token, token_type и expires_in; кешируйте токен почти до истечения. Токен,
выданный пользователю, этот API отклоняет с 403.
Эндпоинт отдаётся только по https: по обычному HTTP он отвечает 400 invalid_request, «This server
only accepts HTTPS requests». За reverse-proxy, который терминирует TLS, proxy должен быть доверенным
и передавать X-Forwarded-Proto (служба ОС, шаг 7).
3. Создайте подтверждение
Сама текстовка не возвращается, а значения слотов не пишутся в лог выше
Debug.
Ответ — 201 Created
Cache-Control: no-store.
4. Прочитайте исход
matched_type — имя сравнимого типа, совпавшего с expected_identities, или null. Читать результат
может только клиент, создавший транзакцию: любая другая — неизвестная, удалённая после
CompletedRetentionSeconds или чужая — отвечает одинаково, 404 transaction_not_found.
5. Узнайте, кто подтвердил (необязательно)
СAllowConfirmationTokenGrant подтверждённую транзакцию можно один раз обменять на id_token
подтвердившего:
id_token несёт sub, а со scope channel — ещё channel_type и channel_user_id. Правила обмена —
однократное погашение, invalid_grant на любое «нет», что узнаёт приложение — в
привязке с вашего сервера.
Ошибки запроса создания
Любой отказ — Problem Details (application/problem+json): код в title, а detail не называет ни
одного переданного вами значения. При любом отказе ничего не создаётся.
Дальше
Привязка с вашего сервера
Узнайте, какой аккаунт мессенджера подтвердил, и сохраните его рядом с пользователем.
Veriqa:MessageTemplates
Лестницы, редакции, уровни и оси сообщений.