Перейти к содержанию

Accounts API

Эндпоинты управления аккаунтами TigerBeetle и PSP-регистрации. Все запросы требуют HMAC-аутентификации (X-Service-Id, X-Timestamp, X-Signature, X-User-Id). DTO-контракты — см. ../reference/passport/01-dto-contracts.md.

Endpoint Назначение Caller (allowed)
POST /accounts Создать TB-аккаунт (USER/MERCHANT/AGENT) auth-center, admin-*
GET /accounts/:name/balance Получить live-баланс из TigerBeetle любой сервис c HMAC
GET /accounts/:name/transactions Постраничная история транзакций аккаунта любой сервис c HMAC
POST /accounts/register-ipps Зарегистрировать IPPS-кошелёк для USER_WALLET auth-center (после OTP)

POST /accounts

Назначение: Идемпотентно создать TigerBeetle-аккаунт для пользователя, мерчанта или агента. Для entityType=user создаётся один USER_WALLET; для merchant/agent — пара primary + settlement (см. memory: multi-entity accounts design).

Auth: - HMAC обязателен. - entityType=user — разрешено только auth-center и admin-panel. - entityType=merchant|agent — разрешено только admin-tool и admin-panel. - Sanity-check: body.userId ДОЛЖЕН совпадать с X-User-Id.

Request body:

{
  "userId":     12345,
  "currency":   "THB",
  "entityType": "user"
}
Поле Тип Описание
userId int (> 0) Serverpod user ID; должен совпадать с X-User-Id.
currency "THB" ISO 4217. Только THB на Phase 1.
entityType "user"\|"merchant"\|"agent" По умолчанию user.

Response (user):

{
  "accountName": "user.12345.THB",
  "tbAccountId": "01J9F7K2A0RZQ4N8XB5T3M6PWE"
}

Response (merchant/agent):

{
  "primary":    { "accountName": "merchant.42.THB",            "tbAccountId": "...", "created": true  },
  "settlement": { "accountName": "merchant.42.THB.settlement", "tbAccountId": "...", "created": false }
}

Status codes:

Code Когда
201 Хотя бы один аккаунт был создан
200 Все запрошенные аккаунты уже существовали (идемпотентный replay)
401 Нет/невалидный HMAC
403 Caller не вправе создавать данный entityType
422 Валидация (нет X-User-Id, mismatch userId, и т. п.)

curl:

curl -X POST https://pm.internal/accounts \
  -H "X-Service-Id: auth-center" \
  -H "X-Timestamp: 1716969600" \
  -H "X-Signature: <hmac-sha256>" \
  -H "X-User-Id: 12345" \
  -H "Content-Type: application/json" \
  -d '{"userId":12345,"currency":"THB","entityType":"user"}'

Source: src/ledger/routes.ts:40.


GET /accounts/:name/balance

Назначение: Получить актуальный баланс аккаунта напрямую из TigerBeetle (credits_posted - debits_posted). Не читает PostgreSQL: данные — live, без задержки outbox.

Auth: - HMAC обязателен. - Из Flutter-приложения вызывается через nginx-маршрут /api/pm/accounts/... (см. /CLAUDE.md) c auth_request через Auth Center: nginx проверяет JWT через Auth Center, получает X-User-Id, добавляет HMAC-заголовки и проксирует в PM /accounts/:name/balance. Serverpod НЕ читает баланс из БД напрямую.

Важно: :name обязателен в пути. Маршрута GET /accounts/balance без имени НЕ существует.

Path params:

Поле Тип Описание
name string Имя аккаунта (user.123.THB, merchant.42.THB, ...) ЛИБО TB UUID.

Response 200:

{
  "accountName": "user.12345.THB",
  "balance":     "1234567",
  "currency":    "THB"
}
Поле Тип Описание
accountName string Эхо переданного :name.
balance string Баланс в сатангах (1 THB = 100 сатангов). Строка из-за bigint.
currency string Если :name = UUID — всегда THB; иначе — последний сегмент имени.

Status codes:

Code Когда
200 OK
401 Нет/невалидный HMAC
404 Аккаунт не найден в TigerBeetle

curl (прямой вызов PM):

curl https://pm.internal/accounts/user.12345.THB/balance \
  -H "X-Service-Id: auth-center" \
  -H "X-Timestamp: 1716969600" \
  -H "X-Signature: <hmac-sha256>" \
  -H "X-User-Id: 12345"

curl (через nginx из Flutter):

curl https://app.onewallet.th/api/pm/accounts/user.12345.THB/balance \
  -H "Authorization: Bearer <serverpod-jwt>"

Source: src/ledger/routes.ts:138.


GET /accounts/:name/transactions

Назначение: Постраничная история транзакций аккаунта из pm.tx_history (отражает posted transfers, индекс по accountName). Сортировка — createdAt DESC.

Auth: HMAC обязателен.

:name обязателен. Маршрут GET /accounts/history НЕ существует — правильное имя /accounts/:name/transactions.

