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

Платёжные сценарии

Подборка ключевых сюжетов, которые ежедневно проживает кошелёк OneWallet: от перевода другу до пополнения через банк и корректирующих операций администратора. Для каждого сценария — описание участников, ход событий, точки ветвления и диаграмма последовательности. Технические подробности (имена таблиц, протоколы, идентификаторы счетов) намеренно опущены — их место в разделе dev/.

В заголовках в скобках указано техническое имя операции — оно служит точкой связи с разделом для разработчиков. В прозе же мы остаёмся в терминах пользователя: «Клиент», «Магазин», «Банк», «Платёжный менеджер».


1. «Алиса переводит Бобу» (P2P_TRANSFER)

Самый частый сценарий: один житель кошелька отправляет деньги другому. Алиса открывает приложение, выбирает Боба из списка контактов, вводит сумму и подтверждает перевод биометрией. Через мгновение Боб получает push-уведомление о зачислении.

Перевод между двумя пользователями кошелька выполняется мгновенно и не требует обращения во внешний банк — деньги уже находятся внутри системы, нужно лишь переписать остатки между внутренними счетами. Поскольку перевод не покидает периметр кошелька, комиссии за него обычно нет (или она минимальна и заранее показывается Алисе на экране подтверждения).

Ключевые моменты:

  • Алиса видит итоговую сумму к списанию до подтверждения — не «примерно столько», а ровно столько.
  • Если у Алисы не хватает средств, операция отклоняется до того, как Боб что-либо узнает.
  • Боб получает уведомление сразу после успешного зачисления и видит перевод в истории операций как доход.
  • Алиса видит свой перевод как расход с пометкой получателя.
sequenceDiagram
    actor Алиса as Алиса
    participant App as Приложение
    participant Auth as Auth Center
    participant PM as Платёжный менеджер
    actor Боб as Боб

    Алиса->>App: выбирает Боба, вводит сумму
    App->>Auth: запрос на перевод
    Auth->>PM: создать платёж (от Алисы к Бобу)
    PM->>PM: проверка остатка и лимитов
    PM->>PM: списание со счёта Алисы, зачисление Бобу
    PM-->>Auth: успех
    Auth-->>App: подтверждение перевода
    App-->>Алиса: «Перевод выполнен»
    PM->>Боб: push-уведомление «Вам поступил перевод»

2. «Магазин выставляет счёт через QR» (MERCHANT_INVOICE / INVOICE_PAYMENT)

В кафе кассир пробивает заказ на 320 бат и нажимает «Принять оплату». На экране кассы появляется QR-код. Клиент наводит камеру приложения, видит сумму и название кафе, подтверждает оплату — и кассир сразу слышит писк подтверждения.

Этот сценарий отличается от P2P тем, что он двухфазный: магазин первым формирует приглашение к оплате (инвойс), а клиент — отдельным действием — это приглашение принимает. До момента подтверждения клиентом со счёта никто ничего не списывает, инвойс просто «висит» в системе и ждёт.

Что важно понимать про инвойсы:

  • У инвойса есть срок жизни: если клиент не подтвердил за условные 5 минут, QR «протухает» и оплатить по нему уже нельзя — кассиру придётся пробить заказ заново.
  • Магазин может отменить инвойс сам — например, если клиент передумал и ушёл.
  • В момент создания инвойса плательщик ещё неизвестен — он определяется только когда кто-то сканирует QR.
  • Сумма и реквизиты в QR подписаны магазином, чтобы клиент не мог «подменить» сумму в приложении.
  • Комиссия эквайринга (если она есть) удерживается с магазина, а не с клиента — клиент видит ровно ту сумму, что на чеке.
sequenceDiagram
    actor Магазин as Магазин (касса)
    participant PM as Платёжный менеджер
    actor Клиент as Клиент
    participant App as Приложение

    Магазин->>PM: создать инвойс на 320 бат
    PM->>PM: сохранить инвойс, выпустить подписанный QR
    PM-->>Магазин: QR-код
    Магазин-->>Клиент: показывает QR на экране кассы

    Клиент->>App: сканирует QR
    App->>PM: запрос реквизитов инвойса
    PM-->>App: сумма, название магазина, срок
    App-->>Клиент: «Оплатить 320 бат в Кафе Лотос?»

    Клиент->>App: подтверждает биометрией
    App->>PM: подтвердить оплату инвойса
    PM->>PM: списание с клиента, зачисление магазину
    PM-->>App: успех
    PM->>Магазин: уведомление «оплачено»
    App-->>Клиент: «Чек на 320 бат»

3. «Клиент тапает NFC-меткой в магазине» (NFC_CHARGE)

В небольшой лавке висит наклейка с NFC-меткой. Клиент подносит к ней телефон, в приложении мгновенно появляется экран «Оплатить 80 бат в Лавке у дома?», клиент подтверждает — и продавец получает оповещение об оплате.

