商户后台 返回主页

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 收款订单的创建与回调处理。

PHP
Python
Node.js
// 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_keystring必填商户 API Key,在后台「API 配置」中获取
timestampint必填当前 Unix 时间戳(秒),服务端允许 ±300 秒误差
noncestring必填随机字符串,≥8位,15 分钟内不可复用(防重放)
signstring必填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');

创建收款订单

POST /api/pay/create 需签名鉴权

创建一笔 USDT 收款订单。同一 merchant_order_no 若存在未过期的待支付订单,将返回原订单(幂等)。

计费说明:创建订单仅校验 API Key 与签名,不校验授权/点数。授权过期或点数不足时仍可生成收款码,但链上到账后可能不监听、不自动回调商户。

请求参数

参数类型必填说明
merchant_order_nostring必填商户侧唯一订单号,≤64 字符
amountnumber必填收款金额,>0
currencystring可选法币币种:CNY(默认)/ USD / USDT
chainstring可选收款链:TRC20 / BEP20 / ERC20;留空按 TRC20→BEP20→ERC20 优先级自动选默认链,收银台可切换
notify_urlstring可选异步回调地址,覆盖后台默认配置
return_urlstring可选支付成功后收银台前端跳转地址
attachstring可选透传参数,回调时原样返回,≤500 字符
api_key / timestamp / nonce / sign必填公共鉴权参数,见「接口鉴权」

响应数据 data

字段类型说明
order_nostring系统订单号,格式 UPG + 时间 + 随机
merchant_order_nostring商户订单号(原样返回)
chainstring|null收款链
chain_pendingbool是否待选链(当前创建接口固定为 false)
pay_addressstring|nullUSDT 收款地址
usdt_amountstring需转账的精确 USDT 金额
fiat_amountnumber法币金额
fiat_currencystring法币币种
exchange_ratefloat下单时汇率快照(1 USDT = X 法币)
pay_urlstring收银台页面 URL,可直接跳转引导用户支付
return_urlstring支付成功后收银台跳转地址
qr_urlstring二维码数据 URL
expire_atstring订单过期时间 Y-m-d H:i:s
expire_secondsint距过期剩余秒数
statusint0 = 待支付
status_textstring状态中文描述

响应示例

{
  "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
  }
}

查询订单状态

GET /api/pay/query 需签名鉴权

通过系统订单号或商户订单号查询订单当前状态。

请求参数

参数类型必填说明
order_nostring二选一系统订单号
merchant_order_nostring二选一商户订单号
api_key / timestamp / nonce / sign必填公共鉴权参数

响应数据 data

字段类型说明
order_nostring系统订单号
merchant_order_nostring商户订单号
statusint见「订单状态码」
status_textstring状态中文描述
chainstring收款链
pay_addressstring收款地址
usdt_amountstring应付 USDT
actual_amountstring实付 USDT(到账后填充)
fiat_amountnumber法币金额
fiat_currencystring法币币种
exchange_ratefloat下单时汇率快照
tx_hashstring链上交易哈希
from_addressstring付款方地址
confirmationsint区块确认数
paid_atstring|null支付时间
expire_atstring过期时间
created_atstring创建时间

查询可用收款链

GET /api/pay/chains 需签名鉴权(不校验授权/点数)

查询当前商户已配置的收款链,可选传入金额做计费预检。建议在创建订单前调用,避免必然失败的请求。

请求参数

参数类型必填说明
amountnumber可选与 currency 配合,用于计费预检
currencystring可选CNY / USD / USDT,默认 USDT
api_key / timestamp / nonce / sign必填公共鉴权参数

响应数据 data

字段类型说明
chainsarray已配置地址的链,如 ["TRC20","BEP20"]
chain_optionsarray各链详情:chain、enabled、reason、notice
billing_activebool计费状态是否正常
billing_noticestring计费异常时的提示

回调通知规范

链上到账
UPG 监控服务
POST 您的 notify_url
返回 OK
重要:您的服务器必须在 10 秒内响应 HTTP 200 且 Body 为 OK,否则系统将按指数退避策略重试,最多 5 次。

回调参数

参数类型说明
order_nostring系统订单号
merchant_order_nostring商户订单号
statusint1 = 已支付(回调仅在支付成功时触发)
chainstring收款链
pay_addressstring收款地址
usdt_amountstring应付 USDT
actual_amountstring链上实付 USDT
fiat_amountnumber法币金额
fiat_currencystring法币币种
tx_hashstring链上交易哈希,全局唯一
paid_atstring链上支付时间
attachstring商户透传参数(原样返回)
timestampint回调发送时间戳
signstring回调签名,使用 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 作为幂等键。
计费限制:授权过期或点数不足时,即使链上到账也可能跳过回调,需充值后手动补调。
计费限制:授权过期或点数不足时,即使链上到账也可能跳过回调,需充值后手动补调。

验证回调签名(辅助接口)

POST /api/pay/verify-callback 需签名鉴权

将收到的回调数据提交至此接口,由网关侧帮助验证 notify_secret 签名是否正确。适合调试阶段使用,生产环境建议在本地验签(见上节)。

请求参数

参数类型必填说明
signstring必填待验证的回调 sign 值
(其他回调字段)mixed必填将完整回调 body 的其他字段原样传入
api_key / timestamp / nonce / sign必填公共鉴权参数(覆盖同名字段时注意区分)

响应数据

字段类型说明
validbooltrue 签名正确 / false 签名错误

订单状态码

状态值含义说明
0 待支付等待用户转账订单有效,未检测到链上支付
1 已支付支付成功链上已到账并确认,回调已/将发送
2 已过期订单过期超过有效期(默认30分钟)未收到支付
3 异常风控异常检测到风险交易,需人工处理

错误码

HTTP状态code常见原因
400400参数缺失或格式错误(如金额为负、订单号超长)
401401鉴权失败:api_key 无效、签名错误、时间戳超时、nonce 复用
403403账户被禁用、授权已过期或未开通(查询等需完整鉴权的接口;创建订单不校验授权)
404404订单不存在(或不属于当前商户)
429429请求频率超限(默认60次/分钟)
500500服务器内部错误,可联系管理员排查

错误响应格式

{
  "code": 401,
  "message": "签名验证失败",
  "data": null
}

SDK 下载

选择适合您系统的 SDK 包,解压后按 README 完成配置即可对接。所有包均已内嵌依赖,无需额外安装。

??
通用 PHP SDK
适用于 ThinkPHP、Laravel、CodeIgniter、原生 PHP 等,含创建订单和回调处理示例。
? 下载
??
WooCommerce 插件
适用于 WordPress + WooCommerce 商城,解压后上传至 /wp-content/plugins/ 激活即用。
? 下载
??
独角发卡插件
适用于独角发卡(Dujiaoka)系统,将 upg 文件夹放入 app/Payments/ 后在后台配置。
? 下载
以上下载链接需要登录商户账号后访问。

常见问题

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分钟。

更多问题请联系平台管理员,或在商户后台中使用「收银台测试」页面验证您的签名实现是否正确。