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 |
int (> 0) |
Serverpod user ID; должен совпадать с X-User-Id. |
currency |
"THB" |
ISO 4217. Только THB на Phase 1. |
entityType |
"user"\|"merchant"\|"agent" |
По умолчанию user. |
Response (user):
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 |
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 |
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.md —
POST /intents, используетaccountNameиз этого API. - /CLAUDE.md — nginx-проксирование
/api/pm/accounts/balanceи кросс-сервисные правила.