Code Examples

多语言接入 Demo

下面的示例都围绕三个动作:创建支付订单、查询订单状态、校验 Webhook 签名。代码里已经加入入门备注,把 BASE_URL 换成你的网关域名,把 MERCHANT_API_KEYWEBHOOK_SECRET 换成商户后台生成的值即可。

BASE_URLhttps://pay.example.com
API Keysk_live_xxx
Webhook Secretwhsec_xxx

cURL

适合先在终端验证 API Key 和订单创建流程。

# 网关地址,正式环境换成你的域名,例如 https://pay.your-domain.com
BASE_URL="https://pay.example.com"

# 商户 API Key,只能放在服务端或本地终端,不能放到网页前端
MERCHANT_API_KEY="sk_live_xxx"

# 创建一笔支付订单。返回结果里会包含 pay_amount、deposit_address、checkout_url 等字段
# amount:用户原本要支付的金额,系统会在返回值里分配唯一后缀金额
# merchant_order_id:你自己系统里的订单号,建议每笔业务订单唯一
# customer_reference:你自己系统里的用户标识,方便后续对账和排查
# notify_url:支付成功后,网关会把 payment.paid 事件推送到这个地址
curl -s "$BASE_URL/v1/payment-orders" \
  -H "authorization: Bearer $MERCHANT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "amount": "100",
    "merchant_order_id": "order-1001",
    "customer_reference": "user-123",
    "notify_url": "https://merchant.example/webhooks/usdt"
  }'
# 创建订单后,把返回里的 data.id 填到这里
ORDER_ID="po_xxx"

# 查询订单状态。业务系统可以用它主动核对订单是否已经 paid
curl -s "$BASE_URL/v1/payment-orders/$ORDER_ID" \
  -H "authorization: Bearer $MERCHANT_API_KEY"

Node.js

Node.js 18+ 已内置 fetch。适合 Next.js、Express、NestJS 等服务端项目。

// 从环境变量读取配置,避免把真实密钥写进代码仓库
const BASE_URL = process.env.USDT_GATEWAY_URL;
const API_KEY = process.env.USDT_MERCHANT_API_KEY;

// 创建支付订单。这个函数应当在你的服务端调用,而不是浏览器里调用
async function createPaymentOrder() {
  const res = await fetch(`${BASE_URL}/v1/payment-orders`, {
    method: "POST",
    headers: {
      // Bearer Token 是商户身份凭证
      "authorization": `Bearer ${API_KEY}`,
      // 请求体使用 JSON 格式
      "content-type": "application/json"
    },
    body: JSON.stringify({
      // 用户原始订单金额,不需要自己拼接后缀
      amount: "100",
      // 你的业务订单号,用来做幂等和对账
      merchant_order_id: "order-1001",
      // 可选:你的用户 ID、邮箱、手机号脱敏值等
      customer_reference: "user-123",
      // 可选:这笔订单支付成功后优先通知这个地址
      notify_url: "https://merchant.example/webhooks/usdt"
    })
  });

  // 非 2xx 通常代表参数错误、API Key 错误或商户状态异常
  if (!res.ok) throw new Error(await res.text());

  // 返回 JSON,常用字段在 data 里面
  return res.json();
}

