1
0
forked from BRT/arc
arc/android/black/PayByCardScript.cpp
2026-04-08 15:43:59 +07:00

385 lines
16 KiB
C++
Raw Permalink 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.

#include "PayByCardScript.h"
#include <QProcess>
#include <QThread>
#include "adb/AdbUtils.h"
#include "dao/BankProfileDAO.h"
#include "dao/DeviceDAO.h"
#include "dao/TransactionDAO.h"
#include "AppLogger.h"
namespace Black {
PayByCardScript::PayByCardScript(
MaterialInfo 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 BankProfileInfo app = BankProfileDAO::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.adbSerial, 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 BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode);
if (!runAppAndGoToHomeScreen(deviceId, app.package, pinCode, width, height)) {
m_error = "Cannot reach home screen: " + DeviceDAO::getDeviceById(m_account.deviceId).name + " (" + deviceId + ")";
qWarning() << m_error;
AppLogger::log("black/PayByCard", deviceId, m_error, {}, "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;
AppLogger::log("black/PayByCard", deviceId, m_error,
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
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;
AppLogger::log("black/PayByCard", deviceId, m_error,
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
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;
AppLogger::log("black/PayByCard", deviceId, m_error,
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
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;
AppLogger::log("black/PayByCard", deviceId, m_error,
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
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<int>(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<int>(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;
AppLogger::log("black/PayByCard", deviceId, m_error,
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
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;
AppLogger::log("black/PayByCard", deviceId, m_error,
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
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