diff --git a/android/black/CommonScript.cpp b/android/black/CommonScript.cpp index 96c05a4..4224cb0 100644 --- a/android/black/CommonScript.cpp +++ b/android/black/CommonScript.cpp @@ -196,13 +196,9 @@ void CommonScript::updateTransactionData( void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) { const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, appCode); - if (!app.bankProfileId.isEmpty()) { - qDebug() << "[Black] bankProfileId already exists:" << app.bankProfileId; - return; - } const QMap currencyCodes = { - {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944} + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32} }; const int currencyCode = currencyCodes.value(app.currency.toUpper(), 840); const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts); @@ -220,9 +216,16 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr NetworkService ns; ns.setDeviceId(deviceId); ns.setPayload(QJsonDocument(profileBody).toJson()); - ns.addExtra("app_id", app.id); - ns.postBankProfile(); - qDebug() << "[Black] createBankProfileIfNeeded: postBankProfile done"; + + if (app.bankProfileId.isEmpty()) { + ns.addExtra("app_id", app.id); + ns.postBankProfile(); + qDebug() << "[Black] createBankProfileIfNeeded: postBankProfile done"; + } else { + ns.addExtra("bank_profile_id", app.bankProfileId); + ns.patchBankProfile(); + qDebug() << "[Black] createBankProfileIfNeeded: patchBankProfile done"; + } } void CommonScript::stop() { diff --git a/android/black/GetCardInfoScript.cpp b/android/black/GetCardInfoScript.cpp index c27621c..b23f4b7 100644 --- a/android/black/GetCardInfoScript.cpp +++ b/android/black/GetCardInfoScript.cpp @@ -2,10 +2,14 @@ #include +#include +#include + #include "adb/AdbUtils.h" #include "dao/AccountDAO.h" #include "dao/ApplicationDAO.h" #include "dao/DeviceDAO.h" +#include "net/NetworkService.h" namespace Black { @@ -115,12 +119,28 @@ void GetCardInfoScript::doStart() { qWarning() << "[Black::GetCardInfo] Failed to upsert account"; } - // 6. Отправляем данные аккаунтов на сервер - AccountInfo savedAccount = AccountDAO::findAppAccount(m_deviceId, m_appCode, lastNumbers); - if (savedAccount.id != -1) { - QList> accounts; - accounts.append({savedAccount, Node()}); - postAccountsData(accounts); + // 6. Отправляем материал на сервер (если ещё нет material_id) + const AccountInfo savedAccount = AccountDAO::findAppAccount(m_deviceId, m_appCode, lastNumbers); + const ApplicationInfo updatedApp = ApplicationDAO::getApplication(m_deviceId, m_appCode); + + if (savedAccount.id != -1 && !updatedApp.bankProfileId.isEmpty()) { + if (savedAccount.materialId.isEmpty()) { + NetworkService ns; + ns.setDeviceId(m_deviceId); + + QJsonObject materialBody; + materialBody["bank_profile_id"] = updatedApp.bankProfileId; + materialBody["card_number"] = savedAccount.cardNumber; + materialBody["name"] = updatedApp.fullName; + ns.setPayload(QJsonDocument(materialBody).toJson()); + ns.addExtra("account_id", savedAccount.id); + ns.postMaterial(); + qDebug() << "[Black::GetCardInfo] postMaterial done for CVU" << lastNumbers; + } else { + qDebug() << "[Black::GetCardInfo] material already exists for CVU" << lastNumbers << ", skipping"; + } + } else { + qWarning() << "[Black::GetCardInfo] bankProfileId empty or account not found, skipping material"; } qDebug() << "[Black::GetCardInfo] Done. CVU:" << cvuNumber << "balance:" << balance; diff --git a/android/black/GetLastTransactionsScript.cpp b/android/black/GetLastTransactionsScript.cpp new file mode 100644 index 0000000..87f09d9 --- /dev/null +++ b/android/black/GetLastTransactionsScript.cpp @@ -0,0 +1,168 @@ +#include "GetLastTransactionsScript.h" + +#include +#include + +#include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" +#include "dao/TransactionDAO.h" +#include "AppLogger.h" + +namespace Black { + +GetLastTransactionsScript::GetLastTransactionsScript( + QString deviceId, + QString appCode, + QObject *parent +) : CommonScript(parent), + m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)) { +} + +GetLastTransactionsScript::~GetLastTransactionsScript() = default; + +void GetLastTransactionsScript::doStart() { + const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode); + + if (device.id.isEmpty() || app.id == -1) { + m_error = "Device or app not found: " + m_deviceId + " " + m_appCode; + qCritical() << m_error; + emit finishedWithResult(m_error); + return; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + + // Если не на домашнем экране — перезапускаем приложение + if (!xmlScreenParser.isHomeScreen()) { + qDebug() << "[Black::GetLastTransactions] Not on home screen, restarting app..."; + if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, + device.screenWidth, device.screenHeight)) { + m_error = "Cannot reach home screen: " + m_deviceId; + qWarning() << m_error; + AppLogger::log("black/GetLastTransactions", m_deviceId, m_error, + AdbUtils::captureScreenshotBytes(m_deviceId), + "FETCH_TRANSACTIONS"); + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + } + + // Тапаем "Ver más" рядом с "Tu actividad" для перехода на экран транзакций + Node verMasBtn = xmlScreenParser.findVerMasAfterTuActividad(); + if (verMasBtn.isEmpty()) { + // Попробуем проскроллить вниз чтобы увидеть "Tu actividad" + "Ver más" + for (int i = 0; i < 5; ++i) { + const int x = device.screenWidth / 2; + const int offset = device.screenHeight / 4; + AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight - offset, x, offset); + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + verMasBtn = xmlScreenParser.findVerMasAfterTuActividad(); + if (!verMasBtn.isEmpty()) break; + } + } + + if (verMasBtn.isEmpty()) { + m_error = "Button 'Ver más' not found: " + m_deviceId; + qWarning() << m_error; + emit finishedWithResult(m_error); + return; + } + + qDebug() << "[Black::GetLastTransactions] Tapping 'Ver más' at" << verMasBtn.x() << verMasBtn.y(); + AdbUtils::makeTap(m_deviceId, verMasBtn.x(), verMasBtn.y()); + QThread::msleep(2000); + + // Ждём загрузки экрана транзакций + // Сперва появляется экран загрузки (Tu Actividad без транзакций), + // затем подгружаются транзакции + bool loaded = false; + for (int attempt = 0; attempt < 15; ++attempt) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + if (xmlScreenParser.isTransactionListScreen()) { + loaded = true; + break; + } + if (xmlScreenParser.isTransactionLoadingScreen()) { + qDebug() << "[Black::GetLastTransactions] Loading screen detected, waiting for transactions, attempt" << attempt + 1 << "/ 15"; + } else { + qDebug() << "[Black::GetLastTransactions] Waiting for transaction screen, attempt" << attempt + 1 << "/ 15"; + } + QThread::msleep(1500); + } + + if (!loaded) { + // Проверяем: если экран загрузки есть, но транзакций нет — это нормально (пустой аккаунт) + if (xmlScreenParser.isTransactionLoadingScreen()) { + qDebug() << "[Black::GetLastTransactions] No transactions found (empty account)"; + AdbUtils::goBack(m_deviceId); + QThread::msleep(1000); + emit finishedWithResult(m_error); + return; + } + m_error = "Transaction list screen not loaded: " + m_deviceId; + qWarning() << m_error; + emit finishedWithResult(m_error); + return; + } + + // Находим accountId для сохранения транзакций + int accountId = -1; + const QList accounts = AccountDAO::getAccounts(m_deviceId, m_appCode); + if (!accounts.isEmpty()) { + accountId = accounts.first().id; + qDebug() << "[Black::GetLastTransactions] Using accountId:" << accountId; + } else { + qWarning() << "[Black::GetLastTransactions] No account found for" << m_deviceId << m_appCode; + } + + // Берём все транзакции что видны на экране + const QTimeZone argTz("America/Argentina/Buenos_Aires"); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + QList pageItems = xmlScreenParser.parseTransactionItems(); + + qDebug() << "[Black::GetLastTransactions] Found" << pageItems.size() << "transactions on screen"; + + for (const TransactionInfo &tx : pageItems) { + TransactionInfo saveTx = tx; + saveTx.bankName = "black"; + saveTx.status = TransactionStatus::Complete; + saveTx.timestamp = QDateTime::currentDateTimeUtc(); + + // Парсим дату из bankTime: "21 de febrero de 2026 - 09:39" + QDateTime parsed = xmlScreenParser.parseSpanishDate(saveTx.bankTime); + if (parsed.isValid()) { + parsed.setTimeZone(argTz); + saveTx.completeTime = parsed; + } + + qDebug() << "[Black::GetLastTransactions]" + << "date:" << saveTx.bankTime + << "desc:" << saveTx.description + << "name:" << saveTx.name + << "amount:" << (saveTx.amount >= 0 ? "+" : "") + QString::number(saveTx.amount, 'f', 2); + + // Сохраняем в БД + if (accountId != -1) { + saveTx.accountId = accountId; + if (TransactionDAO::insertTransactionIfNotExists(saveTx)) { + qDebug() << "[Black::GetLastTransactions] Saved transaction to DB"; + } + } + } + + qDebug() << "[Black::GetLastTransactions] Total transactions:" << pageItems.size(); + + // Возвращаемся на домашний экран + AdbUtils::goBack(m_deviceId); + QThread::msleep(1000); + + emit finishedWithResult(m_error); +} + +} // namespace Black diff --git a/android/black/GetLastTransactionsScript.h b/android/black/GetLastTransactionsScript.h new file mode 100644 index 0000000..248e9a5 --- /dev/null +++ b/android/black/GetLastTransactionsScript.h @@ -0,0 +1,27 @@ +#pragma once +#include "CommonScript.h" + +namespace Black { + +class GetLastTransactionsScript final : public CommonScript { + Q_OBJECT + +public: + explicit GetLastTransactionsScript( + QString deviceId, + QString appCode, + QObject *parent = nullptr + ); + + ~GetLastTransactionsScript() override; + +protected: + void doStart() override; + +private: + const QString m_deviceId; + const QString m_appCode; + QString m_error; +}; + +} // namespace Black diff --git a/android/black/GetProfileInfoScript.cpp b/android/black/GetProfileInfoScript.cpp index 651d082..ac86346 100644 --- a/android/black/GetProfileInfoScript.cpp +++ b/android/black/GetProfileInfoScript.cpp @@ -5,6 +5,7 @@ #include #include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" #include "dao/ApplicationDAO.h" #include "dao/DeviceDAO.h" #include "net/NetworkService.h" @@ -161,6 +162,18 @@ void GetProfileInfoScript::doStart() { // 7. Создаём bank profile если нет createBankProfileIfNeeded(m_deviceId, m_appCode); + // 8. Отправляем данные аккаунтов на сервер если нет material_id + const QList allAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode); + QList> accountsToPost; + for (const auto &acc : allAccounts) { + if (acc.materialId.isEmpty()) { + accountsToPost.append({acc, Node()}); + } + } + if (!accountsToPost.isEmpty()) { + postAccountsData(accountsToPost); + } + AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); } diff --git a/android/black/PayByCardScript.cpp b/android/black/PayByCardScript.cpp new file mode 100644 index 0000000..b3b7d52 --- /dev/null +++ b/android/black/PayByCardScript.cpp @@ -0,0 +1,374 @@ +#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 diff --git a/android/black/PayByCardScript.h b/android/black/PayByCardScript.h new file mode 100644 index 0000000..db20b97 --- /dev/null +++ b/android/black/PayByCardScript.h @@ -0,0 +1,36 @@ +#pragma once +#include "CommonScript.h" + +namespace Black { + +class PayByCardScript final : public CommonScript { + Q_OBJECT + +public: + PayByCardScript( + AccountInfo account, + QString cardNumber, + double amount, + QObject *parent = nullptr + ); + + ~PayByCardScript() override; + +protected: + void doStart() override; + +private: + const AccountInfo m_account; + const QString m_cardNumber; + const double m_amount; + QString m_error; + + void makePayment( + const QString &deviceId, + const QString &pinCode, + int width, + int height + ); +}; + +} // namespace Black diff --git a/android/black/ScreenXmlParser.cpp b/android/black/ScreenXmlParser.cpp index ac26d93..0db3069 100644 --- a/android/black/ScreenXmlParser.cpp +++ b/android/black/ScreenXmlParser.cpp @@ -1,5 +1,6 @@ #include "ScreenXmlParser.h" +#include #include #include #include @@ -306,4 +307,239 @@ QString ScreenXmlParser::parseCVUNumber() { return {}; } +Node ScreenXmlParser::findVerMasAfterTuActividad() { + // "Ver más" идёт сразу после "Tu actividad" на домашнем экране + bool afterTuActividad = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Tu actividad") { + afterTuActividad = true; + continue; + } + if (afterTuActividad && node.contentDesc == QString::fromUtf8("Ver más") && node.clickable) { + return node; + } + // Если встретили другую секцию — прекращаем поиск + if (afterTuActividad && node.contentDesc == "Tarjetas") { + break; + } + } + return {}; +} + +bool ScreenXmlParser::isTransactionLoadingScreen() { + // Экран загрузки: заголовок "Tu Actividad" есть, но транзакций нет + // Может содержать "Aún no tienes actividad" + bool hasTuActividad = false; + bool hasTransferencia = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Tu Actividad") { + hasTuActividad = true; + } + if (node.contentDesc.startsWith("Transferencia")) { + hasTransferencia = true; + } + } + return hasTuActividad && !hasTransferencia; +} + +bool ScreenXmlParser::isTransactionListScreen() { + // Экран транзакций определяется по заголовку "Tu Actividad" (с большой A) + // + наличие транзакций (ImageView с "Transferencia") + // На домашнем экране — "Tu actividad" (с маленькой a) + bool hasTuActividad = false; + bool hasTransferencia = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Tu Actividad") { + hasTuActividad = true; + } + if (node.contentDesc.startsWith("Transferencia")) { + hasTransferencia = true; + } + } + return hasTuActividad && hasTransferencia; +} + +QList ScreenXmlParser::parseTransactionItems() { + // Транзакции — ImageView с content-desc формата: + // "Transferencia\n21 de febrero de 2026 - 09:39\n$-1.857,\n00" + // "Transferencia externa de NOELIA ISABEL LEZCANO\n21 de febrero de 2026 - 09:35\n$123,\n45" + QList result; + + for (const Node &node : m_nodes) { + if (node.className != "android.widget.ImageView") continue; + if (!node.contentDesc.startsWith("Transferencia")) continue; + + QStringList lines = node.contentDesc.split('\n'); + if (lines.size() < 3) continue; + + TransactionInfo tx; + + // Строка 1: описание, может содержать имя отправителя + // "Transferencia" или "Transferencia externa de NOELIA ISABEL LEZCANO" + const QString descLine = lines[0].trimmed(); + tx.description = descLine; + if (descLine.contains("externa de ")) { + tx.name = descLine.mid(descLine.indexOf("externa de ") + 11).trimmed(); + } + + // Строка 2: дата "21 de febrero de 2026 - 09:39" + tx.bankTime = lines[1].trimmed(); + + // Строки 3+: сумма "$-1.857,\n00" → объединяем + QString amountStr; + for (int i = 2; i < lines.size(); ++i) { + amountStr += lines[i].trimmed(); + } + // "$-1.857,00" → убираем "$", "." (тысячный разделитель), заменяем "," на "." + amountStr.remove('$'); + amountStr.remove(' '); + bool isNegative = amountStr.contains('-'); + amountStr.remove('-'); + amountStr.remove('.'); // тысячный разделитель + amountStr.replace(',', '.'); + bool ok = false; + tx.amount = amountStr.toDouble(&ok); + if (ok && isNegative) tx.amount = -tx.amount; + + tx.type = TransactionType::Card; + + result.append(tx); + } + + return result; +} + +QDateTime ScreenXmlParser::parseSpanishDate(const QString &dateStr) { + // Формат: "21 de febrero de 2026 - 09:39" + static const QMap spanishMonths = { + {"enero", 1}, {"febrero", 2}, {"marzo", 3}, {"abril", 4}, + {"mayo", 5}, {"junio", 6}, {"julio", 7}, {"agosto", 8}, + {"septiembre", 9}, {"octubre", 10}, {"noviembre", 11}, {"diciembre", 12} + }; + + QRegularExpression re(R"((\d{1,2})\s+de\s+(\w+)\s+de\s+(\d{4})\s*-\s*(\d{2}):(\d{2}))"); + QRegularExpressionMatch match = re.match(dateStr); + if (!match.hasMatch()) return {}; + + const int day = match.captured(1).toInt(); + const QString monthName = match.captured(2).toLower(); + const int year = match.captured(3).toInt(); + const int hour = match.captured(4).toInt(); + const int minute = match.captured(5).toInt(); + + const int month = spanishMonths.value(monthName, 0); + if (month == 0) return {}; + + return QDateTime(QDate(year, month, day), QTime(hour, minute)); +} + +bool ScreenXmlParser::isPayInputCardNumberScreen() { + for (const Node &node : m_nodes) { + if (node.hint == "Ingresar CBU, CVU o Alias") { + return true; + } + } + return false; +} + +bool ScreenXmlParser::isPayTransferConfirmScreen() { + // Экран с именем получателя, Alias/CBU и кнопкой "Transferir" + // Но без "Vas a transferir" (это уже финальный экран) + bool hasTransferir = false; + bool hasVasATransferir = false; + bool hasAlias = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Transferir" && node.clickable) { + hasTransferir = true; + } + if (node.contentDesc.startsWith("Vas a transferir")) { + hasVasATransferir = true; + } + if (node.contentDesc.startsWith("Alias:")) { + hasAlias = true; + } + } + return hasTransferir && hasAlias && !hasVasATransferir; +} + +bool ScreenXmlParser::isPayInputSumScreen() { + for (const Node &node : m_nodes) { + if (node.contentDesc.contains(QString::fromUtf8("Cuánto vas a transferír"))) { + return true; + } + } + return false; +} + +bool ScreenXmlParser::isPayConfirmScreen() { + for (const Node &node : m_nodes) { + if (node.contentDesc.startsWith("Vas a transferir")) { + return true; + } + } + return false; +} + +bool ScreenXmlParser::isPaySuccessScreen() { + for (const Node &node : m_nodes) { + if (node.contentDesc == "Dinero enviado!") { + return true; + } + } + return false; +} + +QString ScreenXmlParser::parseRecipientNameFromConfirm() { + // "Vas a transferir \na JOEL ARCHENTI" + for (const Node &node : m_nodes) { + if (node.contentDesc.startsWith("Vas a transferir")) { + QStringList lines = node.contentDesc.split('\n'); + if (lines.size() >= 2) { + QString nameLine = lines[1].trimmed(); + // Убираем "a " в начале + if (nameLine.startsWith("a ")) { + nameLine = nameLine.mid(2); + } + return nameLine.trimmed(); + } + } + } + return {}; +} + +QString ScreenXmlParser::parsePaySuccessDateTime() { + // Дата/время: "12/03/26 04:01" — содержит "/" и ":" + for (const Node &node : m_nodes) { + const QString desc = node.contentDesc.trimmed(); + if (desc.contains('/') && desc.contains(':') && desc.length() <= 20) { + return desc; + } + } + return {}; +} + +QString ScreenXmlParser::parsePaySuccessCoelsaId() { + // "Id Coelsa" → следующий node содержит ID + bool afterIdCoelsa = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Id Coelsa") { + afterIdCoelsa = true; + continue; + } + if (afterIdCoelsa && !node.contentDesc.isEmpty()) { + return node.contentDesc.trimmed(); + } + } + return {}; +} + +Node ScreenXmlParser::findEditTextByHint(const QString &hint) { + for (const Node &node : m_nodes) { + if (node.className == "android.widget.EditText" && node.hint == hint) { + return node; + } + } + return {}; +} + } // namespace Black diff --git a/android/black/ScreenXmlParser.h b/android/black/ScreenXmlParser.h index 5d55c32..cc9ade0 100644 --- a/android/black/ScreenXmlParser.h +++ b/android/black/ScreenXmlParser.h @@ -4,6 +4,7 @@ #include #include "android/xml/Node.h" #include "db/AccountInfo.h" +#include "db/TransactionInfo.h" namespace Black { @@ -64,6 +65,51 @@ public: // Парсит номер CVU (22-значный номер после content-desc="CVU") QString parseCVUNumber(); + // Находит кнопку "Ver más" рядом с "Tu actividad" (для перехода на экран транзакций) + Node findVerMasAfterTuActividad(); + + // Определяет экран загрузки транзакций (заголовок "Tu Actividad", но транзакций ещё нет) + bool isTransactionLoadingScreen(); + + // Определяет экран списка транзакций (заголовок "Tu Actividad" + есть транзакции) + bool isTransactionListScreen(); + + // Парсит все транзакции из content-desc ImageView элементов + // Формат: "Transferencia\nDD de MES de YYYY - HH:MM\n$-1.857,\n00" + QList parseTransactionItems(); + + // Парсит дату на испанском: "21 de febrero de 2026 - 09:39" → QDateTime + static QDateTime parseSpanishDate(const QString &dateStr); + + // --- Экраны оплаты --- + + // Экран ввода CBU/CVU/Alias (hint="Ingresar CBU, CVU o Alias") + bool isPayInputCardNumberScreen(); + + // Экран подтверждения получателя (кнопка "Transferir" + имя получателя) + bool isPayTransferConfirmScreen(); + + // Экран ввода суммы ("¿Cuánto vas a transferír" + EditText + "Continuar") + bool isPayInputSumScreen(); + + // Экран финального подтверждения ("Vas a transferir" + "Transferir") + bool isPayConfirmScreen(); + + // Экран успешного перевода ("Dinero enviado!") + bool isPaySuccessScreen(); + + // Парсит имя получателя из "Vas a transferir \na ИМЯ" + QString parseRecipientNameFromConfirm(); + + // Парсит дату/время с экрана успеха: "12/03/26 04:01" + QString parsePaySuccessDateTime(); + + // Парсит Id Coelsa с экрана успеха + QString parsePaySuccessCoelsaId(); + + // Находит EditText по hint + Node findEditTextByHint(const QString &hint); + private: QList m_nodes; QDomElement m_xml; diff --git a/android/ozon/CommonScript.cpp b/android/ozon/CommonScript.cpp index 28aebe8..45668a1 100644 --- a/android/ozon/CommonScript.cpp +++ b/android/ozon/CommonScript.cpp @@ -292,13 +292,9 @@ void CommonScript::updateTransactionData( void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) { const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, appCode); - if (!app.bankProfileId.isEmpty()) { - qDebug() << "[Ozon] bankProfileId already exists:" << app.bankProfileId; - return; - } const QMap currencyCodes = { - {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944} + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32} }; const int currencyCode = currencyCodes.value(app.currency.toUpper(), 643); const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts); @@ -316,9 +312,16 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr NetworkService ns; ns.setDeviceId(deviceId); ns.setPayload(QJsonDocument(profileBody).toJson()); - ns.addExtra("app_id", app.id); - ns.postBankProfile(); - qDebug() << "[Ozon] createBankProfileIfNeeded: postBankProfile done"; + + if (app.bankProfileId.isEmpty()) { + ns.addExtra("app_id", app.id); + ns.postBankProfile(); + qDebug() << "[Ozon] createBankProfileIfNeeded: postBankProfile done"; + } else { + ns.addExtra("bank_profile_id", app.bankProfileId); + ns.patchBankProfile(); + qDebug() << "[Ozon] createBankProfileIfNeeded: patchBankProfile done"; + } } void CommonScript::stop() { diff --git a/android/ozon/GetCardInfoScript.cpp b/android/ozon/GetCardInfoScript.cpp index 68c15e9..681f01f 100644 --- a/android/ozon/GetCardInfoScript.cpp +++ b/android/ozon/GetCardInfoScript.cpp @@ -174,7 +174,7 @@ void GetCardInfoScript::doStart() { // ISO 4217 numeric currency codes const QMap currencyCodes = { - {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944} + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32} }; const int currencyCode = currencyCodes.value(freshApp.currency.toUpper(), 643); diff --git a/android/ozon/GetProfileInfoScript.cpp b/android/ozon/GetProfileInfoScript.cpp index 59da1c5..7766ebc 100644 --- a/android/ozon/GetProfileInfoScript.cpp +++ b/android/ozon/GetProfileInfoScript.cpp @@ -7,6 +7,7 @@ #include #include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" #include "dao/ApplicationDAO.h" #include "dao/DeviceDAO.h" #include "net/NetworkService.h" @@ -114,32 +115,49 @@ void GetProfileInfoScript::doStart() { qWarning() << m_error; } - // Отправляем bank_profile на сервер если профиля ещё нет - if (app.bankProfileId.isEmpty()) { - const ApplicationInfo freshApp = ApplicationDAO::getApplication(m_deviceId, m_appCode); + // Отправляем bank_profile на сервер + const ApplicationInfo freshApp = ApplicationDAO::getApplication(m_deviceId, m_appCode); - const QMap currencyCodes = { - {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944} - }; - const int currencyCode = currencyCodes.value(freshApp.currency.toUpper(), 643); + const QMap currencyCodes = { + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32} + }; + const int currencyCode = currencyCodes.value(freshApp.currency.toUpper(), 643); - const QStringList parts = fullName.split(' ', Qt::SkipEmptyParts); - QJsonObject profileBody; - profileBody["first_name"] = parts.value(0); - profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : ""; - profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1); - profileBody["bank_name"] = m_appCode; - profileBody["phone_number"] = phone; - profileBody["balance"] = QString::number(qRound64(freshApp.amount * 100)); - profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled"; - profileBody["currency"] = currencyCode; + const QStringList parts = fullName.split(' ', Qt::SkipEmptyParts); + QJsonObject profileBody; + profileBody["first_name"] = parts.value(0); + profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : ""; + profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1); + profileBody["bank_name"] = m_appCode; + profileBody["phone_number"] = phone; + profileBody["balance"] = QString::number(qRound64(freshApp.amount * 100)); + profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled"; + profileBody["currency"] = currencyCode; - NetworkService ns; - ns.setDeviceId(m_deviceId); - ns.setPayload(QJsonDocument(profileBody).toJson()); + NetworkService ns; + ns.setDeviceId(m_deviceId); + ns.setPayload(QJsonDocument(profileBody).toJson()); + + if (freshApp.bankProfileId.isEmpty()) { ns.addExtra("app_id", freshApp.id); ns.postBankProfile(); qDebug() << "[GetProfileInfo] postBankProfile done"; + } else { + ns.addExtra("bank_profile_id", freshApp.bankProfileId); + ns.patchBankProfile(); + qDebug() << "[GetProfileInfo] patchBankProfile done"; + } + + // Отправляем данные аккаунтов на сервер если нет material_id + const QList allAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode); + QList> accountsToPost; + for (const auto &acc : allAccounts) { + if (acc.materialId.isEmpty()) { + accountsToPost.append({acc, Node()}); + } + } + if (!accountsToPost.isEmpty()) { + postAccountsData(accountsToPost); } AdbUtils::tryToKillApplication(m_deviceId, app.package); diff --git a/assets/black/home_screen.xml b/assets/black/home_screen.xml index 3b6fc62..dcf8876 100644 --- a/assets/black/home_screen.xml +++ b/assets/black/home_screen.xml @@ -143,7 +143,7 @@ drawing-order="0" hint=""/> diff --git a/assets/black/pay/pay_confirm.png b/assets/black/pay/pay_confirm.png new file mode 100644 index 0000000..7658b4d Binary files /dev/null and b/assets/black/pay/pay_confirm.png differ diff --git a/assets/black/pay/pay_confirm.xml b/assets/black/pay/pay_confirm.xml new file mode 100644 index 0000000..03f40a9 --- /dev/null +++ b/assets/black/pay/pay_confirm.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/pay/pay_input_card_number.png b/assets/black/pay/pay_input_card_number.png new file mode 100644 index 0000000..711dc07 Binary files /dev/null and b/assets/black/pay/pay_input_card_number.png differ diff --git a/assets/black/pay/pay_input_card_number.xml b/assets/black/pay/pay_input_card_number.xml new file mode 100644 index 0000000..402bdbf --- /dev/null +++ b/assets/black/pay/pay_input_card_number.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/pay/pay_input_sum.png b/assets/black/pay/pay_input_sum.png new file mode 100644 index 0000000..8f0f2b5 Binary files /dev/null and b/assets/black/pay/pay_input_sum.png differ diff --git a/assets/black/pay/pay_input_sum.xml b/assets/black/pay/pay_input_sum.xml new file mode 100644 index 0000000..0cbe2db --- /dev/null +++ b/assets/black/pay/pay_input_sum.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/pay/pay_success.png b/assets/black/pay/pay_success.png new file mode 100644 index 0000000..8c5d151 Binary files /dev/null and b/assets/black/pay/pay_success.png differ diff --git a/assets/black/pay/pay_success.xml b/assets/black/pay/pay_success.xml new file mode 100644 index 0000000..c3d77d5 --- /dev/null +++ b/assets/black/pay/pay_success.xml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/pay/pay_success_document.png b/assets/black/pay/pay_success_document.png new file mode 100644 index 0000000..f592a1b Binary files /dev/null and b/assets/black/pay/pay_success_document.png differ diff --git a/assets/black/pay/pay_success_document.xml b/assets/black/pay/pay_success_document.xml new file mode 100644 index 0000000..edf5198 --- /dev/null +++ b/assets/black/pay/pay_success_document.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/pay/pay_transfer_confirm.png b/assets/black/pay/pay_transfer_confirm.png new file mode 100644 index 0000000..d21a9c4 Binary files /dev/null and b/assets/black/pay/pay_transfer_confirm.png differ diff --git a/assets/black/pay/pay_transfer_confirm.xml b/assets/black/pay/pay_transfer_confirm.xml new file mode 100644 index 0000000..1825f61 --- /dev/null +++ b/assets/black/pay/pay_transfer_confirm.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/transactions_loading_screen.xml b/assets/black/transactions_loading_screen.xml new file mode 100644 index 0000000..2934fee --- /dev/null +++ b/assets/black/transactions_loading_screen.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/transactions_screen.png b/assets/black/transactions_screen.png new file mode 100644 index 0000000..418d406 Binary files /dev/null and b/assets/black/transactions_screen.png differ diff --git a/assets/black/transactions_screen.xml b/assets/black/transactions_screen.xml new file mode 100644 index 0000000..a4415a0 --- /dev/null +++ b/assets/black/transactions_screen.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config.ini b/config.ini index f7e3166..542f5d6 100644 --- a/config.ini +++ b/config.ini @@ -2,7 +2,7 @@ list=ozon, black [common] -currencies=USD, KRW, AZN, RUB +currencies=USD, ARS, RUB version=0.0.1 [db] @@ -27,7 +27,7 @@ transactionUpdate=http://localhost:9999/api/v1/transaction/update [black] android_package_name=app.blackwallet.ar -currency=USD +currency=ARS name=Black [ozon] diff --git a/database/app_data.db b/database/app_data.db index d995045..1325345 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/main.cpp b/main.cpp index 82010ce..ffd23b4 100644 --- a/main.cpp +++ b/main.cpp @@ -21,6 +21,7 @@ #include "dao/SettingsDAO.h" #include "EventHandler.h" #include "net/NetworkService.h" +#include "black/PayByCardScript.h" #include "ozon/PayByPhoneScript.h" #include "widget/loader/ErrorWindow.h" #include "widget/loader/LoaderWindow.h" @@ -100,6 +101,33 @@ void startPayByPhoneTest() { thread->start(); } +void startBlackPayByCardTest() { + QList accounts = AccountDAO::getAccountsByAppCode("black"); + if (accounts.isEmpty()) { + qWarning() << "[BlackPayByCardTest] No black accounts found"; + return; + } + AccountInfo account = accounts.first(); + qDebug() << "[BlackPayByCardTest] Using account:" << account.id << account.deviceId << account.lastNumbers; + + auto *thread = new QThread(nullptr); + auto *worker = new Black::PayByCardScript( + account, + "0070359630004039066690", + 500, + nullptr + ); + worker->moveToThread(thread); + QObject::connect(thread, &QThread::started, worker, &Black::PayByCardScript::start); + QObject::connect(worker, &Black::PayByCardScript::finished, thread, &QThread::quit); + QObject::connect(worker, &Black::PayByCardScript::finished, worker, &QObject::deleteLater); + QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater); + QObject::connect(worker, &Black::PayByCardScript::finishedWithResult, [](const QString &result) { + qDebug() << "[BlackPayByCardTest] Result:" << result; + }); + thread->start(); +} + void startNetworkService(const QCoreApplication &app) { auto *paymentThread = new QThread; auto *networkService = new NetworkService; @@ -229,7 +257,7 @@ int main(int argc, char *argv[]) { startNetworkService(app); startDeviceScreener(app); startEventHandler(app); - // startPayByPhoneTest(); + // startBlackPayByCardTest(); }, Qt::QueuedConnection); }; @@ -260,7 +288,7 @@ int main(int argc, char *argv[]) { startNetworkService(app); startDeviceScreener(app); startEventHandler(app); - // startPayByPhoneTest(); + // startBlackPayByCardTest(); } else if (r == 0) { SettingsDAO::remove("api_key"); showRegistration(); @@ -281,7 +309,7 @@ int main(int argc, char *argv[]) { startNetworkService(app); startDeviceScreener(app); startEventHandler(app); - // startPayByPhoneTest(); + // startBlackPayByCardTest(); } else if (result == 0) { SettingsDAO::remove("api_key"); showRegistration(); diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index d484852..5c72734 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -21,6 +21,8 @@ #include "net/NetworkService.h" #include "birbank/GetLastDaysHistoryBirbankScript.h" #include "birbank/LoginAndCheckAccountsBirbankScript.h" +#include "black/GetLastTransactionsScript.h" +#include "black/LoginAndCheckAccountsScript.h" #include "ozon/GetLastTransactionsScript.h" #include "ozon/LoginAndCheckAccountsScript.h" #include "rshb/GetLastDaysHistoryScript.h" @@ -29,6 +31,7 @@ #include "toss/LoginAndCheckAccountsTossScript.h" #include "ziraat/GetLastDaysHistoryZiraatScript.h" #include "ziraat/LoginAndCheckAccountsZiraatScript.h" +#include "black/PayByCardScript.h" #include "ozon/PayByCardScript.h" #include "ozon/PayByPhoneScript.h" #include "rshb/PayByPhoneScript.h" @@ -254,14 +257,19 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, } if (newStatus != EventStatus::Error) { + const AccountInfo account = AccountDAO::getAccountById(event.accountId); + const QMap currencyCodes = { + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32} + }; + const int currencyCode = currencyCodes.value(account.currency.toUpper(), 643); + QJsonValue taskData; if (event.type == EventType::FetchProfile) { - const AccountInfo account = AccountDAO::getAccountById(event.accountId); EventDAO::updateEventAmount(event.id, account.amount); QJsonObject obj; obj["bank_account_id"] = QString(); obj["amount"] = static_cast(account.amount * 100); - obj["currency"] = 643; // RUB + obj["currency"] = currencyCode; taskData = obj; } else if (event.type == EventType::FetchTransactions) { QList txList = TransactionDAO::getTransactionsWithinHours(event.accountId, 24); @@ -270,7 +278,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, QJsonObject txObj; txObj["bank_transaction_id"] = tx.bankTrExternalId.isEmpty() ? QJsonValue(QJsonValue::Null) : QJsonValue(tx.bankTrExternalId); txObj["amount"] = static_cast(tx.amount * 100); - txObj["currency"] = 643; // RUB + txObj["currency"] = currencyCode; txObj["transaction_type"] = (tx.type == TransactionType::Phone) ? "top-up" : "bank"; txObj["bank_participator_id"]= tx.phone; txObj["status"] = tx.status == TransactionStatus::Complete ? "completed" : (tx.status == TransactionStatus::Wait ? "progress" : "completed"); @@ -297,11 +305,11 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, } else if (event.type == EventType::CreateTransaction) { // Определяем тип транзакции из JSON задачи const QJsonObject taskJson = QJsonDocument::fromJson(event.json.toUtf8()).object(); - const QString txType = taskJson["body"].toObject()["transaction_type"].toString(); + const QString txType = taskJson["body"].toObject()["type"].toString(); QJsonObject obj; obj["amount"] = -static_cast(event.amount * 100); - obj["currency"] = 643; + obj["currency"] = currencyCode; obj["transaction_type"] = txType.isEmpty() ? "top-up" : txType; obj["bank_participator_id"]= event.phone; obj["status"] = "completed"; @@ -389,6 +397,7 @@ void EventHandler::startThreadForTask(const AccountInfo &account, const EventInf else if (appCode == "rshb") setup(new Rshb::LoginAndCheckAccountsScript(deviceId, appCode)); else if (appCode == "toss") setup(new LoginAndCheckAccountsTossScript(deviceId, appCode)); else if (appCode == "ziraat") setup(new LoginAndCheckAccountsZiraatScript(deviceId, appCode)); + else if (appCode == "black") setup(new Black::LoginAndCheckAccountsScript(deviceId, appCode)); else qWarning() << "[EventHandler] FETCH_PROFILE: unknown appCode:" << appCode; } @@ -398,6 +407,7 @@ void EventHandler::startThreadForTask(const AccountInfo &account, const EventInf else if (appCode == "rshb") setup(new Rshb::GetLastDaysHistoryScript(account)); else if (appCode == "toss") setup(new GetLastDaysHistoryTossScript(account)); else if (appCode == "ziraat") setup(new GetLastDaysHistoryZiraatScript(account)); + else if (appCode == "black") setup(new Black::GetLastTransactionsScript(deviceId, appCode)); else qWarning() << "[EventHandler] FETCH_TRANSACTIONS: unknown appCode:" << appCode; } @@ -406,11 +416,12 @@ void EventHandler::startThreadForTask(const AccountInfo &account, const EventInf // Определяем тип транзакции из JSON задачи const QJsonObject taskJson = QJsonDocument::fromJson(event.json.toUtf8()).object(); - const QString txType = taskJson["body"].toObject()["transaction_type"].toString(); + const QString txType = taskJson["body"].toObject()["type"].toString(); if (txType == "bank") { // Перевод по номеру карты - if (appCode == "ozon") setup(new Ozon::PayByCardScript(account, event.phone, event.amount)); + if (appCode == "ozon") setup(new Ozon::PayByCardScript(account, event.phone, event.amount)); + else if (appCode == "black") setup(new Black::PayByCardScript(account, event.phone, event.amount)); else qWarning() << "[EventHandler] CREATE_TRANSACTION bank: unknown appCode:" << appCode; } else { // Перевод по номеру телефона (top-up) diff --git a/views/bank/AccountSettingsWindow.cpp b/views/bank/AccountSettingsWindow.cpp index ee0d8a4..db8bea8 100644 --- a/views/bank/AccountSettingsWindow.cpp +++ b/views/bank/AccountSettingsWindow.cpp @@ -15,6 +15,7 @@ #include "ozon/LoginAndCheckAccountsScript.h" #include "ozon/GetProfileInfoScript.h" #include "ozon/GetCardInfoScript.h" +#include "black/GetLastTransactionsScript.h" #include "ozon/GetLastTransactionsScript.h" #include "toss/LoginAndCheckAccountsTossScript.h" #include "black/LoginAndCheckAccountsScript.h" @@ -291,10 +292,9 @@ AccountSettingsWindow::AccountSettingsWindow( QMessageBox msgBox(this); msgBox.setWindowTitle(tr("Проверить транзакции?")); msgBox.setText( - "Запустится скрипт получения последних транзакций из " + m_applicationInfo.name + + "Запустится скрипт получения транзакций из " + m_applicationInfo.name + ". Окно с настройками закроется."); - QPushButton *runBtn = msgBox.addButton(tr("За 24 часа"), QMessageBox::AcceptRole); - QPushButton *last5Btn = msgBox.addButton(tr("Последние 5"), QMessageBox::AcceptRole); + QPushButton *runBtn = msgBox.addButton(tr("Проверить"), QMessageBox::AcceptRole); QPushButton *cancelBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole); msgBox.setDefaultButton(cancelBtn); msgBox.setModal(true); @@ -302,9 +302,6 @@ AccountSettingsWindow::AccountSettingsWindow( if (msgBox.clickedButton() == runBtn) { startGetLastTransactions(m_applicationInfo.code); close(); - } else if (msgBox.clickedButton() == last5Btn) { - startGetLastTransactions(m_applicationInfo.code, 5); - close(); } }); @@ -455,16 +452,25 @@ void AccountSettingsWindow::startGetCardInfo(const QString &appCode) { } void AccountSettingsWindow::startGetLastTransactions(const QString &appCode, int maxCount) { - if (appCode != "ozon") return; - - QThread *thread = new QThread(nullptr); - auto *worker = new Ozon::GetLastTransactionsScript(m_deviceId, appCode, maxCount, nullptr); - worker->moveToThread(thread); - connect(thread, &QThread::started, worker, &Ozon::GetLastTransactionsScript::start); - connect(worker, &Ozon::GetLastTransactionsScript::finished, thread, &QThread::quit); - connect(worker, &Ozon::GetLastTransactionsScript::finished, worker, &QObject::deleteLater); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); - thread->start(); + if (appCode == "ozon") { + QThread *thread = new QThread(nullptr); + auto *worker = new Ozon::GetLastTransactionsScript(m_deviceId, appCode, maxCount, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Ozon::GetLastTransactionsScript::start); + connect(worker, &Ozon::GetLastTransactionsScript::finished, thread, &QThread::quit); + connect(worker, &Ozon::GetLastTransactionsScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); + } else if (appCode == "black") { + QThread *thread = new QThread(nullptr); + auto *worker = new Black::GetLastTransactionsScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Black::GetLastTransactionsScript::start); + connect(worker, &Black::GetLastTransactionsScript::finished, thread, &QThread::quit); + connect(worker, &Black::GetLastTransactionsScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); + } } void AccountSettingsWindow::sendAppStatus(const QString &deviceId, const QString &code, const QString &status) {