// 主动查询订单,适合后台任务补偿或用户点击“我已付款”后核对
async function getPaymentOrder(orderId) {
  const res = await fetch(`${BASE_URL}/v1/payment-orders/${orderId}`, {
    headers: { "authorization": `Bearer ${API_KEY}` }
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json();
}

Python

适合 Django、FastAPI、Flask 等服务端项目。

import os
import requests

# 从环境变量读取网关地址和商户 API Key
# API Key 属于敏感信息,不要写死到代码里
BASE_URL = os.environ["USDT_GATEWAY_URL"]
API_KEY = os.environ["USDT_MERCHANT_API_KEY"]

def create_payment_order():
    # 创建支付订单。这个接口应当由你的后端服务调用
    response = requests.post(
        f"{BASE_URL}/v1/payment-orders",
        headers={
            # Bearer Token 用来验证商户身份
            "authorization": f"Bearer {API_KEY}",
            # 请求体是 JSON
            "content-type": "application/json",
        },
        json={
            # 用户原始订单金额,网关会返回带后缀的 pay_amount
            "amount": "100",
            # 你的业务订单号,建议保证唯一
            "merchant_order_id": "order-1001",
            # 可选:你的用户标识,方便对账
            "customer_reference": "user-123",
            # 可选:支付成功后的回调地址
            "notify_url": "https://merchant.example/webhooks/usdt",
        },
        # 设置超时,避免网络异常时请求一直卡住
        timeout=20,
    )
    # 非 2xx 会抛出异常,便于上层记录错误日志
    response.raise_for_status()
    return response.json()

def get_payment_order(order_id):
    # 主动查询订单状态,适合补偿任务或客服排查
    response = requests.get(
        f"{BASE_URL}/v1/payment-orders/{order_id}",
        headers={"authorization": f"Bearer {API_KEY}"},
        timeout=20,
    )
    response.raise_for_status()
    return response.json()

PHP

适合 Laravel、ThinkPHP、WordPress 插件或传统 PHP 服务端。

<?php
// 从环境变量读取配置,避免把密钥提交到代码仓库
$baseUrl = getenv('USDT_GATEWAY_URL');
$apiKey = getenv('USDT_MERCHANT_API_KEY');

// 封装一个通用请求函数,后面创建订单、查订单都可以复用
function gateway_request($method, $path, $body = null) {
    global $baseUrl, $apiKey;

    // 目标地址,例如 https://pay.example.com/v1/payment-orders
    $ch = curl_init($baseUrl . $path);

    // authorization 头用于商户鉴权,content-type 表示请求体是 JSON
    $headers = [
        'authorization: Bearer ' . $apiKey,
        'content-type: application/json',
        'accept: application/json',
    ];
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 20,
    ]);
    if ($body !== null) {
        // PHP 数组转 JSON 后发送给网关
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }

    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    // 非 2xx 表示失败,直接抛出网关返回的错误内容
    if ($status < 200 || $status >= 300) {
        throw new Exception($raw);
    }

    // 正常返回 JSON,转成 PHP 数组使用
    return json_decode($raw, true);
}

// 创建支付订单。注意 amount 是用户原始金额,不是带后缀金额
$order = gateway_request('POST', '/v1/payment-orders', [
    'amount' => '100',
    // 你的业务订单号,建议唯一
    'merchant_order_id' => 'order-1001',
    // 可选:你的用户标识
    'customer_reference' => 'user-123',
    // 可选:支付成功后的回调地址
    'notify_url' => 'https://merchant.example/webhooks/usdt',
]);

Go

适合 Go 后端或内部服务直接对接。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func createPaymentOrder() (map[string]any, error) {
	// 从环境变量读取配置,避免把真实 API Key 写进源码
	baseURL := os.Getenv("USDT_GATEWAY_URL")
	apiKey := os.Getenv("USDT_MERCHANT_API_KEY")

	// 请求体里的 amount 是用户原始金额,网关会返回带唯一后缀的 pay_amount
	body, _ := json.Marshal(map[string]string{
		"amount":             "100",
		// 你的业务订单号,建议每笔订单唯一
		"merchant_order_id":  "order-1001",
		// 可选:你的用户标识,方便对账和客服排查
		"customer_reference": "user-123",
		// 可选:支付成功后推送 payment.paid 的地址
		"notify_url":         "https://merchant.example/webhooks/usdt",
	})

	// 创建 POST 请求,目标路径是 /v1/payment-orders
	req, _ := http.NewRequest("POST", baseURL+"/v1/payment-orders", bytes.NewReader(body))

	// authorization 用于商户鉴权,content-type 表示请求体是 JSON
	req.Header.Set("authorization", "Bearer "+apiKey)
	req.Header.Set("content-type", "application/json")

	// 发送请求到 USDT Gateway
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	// 非 2xx 通常是参数错误、API Key 错误或商户状态异常
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		return nil, fmt.Errorf("gateway returned %s", res.Status)
	}

	// 正常响应是 JSON,常用字段在 data 里面
	var payload map[string]any
	return payload, json.NewDecoder(res.Body).Decode(&payload)
}

Java

适合 Spring Boot、Micronaut、Quarkus 或普通 Java 11+ 项目。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class UsdtGatewayDemo {
  // 从环境变量读取配置,避免把密钥写死在代码里
  static final String BASE_URL = System.getenv("USDT_GATEWAY_URL");
  static final String API_KEY = System.getenv("USDT_MERCHANT_API_KEY");

  public static String createPaymentOrder() throws Exception {
    // 请求体是 JSON 字符串。amount 是用户原始金额,网关会返回带唯一后缀的 pay_amount
    String json = """
      {
        "amount": "100",
        "merchant_order_id": "order-1001",
        "customer_reference": "user-123",
        "notify_url": "https://merchant.example/webhooks/usdt"
      }
      """;

    // 创建 POST 请求,目标路径是 /v1/payment-orders
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create(BASE_URL + "/v1/payment-orders"))
      // Bearer Token 用来验证商户身份
      .header("authorization", "Bearer " + API_KEY)
      // 告诉网关请求体是 JSON
      .header("content-type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(json))
      .build();

    // 发送请求并把响应体作为字符串取回
    HttpResponse<String> response = HttpClient.newHttpClient()
      .send(request, HttpResponse.BodyHandlers.ofString());

    // 非 2xx 表示失败,直接把网关错误抛给上层处理
    if (response.statusCode() < 200 || response.statusCode() >= 300) {
      throw new RuntimeException(response.body());
    }

    // 正常情况下 response.body() 是 JSON 字符串
    return response.body();
  }
}

