1
0
forked from BRT/arc
arc/docs/bank-setup-wizard.md

12 KiB
Raw Permalink Blame History

Мастер установки банка (BankSetupWizard)

Файлы: views/bank/BankSetupWizard.{h,cpp}

Назначение

Диалоговое окно, проводящее пользователя через настройку нового банковского профиля на конкретном устройстве: ввод пин-кода → автоматический вход в приложение → парсинг профиля → парсинг карт → выбор активной карты. Все данные одновременно сохраняются в локальную БД и регистрируются на бэкенде (localhost:9999/api/v1/).

Поддерживаемые банки: Ozon, Black (Birbank), Dushanbe City Bank (appCode == "ozon" | "black" | "dushanbe_city_bank"). Остальные банки в мастере не поддерживаются и сразу падают с ошибкой (BankSetupWizard.cpp:443, 494, 549).

Где открывается

Два входа, оба создают BankSetupWizard как обычный QDialog (немодальный):

  • views/device/DeviceSettingsWindow.cpp:176 — из карточки настроек устройства.
  • views/device/DeviceWidget.cpp:278 — из плитки устройства в главном окне.

В обоих местах используется свежая запись из БД, если профиль уже есть; иначе — переданный BankProfileInfo шаблон.

Структура UI: QStackedWidget из 5 страниц

Индексы страниц: 0 Intro, 1 PIN, 2 Progress, 3 Success, 4 Error (BankSetupWizard.cpp:32-38).

Шаг Что показывает Кнопки
1 Intro (createStep1) Иконка + название банка + список из 3 шагов Далее → шаг 2
2 PIN (createStep2) Поле ввода пин-кода (max 10 символов) Назад, Далее → сохраняет m_app.pinCode, переходит на шаг 3 и запускает startVerification()
3 Progress (createStep3) QProgressBar + текстовая метка статуса НазадstopWorkerThread(), возврат на шаг 2
4a Success (createStep4Success) ФИО / Телефон / Email + таблица карт с radio-выбором ЗавершитьapplyActiveCardSelection(), accept()
4b Error (createStep4Error) Человекочитаемый текст ошибки Назад → шаг 2, Начать заново → очищает БД-запись (если профиль ещё не отправлен) и возвращает на шаг 1

Жизненный цикл и пауза других профилей

В конструкторе (BankSetupWizard.cpp:41) сразу вызывается pauseActiveProfiles():

  • Берёт все профили устройства через BankProfileDAO::getApplicationsByDeviceId.
  • Все status == "active" переводятся в "off" локально и выключаются на сервере через NetworkService::toggleBankProfile(enable=false) (BankSetupWizard.cpp:297-321).
  • Коды паузованных профилей запоминаются в m_pausedProfiles.

Это сделано, чтобы фоновый EventHandler / DeviceScreener не дёргали телефон параллельно во время работы мастера.

В деструкторе (BankSetupWizard.cpp:44-47): stopWorkerThread() + resumePausedProfiles() — возвращает паузованные профили в "active" и шлёт toggleBankProfile(enable=true) на сервер.

Цепочка верификации (шаги 3 → 4)

startVerification()runPinCheck()runProfileCheck()runCardCheck()onVerificationSuccess().

Каждый шаг запускает соответствующий банк-скрипт в отдельном QThread (m_workerThread), результат принимает лямбдой через Qt::QueuedConnection. Если шаг вернул непустую строку — onVerificationError(), иначе — следующий шаг.

1. runPinCheck() — проверка пин-кода (BankSetupWizard.cpp:384)

Сначала startVerification() делает базовую проверку, что устройство онлайн (device.status == "device"), затем upsert'ит профиль в БД с status="off", install=true, чтобы получить m_app.id (BankSetupWizard.cpp:362-382).

Дальше создаётся LoginAndCheckAccountsScript с флагом pinCheckOnly=true (3-й или 4-й аргумент в зависимости от банка). Этот флаг говорит скрипту: только дойти до главного экрана, чтобы убедиться что пин валидный, и выйти. Никаких материалов не создаётся.

При успехе: BankProfileDAO::updatePinCodeStatus(appId, "yes", ""), прогресс → 33%, переход к runProfileCheck().

2. runProfileCheck() — данные профиля (BankSetupWizard.cpp:450)

Запускает GetProfileInfoScript. Скрипт парсит ФИО/телефон/email с главного экрана, обновляет bank_profiles в БД и отправляет на сервер POST/PATCH через NetworkService — на этом этапе бэкенд возвращает bank_profile_id.

