Skip to content

Telegram Bot 接入 ​

这套接入适合谁 ​

在 Telegram 里做生意、用 Bot 接单发货的卖家。买家在对话里下单, Bot 给出收款信息,付款成功后 Bot 自动发货。

前置准备 ​

  • 已拿到 API KEY(见立即接入),并配置好收款地址。
  • 一个 Bot:通过 BotFather 创建,拿到 Bot Token。
  • 一个公网可达的 HTTPS 地址用作 webhook,用来收 Telegram 的更新和平台的支付回调。
  • 你有一个服务端来放 API KEY(Bot 逻辑通常本来就在服务端,正好)。

KEY 只放服务端

Bot 的后端就是服务端,API KEY 放这里没问题。但不要把它下发给任何客户端 (Mini App、Web App、桌面客户端)。

安装 ​

Bot 调用 Telegram Bot API 无需 SDK,直接发 HTTPS 请求即可:

bash
# 设置 webhook
curl "https://api.telegram.org/bot$BOT_TOKEN/setWebhook" \
  -d "url=https://your-bot.com/tg/webhook" -d "secret_token=$TG_WEBHOOK_SECRET"

最小可跑示例 ​

推荐的路径是 Bot 内展示收款信息 + 链上付款:平台负责结算,Bot 负责沟通与发货。

js
// Node/Express:Telegram webhook + Web3SDK 下单
import express from 'express'
const app = express()
app.use(express.json())

const TG = `https://api.telegram.org/bot${process.env.BOT_TOKEN}`

