Сценарий «Мерчант-инвойс»: двухфазная оплата по QR¶
Этот документ описывает бизнес-сценарий, в котором магазин выставляет счёт через QR-код, а покупатель оплачивает его позднее. Сценарий двухфазный: «выставление инвойса» (резерв) и «оплата» (подтверждение) разнесены во времени и могут выполняться разными участниками с разных устройств.
Документ адресован продукт-менеджерам, бизнес-аналитикам и интеграторам, которым нужно понимать жизненный цикл инвойса и его альтернативные исходы.
1. Зачем это нужно¶
Классический перевод «здесь и сейчас» (P2P между двумя известными пользователями) не подходит, если:
- магазин формирует счёт до того, как покупатель подошёл к кассе;
- покупатель неизвестен в момент создания инвойса — он определится только после сканирования QR;
- между моментом «магазин показал QR» и «покупатель приложил палец к Face ID» может пройти от нескольких секунд до нескольких минут.
Чтобы развязать «создание оферты» и «фактический перевод денег», PM поддерживает специальный двухфазный канал — MERCHANT_INVOICE. На фазе «резерв» деньги ещё не движутся, движется только запись в реестре инвойсов. На фазе «оплата» определяется плательщик и происходит фактическое списание.
2. Актёры¶
| Актёр | Кто это | Что делает |
|---|---|---|
| Магазин | POS-терминал, касса, mini-app мерчанта | Выставляет инвойс, показывает QR, может его отменить |
| Клиент | Покупатель с мобильным приложением OneWallet | Сканирует QR, подтверждает оплату |
| PM (платёжный шлюз) | Payment Manager | Регистрирует инвойс, проверяет подпись QR, проводит платёж |
| Внутренняя бухгалтерия | Внутренняя учётная система PM | Делает движение средств по счетам (детали — в техдоке) |
3. Sequence-диаграмма полного flow¶
sequenceDiagram
autonumber
actor M as Магазин
actor C as Клиент
participant PM as PM (платёжный шлюз)
participant Buh as Внутренняя бухгалтерия
Note over M,PM: Фаза 1 — Резерв (выставление инвойса)
M->>PM: Выставить инвойс<br/>(сумма, валюта, счёт магазина, TTL)
PM->>PM: Создать запись инвойса (статус CREATED)<br/>Подписать QR-payload<br/>Зафиксировать срок действия
PM-->>M: ID инвойса, qr_signature, expiresAt
Note over M,C: Магазин формирует QR-код и показывает его покупателю.<br/>Между фазами может пройти от секунд до минут.
Note over C,PM: Фаза 2 — Оплата (подтверждение покупателем)
C->>C: Сканирует QR<br/>Локально проверяет подпись
C->>PM: Подтвердить оплату<br/>(ID инвойса, счёт покупателя)
PM->>PM: Проверить статус и срок действия<br/>Зафиксировать плательщика
PM->>Buh: Провести платёж<br/>(детали в техдоке)
Buh-->>PM: Платёж проведён
PM-->>C: Статус: SETTLED
PM-->>M: Push-уведомление: инвойс оплачен
4. Шаг за шагом¶
4.1. Фаза «резерв»: магазин выставляет счёт¶
- Магазин обращается к PM и просит создать инвойс: сумма, валюта, счёт магазина для зачисления, опциональный комментарий, TTL.
- PM:
- создаёт запись инвойса в статусе CREATED;
- не блокирует деньги — плательщик ещё неизвестен, блокировать нечего;
- вычисляет дедлайн (
expiresAt); - подписывает QR-payload секретным ключом (
qr_signature); - возвращает магазину ID инвойса, подпись и срок действия.
- Магазин формирует QR-код и показывает его покупателю (на экране кассы, в чеке, на наклейке).
Результат фазы: инвойс существует, ждёт оплаты, деньги ни у кого не списаны.
4.2. Фаза «оплата»: клиент подтверждает¶
- Клиент сканирует QR в приложении OneWallet.
- Приложение локально проверяет подпись QR — это защищает от подделанных QR-кодов (см. раздел 5).
- Клиент видит сумму, реквизиты магазина и нажимает «Оплатить».
- Приложение отправляет в PM запрос на подтверждение: ID инвойса плюс счёт покупателя.
- PM атомарно фиксирует плательщика и переводит инвойс в статус SETTLED, поручив внутренней бухгалтерии провести фактическое движение средств между счетами клиента и магазина (детали — в техдоке).
- PM уведомляет магазин о факте оплаты в реальном времени.
Результат фазы: деньги списаны со счёта клиента, зачислены на счёт магазина, обе стороны получили подтверждение.
5. Зачем нужен qr_signature¶
QR-код — это просто картинка с данными. Без защиты любой может «нарисовать» QR со своим счётом, выдать его за чужой и собрать платежи на свой кошелёк.
qr_signature — это цифровая подпись, которой PM скрепляет содержимое QR-кода:
- При создании инвойса PM подписывает payload (ID инвойса, счёт магазина, сумма, валюта, срок действия) своим секретным ключом.
- Подпись кладётся в QR-код вместе с данными.
- Приложение клиента проверяет подпись локально перед тем, как показать пользователю экран оплаты. Если подпись не сходится — QR подделан или испорчен, приложение отказывается платить.
- На этапе подтверждения PM ещё раз сверяет подпись со стороны сервера.
Без этой подписи злоумышленник мог бы:
- подменить счёт получателя в чужом QR-коде и угнать платёж;
- сгенерировать «фальшивый инвойс» с произвольной суммой;
- продлить срок действия истёкшего QR.
С подписью все эти атаки отсекаются: любое изменение payload ломает подпись, а ключ известен только PM.
Алгоритм подписи и его параметры (формат, длина, шифр) описаны в технической документации — см. блок «Где это в коде».
6. Срок действия инвойса (TTL)¶
У каждого инвойса есть срок действия — expiresAt. После него инвойс перестаёт быть оплачиваемым.
- По умолчанию — 10 минут. Этого хватает для типового сценария «покупатель у кассы»: чек распечатан, QR на экране, клиент платит сейчас.
- Максимум — 1 час. Магазин может явно запросить более длинный TTL (например, для счёта-самообслуживания в зале ожидания), но больше часа PM не выдаст.
- Конкретное значение
INVOICE_DEFAULT_TTL_SECONDSи потолокINVOICE_MAX_TTL_SECONDSберутся из конфигурации платёжного шлюза.
Зачем ограничивать TTL:
- защита от «вечно висящих» QR, которые могут быть оплачены через неделю по уже неактуальной цене;
- предсказуемость для магазина: касса знает, что после TTL счёт автоматически закроется и его можно перевыставить;
- безопасность: украденный или сфотографированный QR не работает бесконечно.
7. Альтернативы основного flow¶
Помимо «магазин выставил — клиент оплатил», у инвойса есть две альтернативные финальные ветки.
7.1. Альтернатива: магазин отменил инвойс (cancel)¶
Магазин может явно отозвать инвойс, пока он ещё не оплачен. Типовые причины:
- товара нет на складе;
- покупатель передумал и ушёл;
- кассир ошибся в сумме и хочет перевыставить счёт.
sequenceDiagram
autonumber
actor M as Магазин
participant PM as PM (платёжный шлюз)
M->>PM: Отменить инвойс<br/>(ID, причина)
alt Инвойс ещё в статусе CREATED
PM->>PM: Перевести в CANCELED
PM-->>M: Подтверждение отмены
else Инвойс уже оплачен / истёк / отменён
PM-->>M: Ошибка: отменить нельзя
end
Ключевые правила:
- отменить может только тот магазин, который выставлял инвойс (или администратор системы);
- если инвойс уже оплачен или истёк — отмена невозможна, PM возвращает ошибку;
- после отмены статус инвойса — CANCELED, оплатить его больше нельзя;
- деньги нигде не двигались, никаких возвратов не требуется.
7.2. Альтернатива: истёк срок действия (expire)¶
Если ни клиент не подтвердил оплату, ни магазин не отменил инвойс — по истечении TTL инвойс автоматически переходит в статус EXPIRED.
sequenceDiagram
autonumber
participant Sweep as Фоновая задача PM
participant PM as PM (платёжный шлюз)
actor M as Магазин
loop Каждые ~30 секунд
Sweep->>PM: Найти инвойсы<br/>со статусом CREATED<br/>и истёкшим expiresAt
PM->>PM: Перевести в EXPIRED
PM-->>M: Push-уведомление: инвойс истёк
end
Ключевые правила:
- истечение происходит автоматически без действий магазина или клиента;
- если в момент истечения клиент успел нажать «Оплатить» — побеждает тот, кто пришёл первым (PM использует атомарную проверку статуса);
- истёкший инвойс нельзя ни оплатить, ни отменить — он финален;
- магазин может выставить новый инвойс на ту же сумму, это будет отдельная запись с новым ID.
8. Финальные статусы¶
| Статус | Что произошло | Дальнейшие действия |
|---|---|---|
SETTLED |
Клиент оплатил, деньги зачислены магазину | Платёж завершён, возврат — отдельным сценарием |
CANCELED |
Магазин отменил до оплаты | Можно выставить новый инвойс |
EXPIRED |
Истёк TTL без оплаты и без отмены | Можно выставить новый инвойс |
Промежуточные статусы (VALIDATED, AUTHORIZED, SETTLING) существуют внутри фазы «оплата» и видны через детальный аудит — для бизнес-сценария важны только три финальных.
9. Где это в коде¶
Подробности технической реализации (контракт TwoPhaseChannel, алгоритм подписи QR, атомарные UPDATE с гонками, конкретные API-эндпоинты, curl-примеры):
- Двухфазные каналы платежей — теория: интерфейс
TwoPhaseChannel, методыreserve / redeem / cancel / expire, контраст с одно-фазными каналами. - Cookbook: Выставить мерчант-инвойс — практическое руководство для интегратора: пошаговый flow, формат payload, обработка ошибок, curl-примеры всех эндпоинтов.