UPG API 接入文档
UPG(USDT Pay Gateway)是一套无托管的 USDT 链上收款网关。商户调用 API 创建收款订单后,资金将直接打入商户自己配置的收款地址,平台不经手资金。
接入前准备:在商户后台 →「API 配置」中配置 TRC20 / BEP20 / ERC20 收款地址,并获取 API Key 和 API Secret。
接口基本信息
| 接口基础地址 | https://your-domain.com |
| 请求格式 | JSON body(Content-Type: application/json) |
| 响应格式 | JSON,统一信封:{"code":200,"message":"ok","data":{...}} |
| 字符编码 | UTF-8 |
| 支持链 | TRC20(TRON)/ BEP20(BSC)/ ERC20(ETH) |
快速开始
以下示例演示如何用 5 分钟完成首笔 USDT 收款订单的创建与回调处理。
// 1. 构建签名
$params = [
'api_key' => 'YOUR_API_KEY',
'merchant_order_no' => 'ORDER_' . time(),
'amount' => 100.00,
'currency' => 'CNY',
'chain' => 'TRC20',
'notify_url' => 'https://your-site.com/notify',
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(8)),
];
ksort($params);
$parts = [];
foreach ($params as $k => $v) {
if ($v === '' || $v === null) continue;
$parts[] = $k . '=' . $v;
}
$signStr = implode('&', $parts); // api_secret 仅作 HMAC 密钥,不拼入字符串
$params['sign'] = hash_hmac('sha256', $signStr, 'YOUR_API_SECRET');
// 2. 发起请求
$ch = curl_init('https://your-domain.com/api/pay/create');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$resp = json_decode(curl_exec($ch), true);
// 3. 处理结果
if ($resp['code'] === 200) {
$order = $resp['data'];
// 引导用户访问收银台
header('Location: ' . $order['pay_url']);
}
import hmac, hashlib, time, random, string, requests
api_key = 'YOUR_API_KEY'
api_secret = 'YOUR_API_SECRET'
params = {
'api_key': api_key,
'merchant_order_no': f'ORDER_{int(time.time())}',
'amount': 100.00,
'currency': 'CNY',
'chain': 'TRC20',
'notify_url': 'https://your-site.com/notify',
'timestamp': int(time.time()),
'nonce': ''.join(random.choices(string.ascii_lowercase, k=16)),
}
sorted_params = dict(sorted(params.items()))
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params.items() if v != '' and v is not None])
# api_secret 仅作 HMAC 密钥,不拼入字符串
params['sign'] = hmac.new(api_secret.encode(), sign_str.encode(), hashlib.sha256).hexdigest()
resp = requests.post('https://your-domain.com/api/pay/create', json=params, timeout=10)
data = resp.json()
if data['code'] == 200:
print('收银台:', data['data']['pay_url'])
const crypto = require('crypto');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
const API_SECRET = 'YOUR_API_SECRET';
const params = {
api_key: API_KEY,
merchant_order_no:`ORDER_${Date.now()}`,
amount: 100.00,
currency: 'CNY',
chain: 'TRC20',
notify_url: 'https://your-site.com/notify',
timestamp: Math.floor(Date.now() / 1000),
nonce: crypto.randomBytes(8).toString('hex'),
};
// api_secret 仅作 HMAC 密钥,不拼入字符串
const signStr = Object.keys(params).sort()
.filter(k => params[k] !== '' && params[k] != null)
.map(k => `${k}=${params[k]}`).join('&');
params.sign = crypto.createHmac('sha256', API_SECRET).update(signStr).digest('hex');
const { data } = await axios.post('https://your-domain.com/api/pay/create', params);
if (data.code === 200) console.log('收银台:', data.data.pay_url);
接口鉴权
所有业务接口(创建订单、查询订单等)均需通过 HMAC-SHA256 签名鉴权。每次请求需携带以下公共参数:
| 参数 | 类型 | 必填 | 说明 |
| api_key | string | 必填 | 商户 API Key,在后台「API 配置」中获取 |
| timestamp | int | 必填 | 当前 Unix 时间戳(秒),服务端允许 ±300 秒误差 |
| nonce | string | 必填 | 随机字符串,≥8位,15 分钟内不可复用(防重放) |
| sign | string | 必填 | HMAC-SHA256 签名,小写 hex,算法见下节 |
安全提示:API Secret 请勿提交至代码仓库或记录在日志中。如泄露,请立即在商户后台重置。
签名算法
收集所有请求参数(不含 sign 本身)
将公共参数(api_key、timestamp、nonce)与业务参数合并为一个 key-value 对象,排除值为空字符串或 null 的字段。
按参数名 ASCII 升序排列
ksort($params) 或 Object.keys(params).sort()
拼接签名字符串
将排序后的参数按 key=value 格式用 & 连接,跳过空值字段。不追加 api_secret 字符串,api_secret 仅作为下一步 HMAC 的密钥
计算 HMAC-SHA256
以 api_secret 为密钥,对上述字符串计算 HMAC-SHA256,结果转为小写 hex 字符串,即为 sign 的值。
签名示例
参数:api_key=abc,amount=100,nonce=xyz123,timestamp=1700000000
# 排序后拼接(字典序):
api_key=abc&amount=100&nonce=xyz123×tamp=1700000000
# 上面就是完整签名字符串,api_secret 不在字符串内,只作 HMAC 密钥
# PHP:
$sign = hash_hmac('sha256', $signStr, 'YOUR_SECRET');
# Python:
sign = hmac.new(secret.encode(), sign_str.encode(), hashlib.sha256).hexdigest()
# Node.js:
sign = crypto.createHmac('sha256', secret).update(signStr).digest('hex');
创建收款订单
创建一笔 USDT 收款订单。同一 merchant_order_no 若存在未过期的待支付订单,将返回原订单(幂等)。
计费说明:创建订单仅校验 API Key 与签名,不校验授权/点数。授权过期或点数不足时仍可生成收款码,但链上到账后可能不监听、不自动回调商户。
请求参数
| 参数 | 类型 | 必填 | 说明 |
| merchant_order_no | string | 必填 | 商户侧唯一订单号,≤64 字符 |
| amount | number | 必填 | 收款金额,>0 |
| currency | string | 可选 | 法币币种:CNY(默认)/ USD / USDT |
| chain | string | 可选 | 收款链:TRC20 / BEP20 / ERC20;留空按 TRC20→BEP20→ERC20 优先级自动选默认链,收银台可切换 |
| notify_url | string | 可选 | 异步回调地址,覆盖后台默认配置 |
| return_url | string | 可选 | 支付成功后收银台前端跳转地址 |
| attach | string | 可选 | 透传参数,回调时原样返回,≤500 字符 |
| api_key / timestamp / nonce / sign | — | 必填 | 公共鉴权参数,见「接口鉴权」 |
响应数据 data
| 字段 | 类型 | 说明 |
| order_no | string | 系统订单号,格式 UPG + 时间 + 随机 |
| merchant_order_no | string | 商户订单号(原样返回) |
| chain | string|null | 收款链 |
| chain_pending | bool | 是否待选链(当前创建接口固定为 false) |
| pay_address | string|null | USDT 收款地址 |
| usdt_amount | string | 需转账的精确 USDT 金额 |
| fiat_amount | number | 法币金额 |
| fiat_currency | string | 法币币种 |
| exchange_rate | float | 下单时汇率快照(1 USDT = X 法币) |
| pay_url | string | 收银台页面 URL,可直接跳转引导用户支付 |
| return_url | string | 支付成功后收银台跳转地址 |
| qr_url | string | 二维码数据 URL |
| expire_at | string | 订单过期时间 Y-m-d H:i:s |
| expire_seconds | int | 距过期剩余秒数 |
| status | int | 0 = 待支付 |
| status_text | string | 状态中文描述 |
响应示例
{
"code": 200,
"message": "订单创建成功",
"data": {
"order_no": "UPG20240101120000A1B2C3",
"merchant_order_no": "ORDER_1704067200",
"chain": "TRC20",
"pay_address": "TNVxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"usdt_amount": "13.726870",
"fiat_amount": 100,
"fiat_currency": "CNY",
"exchange_rate": 7.2845,
"pay_url": "https://your-domain.com/pay?order=UPG20240101120000A1B2C3",
"qr_url": "https://your-domain.com/pay/qr?order=UPG...",
"expire_at": "2024-01-01 12:30:00",
"expire_seconds": 1800,
"status": 0
}
}
查询订单状态
通过系统订单号或商户订单号查询订单当前状态。
请求参数
| 参数 | 类型 | 必填 | 说明 |
| order_no | string | 二选一 | 系统订单号 |
| merchant_order_no | string | 二选一 | 商户订单号 |
| api_key / timestamp / nonce / sign | — | 必填 | 公共鉴权参数 |
响应数据 data
| 字段 | 类型 | 说明 |
| order_no | string | 系统订单号 |
| merchant_order_no | string | 商户订单号 |
| status | int | 见「订单状态码」 |
| status_text | string | 状态中文描述 |
| chain | string | 收款链 |
| pay_address | string | 收款地址 |
| usdt_amount | string | 应付 USDT |
| actual_amount | string | 实付 USDT(到账后填充) |
| fiat_amount | number | 法币金额 |
| fiat_currency | string | 法币币种 |
| exchange_rate | float | 下单时汇率快照 |
| tx_hash | string | 链上交易哈希 |
| from_address | string | 付款方地址 |
| confirmations | int | 区块确认数 |
| paid_at | string|null | 支付时间 |
| expire_at | string | 过期时间 |
| created_at | string | 创建时间 |
查询可用收款链
查询当前商户已配置的收款链,可选传入金额做计费预检。建议在创建订单前调用,避免必然失败的请求。
请求参数
| 参数 | 类型 | 必填 | 说明 |
| amount | number | 可选 | 与 currency 配合,用于计费预检 |
| currency | string | 可选 | CNY / USD / USDT,默认 USDT |
| api_key / timestamp / nonce / sign | — | 必填 | 公共鉴权参数 |
响应数据 data
| 字段 | 类型 | 说明 |
| chains | array | 已配置地址的链,如 ["TRC20","BEP20"] |
| chain_options | array | 各链详情:chain、enabled、reason、notice |
| billing_active | bool | 计费状态是否正常 |
| billing_notice | string | 计费异常时的提示 |
回调通知规范
链上到账
→
UPG 监控服务
→
POST 您的 notify_url
→
返回 OK
重要:您的服务器必须在 10 秒内响应 HTTP 200 且 Body 为 OK,否则系统将按指数退避策略重试,最多 5 次。
回调参数
| 参数 | 类型 | 说明 |
| order_no | string | 系统订单号 |
| merchant_order_no | string | 商户订单号 |
| status | int | 1 = 已支付(回调仅在支付成功时触发) |
| chain | string | 收款链 |
| pay_address | string | 收款地址 |
| usdt_amount | string | 应付 USDT |
| actual_amount | string | 链上实付 USDT |
| fiat_amount | number | 法币金额 |
| fiat_currency | string | 法币币种 |
| tx_hash | string | 链上交易哈希,全局唯一 |
| paid_at | string | 链上支付时间 |
| attach | string | 商户透传参数(原样返回) |
| timestamp | int | 回调发送时间戳 |
| sign | string | 回调签名,使用 notify_secret 验证 |
验证回调签名(PHP 示例)
$notifySecret = 'YOUR_NOTIFY_SECRET'; // 在商户后台「API 配置」获取
$data = json_decode(file_get_contents('php://input'), true);
// 取出 sign,其余参数参与签名
$sign = $data['sign'];
unset($data['sign']);
// 按 key 升序拼接,notify_secret 仅作 HMAC 密钥
ksort($data);
$parts = [];
foreach ($data as $k => $v) {
if ($v === '' || $v === null) continue;
$parts[] = $k . '=' . $v;
}
$signStr = implode('&', $parts); // notify_secret 仅作 HMAC 密钥
$expected = hash_hmac('sha256', $signStr, $notifySecret);
if (!hash_equals($expected, $sign)) {
http_response_code(400);
exit('INVALID SIGN');
}
// 处理业务:更新订单状态
if ((int)$data['status'] === 1) {
// TODO: 标记订单已支付,发货/开通会员等
updateOrderStatus($data['merchant_order_no'], 1);
}
echo 'OK'; // 必须返回 OK,否则系统会重试
防重复处理:网络抖动可能导致同一回调多次投递,请在处理时先检查本地订单状态,避免重复发货。建议以 tx_hash 作为幂等键。
计费限制:授权过期或点数不足时,即使链上到账也可能跳过回调,需充值后手动补调。
计费限制:授权过期或点数不足时,即使链上到账也可能跳过回调,需充值后手动补调。
常见问题
Q:如何选择收款链?
创建订单时可指定 TRC20 / BEP20 / ERC20;留空则按 TRC20→BEP20→ERC20 优先级自动选默认链。用户进入收银台后仍可切换链。TRC20 手续费最低、到账最快,推荐作为默认链。
Q:用户多付/少付了怎么办?
系统以 tx_hash 唯一识别链上支付,若实付金额与应付不符,订单将标记为状态 3(异常),需要在管理后台人工处理。
Q:回调一直没收到怎么办?
1. 检查后台配置的 notify_url 是否可被外网访问;2. 在商户后台「收款记录」中找到该订单,点击「补调」手动重新触发;3. 确认响应 Body 为 OK 且 HTTP 状态为 200。
Q:商户订单号重复提交会怎样?
若对应的旧订单仍在「待支付」且未过期,接口将返回原订单数据(幂等设计),不会重复创建。若旧订单已过期或已支付,则会创建新订单。
Q:签名验证总是失败?
常见原因:① 参数拼接时包含了空值字段(应跳过);② 数字类型在序列化时多出小数点或空格;③ api_secret 与 api_key 配对错误;④ 服务器时间与标准时间偏差超过5分钟。
更多问题请联系平台管理员,或在商户后台中使用「收银台测试」页面验证您的签名实现是否正确。