#include "PayByCardScript.h" #include #include #include "adb/AdbUtils.h" #include "dao/ApplicationDAO.h" #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" #include "AppLogger.h" namespace Black { PayByCardScript::PayByCardScript( AccountInfo account, QString cardNumber, double amount, QObject *parent ) : CommonScript(parent), m_account(std::move(account)), m_cardNumber(std::move(cardNumber)), m_amount(amount) { } PayByCardScript::~PayByCardScript() = default; void PayByCardScript::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; } makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight); emit finishedWithResult(m_error); } void PayByCardScript::makePayment( const QString &deviceId, const QString &pinCode, const int width, const int height ) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); // 0. Убеждаемся что на домашнем экране if (!xmlScreenParser.isHomeScreen()) { qDebug() << "[Black::PayByCard] Not on home screen, restarting app..."; const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode); if (!runAppAndGoToHomeScreen(deviceId, app.package, pinCode, width, height)) { m_error = "Cannot reach home screen: " + deviceId; qWarning() << m_error; AppLogger::log("black/PayByCard", deviceId, m_error, AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION"); AdbUtils::tryToKillApplication(deviceId, app.package); return; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); } // 1. Тапаем "Enviar a\nCBU o alias" на домашнем экране Node enviarBtn = xmlScreenParser.findNodeByContentDesc("Enviar a\nCBU o alias", true); if (enviarBtn.isEmpty()) { // Попробуем проскроллить for (int i = 0; i < 5; ++i) { const int x = width / 2; const int offset = height / 4; AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset); QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); enviarBtn = xmlScreenParser.findNodeByContentDesc("Enviar a\nCBU o alias", true); if (!enviarBtn.isEmpty()) break; } } if (enviarBtn.isEmpty()) { m_error = "Button 'Enviar a CBU o alias' not found: " + deviceId; qWarning() << m_error; return; } qDebug() << "[Black::PayByCard] Tapping 'Enviar a CBU o alias'"; AdbUtils::makeTap(deviceId, enviarBtn.x(), enviarBtn.y()); QThread::msleep(2000); // 2. Ждём экран ввода номера карты (CBU/CVU/Alias) bool onInputScreen = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); if (xmlScreenParser.isPayInputCardNumberScreen()) { onInputScreen = true; break; } qDebug() << "[Black::PayByCard] Waiting for input card screen, attempt" << attempt + 1; QThread::msleep(1500); } if (!onInputScreen) { m_error = "Input card number screen not loaded: " + deviceId; qWarning() << m_error; return; } // Вводим номер карты в EditText Node editText = xmlScreenParser.findEditTextByHint("Ingresar CBU, CVU o Alias"); if (editText.isEmpty()) { editText = xmlScreenParser.findFirstEditText(); } if (editText.isEmpty()) { m_error = "EditText for card number not found: " + deviceId; qWarning() << m_error; return; } AdbUtils::makeTap(deviceId, editText.x(), editText.y()); QThread::msleep(500); AdbUtils::inputText(deviceId, m_cardNumber); QThread::msleep(1000); // Нажимаем "Continuar" xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node continuarBtn = xmlScreenParser.findNodeByContentDesc("Continuar"); if (continuarBtn.isEmpty()) { continuarBtn = xmlScreenParser.findButtonNode("Continuar", false); } if (continuarBtn.isEmpty()) { m_error = "Button 'Continuar' not found on input card screen: " + deviceId; qWarning() << m_error; return; } qDebug() << "[Black::PayByCard] Tapping 'Continuar' after card number input"; AdbUtils::makeTap(deviceId, continuarBtn.x(), continuarBtn.y()); QThread::msleep(2000); // 3. Ждём экран подтверждения получателя (pay_transfer_confirm) bool onTransferConfirm = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); if (xmlScreenParser.isPayTransferConfirmScreen()) { onTransferConfirm = true; break; } qDebug() << "[Black::PayByCard] Waiting for transfer confirm screen, attempt" << attempt + 1; QThread::msleep(1500); } if (!onTransferConfirm) { m_error = "Transfer confirm screen not loaded: " + deviceId; qWarning() << m_error; return; } // Нажимаем "Transferir" Node transferirBtn = xmlScreenParser.findNodeByContentDesc("Transferir"); if (transferirBtn.isEmpty()) { transferirBtn = xmlScreenParser.findButtonNode("Transferir", false); } if (transferirBtn.isEmpty()) { m_error = "Button 'Transferir' not found on transfer confirm screen: " + deviceId; qWarning() << m_error; return; } qDebug() << "[Black::PayByCard] Tapping 'Transferir' on confirm screen"; AdbUtils::makeTap(deviceId, transferirBtn.x(), transferirBtn.y()); QThread::msleep(2000); // 4. Ждём экран ввода суммы (pay_input_sum) bool onInputSum = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); if (xmlScreenParser.isPayInputSumScreen()) { onInputSum = true; break; } qDebug() << "[Black::PayByCard] Waiting for input sum screen, attempt" << attempt + 1; QThread::msleep(1500); } if (!onInputSum) { m_error = "Input sum screen not loaded: " + deviceId; qWarning() << m_error; return; } // Вводим сумму в EditText Node sumEditText = xmlScreenParser.findFirstEditText(); if (sumEditText.isEmpty()) { m_error = "EditText for sum not found: " + deviceId; qWarning() << m_error; return; } AdbUtils::makeTap(deviceId, sumEditText.x(), sumEditText.y()); QThread::msleep(500); // Удаляем содержимое поля (там "0,00") — удаляем пока не станет "0,00" или пусто for (int d = 0; d < 20; ++d) { QProcess proc; proc.start(AdbUtils::adbPath(), {"-s", deviceId, "shell", "input", "keyevent", "67"}); proc.waitForFinished(2000); } QThread::msleep(500); // Проверяем что осталось в поле после очистки xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node afterClear = xmlScreenParser.findFirstEditText(); qDebug() << "[Black::PayByCard] After clear, field text:" << afterClear.text; // Поле работает как ввод в центах: каждая цифра сдвигает значение влево // Для 500,00 нужно ввести "50000" const QString amountStr = QString::number(static_cast(m_amount * 100)); AdbUtils::inputText(deviceId, amountStr); QThread::msleep(1000); // Проверяем что сумма ввелась верно xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node currentEdit = xmlScreenParser.findFirstEditText(); qDebug() << "[Black::PayByCard] Sum field text:" << currentEdit.text; // Если сумма неверная — пробуем ещё раз: удаляем всё и вводим заново const QString expectedAmount = QString::number(static_cast(m_amount)); QString cleanText = currentEdit.text.trimmed(); cleanText.remove('.'); if (cleanText.endsWith(",00")) cleanText.chop(3); if (cleanText != expectedAmount) { qDebug() << "[Black::PayByCard] Sum mismatch (" << cleanText << "!=" << expectedAmount << "), retrying..."; AdbUtils::makeTap(deviceId, currentEdit.x(), currentEdit.y()); QThread::msleep(300); for (int d = 0; d < 20; ++d) { QProcess proc; proc.start(AdbUtils::adbPath(), {"-s", deviceId, "shell", "input", "keyevent", "67"}); proc.waitForFinished(2000); } QThread::msleep(500); AdbUtils::inputText(deviceId, amountStr); QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); currentEdit = xmlScreenParser.findFirstEditText(); qDebug() << "[Black::PayByCard] Sum field text (retry):" << currentEdit.text; } // Нажимаем "Continuar" xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node continuarSumBtn = xmlScreenParser.findNodeByContentDesc("Continuar"); if (continuarSumBtn.isEmpty()) { continuarSumBtn = xmlScreenParser.findButtonNode("Continuar", false); } if (continuarSumBtn.isEmpty()) { m_error = "Button 'Continuar' not found on input sum screen: " + deviceId; qWarning() << m_error; return; } qDebug() << "[Black::PayByCard] Tapping 'Continuar' after sum input"; AdbUtils::makeTap(deviceId, continuarSumBtn.x(), continuarSumBtn.y()); QThread::msleep(2000); // 5. Ждём экран финального подтверждения (pay_confirm) bool onPayConfirm = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); if (xmlScreenParser.isPayConfirmScreen()) { onPayConfirm = true; break; } qDebug() << "[Black::PayByCard] Waiting for pay confirm screen, attempt" << attempt + 1; QThread::msleep(1500); } if (!onPayConfirm) { m_error = "Pay confirm screen not loaded: " + deviceId; qWarning() << m_error; return; } // Парсим имя получателя из "Vas a transferir \na ИМЯ" const QString recipientName = xmlScreenParser.parseRecipientNameFromConfirm(); qDebug() << "[Black::PayByCard] Recipient name:" << recipientName; // Нажимаем "Transferir" для финального подтверждения Node finalTransferirBtn = xmlScreenParser.findNodeByContentDesc("Transferir"); if (finalTransferirBtn.isEmpty()) { finalTransferirBtn = xmlScreenParser.findButtonNode("Transferir", false); } if (finalTransferirBtn.isEmpty()) { m_error = "Button 'Transferir' not found on pay confirm screen: " + deviceId; qWarning() << m_error; return; } qDebug() << "[Black::PayByCard] Tapping final 'Transferir'"; AdbUtils::makeTap(deviceId, finalTransferirBtn.x(), finalTransferirBtn.y()); QThread::msleep(3000); // 5.5. Проверяем экран ввода PIN-кода xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); if (xmlScreenParser.isPinCodeScreen()) { qDebug() << "[Black::PayByCard] PIN code screen detected, entering PIN..."; inputPinCode(deviceId, pinCode); QThread::msleep(3000); } // 6. Ждём экран успеха (pay_success) — "Dinero enviado!" bool onSuccess = false; for (int attempt = 0; attempt < 15; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); if (xmlScreenParser.isPaySuccessScreen()) { onSuccess = true; break; } // Если PIN-код появился позже — вводим if (xmlScreenParser.isPinCodeScreen()) { qDebug() << "[Black::PayByCard] PIN code screen detected (retry), entering PIN..."; inputPinCode(deviceId, pinCode); QThread::msleep(3000); continue; } qDebug() << "[Black::PayByCard] Waiting for success screen, attempt" << attempt + 1; QThread::msleep(2000); } if (!onSuccess) { m_error = "Pay success screen not loaded: " + deviceId; qWarning() << m_error; return; } qDebug() << "[Black::PayByCard] Payment successful!"; // Парсим данные с экрана успеха const QString bankTime = xmlScreenParser.parsePaySuccessDateTime(); const QString coelsaId = xmlScreenParser.parsePaySuccessCoelsaId(); qDebug() << "[Black::PayByCard] DateTime:" << bankTime << "Id Coelsa:" << coelsaId; // Делаем скриншот экрана успеха как receipt const QByteArray receiptImage = AdbUtils::captureScreenshotBytes(deviceId); // Сохраняем транзакцию TransactionInfo tx; tx.accountId = m_account.id; tx.amount = -m_amount; // исходящий перевод tx.fee = 0; tx.status = TransactionStatus::Complete; tx.type = TransactionType::Card; tx.name = recipientName; tx.description = "Transferencia a " + recipientName; tx.phone = m_cardNumber; tx.bankName = "black"; tx.bankTime = bankTime; tx.bankTrExternalId = coelsaId; tx.timestamp = QDateTime::currentDateTimeUtc(); tx.completeTime = QDateTime::currentDateTimeUtc(); tx.receiptImage = receiptImage; const int txId = TransactionDAO::insertTransaction(tx); if (txId != -1) { qDebug() << "[Black::PayByCard] Transaction saved to DB, id:" << txId; if (!receiptImage.isEmpty()) { TransactionDAO::updateReceiptImage(txId, receiptImage); } } else { qWarning() << "[Black::PayByCard] Failed to save transaction to DB"; } } } // namespace Black