375 lines
15 KiB
C++
375 lines
15 KiB
C++
#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: " + 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<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;
|
||
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
|