qt+c++编写的监听链上钱包转账信息适合做USDT支付通道.本站的支付通道就是基于这么做的。
有几个注意的地方:
申请波场apikey否则查询会有诸多限制。
付款金额带小数,否则容易张冠李戴
超时未支付的订单不处理,代码逻辑很清楚,都加了详细的备注说明,可以参考代码自己扩展需要的功能,反正那些usdt支付平台都是这种方式。
初始化
// 初始化 Tron 监控 TronMonitor tronMonitor; tronMonitor.setMysql(&mysql); tronMonitor.setRedis(&redis); QJsonObject tronCfg = config["tron"].toObject(); tronMonitor.setApiKey(tronCfg["api_key"].toString()); tronMonitor.setReceiveAddress(tronCfg["receive_address"].toString()); tronMonitor.setPollInterval(tronCfg["poll_interval"].toInt(10)); tronMonitor.start();
函数实现
#include "TronMonitor.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDateTime>
#include <QDebug>
TronMonitor::TronMonitor(QObject* parent)
: QObject(parent)
{
m_net = new QNetworkAccessManager(this);
m_timer = new QTimer(this);
// 确保信号槽连接成功
bool connected1 = connect(m_timer, &QTimer::timeout, this, &TronMonitor::onPoll);
bool connected2 = connect(m_net, &QNetworkAccessManager::finished, this, &TronMonitor::onReplyFinished);
if (!connected1 || !connected2) {
qWarning() << "[TronMonitor] 信号槽连接失败!";
}
}
void TronMonitor::start()
{
if (m_receiveAddress.isEmpty()) {
emit logMessage("[TronMonitor] ❌ 未配置收款地址,跳过启动");
return;
}
if (m_apiKey.isEmpty()) {
emit logMessage("[TronMonitor] ❌ 未配置 API Key,跳过启动");
return;
}
// 从 Redis 加载已处理的 tx_hash 列表(防重启后重复处理)
if (m_redis && m_redis->isConnected()) {
QStringList hashes = m_redis->lrange("tron:processed_txs", 0, -1);
for (const QString& hash : hashes) {
m_processedTxHashes.insert(hash);
}
emit logMessage(QString("[TronMonitor] 📦 从 Redis 加载 %1 个已处理交易").arg(hashes.size()));
}
else {
emit logMessage("[TronMonitor] ️ Redis 未连接,无法加载历史交易记录");
}
m_timer->start(m_pollInterval * 1000);
emit logMessage(QString("[TronMonitor] ✅ 已启动,轮询间隔 %1 秒,监听地址: %2")
.arg(m_pollInterval).arg(m_receiveAddress));
// 立即执行一次
onPoll();
}
void TronMonitor::stop()
{
m_timer->stop();
emit logMessage("[TronMonitor] 已停止");
}
void TronMonitor::onPoll()
{
if (m_pendingReply) {
emit logMessage("[TronMonitor] ⚠️ 上一个请求尚未完成,跳过本次轮询");
return;
}
// 计算 10 分钟前的时间戳(毫秒)
qint64 minTimestamp = (QDateTime::currentSecsSinceEpoch() - 600) * 1000;
QDateTime minTime = QDateTime::fromMSecsSinceEpoch(minTimestamp);
emit logMessage(QString("[TronMonitor] 🔍 开始查询链上交易... (范围: %1 ~ 现在)")
.arg(minTime.toString("hh:mm:ss")));
// 构建 TronGrid API URL
QString url = QString("https://api.trongrid.io/v1/accounts/%1/transactions/trc20"
"?limit=100&only_confirmed=true&min_timestamp=%2&contract_address=%3")
.arg(m_receiveAddress)
.arg(QString::number(minTimestamp))
.arg(USDT_CONTRACT);
emit logMessage(QString("[TronMonitor] API URL: %1").arg(url));
emit logMessage(QString("[TronMonitor] API Key: %1...%2")
.arg(m_apiKey.left(8)).arg(m_apiKey.right(4)));
emit logMessage(QString("[TronMonitor] API URL: %1").arg(url));
emit logMessage(QString("[TronMonitor] API Key 长度: %1").arg(m_apiKey.length()));
emit logMessage(QString("[TronMonitor] 🔑 API Key 完整值: [%1]").arg(m_apiKey));
emit logMessage(QString("[TronMonitor] 🔑 API Key HEX: %1")
.arg(QString::fromLatin1(m_apiKey.toUtf8().toHex())));
QNetworkRequest req{ QUrl{url} };
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
req.setRawHeader("TRON-PRO-API-KEY", m_apiKey.toUtf8());
// 忽略 SSL 证书验证
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
req.setSslConfiguration(sslConfig);
// 发送请求并保存 reply 指针
m_pendingReply = m_net->get(req);
// 连接 SSL 错误信号
connect(m_pendingReply, &QNetworkReply::sslErrors, this, [this](const QList<QSslError>& errors) {
for (const QSslError& err : errors) {
emit logMessage(QString("[TronMonitor] ️ SSL 错误: %1").arg(err.errorString()));
}
if (m_pendingReply) {
m_pendingReply->ignoreSslErrors();
}
});
// 设置超时(5秒)
QTimer::singleShot(5000, this, [this]() {
if (m_pendingReply && m_pendingReply->isRunning()) {
emit logMessage("[TronMonitor] ❌ 请求超时(5秒),强制取消");
m_pendingReply->abort();
m_pendingReply = nullptr;
}
});
}
void TronMonitor::onReplyFinished(QNetworkReply* reply)
{
// 清空 pending reply
if (m_pendingReply == reply) {
m_pendingReply = nullptr;
}
if (!reply) {
emit logMessage("[TronMonitor] ❌ reply 为空");
return;
}
qint64 startTime = QDateTime::currentMSecsSinceEpoch();
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QByteArray rawData = reply->readAll();
emit logMessage(QString("[TronMonitor] 📡 HTTP 状态码: %1").arg(httpStatus));
emit logMessage(QString("[TronMonitor] 📦 响应大小: %1 bytes").arg(rawData.size()));
if (reply->error() != QNetworkReply::NoError) {
emit logMessage(QString("[TronMonitor] ❌ API 请求失败: %1 (HTTP: %2, 耗时: %3ms)")
.arg(reply->errorString())
.arg(httpStatus)
.arg(QDateTime::currentMSecsSinceEpoch() - startTime));
if (!rawData.isEmpty()) {
emit logMessage(QString("[TronMonitor] 响应内容: %1").arg(QString::fromUtf8(rawData)));
}
reply->deleteLater();
return;
}
if (rawData.isEmpty()) {
emit logMessage("[TronMonitor] ⚠️ 响应内容为空");
reply->deleteLater();
return;
}
// 打印响应内容前 500 字符用于调试
QString preview = QString::fromUtf8(rawData.left(500));
if (rawData.size() > 500) preview += "...";
emit logMessage(QString("[TronMonitor] 📄 响应预览: %1").arg(preview));
QJsonDocument doc = QJsonDocument::fromJson(rawData);
QJsonObject root = doc.object();
if (!root["success"].toBool()) {
emit logMessage(QString("[TronMonitor] ❌ API 返回失败: %1 (耗时: %2ms)")
.arg(root["Error"].toString())
.arg(QDateTime::currentMSecsSinceEpoch() - startTime));
reply->deleteLater();
return;
}
QJsonArray txs = root["data"].toArray();
qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - startTime;
if (txs.isEmpty()) {
emit logMessage(QString("[TronMonitor] ✅ 无新交易 (耗时: %1ms)").arg(elapsed));
reply->deleteLater();
return;
}
emit logMessage(QString("[TronMonitor] 📊 收到 %1 笔交易 (耗时: %2ms)")
.arg(txs.size()).arg(elapsed));
processTransactions(txs);
reply->deleteLater();
}
void TronMonitor::processTransactions(const QJsonArray& txs)
{
int matchedCount = 0;
int skippedCount = 0;
int totalChecked = txs.size();
emit logMessage(QString("[TronMonitor] 🔄 开始处理 %1 笔交易...").arg(totalChecked));
for (int i = 0; i < txs.size(); i++) {
const QJsonValue& val = txs[i];
QJsonObject tx = val.toObject();
QString txHash = tx["transaction_id"].toString();
QString toAddr = tx["to"].toString().toLower();
QString fromAddr = tx["from"].toString().toLower();
QString contractAddr = tx["token_info"].toObject()["address"].toString().toLower();
QString amountStr = tx["value"].toString();
qint64 txTimestamp = tx["block_timestamp"].toVariant().toLongLong() / 1000; // 转为秒
QDateTime txTime = QDateTime::fromSecsSinceEpoch(txTimestamp);
emit logMessage(QString("[TronMonitor] [%1/%2] 检查交易: %3")
.arg(i + 1).arg(totalChecked).arg(txHash.left(16) + "..."));
// 只处理 USDT TRC-20 转入平台地址的交易
if (contractAddr != QString(USDT_CONTRACT).toLower()) {
emit logMessage(QString("[TronMonitor] ⏭️ 跳过: 非 USDT 合约 (%1)").arg(contractAddr.left(10)));
skippedCount++;
continue;
}
if (toAddr != m_receiveAddress.toLower()) {
emit logMessage(QString("[TronMonitor] ⏭️ 跳过: 收款地址不匹配 (to=%1)").arg(toAddr.left(10)));
skippedCount++;
continue;
}
// 跳过已处理的交易(通过 tx_hash 去重)
if (m_processedTxHashes.contains(txHash)) {
emit logMessage(QString("[TronMonitor] ⏭️ 跳过: 已处理过的交易"));
skippedCount++;
continue;
}
// 将 SUN 转为 USDT(1 USDT = 1,000,000 SUN)
double amountSun = amountStr.toDouble();
double amountUsdt = amountSun / 1000000.0;
// 四舍五入到 2 位小数,与订单金额匹配
amountUsdt = qRound(amountUsdt * 100.0) / 100.0;
emit logMessage(QString("[TronMonitor] 💰 金额: %1 USDT, 时间: %2, 来源: %3")
.arg(amountUsdt, 0, 'f', 3)
.arg(txTime.toString("yyyy-MM-dd hh:mm:ss"))
.arg(fromAddr.left(10) + "..." + fromAddr.right(4)));
// 查找匹配的待支付订单
if (!m_mysql || !m_mysql->isConnected()) {
emit logMessage("[TronMonitor] ❌ MySQL 未连接,跳过订单匹配");
skippedCount++;
continue;
}
// 关键条件:
// 1. status = 0(待支付)
// 2. amount 精确匹配(带小数)
// 3. create_time < txTimestamp(链上交易时间必须晚于订单创建时间)
QString whereClause = QString("status = 0 AND amount = %1 AND payment_method = 'usdt' AND create_time < %2")
.arg(amountUsdt, 0, 'f', 3)
.arg(txTimestamp);
emit logMessage(QString("[TronMonitor] 🔎 查询订单: %1").arg(whereClause));
QVariantList orders = m_mysql->selectList("user_recharge", whereClause, "id ASC", 10);
if (orders.isEmpty()) {
emit logMessage(QString("[TronMonitor] ⚠️ 未找到匹配的待支付订单"));
// 标记为已处理,防止下次重复扫描
m_processedTxHashes.insert(txHash);
if (m_redis && m_redis->isConnected()) {
m_redis->lpush("tron:processed_txs", txHash);
m_redis->expire("tron:processed_txs", 86400); // 保留 24 小时
}
skippedCount++;
continue;
}
emit logMessage(QString("[TronMonitor] 📋 找到 %1 个候选订单").arg(orders.size()));
// 可能有多个订单金额相同,取最早创建的且 create_time < txTimestamp 的
QVariantMap matchedOrder;
for (const QVariant& ord : orders) {
QVariantMap order = ord.toMap();
int orderCreateTime = order["create_time"].toInt();
QDateTime orderTime = QDateTime::fromSecsSinceEpoch(orderCreateTime);
emit logMessage(QString("[TronMonitor] 订单 id=%1, 创建时间=%2, 金额=%3")
.arg(order["id"].toInt())
.arg(orderTime.toString("yyyy-MM-dd hh:mm:ss"))
.arg(order["amount"].toDouble(), 0, 'f', 3));
if (orderCreateTime < txTimestamp) {
matchedOrder = order;
emit logMessage(QString("[TronMonitor] ✅ 匹配成功: 订单创建时间早于链上交易时间"));
break; // 取第一个符合条件的(按 id ASC 排序,即最早创建的)
}
else {
emit logMessage(QString("[TronMonitor] ️ 跳过: 订单创建时间晚于链上交易时间"));
}
}
if (matchedOrder.isEmpty()) {
emit logMessage(QString("[TronMonitor] 所有候选订单的创建时间都晚于链上交易时间,跳过"));
m_processedTxHashes.insert(txHash);
if (m_redis && m_redis->isConnected()) {
m_redis->lpush("tron:processed_txs", txHash);
m_redis->expire("tron:processed_txs", 86400);
}
skippedCount++;
continue;
}
int orderId = matchedOrder["id"].toInt();
int userId = matchedOrder["user_id"].toInt();
emit logMessage(QString("[TronMonitor] 🎯 最终匹配: order_id=%1, user_id=%2, amount=%3 USDT")
.arg(orderId).arg(userId).arg(amountUsdt, 0, 'f', 3));
// 确认订单
confirmOrder(orderId, userId, amountUsdt, txHash, txTimestamp);
// 标记为已处理
m_processedTxHashes.insert(txHash);
if (m_redis && m_redis->isConnected()) {
m_redis->lpush("tron:processed_txs", txHash);
m_redis->expire("tron:processed_txs", 86400); // 保留 24 小时
}
matchedCount++;
}
emit logMessage(QString("[TronMonitor] 本轮总结: 共检查 %1 笔, 匹配 %2 笔, 跳过 %3 笔")
.arg(totalChecked).arg(matchedCount).arg(skippedCount));
}
bool TronMonitor::isTxProcessed(const QString& txHash)
{
return m_processedTxHashes.contains(txHash);
}
void TronMonitor::confirmOrder(int orderId, int userId, double amount, const QString& txHash, qint64 txTimestamp)
{
if (!m_mysql || !m_mysql->isConnected()) {
emit logMessage("[TronMonitor] ❌ MySQL 未连接,无法确认订单");
return;
}
emit logMessage(QString("[TronMonitor] 🔐 开始确认订单: order_id=%1, user_id=%2, amount=%3 USDT")
.arg(orderId).arg(userId).arg(amount, 0, 'f', 3));
// 1. 检查订单是否已被处理(防重复)
QVariantMap order = m_mysql->selectOne("user_recharge", QString("id = %1").arg(orderId));
if (order.isEmpty()) {
emit logMessage(QString("[TronMonitor] ❌ 订单不存在: id=%1").arg(orderId));
return;
}
if (order["status"].toInt() > 0) {
emit logMessage(QString("[TronMonitor] ⚠️ 订单已确认,跳过: id=%1").arg(orderId));
return;
}
emit logMessage(QString("[TronMonitor] 当前订单状态: status=%1, create_time=%2")
.arg(order["status"].toInt())
.arg(QDateTime::fromSecsSinceEpoch(order["create_time"].toInt()).toString("yyyy-MM-dd hh:mm:ss")));
// 2. 更新订单状态为已支付,并保存 tx_hash
QVariantMap updateData;
updateData["status"] = 1;
updateData["pay_time"] = (int)txTimestamp; // 使用链上交易时间
updateData["tx_hash"] = txHash;
bool updateSuccess = m_mysql->update("user_recharge", updateData, QString("id = %1").arg(orderId));
if (!updateSuccess) {
emit logMessage(QString("[TronMonitor] 更新订单失败: %1").arg(m_mysql->lastError()));
return;
}
emit logMessage("[TronMonitor] ✅ 订单状态更新成功");
// 3. 给用户加余额
QString sql = QString("UPDATE %1users SET balance = balance + %2 WHERE id = %3")
.arg(m_mysql->tablePrefix())
.arg(amount, 0, 'f', 3)
.arg(userId);
bool addBalanceSuccess = m_mysql->execute(sql);
if (!addBalanceSuccess) {
emit logMessage(QString("[TronMonitor] ❌ 增加余额失败: %1").arg(m_mysql->lastError()));
return;
}
emit logMessage(QString("[TronMonitor] ✅ 用户余额增加成功: user_id=%1, + %2 USDT").arg(userId).arg(amount, 0, 'f', 3));
// 4. 查询用户最新余额用于日志
QVariantMap userInfo = m_mysql->selectOne("users", QString("id = %1").arg(userId));
double newBalance = userInfo["balance"].toDouble();
emit logMessage(QString("[TronMonitor] 订单确认完成: order_id=%1, user_id=%2, amount=%3 USDT, tx=%4, 新余额=%5 USDT")
.arg(orderId).arg(userId).arg(amount, 0, 'f', 3).arg(txHash.left(16) + "...").arg(newBalance, 0, 'f', 3));
}头文件:
#ifndef TRONMONITOR_H
#define TRONMONITOR_H
#include <QObject>
#include <QTimer>
#include <QNetworkAccessManager>
#include <QSslConfiguration>
#include <QSslSocket>
#include <QSslError>
#include <QSet>
#include "MysqlClient.h"
#include "RedisClient.h"
class TronMonitor : public QObject
{
Q_OBJECT
public:
explicit TronMonitor(QObject* parent = nullptr);
void setMysql(MysqlClient* mysql) { m_mysql = mysql; }
void setRedis(RedisClient* redis) { m_redis = redis; }
void setApiKey(const QString& key) { m_apiKey = key; }
void setReceiveAddress(const QString& addr) { m_receiveAddress = addr; }
void setPollInterval(int seconds) { m_pollInterval = seconds; }
void start();
void stop();
signals:
void logMessage(const QString& msg);
private slots:
void onPoll();
void onReplyFinished(QNetworkReply* reply);
private:
void processTransactions(const QJsonArray& txs);
void confirmOrder(int orderId, int userId, double amount, const QString& txHash, qint64 txTimestamp);
bool isTxProcessed(const QString& txHash);
QNetworkReply* m_pendingReply = nullptr; // 当前待处理的回复
QNetworkAccessManager* m_net = nullptr;
QTimer* m_timer = nullptr;
MysqlClient* m_mysql = nullptr;
RedisClient* m_redis = nullptr;
QString m_apiKey;
QString m_receiveAddress;
QSet<QString> m_processedTxHashes; // 内存缓存已处理的 tx_hash
int m_pollInterval = 30; // 轮询间隔(秒)
static constexpr const char* USDT_CONTRACT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; // USDT TRC-20
};
#endif // TRONMONITOR_H