06 payment scenarios
Платёжные сценарии OneWallet: P2P, пополнение, инвойс, NFC.
Сценарии¶
| Сценарий | Кто создаёт интент | Кто оплачивает | Тип операции |
|---|---|---|---|
| P2P-перевод | consumer | consumer (отправитель) | P2P_TRANSFER |
| Пополнение через агента | agent | — (агент вносит наличные) | TOPUP |
| QR-инвойс | merchant | consumer (сканирует QR) | INVOICE_PAYMENT |
| NFC pull-charge | merchant (через AC) | consumer (владелец метки) | NFC_CHARGE |
Все четыре сценария проходят через единый endpoint POST /intents в Payment Manager (HMAC-подпись обязательна). Auth Center — тонкий фасад: аутентифицирует вызов, обогащает metadata профилями и проксирует запрос.
P2P-перевод¶
Отправитель указывает получателя по номеру телефона (E.164) или сканирует QR получателя.
sequenceDiagram
participant App as Flutter App
participant AC as Auth Center
participant PM as Payment Manager
participant TB as TigerBeetle
App->>AC: resolveRecipientByPhone(phone)
AC-->>App: PhoneResolveResult { recipientUserId, tbAccountId, displayName }
App->>AC: previewIntent(P2P_TRANSFER, amount, currency, recipientPhone)
AC->>PM: POST /intents/quote (HMAC)
PM-->>AC: IntentQuoteResponse { fee, total }
AC-->>App: fee breakdown
App->>App: PIN / биометрия
App->>AC: createIntent(idempotencyKey, P2P_TRANSFER, amount, currency, recipientUserId)
AC->>PM: POST /intents (HMAC, metadata с профилями)
PM->>TB: createTransfers (linked: fee + transfer)
TB-->>PM: SETTLED
PM-->>AC: IntentDto { status: COMPLETED }
AC-->>App: IntentDto
App->>AC: getIntent(intentId) [опциональный polling]
AC->>PM: GET /intents/{id}
PM-->>AC: IntentDto
AC-->>App: финальный статус
Шаги:
- AC резолвит телефон через хеш PII → возвращает
tbAccountIdи отображаемое имя. previewIntent— расчёт комиссии без создания записи в БД (stateless в PM).- Пользователь подтверждает PIN / биометрией на устройстве.
createIntent— AC обогащает запрос профилями (fromName,toName, tier, VAT), отправляет HMAC-запрос.- PM создаёт linked-трансферы в TigerBeetle (основной + комиссия), синхронно возвращает
COMPLETED. - Flutter при необходимости поллит
getIntentдо получения терминального статуса.
Оплата QR-инвойса¶
Мерчант создаёт QR → покупатель сканирует → платит.
sequenceDiagram
participant ConsApp as Flutter App (consumer)
participant AC as Auth Center
participant PM as Payment Manager
participant TB as TigerBeetle
ConsApp->>ConsApp: сканирует QR-код мерчанта
ConsApp->>AC: previewInvoice(qrRawPayload, appId)
AC->>AC: декодирует QR, проверяет подпись + TTL
AC->>PM: GET /intents/{id}
PM-->>AC: IntentDto { status: CREATED }
AC->>PM: POST /intents/quote (INVOICE_PAYMENT)
PM-->>AC: fee breakdown
AC-->>ConsApp: InvoicePreviewResponse { amount, fee, merchantName, stepUpRequired }
ConsApp->>ConsApp: PIN / биометрия
ConsApp->>AC: confirmInvoice(intentId, StepUpProof)
AC->>PM: POST /intents/{id}/confirm (HMAC)
PM->>TB: createTransfers (consumer → merchant)
TB-->>PM: SETTLED
PM-->>AC: IntentDto { status: COMPLETED }
AC-->>ConsApp: IntentDto
Шаги:
- AC декодирует QR, верифицирует HMAC-подпись (созданную PM при
createInvoice) и TTL. previewInvoiceвозвращает разбивку комиссии и флагstepUpRequired(биометрия/PIN).- После подтверждения пользователем AC вызывает
POST /intents/{id}/confirm— PM переводит деньги в TigerBeetle. - QR истекает автоматически — PM вернёт
INVOICE_EXPIREDпри попытке оплатить просроченный QR.
Пополнение (Topup)¶
Агент принимает наличные и зачисляет средства на кошелёк клиента.
sequenceDiagram
participant AgentApp as Merchant/Agent App
participant AC as Auth Center
participant PM as Payment Manager
participant TB as TigerBeetle
AgentApp->>AC: createTopup(consumerPhone, amount, currency, idempotencyKey)
AC->>AC: requireAccountType([agent])
AC->>AC: resolveConsumer по phoneHash
AC->>PM: POST /intents (HMAC, operationType=TOPUP, metadata)
PM->>TB: createTransfers (agent liquidity pool → consumer wallet)
TB-->>PM: SETTLED
PM-->>AC: IntentDto { status: COMPLETED }
AC-->>AgentApp: IntentDto
Шаги:
- Только
agent-аккаунты могут вызыватьcreateTopup— guardrequireAccountType(['agent']). - AC ищет потребителя по хешу телефона (PII не передаётся в PM).
- PM зачисляет средства из пула ликвидности агента на кошелёк клиента.
- Операция синхронная —
IntentDto.status = COMPLETEDвозвращается сразу.
NFC Pull-charge¶
Мерчант прикладывает NFC-карту клиента к своему устройству; деньги списываются с кошелька клиента.
sequenceDiagram
participant MerchApp as Merchant App
participant Chip as NFC Chip (NTAG)
participant AC as Auth Center
participant PM as Payment Manager
participant TB as TigerBeetle
MerchApp->>Chip: READ_CNT + NDEF read
Chip-->>MerchApp: token (22 chars base64url) + counter (uint24)
MerchApp->>AC: chargeByNfc(token, counter, amount, currency, idempotencyKey)
AC->>AC: requireAccountType([merchant])
AC->>AC: NfcTagService.resolveAndConsume(token, counter, idempotencyKey)
Note over AC: проверка: active, TTL, монотонность счётчика,<br/>идемпотентность (ретрай vs клон)
AC->>AC: assertNfcEnabled(customerUserId)
AC->>AC: checkTagLimits(tag, amount)
AC->>PM: POST /intents (HMAC merchant key, NFC_CHARGE, metadata)
PM->>TB: createTransfers (consumer wallet → merchant wallet)
TB-->>PM: SETTLED
PM-->>AC: IntentDto { status: COMPLETED }
AC-->>MerchApp: NfcChargeResult
Шаги:
- Приложение мерчанта считывает
tokenиз NDEF-записи чипа и аппаратный счётчик касаний (READ_CNT). resolveAndConsume— атомарный UPDATE счётчика в БД; регресс или чужой idempotencyKey →NFC_CLONE_DETECTED.- Проверяются per-tag лимиты (
perTapLimit,dailyLimit) до обращения к PM. - Запрос в PM подписывается привилегированным ключом
auth-center-merchant(отдельный HMAC от обычногоauth-center). - PM выполняет расчёт синхронно — ответ уже содержит терминальный статус.