В отличие от QR-инвойса, здесь сценарий синхронный и инициируется со стороны магазина по факту касания: NFC-метка передаёт приложению идентификатор магазина и (если задано) фиксированную сумму, после чего платёж проходит за один шаг, без отдельной фазы «создать инвойс — подождать клиента». По сути это «pull-charge»: магазин запрашивает деньги, и при подтверждении клиента они списываются немедленно.

Особенности этого сценария:

  • Касание метки не списывает деньги автоматически — клиент обязательно подтверждает оплату в приложении.
  • Метка может быть «открытой» (продавец называет сумму голосом, клиент вводит её сам) или «фиксированной» (например, парковка за 50 бат — сумма прошита в метке).
  • Сценарий рассчитан на быстрые мелкие платежи — кофе, парковка, проезд, маленькие магазины — и оптимизирован под минимум тапов.
  • Если клиент не подтвердил за несколько секунд, операция автоматически отменяется и никакой «висящей» транзакции не остаётся.
sequenceDiagram
    actor Клиент as Клиент
    participant App as Приложение
    participant NFC as NFC-метка магазина
    participant PM as Платёжный менеджер
    actor Магазин as Магазин

    Клиент->>NFC: подносит телефон
    NFC-->>App: ID магазина (и сумма, если задана)
    App-->>Клиент: «Оплатить 80 бат в Лавке у дома?»
    Клиент->>App: подтверждает
    App->>PM: запрос на списание в пользу магазина
    PM->>PM: проверка лимитов и остатка
    PM->>PM: списание с клиента, зачисление магазину
    PM-->>App: успех
    App-->>Клиент: «Оплачено»
    PM->>Магазин: уведомление о новом платеже

4. «Пользователь выводит деньги на банковский счёт» (WITHDRAWAL / IPPS_WITHDRAWAL)

Алиса хочет перевести 5 000 бат из кошелька на свой банковский счёт. Она выбирает в приложении «Вывести», указывает счёт получателя (либо номер телефона, привязанный к PromptPay), сумму и подтверждает операцию. Через несколько секунд деньги уходят в банк-получатель.

Это первый сценарий, где появляется внешний участник — банк. Деньги не просто перекладываются внутри кошелька, а физически уходят в банковскую систему. Поэтому здесь:

  • Появляется реальная комиссия за межбанковский перевод, которую кошелёк показывает Алисе на экране подтверждения.
  • Операция не моментальная: банковский перевод может занять от секунд до минут.
  • Пока банк не подтвердил приём, деньги у Алисы уже зарезервированы (нельзя потратить их повторно), но ещё не «ушли окончательно». Это промежуточное состояние видно в истории как «в обработке».
  • Если банк отклонил перевод (неверные реквизиты, недоступность), деньги возвращаются на счёт Алисы автоматически — без обращения в поддержку.
sequenceDiagram
    actor Алиса as Алиса
    participant App as Приложение
    participant PM as Платёжный менеджер
    participant Банк as Банк-получатель

    Алиса->>App: «Вывести 5 000 бат на счёт X»
    App->>PM: запрос на вывод
    PM->>PM: проверка лимитов, расчёт комиссии
    PM-->>App: «к списанию 5 015 бат» (с комиссией)
    Алиса->>App: подтверждает
    App->>PM: подтверждение вывода
    PM->>PM: резервирует деньги, статус «в обработке»
    PM->>Банк: отправить перевод
    PM-->>App: «Вывод в обработке»
    App-->>Алиса: «Перевод отправлен»

    Банк-->>PM: подтверждение зачисления
    PM->>PM: окончательное списание
    PM->>Алиса: push «Вывод выполнен»

5. «Пользователь пополняет кошелёк через банк» (QP_TOPUP)

Боб открывает приложение, выбирает «Пополнить», указывает сумму 2 000 бат и видит QR-код для оплаты со своего банковского приложения. Он переходит в банк, оплачивает по QR — и через несколько секунд получает уведомление, что 2 000 бат уже в кошельке.

Пополнение — зеркальный сценарий к выводу. Здесь деньги идут из банка в кошелёк. Особенности:

  • Кошелёк сам не инициирует перевод — он лишь выпускает «приглашение» (QR или реквизиты), а клиент оплачивает их со стороны банка.
  • До момента, пока банк не подтвердил приход денег, на счёте Бобу ничего не отображается — никаких «висящих» пополнений в истории нет.
  • Когда банк передаёт уведомление о поступлении, кошелёк сверяет сумму и реквизиты с приглашением и зачисляет деньги.
  • Если клиент случайно перевёл другую сумму или по неверным реквизитам, кошелёк удерживает деньги в служебном статусе и связывается с пользователем для разбора.
  • Комиссия за пополнение (если есть) обычно небольшая или отсутствует — это входная воронка, и владелец кошелька заинтересован, чтобы она была дешёвой.
