# Black - Логика задач [← Главная](index.md) | [События](events.md) Описание логики всех типов задач для банка **Black** (appCode: `black`). > Black — аргентинское банковское приложение. Интерфейс на испанском. Валюта: ARS (код 32). Таймзона: America/Argentina/Buenos_Aires (UTC-3). --- ## Общие механизмы (CommonScript) ### Запуск приложения (`runAppAndGoToHomeScreen`) 1. Убивает приложение если запущено 2. Запускает приложение 3. Ждёт 4.5 секунды 4. Цикл до 10 попыток: - Получает XML-дамп экрана - Проверяет `isHomeScreen()` (наличие "Saldo disponible" + content-desc "Inicio") - Если экран с PIN-кодом (`isPinCodeScreen()` — "¿Olvidaste tu PIN?" или "Ingresá tu PIN"): - Находит EditText с password=true - Тапает для фокуса → ожидание 500мс - Вводит PIN через `AdbUtils::inputText()` - Ожидание 3 сек - Ожидание 1 сек между попытками 5. При неудаче — скриншот ### Прокрутка к кнопке (`swipeToButton`) - До 10 свайпов вверх - После каждого свайпа парсит XML и ищет кнопку - Аналогично Ozon ### Создание банковского профиля (`createBankProfileIfNeeded`) - Аналогично Ozon (общий код в CommonScript) - Валюта по умолчанию: ARS→32 --- ## FETCH_PROFILE **Скрипты:** `Black::LoginAndCheckAccountsScript` → `Black::GetProfileInfoScript` → `Black::GetCardInfoScript` ### Цель Авторизация, сбор профиля (ФИО, телефон, email), получение CVU и баланса. ### Шаги LoginAndCheckAccountsScript 1. **Валидация** — получение DeviceInfo и BankProfileInfo из БД 2. **Запуск приложения** — `runAppAndGoToHomeScreen()` с PIN-кодом 3. **Запуск GetProfileInfoScript** — синхронно, для сбора профиля 4. При ошибке: скриншот + лог + обновление комментария в `BankProfileDAO` ### Шаги GetProfileInfoScript 1. **Домашний экран** — проверка/перезапуск 2. **Парсинг с домашнего экрана**: - fullName из `parseUserNameFromHomeScreen()` — формат "¡Hola!\nИМЯ\ninitials" - Баланс из `parseBalanceFromHomeScreen()` — формат "$1.857,00" → 1857.00 - Обновление баланса в `BankProfileDAO` 3. **Навигация в боковое меню**: - Поиск кнопки "¡Hola!" (аватар) - Тап → ожидание 2 сек - Ожидание `isSideMenu()` (5 × 1 сек) — маркеры: "Tu Información" + "Cerrar sesión" 4. **Навигация к профилю**: - Тап "Tu Información" (content-desc) - Ожидание 2.5 сек - Ожидание `isProfilePage()` (5 × 1 сек) — маркер: content-desc "Tu información" 5. **Парсинг профиля**: - Nombre, Apellido, Email — по hint-атрибутам EditText - Свайп вниз → ожидание 1.5 сек - Телефон — из hint-атрибута - fullName = "nombre apellido" (или fallback на имя с домашнего экрана) 6. **Сохранение**: `BankProfileDAO::updateProfileInfo(id, fullName, phone, email)` 7. **Синхронизация с сервером**: `createBankProfileIfNeeded()` 8. **Отправка аккаунтов**: `postAccountsData()` для аккаунтов без materialId 9. **Завершение** — убийство приложения ### Шаги GetCardInfoScript (CVU) 1. **Домашний экран** — проверка/перезапуск 2. **Парсинг баланса** → обновление в БД 3. **Навигация к CVU**: - Поиск кнопки "Tu CVU" (exact match) - Тап → ожидание 2 сек - Ожидание `isCVUScreen()` (5 × 1 сек) 4. **Парсинг CVU**: - `parseCVUNumber()` — 22-значный номер после маркера "CVU" - lastNumbers = последние 4 цифры CVU 5. **Сохранение в БД**: - `MaterialDAO::upsertAccount()` с: - cardNumber = полный CVU - lastNumbers = CVU[-4:] - currency = "USD" (хардкод) - description = "CVU" 6. **Отправка материала на сервер**: `ns.postMaterial()` (если есть bankProfileId и нет materialId) 7. **Завершение** — убийство приложения ### Ответ серверу (при успехе) ```json { "type": "FETCH_PROFILE", "material_id": "...", "bank_name": "black", "data": { "bank_account_id": "", "amount": 185700, "currency": 32 } } ``` --- ## FETCH_TRANSACTIONS **Скрипт:** `Black::GetLastTransactionsScript` ### Цель Получение списка последних транзакций из истории операций. ### Шаги 1. **Домашний экран** — проверка/перезапуск 2. **Навигация к истории транзакций**: - Поиск кнопки "Ver más" после секции "Tu actividad" через `findVerMasAfterTuActividad()` - Если не найдена — до 5 свайпов вниз (1 сек между свайпами) - Тап "Ver más" → ожидание 2 сек 3. **Ожидание загрузки** (15 × 1.5 сек): - `isTransactionLoadingScreen()` = "Tu Actividad" есть, но транзакций нет (загрузка) - `isTransactionListScreen()` = "Tu Actividad" + видимые транзакции - Если после 15 попыток всё ещё loading → пустой аккаунт (нормально), возврат без ошибки 4. **Парсинг транзакций** (`parseTransactionItems()`): - Источник: ImageView ноды с текстом формата: ``` Transferencia DD de MES de YYYY - HH:MM $amount,cents ``` или (входящие): ``` Transferencia externa de ИМЯ DD de MES de YYYY - HH:MM $amount,cents ``` - Отрицательные суммы: `$-amount` (исходящие переводы) - Парсинг испанских дат (`parseSpanishDate()`): enero, febrero, ..., diciembre 5. **Сохранение в БД** (для каждой транзакции): - bankName = "black" - status = Complete - timestamp = currentDateTimeUtc() - Конвертация времени: парсинг испанской даты → таймзона Buenos Aires - `TransactionDAO::insertTransactionIfNotExists()` 6. **Возврат** — `goBack()` → ожидание 1 сек ### Retry-логика (EventHandler) Аналогична Ozon: ``` "Transaction not found" + < 10 мин → повтор через 15 сек "Transaction not found" + ≥ 10 мин → ошибка ``` ### Ответ серверу ```json { "type": "FETCH_TRANSACTIONS", "material_id": "...", "bank_name": "black", "data": [ { "bank_transaction_id": null, "amount": 185700, "currency": 32, "transaction_type": "bank", "bank_participator_id": "+5491112345678", "status": "completed", "extra": { "fee": 0 }, "created_at": "2025-01-15T14:30:00.000-03:00" } ] } ``` --- ## CREATE_TRANSACTION (bank — по CBU/CVU/Alias) **Скрипт:** `Black::PayByCardScript` ### Цель Перевод на CBU, CVU или Alias получателя. ### Входные параметры - `event.phone` — CBU/CVU/Alias получателя (поле `m_cardNumber`) - `event.amount` — сумма перевода - `m_account` — аккаунт-отправитель ### Шаги 1. **Домашний экран** — проверка/перезапуск 2. **Поиск кнопки оплаты**: - `findNodeByContentDesc("Enviar a\nCBU o alias", contains=true)` - Если не найдена — до 5 свайпов вниз - Тап → ожидание 2 сек 3. **Ввод реквизитов** (10 × 1.5 сек ожидание экрана): - Экран `isPayInputCardNumberScreen()` — hint "Ingresar CBU, CVU o Alias" - Тап на EditText → ожидание 500мс - Ввод CBU/CVU/Alias - Тап "Continuar" → ожидание 2 сек 4. **Подтверждение получателя** (10 × 1.5 сек): - Экран `isPayTransferConfirmScreen()` — кнопка "Transferir" + поле "Alias:" - Тап "Transferir" → ожидание 2 сек 5. **Ввод суммы** (10 × 1.5 сек): - Экран `isPayInputSumScreen()` — "Cuánto vas a transferír" - Тап на EditText → ожидание 500мс - **Очистка поля**: 20 × keyevent 67 (backspace) - **Ввод суммы в сентаво**: `int(amount * 100)` → строка - Пример: 500.00 → "50000" - Проверка: перечитывает поле, если не совпадает — повторяет ввод - Тап "Continuar" → ожидание 2 сек 6. **Финальное подтверждение** (10 × 1.5 сек): - Экран `isPayConfirmScreen()` — "Vas a transferir \na ИМЯ_ПОЛУЧАТЕЛЯ" - Парсинг имени получателя (`parseRecipientNameFromConfirm()`) - Тап "Transferir" → ожидание 3 сек 7. **Ввод PIN** (если запрошен): - `isPinCodeScreen()` → `inputPinCode()` - Ожидание 3 сек 8. **Проверка результата** (15 × 2 сек): - **"Dinero enviado!"** → успех: - Парсинг bankTime: `parsePaySuccessDateTime()` → "DD/MM/YY HH:MM" - Парсинг ID: `parsePaySuccessCoelsaId()` → Coelsa transaction ID - Скриншот как чек - Если снова PIN-экран → повторный ввод PIN - Таймаут → ошибка 9. **Сохранение транзакции**: - amount = -m_amount (отрицательная) - fee = 0 - type = Card, status = Complete - name = имя получателя - description = "Transferencia a ИМЯ" - phone = CBU/CVU/Alias - bankTrExternalId = Coelsa ID - receiptImage = скриншот - `TransactionDAO::insertTransaction()` ### Ответ серверу ```json { "type": "CREATE_TRANSACTION", "material_id": "...", "bank_name": "black", "data": { "amount": -50000, "currency": 32, "transaction_type": "bank", "bank_participator_id": "0000003100012345678901", "status": "completed", "bank_transaction_id": "COELSA-12345", "extra": { "fee": 0 }, "created_at": "2025-01-15T14:30:00.000-03:00" } } ``` --- ## ScreenXmlParser (Black) ### Определение экранов | Метод | Маркеры | |-------|---------| | `isHomeScreen()` | "Saldo disponible" + content-desc "Inicio" | | `isPinCodeScreen()` | "¿Olvidaste tu PIN?" или "Ingresá tu PIN" | | `isSideMenu()` | "Tu Información" + "Cerrar sesión" | | `isProfilePage()` | content-desc "Tu información" | | `isCVUScreen()` | маркер "CVU" | | `isTransactionLoadingScreen()` | "Tu Actividad" без "Transferencia" | | `isTransactionListScreen()` | "Tu Actividad" + транзакции | | `isPayInputCardNumberScreen()` | EditText с hint "Ingresar CBU, CVU o Alias" | | `isPayTransferConfirmScreen()` | "Transferir" + "Alias:" без "Vas a transferir" | | `isPayInputSumScreen()` | "Cuánto vas a transferír" | | `isPayConfirmScreen()` | "Vas a transferir" | | `isPaySuccessScreen()` | "Dinero enviado!" | ### Парсинг данных | Метод | Формат / Логика | |-------|-----------------| | `parseUserNameFromHomeScreen()` | "¡Hola!\nИМЯ\ninitials" → ИМЯ | | `parseBalanceFromHomeScreen()` | "$1.857,00" → 1857.00 (аргентинский формат) | | `parseProfileNombre/Apellido/Email/Phone()` | По hint-атрибутам EditText | | `parseCVUNumber()` | 22-значный номер после маркера "CVU" | | `parseTransactionItems()` | ImageView ноды с "Transferencia" | | `parseSpanishDate()` | "DD de MES de YYYY - HH:MM" → QDateTime | | `parseRecipientNameFromConfirm()` | "Vas a transferir \na ИМЯ" → ИМЯ | | `parsePaySuccessDateTime()` | "DD/MM/YY HH:MM" | | `parsePaySuccessCoelsaId()` | Текст после "Id Coelsa" | ### Парсинг транзакций Формат элемента списка (ImageView text): ``` Transferencia // тип 15 de enero de 2025 - 14:30 // дата (испанский) $1.500,00 // сумма (положительная = входящая) ``` или: ``` Transferencia externa de Juan Perez // тип + имя отправителя 15 de enero de 2025 - 14:30 $-1.500,00 // отрицательная = исходящая ``` ### Испанские месяцы | Месяц | Перевод | |-------|---------| | enero | январь | | febrero | февраль | | marzo | март | | abril | апрель | | mayo | май | | junio | июнь | | julio | июль | | agosto | август | | septiembre | сентябрь | | octubre | октябрь | | noviembre | ноябрь | | diciembre | декабрь |