Нужны checkout-сессия, серверный координатор платежа, поддерживаемая банковская интеграция и подтверждение статуса от провайдера. QR связывает покупателя с покупкой, но не заменяет платёжный протокол. Возврат из приложения банка не доказывает оплату. Идемпотентность, сверка статуса и обработка позднего успеха обязательны; возможность выбора банка без СБП нужно сначала подтвердить интеграционным контрактом.
Как спроектировать оплату на кассе через мобильное приложение без СБП?
Архитектурная задача: идентификация на кассе, переход в банк, подтверждение оплаты и обработка неопределённого результата без повторного списания.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Покупатель оплачивает товары на кассе или КСО через приложение Магнит, не используя СБП. После идентификации он выбирает установленное приложение банка, подтверждает платёж там и возвращается к результату в приложении магазина. Касса также должна получить подтверждение.
Дано: 500 магазинов, DAU 1000 на магазин, в среднем пять касс на магазин. Оплата должна занимать максимум две минуты, открытие страницы оплаты — максимум 250 мс.
Сначала уточнить интеграцию и ограничения
Выбор любого банка не возникает автоматически из deep link. Нужен конкретный поддерживаемый банковский или эквайринговый сценарий вне СБП, договорённость об API и перечень доступных банков. Если такого протокола нет, заданный пользовательский путь нельзя честно обещать. Ниже — архитектура при наличии этой интеграции, а не утверждение о реальном устройстве Магнита.
Для 250 мс уточняют точку измерения: показ подготовленного интерфейса или получение всех банковских данных. Две минуты включают действия человека и внешнего банка, поэтому нужен timeout пользовательской сессии, но нельзя гарантировать окончательное решение банка за это время при любых сбоях.
Компоненты и поток
Касса создаёт checkout-сессию с неизменяемыми суммой, валютой, корзиной и сроком. Она показывает одноразовый непрозрачный QR-идентификатор. Авторизованный клиент связывает сессию с покупкой и видит сумму для подтверждения.
Мобильный backend обращается к координатору платежей. Тот сохраняет попытку оплаты и идемпотентно инициирует операцию через адаптер поддерживаемого банка. Клиент получает безопасный способ перехода, предусмотренный контрактом провайдера.
Результат принимается сервером через проверенное уведомление либо запрашивается по серверному API. Возврат пользователя в приложение служит поводом обновить экран, но не источником статуса «оплачено». Проверка подлинности, повторная доставка и дубли событий — обычные вопросы webhook-интеграции; пример требований есть в документации Stripe. Это иллюстрация свойств протокола, не выбор Stripe для данного проекта.
Состояние хранится в БД. Касса и приложение получают обновление через постоянное соединение или ограниченный polling с восстановлением после разрыва. Потеря уведомления не должна терять сам факт оплаты.
Отказы и безопасность
Повторный запрос одной попытки не создаёт новое списание. Для неопределённого ответа сначала выясняют статус, а не начинают независимую оплату. Дубликаты событий обрабатывают идемпотентно; старое событие не откатывает подтверждённое состояние.
Истечение двух минут не доказывает неуспех платежа. Нужна процедура позднего подтверждения: сверка, завершение заказа либо согласованный возврат. Граница между авторизацией средств и окончательным списанием определяется банковским контрактом.
Сумма берётся с сервера, QR не содержит реквизитов карты, доступ к чужой сессии проверяется. Секреты банковской интеграции не выдаются мобильному клиенту.
Производительность
Всего около 2500 касс. DAU по магазинам не даёт пиковый RPS: нужны часы работы, покупки на посетителя, пики и количество запросов на оплату. Кэшировать можно статику и безопасные справочники, но не заменять кэшем подтверждение списания.
Быстрый интерфейс отделяют от банковского ожидания. Масштабируют stateless API, контролируют лимиты провайдеров, очередь сверки, задержки и долю неопределённых операций. Выполнение требований подтверждают измерением полного сценария, а не одним выбором очереди или CDN.