sequenceDiagram
    actor Боб as Боб
    participant App as Приложение
    participant PM as Платёжный менеджер
    participant Банк as Банк Боба

    Боб->>App: «Пополнить на 2 000 бат»
    App->>PM: запрос на пополнение
    PM->>PM: выпустить приглашение (QR, реквизиты)
    PM-->>App: QR-код пополнения
    App-->>Боб: показывает QR

    Боб->>Банк: оплачивает QR из банковского приложения
    Банк-->>PM: уведомление о поступлении 2 000 бат
    PM->>PM: сверка с приглашением, зачисление Бобу
    PM->>Боб: push «Кошелёк пополнен на 2 000 бат»
    App-->>Боб: новый баланс

6. «Перевод между сервисными счетами» (SERVICE_DEPOSIT)

Не все операции инициирует пользователь. Иногда сам кошелёк должен переместить деньги между внутренними «карманами»: например, начислить пользователю кэшбэк, бонус за приглашённого друга или вернуть удержанную комиссию.

Снаружи такая операция выглядит как «деньги внезапно появились на счёте» — с понятной подписью в истории: «Кэшбэк за оплату в Кафе Лотос», «Бонус за регистрацию», «Возврат комиссии». Изнутри это служебный перевод: со специального технического счёта, на котором лежит бюджет программы лояльности, деньги перетекают на счёт пользователя.

Что отличает служебные переводы:

  • Их инициирует не пользователь, а другой сервис системы (бонусная программа, программа возвратов, маркетинг).
  • Пользователь не подтверждает их вручную — он просто видит уведомление о начислении.
  • Каждый такой перевод обязательно имеет читаемое описание — пользователь должен понять, за что ему начислили.
  • Источник средств — конкретный технический счёт с прозрачным бюджетом; нельзя «начислить из ниоткуда».
sequenceDiagram
    participant Бонус as Бонусная программа
    participant Auth as Auth Center
    participant PM as Платёжный менеджер
    actor Пользователь as Пользователь
    participant App as Приложение

    Бонус->>Auth: «Начислить 50 бат пользователю X за кэшбэк»
    Auth->>PM: запрос на служебное зачисление
    PM->>PM: проверка бюджета программы
    PM->>PM: списание с бюджета, зачисление пользователю
    PM-->>Auth: успех
    PM->>Пользователь: push «Вам начислен кэшбэк 50 бат»
    App-->>Пользователь: показывает запись в истории с описанием

7. «Корректирующая операция администратора» (ADMIN_TRANSFER)

В редких случаях нужно вручную скорректировать остаток: банк ошибочно отправил двойное пополнение, пользователь жалуется на пропавший перевод и проверка подтверждает проблему, или нужно вернуть деньги при разборе спора. Для таких случаев существует отдельный сценарий — административный перевод.

Он намеренно отделён от всех остальных, потому что:

  • Каждая такая операция оставляет след. Указывается администратор, причина, ссылка на тикет или решение. Просто «передвинуть деньги» без обоснования нельзя.
  • Требуется повышенное подтверждение. Сумма выше порога обычно подтверждается вторым администратором (правило четырёх глаз) — никто в одиночку не может произвольно крупно изменить остаток.
  • Пользователь всегда видит, что операцию провёл администратор. В истории такой записи появляется явная пометка с описанием причины, чтобы клиент понимал, что произошло, и не подозревал ошибку.
  • Деньги перемещаются между существующими счетами — административный перевод не «печатает» новые деньги, он лишь исправляет, кому они принадлежат.
sequenceDiagram
    actor Админ as Администратор
    participant Admin as Админ-панель
    participant PM as Платёжный менеджер
    actor Пользователь as Пользователь
    participant App as Приложение

    Админ->>Admin: открывает тикет, выбирает «Корректировка»
    Admin->>Admin: проверка прав, запрос причины и суммы
    Админ->>Admin: указывает: «Возврат двойного списания», 500 бат
    Admin->>PM: запрос на админ-перевод с обоснованием

    alt Сумма выше порога
        PM->>Admin: «Нужно подтверждение второго администратора»
        Admin-->>Админ: ждёт согласования
        Админ->>Admin: второй администратор подтверждает
    end

    PM->>PM: выполнить перевод, записать причину и автора
    PM-->>Admin: успех
    PM->>Пользователь: push «Корректировка: возврат 500 бат»
    App-->>Пользователь: запись в истории с пометкой «Операция администратора. Причина: возврат двойного списания»

Что общего у всех сценариев

Несмотря на разнообразие, все эти истории следуют одной и той же канве:

  1. Намерение — кто-то (пользователь, магазин, сервис, администратор) формулирует, что должно произойти.
  2. Проверка — Платёжный менеджер сверяется с правилами: достаточно ли средств, не превышены ли лимиты, корректны ли реквизиты.
  3. Исполнение — деньги перемещаются между счетами или уходят/приходят из банка.
  4. Уведомление — все заинтересованные стороны получают подтверждение (push, запись в истории, ответ в API).
  5. След — каждая операция оставляет неизменяемую запись с автором, временем и причиной, доступную для разбора.

Эта общая канва — то, что делает кошелёк предсказуемым: какой бы экзотический ни был сценарий, он раскладывается на одни и те же пять шагов.