Intents API — платёжные интенты¶
Документация всех HTTP-эндпоинтов модуля src/intent/ Payment Manager. Здесь — единый платёжный вход (POST /intents), его вспомогательные endpoint'ы (quote, confirm, cancel, get, events) и контракт авторизации.
Связанные документы:
- DTO-контракты:
../reference/passport/01-dto-contracts.md- Коды ошибок:
../reference/passport/05-error-codes.md- Типы операций:
../reference/passport/03-operation-types.md- Каналы:
../reference/passport/02-channels.md- События:
../reference/passport/04-event-types.md
Общие правила¶
Регистрация роутов¶
Все маршруты ниже регистрируются в src/server.ts внутри scope с hmacPlugin:
src/server.ts:152-163
scope.register(hmacPlugin)
scope.register(intentRoutes) // POST /intents, GET /intents/:id, GET /intents/:id/events
scope.register(confirmRoutes) // POST /intents/:id/confirm
scope.register(cancelRoutes) // POST /intents/:id/cancel
scope.register(quoteRoutes) // POST /intents/quote
Auth: HMAC-SHA256¶
Каждый запрос обязан содержать четыре заголовка (см. hmacPlugin):
| Заголовок | Назначение |
|---|---|
X-Service-Id |
Идентификатор сервиса-вызывающего (auth-center, nginx, admin-panel) |
X-Timestamp |
Unix-секунды, окно ±60s от now() |
X-Signature |
HMAC-SHA256(secret, "${X-Timestamp}\n${method}\n${path}\n${rawBody}") в hex |
X-User-Id |
Идентификатор пользователя Serverpod (целое число; 0 — admin bypass) |
Секрет хранится в service_key.secret — единый для всех сервисов, ротация через /admin/service-keys. Без HMAC → 401 INVALID_SIGNATURE или 401 INVALID_TIMESTAMP.
Разрешения сервиса (service_key.permissions)¶
Дополнительный слой авторизации поверх HMAC:
| Поле | Эффект |
|---|---|
permissions.allowedOperationTypes[] |
Whitelist operationType. Пустой массив = разрешено всё |
permissions.fromAccountOverride |
Разрешает body.fromAccountName |
permissions.toAccountOverride |
Разрешает body.toAccountName |
permissions.allowToTbAccountId |
Разрешает body.toTbAccountId |
При нарушении — 403 FORBIDDEN. Подробнее: 06-service-keys.md.
Денежные единицы¶
Все суммы — целые сатанги (1 THB = 100 satang) в типе bigint. В JSON приходят и уходят строками, чтобы не терялись большие значения. Валюта — THB по умолчанию (ISO 4217).
1. POST /intents — создать платёжный интент¶
Назначение¶
Единственный платёжный вход PM. Принимает запрос на любую операцию (P2P_TRANSFER, IPPS_WITHDRAWAL, THAI_QR_PAY, MINIAPP_CHARGE, MINIAPP_CREDIT, WITHDRAWAL, QP_TOPUP, SERVICE_DEPOSIT, ADMIN_TRANSFER, NFC_CHARGE, INVOICE_PAYMENT), маршрутизирует её через канал (channels/*), создаёт запись в pm.intent, запускает шаги канала. Для одно-фазных каналов — синхронно проводит платёж (CREATED → VALIDATED → AUTHORIZED → SETTLED). Для двухфазных (MERCHANT_INVOICE) — резервирует QR/expiresAt и ждёт POST /intents/:id/confirm.
Auth¶
HMAC + X-User-Id. userId записывается в intent.userId и используется как актор для лимитов и auth-policies. Сервис должен иметь operationType в permissions.allowedOperationTypes (или пустой whitelist). Override полей fromAccountName/toAccountName/toTbAccountId — только при соответствующих permission'ах.
Request body¶
Zod-схема: BaseIntentBody (см. 01-dto-contracts.md#11-baseintentbody).
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
idempotencyKey |
uuid v4 | да | Идемпотентность по (idempotencyKey, serviceId). При повторе — HTTP 200 с тем же intent'ом |
operationType |
string | да | Тип операции (см. список выше) |
amount |
int > 0 | да | Сумма в сатангах |
currency |
string(3) | нет (default THB) |
ISO 4217 |
metadata |
object | нет (default {}) |
Operation-specific: ipps, customerUserId, recipientUserId, tags, ttlSeconds, appScope для invoice |
fromAccountName |
string | условно | Override источника. Требует fromAccountOverride permission |
toAccountName |
string | условно | Override получателя. Требует toAccountOverride permission |
toTbAccountId |
uuid | условно | Получатель по TB-UUID. Требует allowToTbAccountId permission |
fromName |
string<=255 | нет | Отображаемое имя плательщика (для истории и чеков) |
toName |
string<=255 | нет | Отображаемое имя получателя |
comment |
string<=500 | нет | Свободный комментарий |
live |
boolean | нет | Если true — PM публикует переходы статусов в Redis-канал intent.{id} |
externalRef |
string | условно | Внешний ID; обязателен для WITHDRAWAL, запрещён для P2P_TRANSFER |
recipientUserId |
int > 0 | условно | Serverpod userId получателя; обязателен для P2P_TRANSFER (если нет toTbAccountId); запрещён для WITHDRAWAL/MINIAPP_CHARGE |
Operation-specific поля валидируются opDef.parseBody(req.body) (handler.ts:103-105) — для каждого operationType своя дискриминированная схема.
Response 201 / 200¶
Zod-схема: CreateIntentResponse (handler.ts:31-44, см. 01-dto-contracts.md#14-createintentresponse).
- 201 Created — новый intent;
- 200 OK — idempotency replay (тот же
(idempotencyKey, serviceId)уже обработан).
| Поле | Тип | Описание |
|---|---|---|
intentId |
uuid | ID созданного интента |
status |
enum | CREATED | VALIDATED | AUTHORIZED | SETTLING | SETTLED | FAILED | MANUAL_REVIEW | CANCELED | EXPIRED |
channel |
string | Канал, выбранный роутером (INTERNAL_P2P, IPPS_TRANSFER, MERCHANT_INVOICE, ...) |
amount |
string | Сумма в сатангах (только при idempotency replay) |
currency |
string | Валюта (только при idempotency replay) |
preFeeAmount |
string | Сумма PRE-комиссий (взимается с отправителя) |
postFeeAmount |
string | Сумма POST-комиссий (вычитается из получателя) |
requiresMonitoring |
boolean | true если интент остался в AUTHORIZED (надо подписаться на Redis-канал intent.{id} за статусом) |
createdAt |
string | ISO 8601 |
expiresAt |
string | Только для двухфазных (MERCHANT_INVOICE) — таймаут оплаты |
qrSignature |
string | Только для MERCHANT_INVOICE — base64url HMAC-SHA256 16 байт, подпись QR |
version |
int | Optimistic concurrency version (важна для confirm с If-Match) |
Возможные ошибки¶
| HTTP | code | Причина |
|---|---|---|
| 400 | BAD_REQUEST |
Отсутствует обязательное поле (X-User-Id, fromAccountName без override) |
| 400 | NO_ROUTE |
Нет строки в payment_route для пары (operationType, amount) |
| 401 | INVALID_SIGNATURE |
HMAC не совпал |
| 401 | INVALID_TIMESTAMP |
X-Timestamp вне окна ±60 секунд |
| 403 | FORBIDDEN |
Сервис неактивен / operationType не в whitelist / override не разрешён |
| 422 | VALIDATION_ERROR |
Zod не распарсил body |
| 422 | ACCOUNT_NOT_FOUND |
Не найден from/to/transit аккаунт |
| 422 | IPPS_NOT_REGISTERED |
Получатель IPPS не зарегистрирован у PSP |
| 422 | IPPS_METADATA_INVALID |
Невалидная структура metadata.ipps |
| 422 | INSUFFICIENT_FUNDS |
TigerBeetle отказал в pending: на источнике недостаточно средств |
| 422 | LIMIT_EXCEEDED |
Превышен лимит из auth-policies. В detail — {ruleName, window, limitType, limit, current, requested} |
| 500 | TB_TRANSFER_ERROR |
TigerBeetle отверг трансфер по иной причине |
Полный реестр — 05-error-codes.md.
curl-пример¶
P2P-перевод 150 THB между двумя пользователями:
TS=$(date +%s)
METHOD="POST"
PATH="/intents"
BODY='{"idempotencyKey":"6f1b3c5e-1234-4abc-9def-0123456789ab","operationType":"P2P_TRANSFER","amount":15000,"currency":"THB","recipientUserId":42,"comment":"Lunch split"}'
SECRET="<service-secret>"
SIG=$(printf "%s\n%s\n%s\n%s" "$TS" "$METHOD" "$PATH" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
curl -X POST https://api.onewallet.local/intents \
-H "Content-Type: application/json" \
-H "X-Service-Id: auth-center" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "X-User-Id: 17" \
--data "$BODY"
Ответ (одно-фазный канал INTERNAL_P2P, синхронный SETTLED):
{
"intentId": "8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c",
"status": "SETTLED",
"channel": "INTERNAL_P2P",
"preFeeAmount": "0",
"postFeeAmount": "0",
"createdAt": "2026-05-29T17:42:11.103Z",
"requiresMonitoring": false
}
Связанный код¶
- Handler:
src/intent/handler.ts:84-446 - Идемпотентность:
handler.ts:118-149 - Channel resolver:
src/intent/router.ts - Двухфазный reserve:
channels/merchant-invoice.ts - Запись событий:
writeIntentEvent()
2. POST /intents/quote — preview PRE-комиссий¶
Назначение¶
Без записи в БД: рассчитать сумму PRE-комиссий для операции. Используется UI чтобы показать «итого к списанию» перед нажатием «отправить». Файл отдельный — src/intent/quote.ts, не параметр у POST /intents.
Auth¶
HMAC. X-User-Id не используется (расчёт не зависит от пользователя). Любой сервис с валидным HMAC.
Request body¶
Zod-схема: QuoteBody (quote.ts:6-11, см. 01-dto-contracts.md#21-quotebody).
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
operationType |
enum | да | P2P_TRANSFER | IPPS_WITHDRAWAL | THAI_QR_PAY | WITHDRAWAL | MINIAPP_CHARGE | MINIAPP_CREDIT | QP_TOPUP | SERVICE_DEPOSIT | ADMIN_TRANSFER | INVOICE_PAYMENT |
amount |
int > 0 | да | Сумма в сатангах, для которой считаются комиссии |
currency |
string(3) | нет (default THB) |
ISO 4217 |
metadata |
object | нет (default {}) |
Передаётся в rule-engine для матчинга правил (tags, признаки операции) |
Response 200¶
Zod-схема: QuoteResponse (quote.ts:13-20, см. 01-dto-contracts.md#22-quoteresponse).
| Поле | Тип | Описание |
|---|---|---|
fee |
string | Итоговая сумма PRE-комиссий в сатангах |
totalCharge |
string | amount + fee — то, что будет списано с отправителя |
breakdown |
array | Разбивка по получателям комиссии: [{ account: "fee.platform", amount: "150" }, ...] |
Возможные ошибки¶
| HTTP | code | Причина |
|---|---|---|
| 401 | INVALID_SIGNATURE |
HMAC |
| 422 | VALIDATION_ERROR |
Тело не прошло Zod (details содержит массив ошибок Zod, см. 05-error-codes.md) |
curl-пример¶
TS=$(date +%s)
BODY='{"operationType":"IPPS_WITHDRAWAL","amount":500000,"currency":"THB","metadata":{"tags":["business"]}}'
SIG=$(printf "%s\nPOST\n/intents/quote\n%s" "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
curl -X POST https://api.onewallet.local/intents/quote \
-H "Content-Type: application/json" \
-H "X-Service-Id: flutter-app" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "X-User-Id: 17" \
--data "$BODY"
Ответ:
{
"fee": "1500",
"totalCharge": "501500",
"breakdown": [
{ "account": "fee.platform.THB", "amount": "1000" },
{ "account": "fee.psp.IPPS.THB", "amount": "500" }
]
}
Связанный код¶
- Handler:
src/intent/quote.ts:28-60 - Калькулятор:
src/rule-engine/fee-calculator.ts
3. POST /intents/:id/confirm — подтверждение MERCHANT_INVOICE плательщиком¶
Назначение¶
Двухфазный flow: мерчант создал инвойс через POST /intents (operationType=INVOICE_PAYMENT) → получил QR с qrSignature/expiresAt. Покупатель сканирует QR, его клиент вызывает этот endpoint, передавая свой payerTbAccountId и payerUserId. PM атомарно проводит CREATED → VALIDATED → AUTHORIZED → SETTLED через P2P-сагу на TigerBeetle.
Auth¶
HMAC. X-User-Id — id покупателя (тот же, что в теле payerUserId). Поддерживает заголовок If-Match: <version> для optimistic concurrency на колонке intent.version (confirm-handler.ts:84-86). Если CAS не сработал → 409 VERSION_MISMATCH с currentVersion.
Request body¶
Zod-схема: ConfirmBody (confirm-handler.ts:28-32, см. 01-dto-contracts.md#31-confirmbody).
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
payerTbAccountId |
uuid | да | TigerBeetle UUID счёта покупателя (tb_account_map.tbAccountId) |
payerUserId |
int > 0 | да | Serverpod userId покупателя; сверяется с account.userId |
idempotencyKey |
uuid | да | Защита от двойного нажатия — повтор → текущий статус, replayed: true |
Заголовок:
| Заголовок | Обязательность | Описание |
|---|---|---|
If-Match |
нет | Ожидаемое значение intent.version. Если не совпадает → 409 VERSION_MISMATCH. Без заголовка — CAS пропускается |
Response 200¶
Zod-схема: см. 01-dto-contracts.md#33-confirmresponse.
| Поле | Тип | Описание |
|---|---|---|
intentId |
uuid | ID инвойса |
status |
enum | SETTLED (норма) или FAILED (при ошибке redeem) |
channel |
string | MERCHANT_INVOICE |
createdAt |
string | ISO 8601 — когда инвойс был создан |
version |
int | Новая версия после успешного UPDATE |
replayed |
boolean | true если это idempotency-replay по тому же idempotencyKey + payerUserId |
Возможные ошибки¶
| HTTP | code | Причина |
|---|---|---|
| 400 | PAYER_ACCOUNT_USER_MISMATCH |
payerUserId не равен account.userId |
| 400 | VALIDATION_ERROR |
Невалидное тело |
| 401 | INVALID_SIGNATURE |
HMAC |
| 404 | INTENT_NOT_FOUND |
По id нет инвойса |
| 404 | PAYER_ACCOUNT_NOT_FOUND |
По payerTbAccountId нет счёта |
| 409 | ALREADY_PROCESSED |
Инвойс уже не в статусе CREATED (в теле — {status: <текущий>}) |
| 409 | EXPIRED |
Инвойс истёк (expiresAt < now()) |
| 409 | VERSION_MISMATCH |
CAS на version не сработал. В теле — {currentVersion} |
| 500 | TB_TRANSFER_ERROR |
TigerBeetle отверг redeem-трансфер |
Подробнее: 05-error-codes.md#3-already_processed, 05-error-codes.md#4-version_mismatch.
curl-пример¶
TS=$(date +%s)
INTENT_ID="8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c"
BODY='{"payerTbAccountId":"11112222-3333-4444-5555-666677778888","payerUserId":42,"idempotencyKey":"af3b4c5d-6e7f-4890-abcd-ef1234567890"}'
SIG=$(printf "%s\nPOST\n/intents/%s/confirm\n%s" "$TS" "$INTENT_ID" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
curl -X POST "https://api.onewallet.local/intents/${INTENT_ID}/confirm" \
-H "Content-Type: application/json" \
-H "X-Service-Id: flutter-app" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "X-User-Id: 42" \
-H "If-Match: 0" \
--data "$BODY"
Ответ:
{
"intentId": "8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c",
"status": "SETTLED",
"channel": "MERCHANT_INVOICE",
"createdAt": "2026-05-29T17:30:00.000Z",
"version": 1
}
Связанный код¶
- Handler:
src/intent/confirm-handler.ts:50-208 - CAS-логика:
confirm-handler.ts:84-117 - Двухфазный канал:
src/channels/merchant-invoice.ts
4. POST /intents/:id/cancel — отмена MERCHANT_INVOICE¶
Назначение¶
Только инициатор инвойса (intent.issuedByUserId) или admin (X-User-Id: 0) может отменить инвойс в статусе CREATED. Атомарный UPDATE → CANCELED. Если инвойс уже не в CREATED (оплачен, отменён, истёк) — 409 CANNOT_CANCEL.
Auth¶
HMAC. Обязательно заголовок X-User-Id — идентификатор актора. PM сравнивает с intent.issuedByUserId; при X-User-Id: 0 — admin bypass. Любой другой userId → 403 UNAUTHORIZED_ACTOR.
Request body¶
Zod-схема: CancelBody (cancel-handler.ts:19-21, см. 01-dto-contracts.md#41-cancelbody).
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
reason |
string(1..50) | нет (default merchant_canceled) |
Причина отмены — записывается в intent_event.reason |
Тело можно не передавать вовсе — будет {reason: 'merchant_canceled'}.
Response 200¶
Zod-схема: см. 01-dto-contracts.md#43-cancelresponse.
| Поле | Тип | Описание |
|---|---|---|
intentId |
uuid | ID инвойса |
status |
enum | CANCELED |
channel |
string | MERCHANT_INVOICE |
preFeeAmount |
string | "0" (комиссии не считаются для отменённого инвойса) |
postFeeAmount |
string | "0" |
requiresMonitoring |
boolean | false |
createdAt |
string | ISO 8601 |
Возможные ошибки¶
| HTTP | code | Причина |
|---|---|---|
| 400 | CANCEL_NOT_SUPPORTED |
Канал инвойса не двухфазный (диагностика — никогда не должно встречаться) |
| 400 | VALIDATION_ERROR |
X-User-Id не задан |
| 401 | INVALID_SIGNATURE |
HMAC |
| 403 | UNAUTHORIZED_ACTOR |
actorUserId ≠ issuedByUserId и не admin |
| 404 | INTENT_NOT_FOUND |
По id нет инвойса |
| 409 | CANNOT_CANCEL |
Инвойс не в статусе CREATED (в теле — {status: <текущий>}) |
curl-пример¶
TS=$(date +%s)
INTENT_ID="8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c"
BODY='{"reason":"customer_changed_mind"}'
SIG=$(printf "%s\nPOST\n/intents/%s/cancel\n%s" "$TS" "$INTENT_ID" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
curl -X POST "https://api.onewallet.local/intents/${INTENT_ID}/cancel" \
-H "Content-Type: application/json" \
-H "X-Service-Id: flutter-app" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "X-User-Id: 17" \
--data "$BODY"
Ответ:
{
"intentId": "8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c",
"status": "CANCELED",
"channel": "MERCHANT_INVOICE",
"preFeeAmount": "0",
"postFeeAmount": "0",
"requiresMonitoring": false,
"createdAt": "2026-05-29T17:30:00.000Z"
}
Связанный код¶
- Handler:
src/intent/cancel-handler.ts:34-78 - Атомарный UPDATE:
channel.cancel()вsrc/channels/merchant-invoice.ts
⚠️ Не путать с
POST /admin/intents/:id/resolve— там админ принудительно разрешает зависший интент (см.admin.md).
5. GET /intents/:id — получить статус интента¶
Назначение¶
Чтение интента по id. Сервис видит только свои интенты (по intent.serviceId). Используется клиентом для polling-fallback (если Redis subscription недоступна) и админ-панелью (через её собственный service-key).
Auth¶
HMAC. intent.serviceId обязан совпадать с X-Service-Id. Иначе — 404 (намеренно не 403, чтобы не утекала информация о существовании интентов другого сервиса).
Path параметры¶
| Параметр | Тип | Описание |
|---|---|---|
id |
uuid | ID интента |
Response 200¶
Zod-схема: IntentDetails (handler.ts:56-69).
| Поле | Тип | Описание |
|---|---|---|
intentId |
uuid | ID интента |
status |
enum | Текущий статус (см. полный список в POST /intents) |
operationType |
string | Тип операции |
channel |
string | Канал, выбранный роутером |
amount |
string | Сумма в сатангах (BigInt → string) |
currency |
string | Валюта |
preFeeAmount |
string | PRE-комиссия |
postFeeAmount |
string | POST-комиссия |
fromAccount |
string | null | Имя счёта-источника (null для невыполненного MERCHANT_INVOICE до подтверждения) |
toAccount |
string | Имя счёта-получателя |
createdAt |
string | ISO 8601 |
updatedAt |
string | ISO 8601 |
Возможные ошибки¶
| HTTP | code | Причина |
|---|---|---|
| 401 | INVALID_SIGNATURE |
HMAC |
| 404 | NOT_FOUND |
Интент не существует или принадлежит другому сервису |
curl-пример¶
TS=$(date +%s)
INTENT_ID="8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c"
SIG=$(printf "%s\nGET\n/intents/%s\n" "$TS" "$INTENT_ID" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
curl "https://api.onewallet.local/intents/${INTENT_ID}" \
-H "X-Service-Id: auth-center" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "X-User-Id: 17"
Ответ:
{
"intentId": "8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c",
"status": "SETTLED",
"operationType": "P2P_TRANSFER",
"channel": "INTERNAL_P2P",
"amount": "15000",
"currency": "THB",
"preFeeAmount": "0",
"postFeeAmount": "0",
"fromAccount": "user.17.wallet.THB",
"toAccount": "user.42.wallet.THB",
"createdAt": "2026-05-29T17:42:11.103Z",
"updatedAt": "2026-05-29T17:42:11.250Z"
}
Связанный код¶
- Handler:
src/intent/handler.ts:449-489
6. GET /intents/:id/events — audit-лог переходов¶
Назначение¶
Возвращает полный лог переходов статусов интента (pm.intent_event), в порядке возрастания createdAt. Используется для:
- диагностики (почему интент
FAILED— в каком переходе); - audit-trail для regulator-отчётов;
- админ-панели (визуализация state machine);
- автотестов: проверить последовательность статусов после операции.
Каждая строка intent_event пишется через writeIntentEvent() при каждом изменении статуса. Полный реестр типов событий: 04-event-types.md.
Auth¶
HMAC. Как и в GET /intents/:id, intent.serviceId обязан совпадать с X-Service-Id. Иначе — 404.
Path параметры¶
| Параметр | Тип | Описание |
|---|---|---|
id |
uuid | ID интента |
Response 200¶
Zod-схема: IntentEventsResponse (handler.ts:71-80).
| Поле | Тип | Описание |
|---|---|---|
events |
array | Массив объектов IntentEvent (см. ниже) |
events[].id |
int | Auto-increment ID строки |
events[].statusFrom |
string|null | Предыдущий статус (null для самого первого CREATED-события) |
events[].statusTo |
string | Новый статус после перехода |
events[].reason |
string|null | Текст причины (заполняется для FAILED и CANCELED событий) |
events[].payload |
object|null | Доп. полезная нагрузка (PSP state changes, outbox-метаданные, см. 04-event-types.md) |
events[].createdAt |
string | ISO 8601 |
Возможные ошибки¶
| HTTP | code | Причина |
|---|---|---|
| 401 | INVALID_SIGNATURE |
HMAC |
| 404 | NOT_FOUND |
Интент не существует или принадлежит другому сервису |
curl-пример¶
TS=$(date +%s)
INTENT_ID="8a2d50c4-7b91-4e6f-9d12-3c4e5f6a7b8c"
SIG=$(printf "%s\nGET\n/intents/%s/events\n" "$TS" "$INTENT_ID" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
curl "https://api.onewallet.local/intents/${INTENT_ID}/events" \
-H "X-Service-Id: admin-panel" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "X-User-Id: 0"
Ответ:
{
"events": [
{ "id": 1051, "statusFrom": null, "statusTo": "CREATED", "reason": null, "payload": null, "createdAt": "2026-05-29T17:42:11.103Z" },
{ "id": 1052, "statusFrom": "CREATED", "statusTo": "VALIDATED", "reason": null, "payload": null, "createdAt": "2026-05-29T17:42:11.150Z" },
{ "id": 1053, "statusFrom": "VALIDATED", "statusTo": "AUTHORIZED", "reason": null, "payload": null, "createdAt": "2026-05-29T17:42:11.220Z" },
{ "id": 1054, "statusFrom": "AUTHORIZED", "statusTo": "SETTLED", "reason": null, "payload": null, "createdAt": "2026-05-29T17:42:11.250Z" }
]
}
Связанный код¶
- Handler:
src/intent/handler.ts:492-534 - Запись:
src/intent/intent-events.ts - Таблица:
pm.intent_event(см.../reference/database/10-intent-event.md)
Сводная таблица endpoint'ов¶
| Endpoint | Method | Зачем | Idempotent? | Заголовки сверх HMAC | Файл |
|---|---|---|---|---|---|
/intents |
POST | Создать платёжный intent | да (по idempotencyKey+serviceId) |
X-User-Id (обязателен) |
handler.ts:84 |
/intents/quote |
POST | Preview PRE-комиссий | да (нет записи) | — | quote.ts:28 |
/intents/:id/confirm |
POST | Покупатель оплачивает invoice | да (по idempotencyKey+payerUserId) |
X-User-Id, опционально If-Match |
confirm-handler.ts:50 |
/intents/:id/cancel |
POST | Отменить invoice | идемпотентен при повторе (CONFLICT) | X-User-Id (issuer / admin) |
cancel-handler.ts:34 |
/intents/:id |
GET | Получить статус | n/a | — | handler.ts:449 |
/intents/:id/events |
GET | Audit-лог переходов | n/a | — | handler.ts:492 |
Смежные документы¶
./admin.md— административные endpoint'ы (POST /admin/intents/:id/resolve, fee-rules, debug-level)./accounts.md—GET /accounts/:name/balance,GET /accounts/:name/transactions./policies.md—POST /policies/evaluate(step-up auth)../reference/passport/01-dto-contracts.md— все Zod-схемы../reference/passport/05-error-codes.md— реестр кодов ошибок../reference/passport/04-event-types.md— событияintent_event.payload