给你的 TG Bot 加上链上收款能力 ------ 从 /buy 命令到自动确认,20 分钟搞定。
做 Telegram Bot 的开发者,大概率遇到过收款难题:
- Stripe / PayPal 在 Bot 里体验很差,要跳外部浏览器
- 很多目标用户在东南亚、中东、CIS,没有信用卡
- 加密货币用户群体天然和 Telegram 高度重合
- 你卖的是数字产品(VPN、会员、积分),不需要复杂的支付流
USDT 是最合适的选择 ------ 用户熟悉、价格稳定(锚定美元)、链上结算不可逆。
本文教你用 IronixPay + grammY 框架搭一个带支付功能的 Telegram Bot。完整代码不到 200 行。
最终效果:
- 用户发
/buy→ 看到商品列表(Inline Button) - 选择商品 → Bot 创建支付链接
- 点击「💳 Pay Now」→ 跳转 IronixPay 支付页
- 用户用钱包转 USDT → 链上确认
- Bot 自动发送「✅ 支付成功」消息
💡 完整源码:github.com/IronixPay/i...
1. 初始化项目
bash
mkdir my-tg-bot && cd my-tg-bot
npm init -y
npm install grammy express dotenv
npm install -D typescript tsx @types/express @types/node
在 Telegram 找 @BotFather → /newbot → 拿到 token。
创建 .env:
env
BOT_TOKEN=123456:ABC-DEF...
IRONIXPAY_SECRET_KEY=sk_test_your_key_here
IRONIXPAY_API_URL=https://sandbox.ironixpay.com
IRONIXPAY_WEBHOOK_SECRET=whsec_your_secret_here
WEBHOOK_PORT=3000
PUBLIC_URL=https://your-domain.com
IronixPay 密钥在 app.ironixpay.com → API Keys 获取。
2. IronixPay API 封装
创建 src/ironixpay.ts:
typescript
import crypto from "node:crypto";
const API_URL = process.env.IRONIXPAY_API_URL || "https://sandbox.ironixpay.com";
const SECRET_KEY = process.env.IRONIXPAY_SECRET_KEY || "";
export async function createCheckoutSession(params: {
amount: number; // 微单位:1 USDT = 1,000,000
currency: "USDT";
network: string;
success_url: string;
cancel_url: string;
client_reference_id?: string;
}) {
const res = await fetch(`${API_URL}/v1/checkout/sessions`, {
method: "POST",
headers: {
Authorization: `Bearer ${SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
if (!res.ok) {
const error = await res.json().catch(() => ({}));
throw new Error(`IronixPay error (${res.status}): ${error.message || res.statusText}`);
}
return res.json();
}
export function verifyWebhookSignature(
payload: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// 拒绝超过 5 分钟的请求(防重放攻击)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) return false;
// IronixPay 签名格式:"{timestamp}.{payload}"
const message = `${timestamp}.${payload}`;
const computed = crypto.createHmac("sha256", secret).update(message).digest("hex");
if (computed.length !== signature.length) return false;
return crypto.timingSafeEqual(
Buffer.from(computed, "hex"),
Buffer.from(signature, "hex")
);
}
为什么用 timingSafeEqual?
普通的 === 字符串比较在第一个不匹配的字符就会返回,攻击者可以通过测量响应时间逐字节猜出正确的签名。timingSafeEqual 保证无论匹配与否,比较时间都是恒定的。
3. Bot 主逻辑
创建 src/index.ts:
typescript
import { Bot, InlineKeyboard } from "grammy";
import { createCheckoutSession } from "./ironixpay.js";
import { startWebhookServer } from "./webhook-server.js";
const bot = new Bot(process.env.BOT_TOKEN!);
const PUBLIC_URL = process.env.PUBLIC_URL || "http://localhost:3000";
// 商品列表(金额用微单位)
const PRODUCTS = [
{ id: "starter", name: "⚡ Starter", price: 9_990_000 }, // $9.99
{ id: "pro", name: "🚀 Pro", price: 29_990_000 }, // $29.99
];
// 内存订单表(生产环境请用数据库!)
const pendingOrders = new Map<string, { chatId: number; productId: string }>();
// /buy --- 展示商品列表
bot.command("buy", async (ctx) => {
const kb = new InlineKeyboard();
for (const p of PRODUCTS) {
kb.text(`${p.name} --- $${(p.price / 1_000_000).toFixed(2)}`, `buy:${p.id}`).row();
}
await ctx.reply("🛒 选择商品:", { reply_markup: kb });
});
// 用户选择商品 → 创建支付
bot.callbackQuery(/^buy:(.+)$/, async (ctx) => {
const product = PRODUCTS.find(p => p.id === ctx.match[1]);
if (!product) return ctx.answerCallbackQuery({ text: "商品不存在" });
await ctx.answerCallbackQuery({ text: "创建支付中..." });
try {
const orderId = `tg_${ctx.from.id}_${Date.now()}`;
const session = await createCheckoutSession({
amount: product.price,
currency: "USDT",
network: "TRON",
success_url: `${PUBLIC_URL}/success`,
cancel_url: `${PUBLIC_URL}/cancel`,
client_reference_id: orderId,
});
// 保存订单,webhook 收到后用来匹配
pendingOrders.set(orderId, { chatId: ctx.chat!.id, productId: product.id });
const kb = new InlineKeyboard().url("💳 Pay Now", session.url);
await ctx.editMessageText(
`${product.name}\n💰 $${(product.price / 1_000_000).toFixed(2)} USDT\n\n点击下方按钮支付:`,
{ reply_markup: kb }
);
} catch (error) {
console.error("支付创建失败:", error);
await ctx.editMessageText("❌ 创建支付失败,请用 /buy 重试");
}
});
// 启动 webhook 服务 + bot
startWebhookServer(bot, pendingOrders);
bot.start({ onStart: () => console.log("🤖 Bot 已启动!") });
用户视角的完整流程:
bash
发 /buy → 看到商品按钮
点 "⚡ Starter --- $9.99" → Bot 创建 IronixPay 支付 Session
点 "💳 Pay Now" → 浏览器打开支付页
用钱包转 USDT → IronixPay 链上验证
Bot 发消息 "✅ 支付已确认!" → 完成!
4. Webhook 服务
创建 src/webhook-server.ts:
typescript
import express from "express";
import type { Bot } from "grammy";
import { verifyWebhookSignature } from "./ironixpay.js";
export function startWebhookServer(
bot: Bot,
pendingOrders: Map<string, { chatId: number; productId: string }>
) {
const app = express();
app.use("/webhooks", express.text({ type: "application/json" }));
app.post("/webhooks/ironixpay", async (req, res) => {
const body = req.body as string;
const sig = (req.headers["x-signature"] as string) || "";
const ts = (req.headers["x-timestamp"] as string) || "";
const secret = process.env.IRONIXPAY_WEBHOOK_SECRET || "";
// 验证 HMAC-SHA256 签名
if (secret && !verifyWebhookSignature(body, sig, ts, secret)) {
return res.status(401).json({ error: "签名无效" });
}
const event = JSON.parse(body);
if (event.event_type === "session.completed") {
const order = pendingOrders.get(event.data.client_reference_id);
if (order) {
const amount = (parseInt(event.data.amount_received) / 1_000_000).toFixed(2);
await bot.api.sendMessage(order.chatId,
`✅ 支付已确认!\n💰 收到 $${amount} USDT\n\n感谢购买!🎉`
);
pendingOrders.delete(event.data.client_reference_id);
}
}
res.json({ received: true });
});
const port = process.env.WEBHOOK_PORT || 3000;
app.listen(port, () => console.log(`🌐 Webhook 服务已启动,端口 ${port}`));
}
为什么需要单独的 Express 服务?
Telegram Bot 用「长轮询」接收消息。但 IronixPay 需要通过 HTTP 主动推送支付通知给你。所以我们在 Bot 旁边跑一个小型 Express 服务来接收 webhook。
5. 跑起来
bash
npx tsx --require dotenv/config src/index.ts
打开 Telegram → 给你的 Bot 发 /buy → 选商品 → 点"Pay Now"。
本地测试 webhook 可以用 curl 模拟:
bash
curl -X POST http://localhost:3000/webhooks/ironixpay \
-H "Content-Type: application/json" \
-d '{"event_type":"session.completed","data":{"amount_received":"9990000","client_reference_id":"你的订单ID"}}'
如果要测真实 webhook,用 ngrok 暴露端口:
bash
npx ngrok http 3000
# 把 ngrok 地址配到 IronixPay 后台的 Webhook URL
完整架构
bash
Telegram 用户 你的服务器 IronixPay 区块链
│ │ │ │
│── /buy ─────────────▶│ │ │
│◀── 商品按钮 ─────────│ │ │
│── 点击 "Starter" ───▶│ │ │
│ │── 创建 Session ──────▶│ │
│ │◀── { url } ──────────│ │
│◀── "Pay Now" 按钮 ──│ │ │
│ │ │ │
│── 点击 Pay Now ─────▶│──── 打开支付页 ──────▶│ │
│ │ │◀── USDT 转账 ──│
│ │ │── 链上验证 ────▶│
│ │◀── Webhook ──────────│ │
│◀── "✅ 支付成功!" ──│ │ │
上线生产
- 切换密钥:
env
IRONIXPAY_SECRET_KEY=sk_live_...
IRONIXPAY_API_URL=https://api.ironixpay.com
- 把内存
pendingOrders换成 Redis 或 PostgreSQL - 部署到服务器,用反向代理(Caddy/Nginx)暴露 Webhook 端口
- 在 IronixPay 后台配置 Webhook URL
生产模式自动支持 7 条链(TRON、BSC、Ethereum、Polygon、Arbitrum、Optimism、Base),用户可以选择自己偏好的链支付。
相关资源
- 📖 IronixPay 文档
- 🤖 grammY 文档
- 🧑💻 完整源码
- 💬 Telegram 支持
IronixPay ------ 一套 API 支持 7 条链的 USDT 收款,不需要任何 Web3 知识。免费开始 →