- Add TaskDumpRecorder to save task-related XML dumps and upload failure info to Telegram. - Integrate TaskDumpRecorder into `PayByPhoneScript` and `PayByCardScript` for Dushanbe and Ozon banks. - Extend ADB utilities to record XML dumps with TaskDumpRecorder and refine keyboard dismissal logic. - Enhance freeze detection with consecutive dump checks in CommonScript and payment scripts. - Update mock server profiles and scenario configurations with new material IDs.
477 lines
20 KiB
C++
477 lines
20 KiB
C++
#include "PayByPhoneScript.h"
|
||
|
||
#include <QRegularExpression>
|
||
#include <QThread>
|
||
|
||
#include <tesseract/baseapi.h>
|
||
#include <leptonica/allheaders.h>
|
||
|
||
#include "adb/AdbUtils.h"
|
||
#include "dump/TaskDumpRecorder.h"
|
||
#include "dao/BankProfileDAO.h"
|
||
#include "dao/DeviceDAO.h"
|
||
#include "dao/TransactionDAO.h"
|
||
#include "AppLogger.h"
|
||
|
||
#include <QCoreApplication>
|
||
static QString recognizeOperationId(const QByteArray &pngData) {
|
||
Pix *pix = pixReadMem(reinterpret_cast<const l_uint8 *>(pngData.constData()),
|
||
static_cast<size_t>(pngData.size()));
|
||
if (!pix) return {};
|
||
|
||
const int w = pixGetWidth(pix);
|
||
const int h = pixGetHeight(pix);
|
||
|
||
auto *api = new tesseract::TessBaseAPI();
|
||
if (api->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "rus+eng")) {
|
||
pixDestroy(&pix); delete api;
|
||
return {};
|
||
}
|
||
api->SetImage(pix);
|
||
api->Recognize(nullptr);
|
||
|
||
int opLineY = -1, opLineH = 0;
|
||
bool prevWasNomer = false;
|
||
tesseract::ResultIterator *ri = api->GetIterator();
|
||
if (ri) {
|
||
do {
|
||
const char *word = ri->GetUTF8Text(tesseract::RIL_WORD);
|
||
if (word) {
|
||
const QString w_str = QString::fromUtf8(word);
|
||
delete[] word;
|
||
if (w_str.contains(QString::fromUtf8("омер")) ||
|
||
w_str.contains(QString::fromUtf8("omep"))) {
|
||
prevWasNomer = true;
|
||
continue;
|
||
}
|
||
if (prevWasNomer &&
|
||
(w_str.contains(QString::fromUtf8("операции")) ||
|
||
w_str.contains(QString::fromUtf8("onepaции")))) {
|
||
int x1, y1, x2, y2;
|
||
ri->BoundingBox(tesseract::RIL_WORD, &x1, &y1, &x2, &y2);
|
||
opLineY = y1;
|
||
opLineH = y2 - y1;
|
||
break;
|
||
}
|
||
prevWasNomer = false;
|
||
}
|
||
} while (ri->Next(tesseract::RIL_WORD));
|
||
}
|
||
api->End();
|
||
delete api;
|
||
|
||
if (opLineY < 0) { pixDestroy(&pix); return {}; }
|
||
|
||
const int cropY = qMax(0, opLineY - opLineH / 2);
|
||
const int cropH = opLineH * 2;
|
||
BOX *box = boxCreate(w / 2, cropY, w / 2, qMin(cropH, h - cropY));
|
||
Pix *cropped = pixClipRectangle(pix, box, nullptr);
|
||
boxDestroy(&box);
|
||
pixDestroy(&pix);
|
||
if (!cropped) return {};
|
||
|
||
auto *api2 = new tesseract::TessBaseAPI();
|
||
if (api2->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "eng")) {
|
||
pixDestroy(&cropped); delete api2;
|
||
return {};
|
||
}
|
||
api2->SetVariable("tessedit_char_whitelist", "0123456789");
|
||
api2->SetPageSegMode(tesseract::PSM_SINGLE_LINE);
|
||
api2->SetImage(cropped);
|
||
char *text = api2->GetUTF8Text();
|
||
QString result = QString::fromUtf8(text).trimmed();
|
||
delete[] text;
|
||
pixDestroy(&cropped);
|
||
api2->End();
|
||
delete api2;
|
||
|
||
result.remove(QRegularExpression("[^0-9]"));
|
||
if (result.length() >= 8) {
|
||
qDebug() << "[Dushanbe::OCR] Precise operation ID:" << result;
|
||
return result;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
namespace Dushanbe {
|
||
|
||
PayByPhoneScript::PayByPhoneScript(
|
||
MaterialInfo account,
|
||
QString phone,
|
||
double amount,
|
||
QObject *parent
|
||
) : CommonScript(parent),
|
||
m_account(std::move(account)),
|
||
m_phone(std::move(phone)),
|
||
m_amount(amount) {
|
||
}
|
||
|
||
PayByPhoneScript::~PayByPhoneScript() = default;
|
||
|
||
void PayByPhoneScript::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;
|
||
}
|
||
|
||
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}};
|
||
TaskDumpRecorder::Scope dumpScope("dushanbe", "CREATE_TRANSACTION_PHONE", device.adbSerial, dumpMeta, &m_error);
|
||
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter));
|
||
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[Dushanbe::PayByPhone] Not on home screen, restarting app...";
|
||
if (!runAppAndGoToHomeScreen(device.adbSerial, app.package, app.pinCode,
|
||
device.screenWidth, device.screenHeight)) {
|
||
m_error = "Cannot reach home screen: " + device.name + " (" + device.id + ")";
|
||
qWarning() << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", device.id, m_error, {}, "CREATE_TRANSACTION");
|
||
AdbUtils::tryToKillApplication(device.adbSerial, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter));
|
||
}
|
||
|
||
makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight);
|
||
emit finishedWithResult(m_error);
|
||
}
|
||
|
||
void PayByPhoneScript::makePayment(
|
||
const QString &deviceId,
|
||
const QString &pinCode,
|
||
const int width,
|
||
const int height
|
||
) {
|
||
// 1. Тапаем "Переводы" в нижней навигации
|
||
Node transfersTab = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Переводы"));
|
||
if (transfersTab.isEmpty()) {
|
||
m_error = "Tab 'Переводы' not found on home screen";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[Dushanbe::PayByPhone] Tapping 'Переводы' tab";
|
||
AdbUtils::makeTap(deviceId, transfersTab.x(), transfersTab.y());
|
||
QThread::msleep(2000);
|
||
|
||
// 2. Ждём экран переводов — ищем "DC (по номеру телефона)"
|
||
bool transfersLoaded = false;
|
||
Node phoneTransferBtn;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||
phoneTransferBtn = xmlScreenParser.findNodeByContentDesc(
|
||
QString::fromUtf8("DC (по номеру телефона)"), true);
|
||
if (!phoneTransferBtn.isEmpty()) {
|
||
transfersLoaded = true;
|
||
break;
|
||
}
|
||
qDebug() << "[Dushanbe::PayByPhone] Waiting for transfers screen, attempt" << attempt + 1;
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!transfersLoaded) {
|
||
m_error = "Transfers screen not loaded or 'DC (по номеру телефона)' not found";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
// 3. Тапаем "DC (по номеру телефона)"
|
||
qDebug() << "[Dushanbe::PayByPhone] Tapping 'DC (по номеру телефона)'";
|
||
AdbUtils::makeTap(deviceId, phoneTransferBtn.x(), phoneTransferBtn.y());
|
||
QThread::msleep(2000);
|
||
|
||
// 4. Ждём экран ввода — ищем EditText с hint "Номер телефона получателя"
|
||
bool inputScreenLoaded = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||
Node editText = xmlScreenParser.findEditTextByHint(
|
||
QString::fromUtf8("Номер телефона получателя"));
|
||
if (editText.isEmpty()) {
|
||
editText = xmlScreenParser.findFirstEditText();
|
||
}
|
||
if (!editText.isEmpty()) {
|
||
inputScreenLoaded = true;
|
||
break;
|
||
}
|
||
qDebug() << "[Dushanbe::PayByPhone] Waiting for phone input screen, attempt" << attempt + 1;
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!inputScreenLoaded) {
|
||
m_error = "Phone input screen not loaded";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
// 5. Вводим номер телефона
|
||
Node phoneEditText = xmlScreenParser.findEditTextByHint(
|
||
QString::fromUtf8("Номер телефона получателя"));
|
||
if (phoneEditText.isEmpty()) {
|
||
phoneEditText = xmlScreenParser.findFirstEditText();
|
||
}
|
||
AdbUtils::makeTap(deviceId, phoneEditText.x(), phoneEditText.y());
|
||
QThread::msleep(500);
|
||
|
||
// Убираем "+" из номера если есть — вводим только цифры
|
||
QString phoneDigits = m_phone;
|
||
phoneDigits.remove('+');
|
||
AdbUtils::inputText(deviceId, phoneDigits);
|
||
QThread::msleep(1000);
|
||
|
||
// Скрываем клавиатуру чтобы поле суммы стало видимым
|
||
AdbUtils::goBack(deviceId);
|
||
QThread::msleep(1000);
|
||
|
||
// 6. Вводим сумму в поле "Сумма"
|
||
Node sumEditText;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||
sumEditText = xmlScreenParser.findEditTextByHint(QString::fromUtf8("Сумма"));
|
||
if (!sumEditText.isEmpty()) break;
|
||
qDebug() << "[Dushanbe::PayByPhone] Waiting for sum input, attempt" << attempt + 1 << "/ 10";
|
||
QThread::msleep(1500);
|
||
}
|
||
if (sumEditText.isEmpty()) {
|
||
m_error = "Sum input field not found";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
AdbUtils::makeTap(deviceId, sumEditText.x(), sumEditText.y());
|
||
QThread::msleep(500);
|
||
|
||
const QString amountStr = (m_amount == static_cast<int>(m_amount))
|
||
? QString::number(static_cast<int>(m_amount))
|
||
: QString::number(m_amount, 'f', 2);
|
||
AdbUtils::inputText(deviceId, amountStr);
|
||
QThread::msleep(1000);
|
||
|
||
// 6a. Скрываем клавиатуру и выбираем карту-источник (под полем "Комментарий")
|
||
AdbUtils::goBack(deviceId);
|
||
QThread::msleep(500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
{
|
||
const QString cardMask = "****" + m_account.lastNumbers;
|
||
Node cardNode;
|
||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||
if (!n.clickable) continue;
|
||
if (n.contentDesc.contains(cardMask)) { cardNode = n; break; }
|
||
}
|
||
if (cardNode.isEmpty()) {
|
||
m_error = "Source card not found: " + cardMask;
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
qDebug() << "[Dushanbe::PayByPhone] Selecting source card:" << cardMask;
|
||
AdbUtils::makeTap(deviceId, cardNode.x(), cardNode.y());
|
||
QThread::msleep(500);
|
||
}
|
||
|
||
// 7. Нажимаем "Далее"
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
Node daleeBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Далее"));
|
||
if (daleeBtn.isEmpty()) {
|
||
daleeBtn = xmlScreenParser.findButtonNode(QString::fromUtf8("Далее"), false);
|
||
}
|
||
if (daleeBtn.isEmpty()) {
|
||
m_error = "Button 'Далее' not found";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[Dushanbe::PayByPhone] Tapping 'Далее'";
|
||
AdbUtils::makeTap(deviceId, daleeBtn.x(), daleeBtn.y());
|
||
QThread::msleep(2000);
|
||
|
||
// 8. После "Далее" кнопка меняется на "Оплатить"
|
||
bool oplatitFound = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||
Node oplatitBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Оплатить"));
|
||
if (oplatitBtn.isEmpty()) {
|
||
oplatitBtn = xmlScreenParser.findButtonNode(QString::fromUtf8("Оплатить"), false);
|
||
}
|
||
if (!oplatitBtn.isEmpty()) {
|
||
qDebug() << "[Dushanbe::PayByPhone] Tapping 'Оплатить'";
|
||
AdbUtils::makeTap(deviceId, oplatitBtn.x(), oplatitBtn.y());
|
||
oplatitFound = true;
|
||
QThread::msleep(3000);
|
||
break;
|
||
}
|
||
qDebug() << "[Dushanbe::PayByPhone] Waiting for 'Оплатить', attempt" << attempt + 1;
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!oplatitFound) {
|
||
m_error = "Button 'Оплатить' not found";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
// 9. Ждём bottom sheet "Подтверждение платежа" и тапаем "Подтверждаю"
|
||
bool confirmed = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||
|
||
if (xmlScreenParser.isPinCodeScreen()) {
|
||
qDebug() << "[Dushanbe::PayByPhone] PIN code screen, entering PIN...";
|
||
inputPinCode(deviceId, pinCode);
|
||
QThread::msleep(3000);
|
||
continue;
|
||
}
|
||
|
||
Node confirmNode = xmlScreenParser.findNodeByContentDesc(
|
||
QString::fromUtf8("Подтверждение платежа"), true);
|
||
if (!confirmNode.isEmpty() &&
|
||
confirmNode.contentDesc.contains(QString::fromUtf8("Подтверждаю"))) {
|
||
// Кнопка "Подтверждаю" всегда внизу bottom sheet — тапаем по 92% высоты экрана
|
||
const int tapY = height * 92 / 100;
|
||
qDebug() << "[Dushanbe::PayByPhone] Tapping 'Подтверждаю' at y=" << tapY;
|
||
AdbUtils::makeTap(deviceId, confirmNode.x(), tapY);
|
||
confirmed = true;
|
||
QThread::msleep(4000);
|
||
break;
|
||
}
|
||
|
||
qDebug() << "[Dushanbe::PayByPhone] Waiting for confirmation sheet, attempt" << attempt + 1;
|
||
QThread::msleep(2000);
|
||
}
|
||
|
||
if (!confirmed) {
|
||
m_error = "Confirmation sheet not found";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
// 10. Ждём результат — "Платеж выполнен" или ошибку
|
||
bool success = false;
|
||
for (int attempt = 0; attempt < 15; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||
|
||
if (xmlScreenParser.isPinCodeScreen()) {
|
||
qDebug() << "[Dushanbe::PayByPhone] PIN code screen (retry), entering PIN...";
|
||
inputPinCode(deviceId, pinCode);
|
||
QThread::msleep(3000);
|
||
continue;
|
||
}
|
||
|
||
if (!xmlScreenParser.findNodeByContentDesc(
|
||
QString::fromUtf8("Платеж выполнен"), true).isEmpty()) {
|
||
success = true;
|
||
break;
|
||
}
|
||
|
||
if (!xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Ошибка"), true).isEmpty() ||
|
||
!xmlScreenParser.findTextNode(QString::fromUtf8("Ошибка")).isEmpty()) {
|
||
m_error = "Transfer failed: error screen detected";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[Dushanbe::PayByPhone] Waiting for result, attempt" << attempt + 1;
|
||
QThread::msleep(2000);
|
||
}
|
||
|
||
if (!success) {
|
||
m_error = "Payment result not received within timeout";
|
||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[Dushanbe::PayByPhone] Payment successful!";
|
||
m_error.clear();
|
||
|
||
// Скриншот чека — сохраняем как m_resultScreenshot и отправляем в мониторинг
|
||
m_resultScreenshot = AdbUtils::captureScreenshotBytes(deviceId);
|
||
const QByteArray &receiptImage = m_resultScreenshot;
|
||
if (!m_resultScreenshot.isEmpty())
|
||
emit screenshotReady(m_resultScreenshot);
|
||
|
||
// Парсим детали из content-desc "Платеж выполнен"
|
||
QString bankTime;
|
||
double fee = 0;
|
||
|
||
Node resultNode = xmlScreenParser.findNodeByContentDesc(
|
||
QString::fromUtf8("Платеж выполнен"), true);
|
||
if (!resultNode.isEmpty()) {
|
||
const QStringList lines = resultNode.contentDesc.split('\n');
|
||
for (int i = 0; i < lines.size(); ++i) {
|
||
const QString line = lines[i].trimmed();
|
||
if (line.startsWith(QString::fromUtf8("Комиссия:")) && i + 1 < lines.size()) {
|
||
fee = lines[i + 1].trimmed().toDouble();
|
||
} else if (line.startsWith(QString::fromUtf8("Дата:")) && i + 1 < lines.size()) {
|
||
bankTime = lines[i + 1].trimmed();
|
||
} else if (line.startsWith(QString::fromUtf8("Время:")) && i + 1 < lines.size()) {
|
||
bankTime += " " + lines[i + 1].trimmed();
|
||
}
|
||
}
|
||
qDebug() << "[Dushanbe::PayByPhone] Result: date=" << bankTime << "fee=" << fee;
|
||
}
|
||
|
||
// Генерируем ID транзакции из даты+времени (без перехода в историю)
|
||
const QString operationId = bankTime.isEmpty()
|
||
? QString("dushanbe_") + QDateTime::currentDateTimeUtc().toString("dd.MM.yyyy_HH:mm:ss")
|
||
: QString("dushanbe_") + QString(bankTime).replace(' ', '_');
|
||
qDebug() << "[Dushanbe::PayByPhone] Generated operationId:" << operationId;
|
||
|
||
// Сохраняем транзакцию
|
||
TransactionInfo tx;
|
||
tx.accountId = m_account.id;
|
||
tx.amount = -m_amount;
|
||
tx.fee = fee;
|
||
tx.status = TransactionStatus::Complete;
|
||
tx.type = TransactionType::Phone;
|
||
tx.phone = m_phone;
|
||
tx.bankName = "dushanbe_city_bank";
|
||
tx.bankTime = bankTime;
|
||
tx.bankTrExternalId = operationId;
|
||
tx.timestamp = QDateTime::currentDateTimeUtc();
|
||
tx.completeTime = QDateTime::currentDateTimeUtc();
|
||
tx.receiptImage = receiptImage;
|
||
|
||
const int txId = TransactionDAO::insertTransaction(tx);
|
||
if (txId != -1) {
|
||
qDebug() << "[Dushanbe::PayByPhone] Transaction saved, id:" << txId
|
||
<< "operationId:" << operationId;
|
||
if (!receiptImage.isEmpty()) {
|
||
TransactionDAO::updateReceiptImage(txId, receiptImage);
|
||
}
|
||
} else {
|
||
qWarning() << "[Dushanbe::PayByPhone] Failed to save transaction";
|
||
}
|
||
}
|
||
|
||
} // namespace Dushanbe
|