01 payment
PaymentEndpoint — платежи, история, preview, resolve, step-up.
Тонкий фасад над Payment Manager (PM). Не содержит платёжной бизнес-логики — аутентифицирует запрос, обогащает PaymentMetadata данными профилей и проксирует HMAC-запрос в PM.
Аутентификация: все методы требуют session.authenticated (JWT Serverpod). _requireUserId() двухшаговый: UUID из сессии → users.authUserId → числовой users.id.
Методы¶
| Метод | accountType | PM вызов | Возвращает |
|---|---|---|---|
getMyTbAccountId(currency) |
любой авторизованный | нет (детерминировано локально) | String — TB UUID кошелька USER_WALLET |
resolveRecipientByPhone(phone) |
consumer, wallet, agent | нет (прямой SELECT) | PhoneResolveResult? — null если не найден, неактивен или самоперевод |
getTransactionHistory({page, limit}) |
любой авторизованный | нет (SELECT из v_tx_history) |
List<VTxHistory> — сортировка DESC по createdAt |
previewIntent(operationType, amount, currency, {recipientPhone?, recipientTbAccountId?}) |
любой авторизованный | PspHmacClient.previewIntent |
IntentQuoteResponse — fee breakdown без создания интента в БД |
createIntent(idempotencyKey, operationType, amount, currency, {recipientUserId?, toTbAccountId?, externalRef?, comment?, flow?}) |
любой авторизованный | PspHmacClient.createIntent |
IntentDto — PM гарантирует idempotency по idempotencyKey |
getIntent(intentId) |
любой авторизованный | PspHmacClient.getIntent |
IntentDto — текущий статус интента |
previewInvoice(qrRawPayload, appId) |
consumer, wallet | PspHmacClient.getIntent + previewIntent + evaluatePolicy |
InvoicePreviewResponse — fee + stepUpRequired + статус |
confirmInvoice(req) |
consumer, wallet | PspHmacClient.evaluatePolicy + confirmIntent |
IntentDto — после валидации step-up proof |
Детали методов¶
previewInvoice выполняет три последовательных PM-вызова: декодирует QR через InvoiceQrDecoder, верифицирует подпись через InvoiceQrSigner, проверяет срок действия и appScope (w=wallet, c=closeloop, a=all).
confirmInvoice — если stepUp.level != NONE, валидирует StepUpProof: тип (PIN/BIOMETRIC→PIN), freshness ≤ 5 мин, наличие pinHash.
_buildMetadata — строит PaymentMetadata в два параллельных раунда Future.wait. Поля: fromUserId/toUserId, accountType, accountTier, isVatPayer, tags, taxId, businessCategoryCode, referralAgentId/UserId, source='mobile_app', flow.
Error codes¶
| PM HTTP | PaymentErrorCode |
Клиент получает |
|---|---|---|
| 401/403 | AUTH_FAILED |
PaymentException |
| 422 | VALIDATION_ERROR |
PaymentException |
| 403 | FORBIDDEN |
PaymentException |
| 404 | NOT_FOUND |
PaymentException |
| 5xx | PSP_UNAVAILABLE |
PaymentException |
| timeout | PSP_TIMEOUT |
PaymentException |
нет строки в v_user_tb_accounts |
WALLET_MISSING |
PaymentException |
session.authenticated == null |
UNAUTHENTICATED |
PaymentException |
| невалидный QR | — | CloseloopException(code: 'INVALID_QR') |
| подпись QR не совпадает | — | CloseloopException(code: 'INVALID_SIGNATURE') |
| QR истёк | — | CloseloopException(code: 'INVOICE_EXPIRED') |
| не тот app scope | — | CloseloopException(code: 'WRONG_APP_SCOPE') |
| инвойс не в статусе CREATED | — | CloseloopException(code: 'INVOICE_NOT_PAYABLE') |
Важно¶
- Все суммы в сатангах (1 THB = 100 сатангов). PM принимает и возвращает только целые числа.
getMyTbAccountIdвозвращает UUID только дляUSER_WALLET— не использовать для MERCHANT/AGENT аккаунтов.- История транзакций читается из
public.v_tx_history(view надpm.tx_history) напрямую без HMAC — это допустимо, т.к. история read-only. flowвcreateIntent:'phone'(через resolveRecipientByPhone) или'qr'(через QR-сканирование). Используется PM для аналитики.