From 14e46cac84a981e9175aa9b7959953d3d9261653 Mon Sep 17 00:00:00 2001 From: slava Date: Tue, 19 May 2026 23:15:44 +0500 Subject: [PATCH] =?UTF-8?q?Add=20retry=20limits=20for=20"=D0=9F=D0=BE?= =?UTF-8?q?=D0=B4=D1=82=D0=B2=D0=B5=D1=80=D0=B6=D0=B4=D0=B0=D1=8E"=20and?= =?UTF-8?q?=20"=D0=9E=D0=BF=D0=BB=D0=B0=D1=82=D0=B8=D1=82=D1=8C"=20taps=20?= =?UTF-8?q?in=20`PayByPhoneScript`=20and=20`PayByCardScript`=20to=20preven?= =?UTF-8?q?t=20infinite=20loops=20and=20improve=20error=20handling=20with?= =?UTF-8?q?=20detailed=20logs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/dushanbe/PayByCardScript.cpp | 40 +++++++++++++++++++++++++-- android/dushanbe/PayByPhoneScript.cpp | 40 +++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/android/dushanbe/PayByCardScript.cpp b/android/dushanbe/PayByCardScript.cpp index 1be5522..f53706c 100644 --- a/android/dushanbe/PayByCardScript.cpp +++ b/android/dushanbe/PayByCardScript.cpp @@ -413,7 +413,16 @@ void PayByCardScript::makePayment( // 10. Ждём результат — "Платеж выполнен" или ошибку. // Промежуточный статус "Выполняется" может висеть до ~1.5 мин, пока банк // подтверждает перевод, поэтому общий таймаут — 60 итераций × 2 сек = 2 мин. + // Лимиты повторных тапов: если первый тап "Подтверждаю" не сработал, + // bottom sheet дисмиссится, виден "Оплатить" → перетапываем; следующий + // sheet → пробуем другую Y. Без лимита получали бесконечный + // "Оплатить ↔ Подтверждаю" со случайными попаданиями в системные экраны + // (security/launcher). bool success = earlySuccess; + int confirmRetries = 0; + int oplatitRetries = 0; + const int maxConfirmRetries = 2; + const int maxOplatitRetries = 2; for (int attempt = 0; !earlySuccess && attempt < 60; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(deviceId, width, height); @@ -458,14 +467,29 @@ void PayByCardScript::makePayment( QString::fromUtf8("Подтверждение платежа"), true); if (!retryConfirm.isEmpty() && retryConfirm.contentDesc.contains(QString::fromUtf8("Подтверждаю"))) { - qDebug() << "[Dushanbe::PayByCard] Confirm sheet still visible, re-tapping 'Подтверждаю'"; + if (confirmRetries >= maxConfirmRetries) { + m_error = "Confirm tap missed " + QString::number(confirmRetries) + + " times (sheet still visible)"; + qWarning() << "[Dushanbe::PayByCard]" << m_error; + AppLogger::log("dushanbe/PayByCard", deviceId, m_error, + AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION"); + return; + } + ++confirmRetries; + qDebug() << "[Dushanbe::PayByCard] Confirm sheet still visible, re-tap" + << confirmRetries << "/" << maxConfirmRetries; const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId); QPoint tap = OcrUtils::findTextCenter(screen, QString::fromUtf8("Подтверждаю"), retryConfirm.y1(), retryConfirm.y2()); if (tap.x() < 0) { - const int tapY = retryConfirm.y2() - qMax(150, height / 14); + // Варьируем Y: первый retry — выше fallback на 100px, + // второй — ещё на 100px выше. Compose-кнопка действия + // обычно на y2 - 150..350; перебираем диапазон. + const int extra = 100 * confirmRetries; + const int tapY = retryConfirm.y2() - qMax(150 + extra, height / 14 + extra); tap = QPoint(retryConfirm.x(), tapY); + qDebug() << "[Dushanbe::PayByCard] OCR miss, fallback tap at" << tap; } AdbUtils::makeTap(deviceId, tap.x(), tap.y()); QThread::msleep(4000); @@ -482,7 +506,17 @@ void PayByCardScript::makePayment( retryOplatit = xmlScreenParser.findButtonNode(QString::fromUtf8("Оплатить"), false); } if (!retryOplatit.isEmpty() && retryOplatit.clickable) { - qDebug() << "[Dushanbe::PayByCard] Sheet dismissed, re-tapping 'Оплатить' to restart confirm flow"; + if (oplatitRetries >= maxOplatitRetries) { + m_error = "Sheet dismissed and re-tap exhausted (" + + QString::number(oplatitRetries) + " retries)"; + qWarning() << "[Dushanbe::PayByCard]" << m_error; + AppLogger::log("dushanbe/PayByCard", deviceId, m_error, + AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION"); + return; + } + ++oplatitRetries; + qDebug() << "[Dushanbe::PayByCard] Sheet dismissed, re-tap 'Оплатить'" + << oplatitRetries << "/" << maxOplatitRetries; AdbUtils::makeTap(deviceId, retryOplatit.x(), retryOplatit.y()); QThread::msleep(3000); continue; diff --git a/android/dushanbe/PayByPhoneScript.cpp b/android/dushanbe/PayByPhoneScript.cpp index 16de45a..8422423 100644 --- a/android/dushanbe/PayByPhoneScript.cpp +++ b/android/dushanbe/PayByPhoneScript.cpp @@ -409,7 +409,16 @@ void PayByPhoneScript::makePayment( // 10. Ждём результат — "Платеж выполнен" или ошибку. // Промежуточный статус "Выполняется" может висеть до ~1.5 мин, пока банк // подтверждает перевод, поэтому общий таймаут — 60 итераций × 2 сек = 2 мин. + // Лимиты повторных тапов: если первый тап "Подтверждаю" не сработал, + // bottom sheet дисмиссится, виден "Оплатить" → перетапываем; следующий + // sheet → пробуем другую Y. Без лимита получали бесконечный + // "Оплатить ↔ Подтверждаю" со случайными попаданиями в системные экраны + // (security/launcher). bool success = earlySuccess; + int confirmRetries = 0; + int oplatitRetries = 0; + const int maxConfirmRetries = 2; + const int maxOplatitRetries = 2; for (int attempt = 0; !earlySuccess && attempt < 60; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(deviceId, width, height); @@ -450,14 +459,29 @@ void PayByPhoneScript::makePayment( QString::fromUtf8("Подтверждение платежа"), true); if (!retryConfirm.isEmpty() && retryConfirm.contentDesc.contains(QString::fromUtf8("Подтверждаю"))) { - qDebug() << "[Dushanbe::PayByPhone] Confirm sheet still visible, re-tapping 'Подтверждаю'"; + if (confirmRetries >= maxConfirmRetries) { + m_error = "Confirm tap missed " + QString::number(confirmRetries) + + " times (sheet still visible)"; + qWarning() << "[Dushanbe::PayByPhone]" << m_error; + AppLogger::log("dushanbe/PayByPhone", deviceId, m_error, + AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION"); + return; + } + ++confirmRetries; + qDebug() << "[Dushanbe::PayByPhone] Confirm sheet still visible, re-tap" + << confirmRetries << "/" << maxConfirmRetries; const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId); QPoint tap = OcrUtils::findTextCenter(screen, QString::fromUtf8("Подтверждаю"), retryConfirm.y1(), retryConfirm.y2()); if (tap.x() < 0) { - const int tapY = retryConfirm.y2() - qMax(150, height / 14); + // Варьируем Y: первый retry — выше fallback на 100px, + // второй — ещё на 100px выше. Compose-кнопка действия + // обычно на y2 - 150..350; перебираем диапазон. + const int extra = 100 * confirmRetries; + const int tapY = retryConfirm.y2() - qMax(150 + extra, height / 14 + extra); tap = QPoint(retryConfirm.x(), tapY); + qDebug() << "[Dushanbe::PayByPhone] OCR miss, fallback tap at" << tap; } AdbUtils::makeTap(deviceId, tap.x(), tap.y()); QThread::msleep(4000); @@ -474,7 +498,17 @@ void PayByPhoneScript::makePayment( retryOplatit = xmlScreenParser.findButtonNode(QString::fromUtf8("Оплатить"), false); } if (!retryOplatit.isEmpty() && retryOplatit.clickable) { - qDebug() << "[Dushanbe::PayByPhone] Sheet dismissed, re-tapping 'Оплатить' to restart confirm flow"; + if (oplatitRetries >= maxOplatitRetries) { + m_error = "Sheet dismissed and re-tap exhausted (" + + QString::number(oplatitRetries) + " retries)"; + qWarning() << "[Dushanbe::PayByPhone]" << m_error; + AppLogger::log("dushanbe/PayByPhone", deviceId, m_error, + AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION"); + return; + } + ++oplatitRetries; + qDebug() << "[Dushanbe::PayByPhone] Sheet dismissed, re-tap 'Оплатить'" + << oplatitRetries << "/" << maxOplatitRetries; AdbUtils::makeTap(deviceId, retryOplatit.x(), retryOplatit.y()); QThread::msleep(3000); continue;