PAYATHON 2026

如何用 setInterval() 每10秒检查支付状态并停止轮询?

支付老李

结论

把定时器句柄保存在当前支付订单对应的变量中,检测到 PAID 后调用 clearInterval(timer)。由于支付状态查询是异步操作,直接使用 setInterval() 可能导致请求重叠。更稳妥的做法是递归调用 setTimeout(),等本次查询结束后再安排下一次查询。

推荐实现:递归 setTimeout()

async function pollPaymentStatus({ billId, message, nick }) {
  let stopped = false;

  const checkPayment = async () => {
    if (stopped) return;

    try {
      const data = await qiwiApi.getBillInfo(billId);

      if (data.status.value === 'PAID') {
        stopped = true;

        const Rcon = require('rcon');
        const conn = new Rcon('**');

        conn
          .on('auth', function () {
            console.log('Authenticated');
            console.log('Sending command: SPONSOR!');
            conn.send(`**`);
          })
          .on('response', function (str) {
            console.log(`Response: ${str}`);
          })
          .on('error', function (err) {
            console.error('RCON error:', err);
          })
          .on('end', function () {
            console.log('Connection closed');
          });

        conn.connect();

        const embed = new Discord.MessageEmbed()
          .setAuthor(
            'GUARD QIWI',
            'https://media.discordapp.net/attachments/899699390082601011/903958418166005760/871755380349341797.png'
          )
          .setDescription('Ваш счет оплачен!');

        await message.author.send({ embeds: [embed] });
        return;
      }

      console.log(`${message.author.id} еще не оплатил счет.`);
    } catch (error) {
      console.error(
        `Не удалось проверить счет ${billId}:`,
        error
      );
    }

    if (!stopped) {
      setTimeout(checkPayment, 10_000);
    }
  };

  await checkPayment();
}

创建账单并发送支付链接后,启动轮询:

await message.author.send({ embeds: [PAYID] });

pollPaymentStatus({
  billId: params.billId,
  message,
  nick
}).catch(console.error);

这段代码会先立即查询一次。若支付尚未完成,就在 10 秒后再次查询。状态变为 PAID 后,函数会直接返回,不再设置新的定时器。

如果必须使用 setInterval()

需要保存 setInterval() 返回的句柄,并将这个句柄传给 clearInterval():

let checking = false;

const timer = setInterval(async () => {
  // 防止上一次异步查询尚未结束时再次发起请求
  if (checking) return;

  checking = true;

  try {
    const data = await qiwiApi.getBillInfo(params.billId);

    if (data.status.value === 'PAID') {
      clearInterval(timer);

      const embed = new Discord.MessageEmbed()
        .setAuthor(
          'GUARD QIWI',
          'https://media.discordapp.net/attachments/899699390082601011/903958418166005760/871755380349341797.png'
        )
        .setDescription('Ваш счет оплачен!');

      await message.author.send({ embeds: [embed] });

      // 在这里执行支付成功后的 RCON 操作
      return;
    }

    console.log(`${message.author.id} еще не оплатил счет.`);
  } catch (error) {
    console.error(
      `Не удалось проверить счет ${params.billId}:`,
      error
    );
  } finally {
    checking = false;
  }
}, 10_000);

clearInterval() 没有生效,通常有以下几种原因:

  • 没有保存 setInterval() 返回的句柄。
  • 传给 clearInterval() 的是另一个定时器句柄。
  • 定时器变量超出了当前可访问的作用域。
  • 每次收到消息时都创建了新定时器,但只清除了其中一个。
  • 异步回调已经开始执行。clearInterval() 只能阻止后续调度,无法中止已经发出的网络请求。

建议增加超时机制

如果用户始终没有支付,轮询不应一直运行。可以设置一个截止时间,例如 15 分钟:

async function pollPaymentStatus({ billId, message }) {
  const deadline = Date.now() + 15 * 60 * 1000;

  const checkPayment = async () => {
    if (Date.now() >= deadline) {
      await message.author.send('Срок ожидания оплаты истек.');
      return;
    }

    try {
      const data = await qiwiApi.getBillInfo(billId);

      if (data.status.value === 'PAID') {
        await message.author.send('Ваш счет оплачен!');
        return;
      }
    } catch (error) {
      console.error(`Ошибка проверки счета ${billId}:`, error);
    }

    setTimeout(checkPayment, 10_000);
  };

  await checkPayment();
}

其他需要修正的地方

私信判断中的表达式存在运算符优先级问题:

if (!message.channel.name === 'DM')

程序会先计算 !message.channel.name,然后再把结果与 'DM' 比较,所以这个判断几乎不可能得到预期结果。应按照当前使用的 discord.js 版本判断频道类型。旧版本中可能这样写:

if (message.channel.type !== 'DM') {
  return message.channel.send(
    'Эта команда работает только в личных сообщениях бота!'
  );
}

频道类型的具体取值由 discord.js 版本决定,应以项目当前版本的 API 为准。

RCON 连接结束时也不应调用:

process.exit();

这行代码会终止整个 Discord Bot 进程,而不是只结束当前这次支付处理。正常关闭 RCON 连接即可。

生产环境中还应使用 billId 作为键保存订单状态,防止同一账单被重复处理。轮询可能因进程重启、并发请求或网络延迟而多次读到 PAID,因此发放权益的操作需要保持幂等。

备注:内容仅供参考。