From c144305240cd80a79a5dbda0f46d8497a1e7ef22 Mon Sep 17 00:00:00 2001 From: slava Date: Thu, 30 Apr 2026 23:41:06 +0700 Subject: [PATCH] =?UTF-8?q?Handle=20declined=20transactions=20and=20enhanc?= =?UTF-8?q?e=20payment=20scripts:=20-=20Add=20unique=20`bank=5Ftransaction?= =?UTF-8?q?=5Fid`=20generation=20for=20declined=20transactions=20in=20`Eve?= =?UTF-8?q?ntHandler`.=20-=20Extend=20timeout=20and=20iteration=20logic=20?= =?UTF-8?q?to=20better=20handle=20intermediate=20statuses=20("=D0=92=D1=8B?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D1=8F=D0=B5=D1=82=D1=81=D1=8F")=20in?= =?UTF-8?q?=20`PayByPhoneScript`=20and=20`PayByCardScript`.=20-=20Update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/dushanbe/GetProfileInfoScript.cpp | 39 ++++++++++--------- android/dushanbe/PayByCardScript.cpp | 46 +++++++++++++++++++---- android/dushanbe/PayByCardScript.h | 1 + android/dushanbe/PayByPhoneScript.cpp | 45 ++++++++++++++++++---- android/dushanbe/PayByPhoneScript.h | 1 + services/EventHandler.cpp | 8 ++++ 6 files changed, 106 insertions(+), 34 deletions(-) diff --git a/android/dushanbe/GetProfileInfoScript.cpp b/android/dushanbe/GetProfileInfoScript.cpp index 7548ea9..f4b8b44 100644 --- a/android/dushanbe/GetProfileInfoScript.cpp +++ b/android/dushanbe/GetProfileInfoScript.cpp @@ -50,28 +50,27 @@ void GetProfileInfoScript::doStart() { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); } - // Парсим баланс с домашнего экрана. Если баланс не подгрузился (==0), - // тапаем кнопку обновления и перечитываем дамп. Лоадера у refresh нет, + // Всегда тапаем кнопку обновления — баланс на экране может быть закэширован + // и не отражать актуальное состояние счёта. Лоадера у refresh нет, // поэтому ждём фиксированно. - double parsedBalance = xmlScreenParser.parseBalanceFromHomeScreen(); - qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance:" << parsedBalance; + Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen(); + if (refreshBtn.x() > 0) { + qDebug() << "[Dushanbe::GetProfileInfo] Tapping refresh to get up-to-date balance"; + AdbUtils::makeTap(m_deviceId, refreshBtn.x(), refreshBtn.y()); + QThread::msleep(3000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + } else { + qWarning() << "[Dushanbe::GetProfileInfo] Refresh button not found, parsing as-is"; + } + + const double parsedBalance = xmlScreenParser.parseBalanceFromHomeScreen(); + qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance after refresh:" << parsedBalance; if (parsedBalance <= 0.0) { - Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen(); - if (refreshBtn.x() > 0) { - qDebug() << "[Dushanbe::GetProfileInfo] Balance is 0, tapping refresh and retrying"; - AdbUtils::makeTap(m_deviceId, refreshBtn.x(), refreshBtn.y()); - QThread::msleep(3000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); - parsedBalance = xmlScreenParser.parseBalanceFromHomeScreen(); - qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance after refresh:" << parsedBalance; - } - if (parsedBalance <= 0.0) { - m_error = "Balance is zero after refresh: " + m_deviceId + " " + m_appCode; - qWarning() << m_error; - AdbUtils::tryToKillApplication(m_deviceId, app.package); - emit finishedWithResult(m_error); - return; - } + m_error = "Balance is zero after refresh: " + m_deviceId + " " + m_appCode; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; } BankProfileDAO::updateAmount(app.id, parsedBalance); diff --git a/android/dushanbe/PayByCardScript.cpp b/android/dushanbe/PayByCardScript.cpp index 5182bf6..7625e7e 100644 --- a/android/dushanbe/PayByCardScript.cpp +++ b/android/dushanbe/PayByCardScript.cpp @@ -142,13 +142,14 @@ void PayByCardScript::doStart() { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter)); } - makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight); + makePayment(device.adbSerial, app.pinCode, app.phone, device.screenWidth, device.screenHeight); emit finishedWithResult(m_error); } void PayByCardScript::makePayment( const QString &deviceId, const QString &pinCode, + const QString &bankProfilePhone, const int width, const int height ) { @@ -386,9 +387,11 @@ void PayByCardScript::makePayment( return; } - // 10. Ждём результат — "Платеж выполнен" или ошибку + // 10. Ждём результат — "Платеж выполнен" или ошибку. + // Промежуточный статус "Выполняется" может висеть до ~1.5 мин, пока банк + // подтверждает перевод, поэтому общий таймаут — 60 итераций × 2 сек = 2 мин. bool success = false; - for (int attempt = 0; attempt < 15; ++attempt) { + for (int attempt = 0; attempt < 60; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(deviceId, width, height); @@ -417,6 +420,35 @@ void PayByCardScript::makePayment( return; } + // Промежуточный статус — банк ещё обрабатывает перевод + if (!xmlScreenParser.findNodeByContentDesc( + QString::fromUtf8("Выполняется"), true).isEmpty()) { + qDebug() << "[Dushanbe::PayByCard] Status 'Выполняется', waiting for completion, attempt" + << attempt + 1; + QThread::msleep(2000); + continue; + } + + // Если bottom sheet "Подтверждение платежа" всё ещё виден — первый тап + // не сработал (мимо OCR-координат), повторяем его той же логикой. + Node retryConfirm = xmlScreenParser.findNodeByContentDesc( + QString::fromUtf8("Подтверждение платежа"), true); + if (!retryConfirm.isEmpty() && + retryConfirm.contentDesc.contains(QString::fromUtf8("Подтверждаю"))) { + qDebug() << "[Dushanbe::PayByCard] Confirm sheet still visible, re-tapping 'Подтверждаю'"; + 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); + tap = QPoint(retryConfirm.x(), tapY); + } + AdbUtils::makeTap(deviceId, tap.x(), tap.y()); + QThread::msleep(4000); + continue; + } + qDebug() << "[Dushanbe::PayByCard] Waiting for result, attempt" << attempt + 1; QThread::msleep(2000); } @@ -460,10 +492,10 @@ void PayByCardScript::makePayment( qDebug() << "[Dushanbe::PayByCard] Result: date=" << bankTime << "fee=" << fee; } - // Генерируем ID транзакции из даты+времени (без перехода в историю) - const QString operationId = bankTime.isEmpty() - ? QString("dushanbe_") + QDateTime::currentDateTimeUtc().toString("dd.MM.yyyy_HH:mm:ss") - : QString("dushanbe_") + QString(bankTime).replace(' ', '_'); + // bank_transaction_id: phone аккаунта + unix timestamp (без перехода в историю). + // Уникальность за счёт . + const QString operationId = QString("dushanbe_") + bankProfilePhone + "_" + + QString::number(QDateTime::currentSecsSinceEpoch()); qDebug() << "[Dushanbe::PayByCard] Generated operationId:" << operationId; // Сохраняем транзакцию diff --git a/android/dushanbe/PayByCardScript.h b/android/dushanbe/PayByCardScript.h index 363eac1..8df31ae 100644 --- a/android/dushanbe/PayByCardScript.h +++ b/android/dushanbe/PayByCardScript.h @@ -28,6 +28,7 @@ private: void makePayment( const QString &deviceId, const QString &pinCode, + const QString &bankProfilePhone, int width, int height ); diff --git a/android/dushanbe/PayByPhoneScript.cpp b/android/dushanbe/PayByPhoneScript.cpp index b5dba4a..aa8f290 100644 --- a/android/dushanbe/PayByPhoneScript.cpp +++ b/android/dushanbe/PayByPhoneScript.cpp @@ -139,13 +139,14 @@ void PayByPhoneScript::doStart() { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter)); } - makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight); + makePayment(device.adbSerial, app.pinCode, app.phone, device.screenWidth, device.screenHeight); emit finishedWithResult(m_error); } void PayByPhoneScript::makePayment( const QString &deviceId, const QString &pinCode, + const QString &bankProfilePhone, const int width, const int height ) { @@ -382,9 +383,11 @@ void PayByPhoneScript::makePayment( return; } - // 10. Ждём результат — "Платеж выполнен" или ошибку + // 10. Ждём результат — "Платеж выполнен" или ошибку. + // Промежуточный статус "Выполняется" может висеть до ~1.5 мин, пока банк + // подтверждает перевод, поэтому общий таймаут — 60 итераций × 2 сек = 2 мин. bool success = false; - for (int attempt = 0; attempt < 15; ++attempt) { + for (int attempt = 0; attempt < 60; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(deviceId, width, height); @@ -410,6 +413,34 @@ void PayByPhoneScript::makePayment( return; } + if (!xmlScreenParser.findNodeByContentDesc( + QString::fromUtf8("Выполняется"), true).isEmpty()) { + qDebug() << "[Dushanbe::PayByPhone] Status 'Выполняется', waiting for completion, attempt" + << attempt + 1; + QThread::msleep(2000); + continue; + } + + // Если bottom sheet "Подтверждение платежа" всё ещё виден — первый тап + // не сработал (мимо OCR-координат), повторяем его той же логикой. + Node retryConfirm = xmlScreenParser.findNodeByContentDesc( + QString::fromUtf8("Подтверждение платежа"), true); + if (!retryConfirm.isEmpty() && + retryConfirm.contentDesc.contains(QString::fromUtf8("Подтверждаю"))) { + qDebug() << "[Dushanbe::PayByPhone] Confirm sheet still visible, re-tapping 'Подтверждаю'"; + 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); + tap = QPoint(retryConfirm.x(), tapY); + } + AdbUtils::makeTap(deviceId, tap.x(), tap.y()); + QThread::msleep(4000); + continue; + } + qDebug() << "[Dushanbe::PayByPhone] Waiting for result, attempt" << attempt + 1; QThread::msleep(2000); } @@ -452,10 +483,10 @@ void PayByPhoneScript::makePayment( qDebug() << "[Dushanbe::PayByPhone] Result: date=" << bankTime << "fee=" << fee; } - // Генерируем ID транзакции из даты+времени (без перехода в историю) - const QString operationId = bankTime.isEmpty() - ? QString("dushanbe_") + QDateTime::currentDateTimeUtc().toString("dd.MM.yyyy_HH:mm:ss") - : QString("dushanbe_") + QString(bankTime).replace(' ', '_'); + // bank_transaction_id: phone аккаунта + unix timestamp (без перехода в историю). + // Уникальность за счёт . + const QString operationId = QString("dushanbe_") + bankProfilePhone + "_" + + QString::number(QDateTime::currentSecsSinceEpoch()); qDebug() << "[Dushanbe::PayByPhone] Generated operationId:" << operationId; // Сохраняем транзакцию diff --git a/android/dushanbe/PayByPhoneScript.h b/android/dushanbe/PayByPhoneScript.h index 32402bd..98f9371 100644 --- a/android/dushanbe/PayByPhoneScript.h +++ b/android/dushanbe/PayByPhoneScript.h @@ -28,6 +28,7 @@ private: void makePayment( const QString &deviceId, const QString &pinCode, + const QString &bankProfilePhone, int width, int height ); diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index 8220615..c534a44 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -297,6 +297,14 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, break; } } + // Скрипт мог упасть до записи транзакции в БД — сервер всё равно + // ждёт уникальный bank_transaction_id. Генерируем по той же схеме, + // что и в success-ветке скриптов: __. + if (declTxId.isEmpty()) { + const BankProfileInfo declApp = BankProfileDAO::getApplication(declAcc.deviceId, event.bankName); + declTxId = event.bankName + "_" + declApp.phone + "_" + + QString::number(QDateTime::currentSecsSinceEpoch()); + } QJsonObject declined; declined["bank_transaction_id"] = declTxId;