Webhook 验签

验签时必须使用原始请求体。签名头格式是 USDT-Signature: t=timestamp,v1=signature

Node.js / Express

import crypto from "node:crypto";
import express from "express";

const app = express();

// Webhook Secret 来自商户后台,用来校验回调是否真的来自网关
const WEBHOOK_SECRET = process.env.USDT_WEBHOOK_SECRET;

// express.raw 很重要:验签必须使用原始 body,不能先被 JSON 中间件解析
app.post("/webhooks/usdt", express.raw({ type: "application/json" }), (req, res) => {
  // usdt-signature 格式示例:t=1710000000,v1=abcdef...
  const header = req.header("usdt-signature") || "";

  // 从签名头里取出时间戳和签名值
  const timestamp = header.match(/t=([^,]+)/)?.[1];
  const signature = header.match(/v1=([^,]+)/)?.[1];

  // 参与签名的数据格式是:timestamp + "." + 原始请求体
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(`${timestamp}.${req.body.toString("utf8")}`)
    .digest("hex");

  // timingSafeEqual 可以减少时序攻击风险;比较前先检查长度,避免抛异常
  const signatureBuffer = Buffer.from(signature || "");
  const expectedBuffer = Buffer.from(expected);
  const valid = signatureBuffer.length === expectedBuffer.length
    && crypto.timingSafeEqual(signatureBuffer, expectedBuffer);

  if (!valid) {
    return res.status(400).send("invalid signature");
  }

  // 验签通过后再解析 JSON。event.type 可能是 payment.paid、withdrawal.paid 等
  const event = JSON.parse(req.body.toString("utf8"));

  // TODO: 根据 event.id 做幂等,避免重复回调导致重复发货
  res.json({ ok: true });
});

Python / FastAPI

import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
WEBHOOK_SECRET = "whsec_xxx"

@app.post("/webhooks/usdt")
async def usdt_webhook(request: Request):
    # 验签必须使用原始请求体,所以这里先读取 bytes
    raw = await request.body()

    # usdt-signature 格式示例:t=1710000000,v1=abcdef...
    header = request.headers.get("usdt-signature", "")
    parts = dict(item.split("=", 1) for item in header.split(",") if "=" in item)
    timestamp = parts.get("t")
    signature = parts.get("v1")

    # 缺少时间戳或签名时,直接拒绝请求
    if not timestamp or not signature:
        raise HTTPException(status_code=400, detail="missing signature")

    # 参与签名的数据格式是:timestamp + "." + 原始请求体
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        timestamp.encode() + b"." + raw,
        hashlib.sha256,
    ).hexdigest()

    # compare_digest 用于安全比较签名
    if not signature or not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=400, detail="invalid signature")

    # 验签通过后再解析业务事件。生产环境请用 event.id 做幂等
    return {"ok": True}

PHP

<?php
// Webhook Secret 来自商户后台
$secret = getenv('USDT_WEBHOOK_SECRET');

// 验签必须使用原始请求体,不能先 json_decode 再签名
$raw = file_get_contents('php://input');

// PHP 会把 usdt-signature 请求头放到 HTTP_USDT_SIGNATURE
$header = $_SERVER['HTTP_USDT_SIGNATURE'] ?? '';

// 从签名头里取出时间戳和签名值
preg_match('/t=([^,]+)/', $header, $t);
preg_match('/v1=([^,]+)/', $header, $v1);
$timestamp = $t[1] ?? '';
$signature = $v1[1] ?? '';

// 参与签名的数据格式是:timestamp + "." + 原始请求体
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, $secret);

// hash_equals 用于安全比较签名
if (!$signature || !hash_equals($expected, $signature)) {
    http_response_code(400);
    echo 'invalid signature';
    exit;
}

// 验签通过后再解析 JSON。生产环境请用事件 ID 做幂等
$event = json_decode($raw, true);
echo json_encode(['ok' => true]);

接入检查表

  • API Key 只放服务端环境变量,不放前端。
  • 创建订单时传入唯一的 merchant_order_id
  • 收银台明确提醒用户必须精确转账 pay_amount
  • Webhook 验签使用原始 body,并用事件 ID 做幂等。
  • 业务发货只认 payment.paid,不要只看用户截图。
  • 上线前用小额 USDT 做真实链上测试。