1
0
forked from BRT/arc
arc/docs/ozon_tasks.md
slava de8c2e84d0 - Add toggleMaterial logic to NetworkService for enabling/disabling materials via PATCH request.
- 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.
2026-03-31 11:40:34 +07:00

385 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (100900мс между цифрами)
- Ожидание 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