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

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: финальный статус

Шаги:

  1. AC резолвит телефон через хеш PII → возвращает tbAccountId и отображаемое имя.
  2. previewIntent — расчёт комиссии без создания записи в БД (stateless в PM).
  3. Пользователь подтверждает PIN / биометрией на устройстве.
  4. createIntent — AC обогащает запрос профилями (fromName, toName, tier, VAT), отправляет HMAC-запрос.
  5. PM создаёт linked-трансферы в TigerBeetle (основной + комиссия), синхронно возвращает COMPLETED.
  6. 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

Шаги:

  1. AC декодирует QR, верифицирует HMAC-подпись (созданную PM при createInvoice) и TTL.
  2. previewInvoice возвращает разбивку комиссии и флаг stepUpRequired (биометрия/PIN).
  3. После подтверждения пользователем AC вызывает POST /intents/{id}/confirm — PM переводит деньги в TigerBeetle.
  4. 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

Шаги:

  1. Только agent-аккаунты могут вызывать createTopup — guard requireAccountType(['agent']).
  2. AC ищет потребителя по хешу телефона (PII не передаётся в PM).
  3. PM зачисляет средства из пула ликвидности агента на кошелёк клиента.
  4. Операция синхронная — 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

Шаги:

  1. Приложение мерчанта считывает token из NDEF-записи чипа и аппаратный счётчик касаний (READ_CNT).
  2. resolveAndConsume — атомарный UPDATE счётчика в БД; регресс или чужой idempotencyKey → NFC_CLONE_DETECTED.
  3. Проверяются per-tag лимиты (perTapLimit, dailyLimit) до обращения к PM.
  4. Запрос в PM подписывается привилегированным ключом auth-center-merchant (отдельный HMAC от обычного auth-center).
  5. PM выполняет расчёт синхронно — ответ уже содержит терминальный статус.