# Ozon - Логика задач [← Главная](index.md) | [События](events.md) Описание логики всех типов задач для банка **Ozon** (appCode: `ozon`). --- ## Общие механизмы (CommonScript) ### Запуск приложения (`runAppAndGoToHomeScreen`) 1. Убивает приложение если запущено (`AdbUtils::tryToKillApplication`) 2. Запускает приложение (`AdbUtils::tryToStartApplication`) 3. Ждёт 4.5 секунды на старт 4. Цикл до 10 попыток: - Получает XML-дамп экрана - Проверяет `isHomeScreen()` (наличие кнопок "Перевести" + "Пополнить") - Закрывает баннеры: диалоги, баннер безопасности, Google Play, рекламный bottom-sheet - Если экран с PIN-кодом — вводит PIN (100–900мс между цифрами) - Ожидание 1 сек между попытками 5. При неудаче — скриншот + лог ошибки ### Навигация к продуктам (`goToAllProducts`) 1. Закрывает баннеры на домашнем экране 2. Ищет вкладку "Финансы" 3. Тапает до 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 1. **Валидация** — получение DeviceInfo и BankProfileInfo из БД 2. **Запуск приложения** — `runAppAndGoToHomeScreen()` с PIN-кодом 3. **Создание профиля** — `createBankProfileIfNeeded()` (синхронизация fullName/phone) 4. **Сбор аккаунтов** (если не режим fetchProfileOnly): - Навигация к "Финансы" через `goToAllProducts()` - Парсинг видимых аккаунтов из XML - Upsert в `MaterialDAO` - Отправка на сервер 5. **Запуск GetCardInfoScript** — синхронно, для получения полных номеров карт ### Шаги GetCardInfoScript 1. **Навигация на домашний экран** 2. **Парсинг профиля** (если нет в БД): - Тап на имя пользователя вверху экрана - Парсинг fullName и phone - Обновление `BankProfileDAO` 3. **Загрузка карт** — `parseCardsOnHomeScreen()` (до 5 попыток, 1.5 сек) 4. **Upsert всех карт** в `MaterialDAO` 5. **Синхронизация с сервером**: - Создание устройства (POST) если нет `apiId` - POST/PATCH банковского профиля с балансом, ФИО, телефоном 6. **Получение полных номеров карт** (для каждой карты): - Тап на карту → ожидание экрана карты (10 × 2 сек) - Тап "Показать" → ожидание появления номера (10 × 2 сек) - Regex: `\d{4}[\s]\d{4}[\s]\d{4}[\s]\d{4}` (с unicode-пробелами) - Обработка баннера "Понятно" - Сохранение в `MaterialDAO::updateCardNumber()` - При неудаче: статус карты → Off, лог + скриншот 7. **Отправка материалов** на сервер (`ns.postMaterial()`) 8. **Завершение** — убийство приложения ### Ответ серверу (при успехе) ```json { "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 часа | ### Шаги (режим поиска) 1. **Навигация к списку операций** (`navigateToTransactionList`): - Закрытие рекламного баннера - Проверка домашнего экрана (перезапуск если нужно) - Свайп до кнопки "Все" (до 10 свайпов) - Ожидание экрана "Операции" (10 × 1.5 сек) 2. **Поиск транзакции** (`searchTransaction`): - Цикл до 15 прокруток - Для каждой видимой кнопки-транзакции: - Парсинг суммы из текста кнопки - Проверка совпадения: `|amount - searchAmount| ≤ 0.01` - Парсинг даты из текста, проверка: `|time - searchTime| ≤ 1 час` - При совпадении: - Тап на транзакцию → парсинг деталей (телефон, имя, время) - Вставка в `TransactionDAO` - Возврат 3. **Если не найдена** — возвращает `"Transaction not found"` ### Шаги (режим сканирования) 1. Навигация к списку операций (аналогично) 2. **Сканирование** (`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) ### Ответ серверу (при успехе) ```json { "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` — имя банка-получателя ### Шаги 1. **Подготовка**: - Закрытие рекламных баннеров - Проверка домашнего экрана (перезапуск если нужно) - Закрытие: баннеры, Google Play, "Заказать карту" 2. **Тап "Перевести"** → ожидание 2 сек 3. **Ожидание "Платежи и переводы"** (10 × 1.5 сек) 4. **Тап "По номеру карты"** (секция OTHER_WAYS) → ожидание 2 сек 5. **Ожидание "Перевод по номеру карты"** (10 × 1.5 сек) 6. **Ввод номера карты**: - Поиск EditText с hint "Номер карты получателя" - `AdbUtils::inputText(cardNumber)` - Ожидание 2 сек 7. **Ввод суммы**: - Поиск EditText с hint "Сумма от" - Ввод суммы (целое число) - Ожидание 1 сек 8. **Ожидание расчёта комиссии** (15 × 1.5 сек): - Поиск текста "Комиссия" + парсинг суммы после него - Извлечение цифр из формата "45 ₽" - Fallback: комиссия = 0 9. **Тап "Перевести"** (contains-match) → ожидание 3 сек 10. **Проверка результата** (15 × 2 сек): - **"Успешно"** → сохранение транзакции: - amount = -сумма (отрицательная) - fee = рассчитанная комиссия - type = Card, status = Complete - timestamp = Moscow timezone - **"Не удалось"** → ошибка - **Таймаут** → "Не дождались результата" 11. **Получение чека** (при успехе): - Тап "Документы" - Ожидание "Поделиться" (20 × 1.5 сек) - Скриншот → OCR → извлечение ID операции - Обновление БД: receipt image + external ID ### Ответ серверу ```json { "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` — имя банка-получателя (для СБП) ### Шаги 1. **Подготовка** — аналогично PayByCard 2. **Тап "Перевести"** → ожидание 2 сек 3. **Ожидание страницы платежей** → поиск EditText (10 × 1.5 сек) 4. **Навигация к "Кому перевести"**: - Тап на первый EditText → ожидание 2 сек - Ожидание экрана "Кому перевести" (10 × 1.5 сек) 5. **Ввод номера телефона**: - Поиск первого EditText - `AdbUtils::inputText(phone)` - Ожидание 2 сек 6. **Выбор получателя** (цикл 20 попыток): - **Вариант A**: Найдена кнопка с номером телефона → тап - **Вариант B**: Выбор банка: - Тап "Другой банк" (content-desc="other-bank-item") - Включение ADB IME клавиатуры - Ввод названия банка через `AdbUtils::inputAdbIMEText()` - Отключение ADB IME - Поиск банка в списке (10 × 1.5 сек) - Если банк не найден → ошибка "Банк '...' не найден" - **Условие выхода**: текст "По номеру телефона" вверху экрана (y < height/7) 7. **Ввод суммы**: - Первый EditText → ввод суммы (целое число) - Ожидание 1 сек 8. **Тап "Продолжить"** → ожидание 2 сек 9. **Ожидание "Подтвердите перевод"** (10 × 1.5 сек) 10. **Тап "Перевести ..."** (contains-match) → ожидание 3 сек 11. **Проверка результата** — аналогично 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