После завершения обработчик onProfileResult дочитывает запись из БД (BankProfileDAO::getApplication) и проверяет, что bankProfileId непустой (BankSetupWizard.cpp:463). Если бэкенд не вернул id — ошибка "Не удалось зарегистрировать банковский профиль на сервере. Возможно, банк ещё не поддерживается сервером." Прогресс → 66%, переход к runCardCheck().

3. runCardCheck() — карты (BankSetupWizard.cpp:501)

Запускает GetCardInfoScript. Скрипт обходит все карты в приложении, парсит полный номер карты (для Ozon/Dushanbe — через OCR/XML), складывает в БД и отправляет POST material на сервер.

После завершения onCardResult читает materials = MaterialDAO::getAccounts(...), и если хотя бы одна карта есть, но ни у одной нет materialId (т.е. ничего не синхронизировалось с сервером) — ошибка "Не удалось отправить данные карт на сервер" (BankSetupWizard.cpp:518). Прогресс → 100%, вызов onVerificationSuccess().

4. onVerificationSuccess() (BankSetupWizard.cpp:556)

  • Перечитывает профиль из БД, формирует HTML с ФИО/телефоном/email.
  • Загружает карты, и если хоть у одной cardNumber.isEmpty() — уходит в Error ("Не удалось получить полный номер карты, распознаны только последние 4 цифры") (BankSetupWizard.cpp:574-580). Это страховка поверх защиты в самих скриптах.
  • Заполняет таблицу: 4 колонки (Номер / Сумма / Валюта / Активна-radio).
  • Логика автовыбора:
    • 0 карт → строка "Карты не найдены", кнопка Завершить активна.
    • 1 карта → автовыбор, колонка radio скрыта.
    • ≥2 карт → первая карта выбрана по умолчанию, можно перевыбрать.

5. applyActiveCardSelection() (BankSetupWizard.cpp:631) — при нажатии Завершить

  • MaterialDAO::activateExclusive(deviceId, appCode, selectedMaterialId) — делает выбранную карту единственной активной в этом профиле; возвращает список деактивированных.
  • Для выбранной карты шлёт NetworkService::toggleMaterial(enable=true), для каждой деактивированной — toggleMaterial(enable=false). Каждый запрос — в своём QThread, fire-and-forget.

Обработка ошибок

onVerificationError() (BankSetupWizard.cpp:690) проганяет текст через humanReadableError() (BankSetupWizard.cpp:659), который:

  1. Определяет этап по подстрокам ("пин-кода" / "профиля" / "карт").
  2. Распознаёт типовые сообщения скриптов ("Cant open the home screen", "No cards found", "Cannot find user name", и т.д.) и подставляет понятный русский текст с подсказкой что делать.
  3. Всё незнакомое → "Произошла непредвиденная ошибка. Попробуйте повторить позже."

Кнопка «Начать заново» на странице Error дополнительно чистит БД, но только если профиль ещё не зарегистрирован на сервере (current.bankProfileId.isEmpty(), BankSetupWizard.cpp:277-283): удаляет материалы (MaterialDAO::deleteAccountsByApp) и сам профиль (BankProfileDAO::deleteProfile), сбрасывает m_app.id = -1. Это предотвращает дубликаты при повторной попытке.

Управление воркер-потоком

stopWorkerThread() (BankSetupWizard.cpp:342) — критичный кусок:

  1. requestInterruption() + quit(), ждёт до 3 секунд wait(3000).
  2. Если поток залип (обычно — внутри QProcess::waitForFinished в AdbUtils::startProcess): отсоединяет QThread от родителя (setParent(nullptr)) и вешает deleteLater на сигнал finished. Без этого ~QWidget удалит работающий QThreadqFatal.

Каждый run*Check() сначала зовёт stopWorkerThread(), поэтому переход на следующий шаг безопасен даже при гонках.

Краткая последовательность данных

Пользователь вводит PIN
        ↓
BankProfileDAO::upsertApplication (status=off, install=true)
        ↓
LoginAndCheckAccountsScript (pinCheckOnly=true)  ─→  только верифицирует вход
        ↓
BankProfileDAO::updatePinCodeStatus("yes", "")
        ↓
GetProfileInfoScript  ─→  парсит профиль → POST/PATCH bank_profile → bankProfileId
        ↓
GetCardInfoScript     ─→  парсит карты   → POST material для каждой → materialId
        ↓
Таблица карт + выбор активной
        ↓
MaterialDAO::activateExclusive + toggleMaterial на сервере
        ↓
accept()  →  resumePausedProfiles() в деструкторе возвращает остальные профили в active