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 私聊 + 平台订单,地址与金额由平台下单返回 |