async function tg(method, body) {
  return fetch(`${TG}/${method}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then((r) => r.json())
}

// 1) 买家在 Bot 里点了"购买"
app.post('/tg/webhook', async (req, res) => {
  res.sendStatus(200)   // 先应答 Telegram,避免它重推
  const msg = req.body.message
  if (!msg || !msg.text?.startsWith('/buy')) return

  const chatId = msg.chat.id
  const orderNo = `TG-${msg.from.id}-${Date.now()}`

  // 2) 你服务端创建平台订单(带 API KEY,别在客户端做)
  const r = await fetch(`${process.env.API_BASE}/api/pay/open/createOrder`, {
    method: 'POST',
    headers: { 'X-Pay-Api-Key': process.env.PAY_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      merchantOrderNo: orderNo,     // 幂等键
      chainId: 2,
      tokenId: 1,
      amount: '29.9',               // 代币单位小数字符串
      subject: `Telegram 订单 ${orderNo}`,
      notifyUrl: 'https://your-bot.com/pay/notify',
    }),
  }).then((x) => x.json())

  if (r.code !== 0) return tg('sendMessage', { chat_id: chatId, text: `下单失败:${r.msg}` })
  const o = r.data.order

  // 3) 把收款信息发给买家(金额用 payAmountMinimal 对应的显示值)
  await tg('sendMessage', {
    chat_id: chatId,
    parse_mode: 'HTML',
    text:
      `订单 <code>${orderNo}</code>\n` +
      `应付:<b>${o.payAmountDisplay}</b> USDT(${o.chainKey})\n` +
      `收款地址:<code>${o.payAddress}</code>\n\n` +
      `请务必按此金额转账,付款后点下面按钮。`,
    reply_markup: {
      inline_keyboard: [[
        { text: '我已完成付款', callback_data: `paid:${o.orderNo}` },
        { text: '刷新状态', callback_data: `check:${o.orderNo}` },
      ]],
    },
  })
})

// 4) 处理按钮回调(Telegram 的 callback query)
app.post('/tg/callback', async (req, res) => { /* 与上面同一个 webhook 亦可 */ })

处理回调查询用 answerCallbackQuery:

js
async function onCallbackQuery(q) {
  const [action, orderNo] = (q.data || '').split(':')
  if (action === 'check' || action === 'paid') {
    // 用平台查询接口核对真实状态(不要只信买家的说法)
    const r = await fetch(
      `${process.env.API_BASE}/api/pay/open/queryOrder?orderNo=${orderNo}`,
      { headers: { 'X-Pay-Api-Key': process.env.PAY_API_KEY } },
    ).then((x) => x.json())

    const status = r.data?.status
    const text =
      status === 3 || status === 4 ? '已确认收款,正在发货…' :
      status === 2 ? '链上已看到这笔款,等待确认…' :
      '还没收到这笔付款,请稍候再试。'

    await tg('answerCallbackQuery', { callback_query_id: q.id, text, show_alert: false })
  }
}

如果要用 Telegram 的 Invoice 流程 ​

Telegram Bot API 提供 sendInvoice / pre_checkout_query / successful_payment 这套支付流程, 适合"你已经接了某个 Telegram 支付提供商"的场景:

js
// 发送一张 invoice(金额单位由 currency 决定)
await tg('sendInvoice', {
  chat_id: chatId,
  title: `订单 ${orderNo}`,
  description: 'USDT 收款',
  payload: orderNo,                      // 你的商户订单号,回执里会带回来
  provider_token: process.env.TG_PROVIDER_TOKEN,   // 来自你接的支付提供商
  currency: 'USD',
  prices: [{ label: 'Total', amount: 2990 }],      // 最小单位整数
})

// 结算前必须应答(10 秒内),否则买家看到支付失败
async function onPreCheckoutQuery(q) {
  await tg('answerPreCheckoutQuery', { pre_checkout_query_id: q.id, ok: true })
}

// 结算成功
async function onSuccessfulPayment(m) {
  const orderNo = m.successful_payment.invoice_payload
  await markPaid(orderNo, m.successful_payment.provider_payment_charge_id)
}

两条路别混

sendInvoice 的结算走 Telegram 支付提供商,不是链上; 而 Web3SDK 的收款是链上结算,发货依据是平台的回调。 如果你要收 USDT,请用上面的"收款信息 + 链上付款"路径; sendInvoice 只在你也接了 Telegram Payments 时才有意义。两者选一条,别在同一个订单上同时用。

回调处理 ​

平台支付回调的接收端就是你的 Bot 后端:

js
import crypto from 'node:crypto'

function verify(rawBody, ts, sig) {
  const want = crypto
    .createHmac('sha256', process.env.PAY_API_KEY)
    .update(ts).update(rawBody)          // 时间戳 + 原始正文
    .digest('hex')
  return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(sig))
}

app.post('/pay/notify', express.raw({ type: '*/*' }), async (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verify(raw, req.get('X-Pay-Timestamp'), req.get('X-Pay-Signature'))) {
    return res.sendStatus(401)
  }
  const p = JSON.parse(raw)
  if (isProcessed(p.merchantOrderNo)) return res.sendStatus(200)   // 幂等
  await markPaid(p.merchantOrderNo, p.txHash)
  await deliverGoods(p.merchantOrderNo)     // 发货
  res.sendStatus(200)                        // 2xx 才会停止重试
})
  • 幂等:回调会重试,用 merchantOrderNo 去重,避免重复发货。
  • 快返回:约 10 秒超时;发货这类动作放队列,或先落库再异步发。
  • 以查询为准:买家说"我付了"时,用查询订单核对,不要只信对话里的说法。

常见问题 ​

问题说明
Bot 里能放 KEY 吗能,Bot 后端就是服务端。但不要下发到客户端
怎么防别人伪造"已付款"按钮只是触发查询;真伪以平台查询接口或回调验签为准
webhook 收不到更新检查 setWebhook 的 URL、secret_token 校验,以及服务器是否公网可达
回调重复到达按 merchantOrderNo 幂等;返回 2xx 停止重试
买家在群里发地址行不行不建议。用 Bot 私聊 + 平台订单,地址与金额由平台下单返回

相关 ​

面向全球中小商家的 Web3 支付基础设施