Included XML files for the "pay to new number" and "pay to old number" features in the Ozon platform.
336 lines
14 KiB
C++
336 lines
14 KiB
C++
#include "PayByPhoneScript.h"
|
||
|
||
#include <QProcess>
|
||
#include <QRegularExpression>
|
||
#include <QThread>
|
||
#include <utility>
|
||
|
||
#include "adb/AdbUtils.h"
|
||
#include "dao/AccountDAO.h"
|
||
#include "dao/ApplicationDAO.h"
|
||
#include "dao/DeviceDAO.h"
|
||
#include "dao/TransactionDAO.h"
|
||
|
||
namespace Ozon {
|
||
|
||
PayByPhoneScript::PayByPhoneScript(
|
||
AccountInfo account,
|
||
QString phone,
|
||
QString bankName,
|
||
const double amount,
|
||
QObject *parent
|
||
) : CommonScript(parent),
|
||
m_account(std::move(account)), m_phone(std::move(phone)),
|
||
m_bankName(std::move(bankName)), m_amount(amount) {
|
||
}
|
||
|
||
PayByPhoneScript::~PayByPhoneScript() = default;
|
||
|
||
void PayByPhoneScript::doStart() {
|
||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||
|
||
if (device.id.isEmpty() || app.id == -1) {
|
||
m_error = "Device or app not found: " + m_account.deviceId;
|
||
qCritical() << m_error;
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||
|
||
// Закрываем рекламный bottom sheet
|
||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||
qDebug() << "[PayByPhone] Ad banner detected, closing...";
|
||
AdbUtils::makeTap(device.id, adNode.x(), adNode.y());
|
||
QThread::msleep(1000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||
}
|
||
|
||
// Если не на домашнем экране — перезапускаем
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[PayByPhone] Not on home screen, restarting app...";
|
||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||
device.screenWidth, device.screenHeight)) {
|
||
m_error = "Cannot reach home screen: " + device.id;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(device.id, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||
}
|
||
|
||
// Закрываем баннеры
|
||
tryToCloseAllBannersOnHomePage(device.id, device.screenWidth, device.screenHeight);
|
||
QThread::msleep(500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||
|
||
// Закрываем баннер Google Play "Доступно обновление"
|
||
if (Node gpNode = xmlScreenParser.tryToFindGooglePlayBanner(); !gpNode.isEmpty()) {
|
||
qDebug() << "[PayByPhone] Google Play update banner detected, closing...";
|
||
AdbUtils::makeTap(device.id, gpNode.x(), gpNode.y());
|
||
QThread::msleep(1500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||
}
|
||
|
||
// Закрываем баннер "Заказать карту" — тапаем сверху экрана
|
||
if (Node orderCardNode = xmlScreenParser.findTextNode("Заказать карту"); !orderCardNode.isEmpty()) {
|
||
qDebug() << "[PayByPhone] 'Заказать карту' banner detected, closing...";
|
||
AdbUtils::makeTap(device.id, device.screenWidth / 2, 100);
|
||
QThread::msleep(1000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||
}
|
||
|
||
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
|
||
|
||
emit finishedWithResult(m_error);
|
||
}
|
||
|
||
void PayByPhoneScript::makePayment(
|
||
const QString &deviceId,
|
||
const QString &pinCode,
|
||
const int width,
|
||
const int height
|
||
) {
|
||
// 1. Нажимаем "Перевести" на домашнем экране
|
||
Node transferBtn = xmlScreenParser.findButtonNode("Перевести", false);
|
||
if (transferBtn.isEmpty()) {
|
||
m_error = "Кнопка 'Перевести' не найдена на домашнем экране";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[PayByPhone] Tapping 'Перевести'...";
|
||
AdbUtils::makeTap(deviceId, transferBtn.x(), transferBtn.y());
|
||
QThread::msleep(2000);
|
||
|
||
// 2. Ждём экран "Платежи и переводы" с полем "Телефон получателя"
|
||
bool paymentsPageLoaded = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
Node phoneField = xmlScreenParser.findFirstEditText();
|
||
if (!phoneField.isEmpty()) {
|
||
paymentsPageLoaded = true;
|
||
break;
|
||
}
|
||
qDebug() << "[PayByPhone] Waiting for payments page, attempt" << attempt + 1 << "/ 10";
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!paymentsPageLoaded) {
|
||
m_error = "Экран 'Платежи и переводы' не загрузился";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
// 3. Тапаем на поле "Телефон получателя" чтобы перейти на экран "Кому перевести"
|
||
Node phoneFieldTap = xmlScreenParser.findFirstEditText();
|
||
qDebug() << "[PayByPhone] Tapping 'Телефон получателя'...";
|
||
AdbUtils::makeTap(deviceId, phoneFieldTap.x(), phoneFieldTap.y());
|
||
QThread::msleep(2000);
|
||
|
||
// 4. Ждём экран "Кому перевести"
|
||
bool phoneScreenLoaded = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
Node title = xmlScreenParser.findTextNode("Кому перевести");
|
||
if (!title.isEmpty()) {
|
||
phoneScreenLoaded = true;
|
||
break;
|
||
}
|
||
qDebug() << "[PayByPhone] Waiting for 'Кому перевести', attempt" << attempt + 1 << "/ 10";
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!phoneScreenLoaded) {
|
||
m_error = "Экран 'Кому перевести' не загрузился";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
// 5. Тапаем на EditText и вводим номер телефона
|
||
Node phoneInput;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
phoneInput = xmlScreenParser.findFirstEditText();
|
||
if (!phoneInput.isEmpty()) break;
|
||
qDebug() << "[PayByPhone] Waiting for phone input field, attempt" << attempt + 1 << "/ 10";
|
||
QThread::msleep(1500);
|
||
}
|
||
if (phoneInput.isEmpty()) {
|
||
m_error = "Поле ввода телефона не найдено";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
AdbUtils::makeTap(deviceId, phoneInput.x(), phoneInput.y());
|
||
QThread::msleep(500);
|
||
AdbUtils::inputText(deviceId, m_phone);
|
||
QThread::msleep(2000);
|
||
|
||
// 6. Ждём экран "По номеру телефона", обрабатывая промежуточные экраны
|
||
bool sumScreenLoaded = false;
|
||
bool bankSelected = false;
|
||
bool otherBankClicked = false;
|
||
for (int attempt = 0; attempt < 20; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
|
||
// Проверяем: уже на экране суммы?
|
||
Node sumTitle = xmlScreenParser.findTextNode("По номеру телефона");
|
||
if (!sumTitle.isEmpty() && sumTitle.y() < 300) {
|
||
sumScreenLoaded = true;
|
||
break;
|
||
}
|
||
|
||
// Ищем кнопку с номером телефона (новый перевод / последние переводы)
|
||
QString phoneDigits = m_phone;
|
||
phoneDigits.remove(QRegularExpression("[^0-9]"));
|
||
bool foundPhoneBtn = false;
|
||
for (const Node &btn : xmlScreenParser.findAllButtons()) {
|
||
QString btnDigits = btn.text;
|
||
btnDigits.remove(QRegularExpression("[^0-9]"));
|
||
if (btnDigits.contains(phoneDigits) || phoneDigits.contains(btnDigits.right(10))) {
|
||
qDebug() << "[PayByPhone] Found button with phone number:" << btn.text;
|
||
AdbUtils::makeTap(deviceId, btn.x(), btn.y());
|
||
QThread::msleep(2000);
|
||
foundPhoneBtn = true;
|
||
break;
|
||
}
|
||
}
|
||
if (foundPhoneBtn) continue;
|
||
|
||
// Экран выбора банка: ищем "Другой банк" (текст) или EditText с hint "Название банка"
|
||
if (!bankSelected) {
|
||
// Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой)
|
||
bool foundOtherBank = false;
|
||
{
|
||
Node node;
|
||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||
if (n.contentDesc.contains("other-bank-item")) {
|
||
node = n;
|
||
break;
|
||
}
|
||
}
|
||
if (!node.isEmpty()) {
|
||
qDebug() << "[PayByPhone] Tapping 'Другой банк' (other-bank-item)...";
|
||
AdbUtils::makeTap(deviceId, node.x(), node.y());
|
||
QThread::msleep(2000);
|
||
foundOtherBank = true;
|
||
otherBankClicked = true;
|
||
}
|
||
}
|
||
if (foundOtherBank) continue;
|
||
|
||
// Вариант 2: уже на экране поиска банка (EditText) — только после клика на "Другой банк"
|
||
if (otherBankClicked) {
|
||
Node bankInput = xmlScreenParser.findFirstEditText();
|
||
if (!bankInput.isEmpty()) {
|
||
qDebug() << "[PayByPhone] Bank search screen detected, entering bank name...";
|
||
AdbUtils::makeTap(deviceId, bankInput.x(), bankInput.y());
|
||
QThread::msleep(500);
|
||
|
||
// Ввод кириллицы через ADB Keyboard
|
||
AdbUtils::enableAdbIMEKeyboard(deviceId);
|
||
QThread::msleep(500);
|
||
AdbUtils::makeTap(deviceId, bankInput.x(), bankInput.y());
|
||
QThread::msleep(500);
|
||
AdbUtils::inputAdbIMEText(deviceId, m_bankName);
|
||
QThread::msleep(2000);
|
||
AdbUtils::disableAdbIMEKeyboard(deviceId);
|
||
qDebug() << "[PayByPhone] Entered bank name via ADB IME:" << m_bankName;
|
||
QThread::msleep(2000);
|
||
|
||
// Ищем банк в списке и кликаем по нему (кнопка с числовым content-desc)
|
||
bool bankFound = false;
|
||
for (int ba = 0; ba < 10; ++ba) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
for (const Node &node : xmlScreenParser.findAllNodes()) {
|
||
if (node.className == "android.widget.Button"
|
||
&& !node.contentDesc.isEmpty()
|
||
&& node.contentDesc != "base-go-back-button"
|
||
&& node.contentDesc != "native-toolbar-close-button"
|
||
&& node.contentDesc != "clear-input-icon") {
|
||
qDebug() << "[PayByPhone] Found bank in list, tapping:" << node.contentDesc << "bounds:" << node.x() << node.y();
|
||
AdbUtils::makeTap(deviceId, node.x(), node.y());
|
||
bankFound = true;
|
||
break;
|
||
}
|
||
}
|
||
if (bankFound) break;
|
||
qDebug() << "[PayByPhone] Waiting for bank list, attempt" << ba + 1 << "/ 10";
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!bankFound) {
|
||
m_error = "Банк '" + m_bankName + "' не найден в списке";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
bankSelected = true;
|
||
QThread::msleep(2000);
|
||
continue;
|
||
}
|
||
} // otherBankClicked
|
||
}
|
||
|
||
qDebug() << "[PayByPhone] Waiting for 'По номеру телефона', attempt" << attempt + 1 << "/ 20";
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!sumScreenLoaded) {
|
||
m_error = "Экран ввода суммы не загрузился";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
// 7. Вводим сумму
|
||
Node sumInput = xmlScreenParser.findFirstEditText();
|
||
if (sumInput.isEmpty()) {
|
||
m_error = "Поле ввода суммы не найдено";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
AdbUtils::makeTap(deviceId, sumInput.x(), sumInput.y());
|
||
QThread::msleep(500);
|
||
AdbUtils::inputText(deviceId, QString::number(static_cast<int>(m_amount)));
|
||
QThread::msleep(1000);
|
||
|
||
// 6. Нажимаем "Продолжить"
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||
if (continueBtn.isEmpty()) {
|
||
m_error = "Кнопка 'Продолжить' не найдена";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
AdbUtils::makeTap(deviceId, continueBtn.x(), continueBtn.y());
|
||
QThread::msleep(2000);
|
||
|
||
// 7. Ждём экран "Подтвердите перевод" с кнопкой "Перевести ..."
|
||
bool confirmScreenLoaded = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
Node confirmTitle = xmlScreenParser.findTextNode("Подтвердите перевод");
|
||
if (!confirmTitle.isEmpty()) {
|
||
confirmScreenLoaded = true;
|
||
break;
|
||
}
|
||
qDebug() << "[PayByPhone] Waiting for 'Подтвердите перевод', attempt" << attempt + 1 << "/ 10";
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!confirmScreenLoaded) {
|
||
m_error = "Экран подтверждения не загрузился";
|
||
qWarning() << "[PayByPhone]" << m_error;
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[PayByPhone] Confirm screen loaded, stopping before confirmation";
|
||
}
|
||
|
||
} // namespace Ozon
|