12 KiB
Мастер установки банка (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), который:
- Определяет этап по подстрокам ("пин-кода" / "профиля" / "карт").
- Распознаёт типовые сообщения скриптов (
"Cant open the home screen","No cards found","Cannot find user name", и т.д.) и подставляет понятный русский текст с подсказкой что делать. - Всё незнакомое → "Произошла непредвиденная ошибка. Попробуйте повторить позже."
Кнопка «Начать заново» на странице Error дополнительно чистит БД, но только если профиль ещё не зарегистрирован на сервере (current.bankProfileId.isEmpty(), BankSetupWizard.cpp:277-283): удаляет материалы (MaterialDAO::deleteAccountsByApp) и сам профиль (BankProfileDAO::deleteProfile), сбрасывает m_app.id = -1. Это предотвращает дубликаты при повторной попытке.
Управление воркер-потоком
stopWorkerThread() (BankSetupWizard.cpp:342) — критичный кусок:
requestInterruption()+quit(), ждёт до 3 секундwait(3000).- Если поток залип (обычно — внутри
QProcess::waitForFinishedвAdbUtils::startProcess): отсоединяетQThreadот родителя (setParent(nullptr)) и вешаетdeleteLaterна сигналfinished. Без этого~QWidgetудалит работающийQThread→qFatal.
Каждый 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