- Implement `toggleMaterialStatus` in `AccountSettingsWindow` for UI control and threading integration. - Enhance `MaterialDAO` with numeric validation for `lastNumbers` field to ensure data integrity. - Update `PayByCardScript` and `PayByPhoneScript` to handle PIN entry if required after payment confirmation. - Introduce dynamic UI adjustments for material toggling, including a new toggle button and layout refinements. - Add documentation for task logic in `ozon_tasks.md` and `black_tasks.md`. - Switch default currency in `GetCardInfoScript` (Black app) from USD to ARS.
16 KiB
Ozon - Логика задач
Описание логики всех типов задач для банка Ozon (appCode: ozon).
Общие механизмы (CommonScript)
Запуск приложения (runAppAndGoToHomeScreen)
- Убивает приложение если запущено (
AdbUtils::tryToKillApplication) - Запускает приложение (
AdbUtils::tryToStartApplication) - Ждёт 4.5 секунды на старт
- Цикл до 10 попыток:
- Получает XML-дамп экрана
- Проверяет
isHomeScreen()(наличие кнопок "Перевести" + "Пополнить") - Закрывает баннеры: диалоги, баннер безопасности, Google Play, рекламный bottom-sheet
- Если экран с PIN-кодом — вводит PIN (100–900мс между цифрами)
- Ожидание 1 сек между попытками
- При неудаче — скриншот + лог ошибки
Навигация к продуктам (goToAllProducts)
- Закрывает баннеры на домашнем экране
- Ищет вкладку "Финансы"
- Тапает до 3 раз с ожиданием 1 сек
Прокрутка к кнопке (swipeToButton)
- До 10 свайпов вверх (от 3/4 высоты к 1/4 высоты экрана)
- После каждого свайпа парсит XML и ищет кнопку
- Ожидание 1 сек между свайпами
Создание банковского профиля (createBankProfileIfNeeded)
- Читает fullName и phone из
BankProfileDAO - Нормализует телефон: убирает нецифровые символы, добавляет код страны из
config.ini - Маппинг валют: RUB→643, USD→840, KRW→410, AZN→944, ARS→32
- POST (новый) или PATCH (существующий) на сервер
FETCH_PROFILE
Скрипт: Ozon::LoginAndCheckAccountsScript + Ozon::GetCardInfoScript
Цель
Авторизация в приложении, получение баланса, полных номеров карт и синхронизация с сервером.
Шаги LoginAndCheckAccountsScript
- Валидация — получение DeviceInfo и BankProfileInfo из БД
- Запуск приложения —
runAppAndGoToHomeScreen()с PIN-кодом - Создание профиля —
createBankProfileIfNeeded()(синхронизация fullName/phone) - Сбор аккаунтов (если не режим fetchProfileOnly):
- Навигация к "Финансы" через
goToAllProducts() - Парсинг видимых аккаунтов из XML
- Upsert в
MaterialDAO - Отправка на сервер
- Навигация к "Финансы" через
- Запуск GetCardInfoScript — синхронно, для получения полных номеров карт
Шаги GetCardInfoScript
- Навигация на домашний экран
- Парсинг профиля (если нет в БД):
- Тап на имя пользователя вверху экрана
- Парсинг fullName и phone
- Обновление
BankProfileDAO
- Загрузка карт —
parseCardsOnHomeScreen()(до 5 попыток, 1.5 сек) - Upsert всех карт в
MaterialDAO - Синхронизация с сервером:
- Создание устройства (POST) если нет
apiId - POST/PATCH банковского профиля с балансом, ФИО, телефоном
- Создание устройства (POST) если нет
- Получение полных номеров карт (для каждой карты):
- Тап на карту → ожидание экрана карты (10 × 2 сек)
- Тап "Показать" → ожидание появления номера (10 × 2 сек)
- Regex:
\d{4}[\s]\d{4}[\s]\d{4}[\s]\d{4}(с unicode-пробелами) - Обработка баннера "Понятно"
- Сохранение в
MaterialDAO::updateCardNumber() - При неудаче: статус карты → Off, лог + скриншот
- Отправка материалов на сервер (
ns.postMaterial()) - Завершение — убийство приложения
Ответ серверу (при успехе)
{
"type": "FETCH_PROFILE",
"material_id": "...",
"bank_name": "ozon",
"data": {
"bank_account_id": "",
"amount": 1500000,
"currency": 643
}
}
amount— в копейках (баланс × 100)
FETCH_TRANSACTIONS
Скрипт: Ozon::GetLastTransactionsScript
Цель
Поиск конкретной транзакции по сумме и времени в истории операций банка.
Входные параметры (из JSON задачи)
body.amount— сумма в копейках (делится на 100)body.created_at— время транзакции (ISO формат)
Два режима работы
| Режим | Условие | Поведение |
|---|---|---|
| Поиск | searchAmount > 0 |
Ищет конкретную транзакцию |
| Сканирование | searchAmount == 0 |
Собирает все транзакции за 24 часа |
Шаги (режим поиска)
-
Навигация к списку операций (
navigateToTransactionList):- Закрытие рекламного баннера
- Проверка домашнего экрана (перезапуск если нужно)
- Свайп до кнопки "Все" (до 10 свайпов)
- Ожидание экрана "Операции" (10 × 1.5 сек)
-
Поиск транзакции (
searchTransaction):- Цикл до 15 прокруток
- Для каждой видимой кнопки-транзакции:
- Парсинг суммы из текста кнопки
- Проверка совпадения:
|amount - searchAmount| ≤ 0.01 - Парсинг даты из текста, проверка:
|time - searchTime| ≤ 1 час
- При совпадении:
- Тап на транзакцию → парсинг деталей (телефон, имя, время)
- Вставка в
TransactionDAO - Возврат
-
Если не найдена — возвращает
"Transaction not found"
Шаги (режим сканирования)
- Навигация к списку операций (аналогично)
- Сканирование (
scanRecentTransactions):- Для каждой транзакции:
- Тап → ожидание деталей (10 × 1.5 сек)
- Парсинг кнопки и деталей
- Фильтр по времени: пропускает транзакции старше 24 часов
- Получение чека:
- Тап "Документы"
- Ожидание "Поделиться" (20 × 1.5 сек)
- Скриншот → OCR (Tesseract, rus+eng)
- Извлечение ID операции через regex:
ID операции = ...(буквенно-цифровой)№ Ф-YYYY-XXXXXXX
- Вставка в
TransactionDAO - Возврат назад → ожидание списка
- Для каждой транзакции:
Retry-логика (EventHandler)
Результат "Transaction not found"
└─ Прошло < 10 мин? → статус Wait, повтор через 15 сек
└─ Прошло ≥ 10 мин? → статус Error
Между повторами другие задачи могут выполниться (см. приоритеты в events.md)
Ответ серверу (при успехе)
{
"type": "FETCH_TRANSACTIONS",
"material_id": "...",
"bank_name": "ozon",
"data": [
{
"bank_transaction_id": "Ф-2025-1234567",
"amount": 50000,
"currency": 643,
"transaction_type": "top-up",
"bank_participator_id": "+79001234567",
"status": "completed",
"extra": { "fee": 0 },
"created_at": "2025-01-15T14:30:00.000+03:00"
}
]
}
Возвращаются все транзакции аккаунта за последние 24 часа (не только найденная).
CREATE_TRANSACTION (bank — по номеру карты)
Скрипт: Ozon::PayByCardScript
Цель
Перевод по номеру карты получателя.
Входные параметры
event.phone— номер карты получателяevent.amount— сумма переводаevent.comment— имя банка-получателя
Шаги
-
Подготовка:
- Закрытие рекламных баннеров
- Проверка домашнего экрана (перезапуск если нужно)
- Закрытие: баннеры, Google Play, "Заказать карту"
-
Тап "Перевести" → ожидание 2 сек
-
Ожидание "Платежи и переводы" (10 × 1.5 сек)
-
Тап "По номеру карты" (секция OTHER_WAYS) → ожидание 2 сек
-
Ожидание "Перевод по номеру карты" (10 × 1.5 сек)
-
Ввод номера карты:
- Поиск EditText с hint "Номер карты получателя"
AdbUtils::inputText(cardNumber)- Ожидание 2 сек
-
Ввод суммы:
- Поиск EditText с hint "Сумма от"
- Ввод суммы (целое число)
- Ожидание 1 сек
-
Ожидание расчёта комиссии (15 × 1.5 сек):
- Поиск текста "Комиссия" + парсинг суммы после него
- Извлечение цифр из формата "45 ₽"
- Fallback: комиссия = 0
-
Тап "Перевести" (contains-match) → ожидание 3 сек
-
Проверка результата (15 × 2 сек):
- "Успешно" → сохранение транзакции:
- amount = -сумма (отрицательная)
- fee = рассчитанная комиссия
- type = Card, status = Complete
- timestamp = Moscow timezone
- "Не удалось" → ошибка
- Таймаут → "Не дождались результата"
- "Успешно" → сохранение транзакции:
-
Получение чека (при успехе):
- Тап "Документы"
- Ожидание "Поделиться" (20 × 1.5 сек)
- Скриншот → OCR → извлечение ID операции
- Обновление БД: receipt image + external ID
Ответ серверу
{
"type": "CREATE_TRANSACTION",
"material_id": "...",
"bank_name": "ozon",
"data": {
"amount": -50000,
"currency": 643,
"transaction_type": "bank",
"bank_participator_id": "2200123456789012",
"status": "completed",
"bank_transaction_id": "Ф-2025-1234567",
"extra": { "fee": 4500 },
"created_at": "2025-01-15T14:30:00.000+03:00"
}
}
CREATE_TRANSACTION (top-up — по номеру телефона)
Скрипт: Ozon::PayByPhoneScript
Цель
Перевод по номеру телефона с выбором банка-получателя.
Входные параметры
event.phone— номер телефона получателяevent.amount— сумма переводаevent.comment— имя банка-получателя (для СБП)
Шаги
-
Подготовка — аналогично PayByCard
-
Тап "Перевести" → ожидание 2 сек
-
Ожидание страницы платежей → поиск EditText (10 × 1.5 сек)
-
Навигация к "Кому перевести":
- Тап на первый EditText → ожидание 2 сек
- Ожидание экрана "Кому перевести" (10 × 1.5 сек)
-
Ввод номера телефона:
- Поиск первого EditText
AdbUtils::inputText(phone)- Ожидание 2 сек
-
Выбор получателя (цикл 20 попыток):
- Вариант A: Найдена кнопка с номером телефона → тап
- Вариант B: Выбор банка:
- Тап "Другой банк" (content-desc="other-bank-item")
- Включение ADB IME клавиатуры
- Ввод названия банка через
AdbUtils::inputAdbIMEText() - Отключение ADB IME
- Поиск банка в списке (10 × 1.5 сек)
- Если банк не найден → ошибка "Банк '...' не найден"
- Условие выхода: текст "По номеру телефона" вверху экрана (y < height/7)
-
Ввод суммы:
- Первый EditText → ввод суммы (целое число)
- Ожидание 1 сек
-
Тап "Продолжить" → ожидание 2 сек
-
Ожидание "Подтвердите перевод" (10 × 1.5 сек)
-
Тап "Перевести ..." (contains-match) → ожидание 3 сек
-
Проверка результата — аналогично PayByCard:
- "Успешно" → транзакция (type = Phone)
- "Не удалось" → ошибка
- Получение чека через OCR
Ответ серверу
Аналогичен PayByCard, но:
transaction_type:"top-up"bank_participator_id: номер телефона
ScreenXmlParser (Ozon)
Определение экранов
| Метод | Маркеры |
|---|---|
isHomeScreen() |
Кнопки "Перевести" + "Пополнить" |
isProfileScreen() |
Заголовок "Аккаунт" + кнопка "Выйти" |
isTransactionListScreen() |
WebView с text="Операции" |
Баннеры и диалоги
| Тип | Обнаружение | Действие |
|---|---|---|
| Диалог/пуш | Кнопки закрытия, "Позже" | Тап на кнопку закрытия |
| Безопасность | "Мы заботимся о вашей безопасности" | Тап "Отложить" |
| Биометрия | "Быстрый вход" | Тап "Не сейчас" |
| Реклама | resource-id "design_bottom_sheet" | Тап "touch_outside" или back |
| Google Play | "Закрыть диалоговое окно" / "Доступно обновление" | Тап закрытия |
PIN-код (tryToFindPinCode)
Метод 1: Поиск кликабельных нод с текстом ^(?:key\s+)?(\d)$ → нужны все 10 цифр
Метод 2 (fallback): Поиск сетки 4×3 из кликабельных элементов одного размера (±10px)
Парсинг карт (parseCardsOnHomeScreen)
- Ищет "Основной счёт X ₽" для общего баланса
- Парсит кнопки с текстом, оканчивающимся на 4 цифры
- Regex для суммы:
([\d\s]+(?:[.,]\d+)?)\s*₽
Парсинг транзакций
parseTransactionButtonText()— извлекает сумму и описание из текста кнопки в спискеparseTransactionDetail()— парсит детали: bankTime (DD.MM.YYYY, HH:mm), phone (+...), name