Telegram Bot 接入 USDT 支付完整教程

给你的 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 ──────────│                │
     │◀── "✅ 支付成功!" ──│                       │                │

上线生产

  1. 切换密钥:
env 复制代码
IRONIXPAY_SECRET_KEY=sk_live_...
IRONIXPAY_API_URL=https://api.ironixpay.com
  1. 把内存 pendingOrders 换成 Redis 或 PostgreSQL
  2. 部署到服务器,用反向代理(Caddy/Nginx)暴露 Webhook 端口
  3. 在 IronixPay 后台配置 Webhook URL

生产模式自动支持 7 条链(TRON、BSC、Ethereum、Polygon、Arbitrum、Optimism、Base),用户可以选择自己偏好的链支付。


相关资源


IronixPay ------ 一套 API 支持 7 条链的 USDT 收款,不需要任何 Web3 知识。免费开始 →

相关推荐
Rust研习社11 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒12 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro12 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax13 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH13 小时前
Koa和Express的区别
后端
MariaH13 小时前
Koa框架的使用
后端
luckdewei14 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某15 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy16 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom16 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github