diff --git a/CMakeLists.txt b/CMakeLists.txt index c5f75de..d8a708a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) +add_compile_definitions(QT_MESSAGELOGCONTEXT) + if (WIN32) # Укажи путь к Qt6 set(CMAKE_PREFIX_PATH "C:/Qt/6.9.0/msvc2022_arm64") @@ -75,6 +77,7 @@ if (WIN32) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/database/app_data.db" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/config.ini" DESTINATION "${CMAKE_BINARY_DIR}") elseif (APPLE) target_link_libraries(ARCDesktopProject diff --git a/config.ini b/config.ini index e2efd37..551c814 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,16 @@ +[common] +varsion=0.0.1 + [apps] list=rshb [rshb] -name=Рос Сельхоз Банк -android_package_name=ru.rshb.dbo \ No newline at end of file +name=Росcельхозбанк +android_package_name=ru.rshb.dbo + +[net] +appInfoUpdate="http://localhost:9999/api/v1/app/update" +accountUpdate=http://localhost:9999/api/v1/account/update +paymentsGet=http://localhost:9999/api/v1/payments +transactionUpdate=http://localhost:9999/api/v1/transaction/update +transactionInsert=http://localhost:9999/api/v1/transaction/insert \ No newline at end of file diff --git a/database/app_data.db b/database/app_data.db index ba7d920..7f38617 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/database/dao/DeviceDAO.cpp b/database/dao/DeviceDAO.cpp index 69cdbe8..ba54022 100644 --- a/database/dao/DeviceDAO.cpp +++ b/database/dao/DeviceDAO.cpp @@ -45,6 +45,28 @@ QList DeviceDAO::getAllDevices(const bool online) { return devices; } +QList DeviceDAO::getOnlineDeviceCodes() { + QList codes; + QSqlQuery query(DatabaseManager::instance().database()); + QString sql = R"( + SELECT id + FROM devices + WHERE image IS NOT NULL AND status = 'device' + )"; + query.prepare(sql); + + if (!query.exec()) { + qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text(); + return {}; + } + + while (query.next()) { + codes.append(query.value("id").toString()); + } + + return codes; +} + DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) { QSqlQuery query(DatabaseManager::instance().database()); diff --git a/database/dao/DeviceDAO.h b/database/dao/DeviceDAO.h index 2987c92..ce60436 100644 --- a/database/dao/DeviceDAO.h +++ b/database/dao/DeviceDAO.h @@ -13,4 +13,6 @@ public: [[nodiscard]] static bool updateImage(const QString &deviceId, const QByteArray &imageData); [[nodiscard]] static QList getAllDevices(bool online); + + [[nodiscard]] static QList getOnlineDeviceCodes(); }; diff --git a/database/dao/EventDAO.cpp b/database/dao/EventDAO.cpp index 82c2540..a3c7d28 100644 --- a/database/dao/EventDAO.cpp +++ b/database/dao/EventDAO.cpp @@ -12,6 +12,7 @@ EventInfo parseEvent(const QSqlQuery &query) { event.status = eventStatusFromString(query.value("status").toString()); event.externalId = query.value("external_id").toInt(); event.accountId = query.value("account_id").toInt(); + event.deviceId = query.value("device_id").toString(); event.amount = query.value("amount").toDouble(); event.phone = query.value("phone").toString(); event.bankName = query.value("bank_name").toString(); @@ -26,22 +27,23 @@ int EventDAO::insertEvent(const EventInfo &event) { query.prepare(R"( INSERT INTO events ( - status, external_id, account_id, amount, phone, bank_name, timestamp + status, external_id, account_id, amount, phone, bank_name, timestamp, device_id ) VALUES ( - :status, :external_id, :account_id, :amount, :phone, :bank_name, :timestamp + :status, :external_id, :account_id, :amount, :phone, :bank_name, :timestamp, :device_id ) )"); query.bindValue(":status", eventStatusToString(event.status)); query.bindValue(":external_id", event.externalId); query.bindValue(":account_id", event.accountId); + query.bindValue(":device_id", event.deviceId); query.bindValue(":amount", event.amount); query.bindValue(":phone", event.phone); query.bindValue(":bank_name", event.bankName); query.bindValue(":timestamp", DateUtils::toUtcString(DateUtils::getUtcNow())); if (!query.exec()) { - qWarning() << "Ошибка при вставке event:" << query.lastError().text(); + qCritical() << "Ошибка при вставке event:" << query.lastError().text(); return -1; } @@ -78,7 +80,7 @@ bool EventDAO::updateEvent( } if (!query.exec()) { - qWarning() << "Ошибка при обновлении event:" << query.lastError().text(); + qCritical() << "Ошибка при обновлении event:" << query.lastError().text(); return false; } return true; @@ -89,14 +91,14 @@ QList EventDAO::getAllEvents(const EventStatus &status) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - SELECT id, account_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp + SELECT id, account_id, device_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp FROM events WHERE status = :status )"); query.bindValue(":status", eventStatusToString(status)); if (!query.exec()) { - qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text(); + qWarning() << "Ошибка при получении getAllEvents:" << query.lastError().text(); return events; } @@ -106,3 +108,26 @@ QList EventDAO::getAllEvents(const EventStatus &status) { return events; } + +EventInfo EventDAO::getFirstWaitEventByDeviceId(const QString &key) { + QSqlQuery query(DatabaseManager::instance().database()); + query.prepare(R"( + SELECT id, account_id, device_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp + FROM events + WHERE status = :status AND device_id = :device_id + ORDER BY timestamp ASC + LIMIT 1 + )"); + query.bindValue(":status", eventStatusToString(EventStatus::Wait)); + query.bindValue(":device_id", key); + + if (!query.exec()) { + qCritical() << "Ошибка при получении getFirstWaitEventByDeviceId:" << query.lastError().text(); + return {}; + } + + while (query.next()) { + return parseEvent(query); + } + return {}; +} diff --git a/database/dao/EventDAO.h b/database/dao/EventDAO.h index 9e4982b..3572a5d 100644 --- a/database/dao/EventDAO.h +++ b/database/dao/EventDAO.h @@ -12,4 +12,6 @@ public: ); [[nodiscard]] static QList getAllEvents(const EventStatus &status); + + [[nodiscard]] static EventInfo getFirstWaitEventByDeviceId(const QString &key); }; diff --git a/main.cpp b/main.cpp index 82388d9..edbb7c9 100644 --- a/main.cpp +++ b/main.cpp @@ -16,6 +16,7 @@ #include #include +#include "EventHandler.h" #include "net/NetworkService.h" #include "rshb/GetLastDaysHistoryScript.h" #include "rshb/HomeScreenScript.h" @@ -145,7 +146,7 @@ static QFile logFile; static QMutex logMutex; void messageHandler( - QtMsgType type, + const QtMsgType type, const QMessageLogContext &context, const QString &msg ) { @@ -166,8 +167,9 @@ void messageHandler( break; } - const QString line = QString("%1 [%2] %3\n").arg(timestamp, level, msg); + const QString line = QString("%1 [%2] [%3:%4] %5\n") + .arg(timestamp, level, context.function, QString::number(context.line), msg); // Записываем в файл (потокобезопасно) { QMutexLocker locker(&logMutex); @@ -178,7 +180,7 @@ void messageHandler( } // По желанию — дублировать в консоль - QByteArray localMsg = line.toLocal8Bit(); + const QByteArray localMsg = line.toLocal8Bit(); fprintf(stderr, "%s", localMsg.constData()); if (type == QtFatalMsg) { @@ -196,21 +198,28 @@ int main(int argc, char *argv[]) { // Устанавливаем свой обработчик qInstallMessageHandler(messageHandler); - auto *thread = new QThread; - auto *worker = new NetworkService; - - // FIXME доставить десктоп ид из настроеек - worker->setDeviceData("DT88bokcwQ", ""); + auto *worker = new EventHandler; worker->moveToThread(thread); - QObject::connect(thread, &QThread::started, worker, &NetworkService::getPayments); - QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit); - QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater); + QObject::connect(thread, &QThread::started, worker, &EventHandler::start); + QObject::connect(worker, &EventHandler::finished, thread, &QThread::quit); + QObject::connect(worker, &EventHandler::finished, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); - // setupScriptAutoPaymentAndThread(app); - // setupWorkerAndThread(app); + auto *paymentThread = new QThread; + auto *networkService = new NetworkService; + + // FIXME доставить десктоп ид из настроеек + networkService->setDeviceData("DT88bokcwQ", ""); + networkService->moveToThread(paymentThread); + QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::start); + QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit); + QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater); + QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater); + paymentThread->start(); + + setupWorkerAndThread(app); MainWindow window; window.show(); diff --git a/migrations.sql b/migrations.sql index 987561f..d995f42 100644 --- a/migrations.sql +++ b/migrations.sql @@ -74,6 +74,7 @@ CREATE TABLE IF NOT EXISTS events id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER, external_id INTEGER, + device_id TEXT, amount REAL, phone TEXT, @@ -85,5 +86,6 @@ CREATE TABLE IF NOT EXISTS events complete_time DATETIME, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE, + FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE, UNIQUE (external_id) ); \ No newline at end of file diff --git a/models/db/EventInfo.h b/models/db/EventInfo.h index 6973f25..95ce7d9 100644 --- a/models/db/EventInfo.h +++ b/models/db/EventInfo.h @@ -31,6 +31,7 @@ struct EventInfo { int id = -1; int externalId = -1; int accountId = -1; + QString deviceId; double amount = 0; QString phone; diff --git a/services/DeviceScreener.cpp b/services/DeviceScreener.cpp index a3051ea..39ae889 100644 --- a/services/DeviceScreener.cpp +++ b/services/DeviceScreener.cpp @@ -1,6 +1,6 @@ #include "DeviceScreener.h" -#include +#include #include #include #include @@ -16,11 +16,13 @@ #include DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) { + const QSettings settings("config.ini", QSettings::IniFormat); + m_urlAppUpdate = settings.value("net/appInfoUpdate").toString(); } DeviceScreener::~DeviceScreener() = default; -void postDevicesData(const QList &devices) { +void DeviceScreener::postDevicesData(const QList &devices) { auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart jsonPart; @@ -60,7 +62,7 @@ void postDevicesData(const QList &devices) { multiPart->append(uniquePart); QNetworkAccessManager manager; // создаём внутри потока - QUrl url("http://localhost:9999/api/v1/app/update"); + QUrl url(m_urlAppUpdate); QNetworkRequest request(url); // делаем запрос @@ -82,10 +84,12 @@ void postDevicesData(const QList &devices) { QJsonDocument doc = QJsonDocument::fromJson(raw, &err); if (err.error == QJsonParseError::NoError && doc.isObject()) { QJsonObject root = doc.object(); - qDebug() << "Успешный ответ: " << root.value("success").toBool(); + if (!root.value("success").toBool()) { + qWarning() << root; + } } } else { - qWarning() << "Ошибка:" << reply->errorString(); + qWarning() << "Response error:" << reply->errorString() << m_urlAppUpdate; } reply->deleteLater(); } @@ -116,12 +120,14 @@ void DeviceScreener::start() { } } - postDevicesData(deviceInfos); + if (!deviceInfos.empty()) { + postDevicesData(deviceInfos); + } if (!DeviceDAO::markDevicesOfflineExcept(onlineIds)) { qDebug() << "Cant mark devices offline"; } - QThread::sleep(3); + QThread::msleep(1000); } emit finished(); } diff --git a/services/DeviceScreener.h b/services/DeviceScreener.h index e004ca4..b26688a 100644 --- a/services/DeviceScreener.h +++ b/services/DeviceScreener.h @@ -21,10 +21,13 @@ signals: private: bool m_running = true; + QString m_urlAppUpdate; static QList > readAdbDevices(); static QByteArray takeScreenshot(const QString &deviceId); static DeviceInfo readDeviceInfo(const QString &deviceId); + + void postDevicesData(const QList &devices); }; diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp new file mode 100644 index 0000000..5c709b7 --- /dev/null +++ b/services/EventHandler.cpp @@ -0,0 +1,130 @@ +#include "EventHandler.h" + +#include +#include +#include + +#include "dao/AccountDAO.h" +#include "dao/DeviceDAO.h" +#include "dao/EventDAO.h" +#include "rshb/CommonScript.h" +#include "rshb/PayByPhoneScript.h" + +EventHandler::EventHandler(QObject *parent) : QObject(parent) { + m_checkTimer = new QTimer(this); + m_checkTimer->setInterval(1000); + connect(m_checkTimer, &QTimer::timeout, this, &EventHandler::checkAndRestart); +} + +EventHandler::~EventHandler() = default; + +void EventHandler::start() { + m_devices = DeviceDAO::getOnlineDeviceCodes(); + + clearAll(); + // Запустим сразу потоки для всех ключей + for (auto &key: m_devices) { + startThreadForKey(key); + } + + m_checkTimer->start(); +} + +void EventHandler::checkAndRestart() { + if (!m_running) { + return; + } + m_devices = DeviceDAO::getOnlineDeviceCodes(); + + for (const auto &key: m_devices) { + const QThread *thr = m_threads.value(key, nullptr); + if (!thr || !thr->isRunning()) { + startThreadForKey(key); + } + } +} + +void EventHandler::startThreadForKey(const QString &key) { + const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(key); + if (event.id == -1) { + return; + } + if (!EventDAO::updateEvent(event.id, EventStatus::InProgress)) { + qCritical() << "Cant update event: " << event.id; + return; + } + + qDebug() << "Thread for key" << key << "running"; + // FIXME пока это только оплата, + const AccountInfo account = AccountDAO::getAccountById(event.accountId); + const auto thr = new QThread(this); + auto *worker = new PayByPhoneScript(account, event.phone, event.bankName, event.amount); + + worker->moveToThread(thr); + connect(thr, &QThread::started, worker, &PayByPhoneScript::start); + connect(worker, &PayByPhoneScript::finished, thr, &QThread::quit); + connect(worker, &PayByPhoneScript::finished, worker, &QObject::deleteLater); + connect(thr, &QThread::finished, thr, &QObject::deleteLater); + + // При нормальном завершении — перезапустить новый поток + connect(worker, &PayByPhoneScript::finished, this, + [this, key]() { + // qDebug() << "Thread finished for key" << key << "- restarting."; + if (const auto tm = m_timers.take(key)) { + tm->stop(); + tm->deleteLater(); + } + m_threads.remove(key); + }); + + // Таймер на 5 минут — если не успел finish(), убить и перезапустить + const auto timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, [this, key]() { + qWarning() << "Timeout for key" << key << "- killing stuck thread."; + if (QThread *thread = m_threads.value(key); thread && thread->isRunning()) { + thread->requestInterruption(); // мягкое прерывание + if (!thread->wait(500)) { + qWarning() << "Hard terminate for key" << key; + thread->terminate(); + thread->wait(); + } + } + if (const auto tm = m_timers.take(key)) { + tm->deleteLater(); + } + m_threads.remove(key); + }); + + // Сохраняем и запускаем + m_threads[key] = thr; + m_timers[key] = timer; + + timer->start(5 * 60 * 1000); + thr->start(); +} + +void EventHandler::clearAll() { + // Останавливаем и удаляем старые + for (QThread *thread: m_threads) { + if (thread->isRunning()) { + thread->requestInterruption(); + thread->terminate(); + thread->wait(); + } + thread->deleteLater(); + } + m_threads.clear(); + + for (QTimer *timer: m_timers) { + timer->stop(); + timer->deleteLater(); + } + m_timers.clear(); +} + +void EventHandler::stop() { + m_running = false; + m_checkTimer->stop(); + clearAll(); +} diff --git a/services/EventHandler.h b/services/EventHandler.h new file mode 100644 index 0000000..c6f1380 --- /dev/null +++ b/services/EventHandler.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include + +class EventHandler final : public QObject { + Q_OBJECT + +public: + explicit EventHandler(QObject *parent = nullptr); + + ~EventHandler() override; + +public slots: + void start(); + + void stop(); + +signals: + void finished(); + +private slots: + void checkAndRestart(); // вызывается по таймеру + +private: + bool m_running = true; + QMap m_threads; + QMap m_timers; + QTimer *m_checkTimer = nullptr; + + QStringList m_devices; + + void clearAll(); + + void startThreadForKey(const QString &key); +}; diff --git a/services/net/NetworkService.cpp b/services/net/NetworkService.cpp index 0239fc2..e850f84 100644 --- a/services/net/NetworkService.cpp +++ b/services/net/NetworkService.cpp @@ -1,11 +1,13 @@ #include "NetworkService.h" -#include #include #include #include #include #include +#include +#include +#include #include "dao/AccountDAO.h" #include "dao/EventDAO.h" @@ -13,6 +15,12 @@ #include "db/EventInfo.h" NetworkService::NetworkService(QObject *parent) : QObject(parent) { + const QSettings settings("config.ini", QSettings::IniFormat); + m_urlAccountUpdate = settings.value("net/accountUpdate").toString(); + m_urlPaymentsGet = settings.value("net/paymentsGet").toString(); + m_urlTransactionUpdate = settings.value("net/transactionUpdate").toString(); + m_urlTransactionInsert = settings.value("net/transactionInsert").toString(); + m_manager = new QNetworkAccessManager(this); } NetworkService::~NetworkService() = default; @@ -40,11 +48,10 @@ void addFormMetadata(QHttpMultiPart *multiPart, const QByteArray &fieldValue) { } QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path) { - QNetworkAccessManager manager; // создаём внутри потока const QUrl url(path); const QNetworkRequest request(url); - QNetworkReply *reply = manager.post(request, multiPart); + QNetworkReply *reply = m_manager->post(request, multiPart); multiPart->setParent(reply); QEventLoop loop; @@ -64,20 +71,17 @@ QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path) if (root.value("success").toBool()) { result = root.value("data"); } else { - qWarning() << "Ошибка:" << root; + qCritical() << "Ошибка:" << root; } } } else { - qWarning() << "Ошибка:" << reply->errorString(); + qCritical() << "Ошибка:" << reply->errorString() << path; } reply->deleteLater(); return result; } - QJsonArray NetworkService::get(const QString &path, const QMap ¶ms) { - QNetworkAccessManager manager; // создаём внутри потока - QUrl url(path); QUrlQuery query; for (auto it = params.constBegin(); it != params.constEnd(); ++it) { @@ -86,7 +90,8 @@ QJsonArray NetworkService::get(const QString &path, const QMap url.setQuery(query); const QNetworkRequest request(url); - QNetworkReply *reply = manager.get(request); + QNetworkReply *reply = m_manager->get(request); + QScopedPointer guard(reply); QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); @@ -121,9 +126,9 @@ void NetworkService::postAccountsData() { addFormMetadata(multiPart, m_json); addFormField(multiPart, "desktopUniqueName", m_desktopId); addFormField(multiPart, "androidUniqueName", m_deviceId); - const bool isSuccess = post(multiPart, "http://localhost:9999/api/v1/account/update").toBool(); + const bool isSuccess = post(multiPart, m_urlAccountUpdate).toBool(); if (!isSuccess) { - qDebug() << "Cant post accounts data"; + qCritical() << "Cant post accounts data"; } emit finished(); @@ -132,7 +137,7 @@ void NetworkService::postAccountsData() { void NetworkService::getPayments() { QMap params; params["desktopUniqueName"] = m_desktopId; - const QJsonArray data = get("http://localhost:9999/api/v1/payments", params); + const QJsonArray data = get(m_urlPaymentsGet, params); for (QJsonValue item: data) { QJsonObject obj = item.toObject(); @@ -149,20 +154,16 @@ void NetworkService::getPayments() { AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumbers); eventInfo.accountId = account.id; + eventInfo.deviceId = account.deviceId; - - int id = EventDAO::insertEvent(eventInfo); - if (id == -1) { - qDebug() << "Cant insert event"; - } else { - if (!EventDAO::updateEvent(id, EventStatus::Wait)) { - qDebug() << "Cant update event"; - } + if (int id = EventDAO::insertEvent(eventInfo); id == -1) { + qCritical() << "Cant insert event: " << eventInfo.externalId; } } - - emit finished(); + if (!m_running) { + emit finished(); + } } @@ -174,9 +175,9 @@ void NetworkService::updateTransactionData() { addFormField(multiPart, "androidUniqueName", m_deviceId); addFormField(multiPart, "lastNumbers", m_extraData["lastNumbers"].toString()); - const bool isSuccess = post(multiPart, "http://localhost:9999/api/v1/transaction/update").toBool(); + const bool isSuccess = post(multiPart, m_urlTransactionUpdate).toBool(); if (!isSuccess) { - qDebug() << "Cant update transaction"; + qCritical() << "Cant update transaction: updateTransactionData"; } emit finished(); @@ -190,16 +191,23 @@ void NetworkService::insertTransactionData() { addFormField(multiPart, "androidUniqueName", m_deviceId); addFormField(multiPart, "lastNumbers", m_extraData["lastNumbers"].toString()); - const int id = post(multiPart, "http://localhost:9999/api/v1/transaction/insert").toInt(); + const int id = post(multiPart, m_urlTransactionInsert).toInt(); if (id > 0) { const int transactionId = m_extraData["transactionId"].toInt(); if (!TransactionDAO::updateExternalTransactionId(transactionId, id)) { - qDebug() << "Cant update transaction id"; + qCritical() << "Cant update transaction id: " << id; } } emit finished(); } +void NetworkService::start() { + QTimer *poller = new QTimer(this); + poller->setInterval(1000); + connect(poller, &QTimer::timeout, this, &NetworkService::getPayments); + poller->start(); +} + void NetworkService::stop() { m_running = false; } diff --git a/services/net/NetworkService.h b/services/net/NetworkService.h index 41f35bd..717c3d6 100644 --- a/services/net/NetworkService.h +++ b/services/net/NetworkService.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -29,6 +30,7 @@ public slots: void updateTransactionData(); + void start(); void stop(); signals: @@ -41,6 +43,13 @@ private: QMap m_extraData = {}; bool m_running = true; + QString m_urlAccountUpdate; + QString m_urlPaymentsGet; + QString m_urlTransactionUpdate; + QString m_urlTransactionInsert; + + QNetworkAccessManager *m_manager; + QJsonValue post(QHttpMultiPart *multiPart, const QString &path);