Path params:

Поле Тип Описание
name string Имя аккаунта или TB UUID. При UUID выполняется lookup в pm.tb_account_map.

Query params:

Поле Тип По умолчанию Описание
limit int 20 Максимум 100.
offset int 0 Смещение пагинации.

Response 200:

{
  "transactions": [
    {
      "id":            42,
      "intentId":      "01J9F7K2A0RZQ4N8XB5T3M6PWE",
      "operationType": "P2P_TRANSFER",
      "direction":     "DEBIT",
      "amount":        "100000",
      "feeAmount":     "500",
      "currency":      "THB",
      "createdAt":     "2026-05-29T10:54:00.000Z"
    }
  ]
}
Поле Тип Описание
id int PK строки pm.tx_history.
intentId string UUID интента-источника (см. intents.md).
operationType string См. enum в ../reference/passport/01-dto-contracts.md.
direction string DEBIT или CREDIT относительно :name.
amount string Сумма в сатангах (bigint как строка).
feeAmount string Комиссия в сатангах.
currency string ISO 4217.
createdAt string ISO 8601 UTC.

Если по TB UUID не нашёлся accountName — возвращается {"transactions": []} (НЕ 404).

curl:

curl "https://pm.internal/accounts/user.12345.THB/transactions?limit=20&offset=0" \
  -H "X-Service-Id: auth-center" \
  -H "X-Timestamp: 1716969600" \
  -H "X-Signature: <hmac-sha256>" \
  -H "X-User-Id: 12345"

Source: src/ledger/routes.ts:169.


POST /accounts/register-ipps

Назначение: Зарегистрировать PromptPay/IPPS-кошелёк у PSP IPPS для существующего USER_WALLET. Идемпотентен — повторный вызов после успешной регистрации возвращает 200 без обращения к PSP. Вызывается Auth Center'ом после успешной OTP-верификации пользователя.

Auth: - HMAC обязателен. - Сервис должен иметь IPPS_WITHDRAWAL или THAI_QR_PAY в permissions.allowedOperationTypes (см. pm.service_key). - X-User-Id обязателен — определяет, для какого USER_WALLET регистрируется кошелёк.

Request body:

{
  "externalUserId":      "user-12345",
  "customerEnglishName": "SOMCHAI JAIDEE",
  "senderTaxId":         "1234567890123",
  "senderAccountName":   "SOMCHAI JAIDEE",
  "senderMobileNumber":  "+66891234567",
  "documentExpiry":      "2030-01-01",
  "dateOfBirth":         "1990-05-15",
  "phoneNumber":         "+66891234567",
  "nationality":         "TH",
  "address":             "123 Sukhumvit Rd, Bangkok 10110"
}
Поле Тип Описание
externalUserId string Внешний ID для PSP (обычно user-{userId}).
customerEnglishName string Полное имя транслитом.
senderTaxId string Тайский national ID или tax ID (13 цифр).
senderAccountName string Имя как в банке.
senderMobileNumber string? Опционально.
documentExpiry string YYYY-MM-DD.
dateOfBirth string YYYY-MM-DD.
phoneNumber string Телефон.
nationality string ISO-2 (например, TH).
address string Адрес проживания.

Response:

{
  "walletId":          "IPPS-WLT-9F8E7D",
  "alreadyRegistered": false
}
Поле Тип Описание
walletId string IPPS wallet ID, записывается в pm.tb_account_map.ippsWalletId.
alreadyRegistered boolean true если кошелёк уже существовал на стороне PSP.

Status codes:

Code Когда
201 Кошелёк только что зарегистрирован
200 Уже был зарегистрирован — idempotent replay
400 Нет/невалидный X-User-Id
401 Нет/невалидный HMAC
403 Сервис не имеет прав на IPPS-операции
404 Не найден USER_WALLET для данного userId

curl:

curl -X POST https://pm.internal/accounts/register-ipps \
  -H "X-Service-Id: auth-center" \
  -H "X-Timestamp: 1716969600" \
  -H "X-Signature: <hmac-sha256>" \
  -H "X-User-Id: 12345" \
  -H "Content-Type: application/json" \
  -d '{
    "externalUserId":"user-12345",
    "customerEnglishName":"SOMCHAI JAIDEE",
    "senderTaxId":"1234567890123",
    "senderAccountName":"SOMCHAI JAIDEE",
    "documentExpiry":"2030-01-01",
    "dateOfBirth":"1990-05-15",
    "phoneNumber":"+66891234567",
    "nationality":"TH",
    "address":"123 Sukhumvit Rd, Bangkok 10110"
  }'

Source: src/accounts/register-ipps.ts:30.


Связанные документы

  • ../reference/passport/01-dto-contracts.md — канонические DTO AccountResponse, BalanceResponse, TxRow, RegisterIppsBody.
  • intents.mdPOST /intents, использует accountName из этого API.
  • /CLAUDE.md — nginx-проксирование /api/pm/accounts/balance и кросс-сервисные правила.