创建代付订单
用于创建一笔代付订单。成功后平台会先冻结待付金额和网络费,并返回完整订单详情。
如果不传 fromAddress,系统会自动选择满足余额条件的可用付款地址;没有满足余额条件的地址时会返回地址余额不足。
请求头
| 字段名称 | 字段类型 | 是否必填 | 说明 |
|---|---|---|---|
| x-api-key | string | 是 | 商户 API Key,使用商户后台 API 接入页面中的 mch_ 开头字符串。 |
| x-merchant-uid | string | 是 | 商户 UID。 |
| x-timestamp | string | 是 | 13 位毫秒时间戳,默认允许和服务器时间相差 5 分钟。 |
| x-nonce | string | 是 | 每次请求唯一的随机字符串,最长 128 位,重复使用会被拒绝。 |
| x-signature | string | 是 | 使用 HMAC-SHA256 生成的十六进制签名,签名原文由 Merchant UID、时间戳、nonce、请求方法、路径、规范化 query 和规范化 body 组成。 |
接口参数
| 字段名称 | 字段类型 | 是否必填 | 说明 |
|---|---|---|---|
| chainCode | string | 是 | 链编码,支持 TRON / BSC / SOL。 |
| tokenSymbol | string | 是 | 代币符号。 |
| fromAddress | string | 否 | 指定付款地址;不传时系统自动分派。 最长 128 个字符。 |
| toAddress | string | 是 | 收款地址。 最长 128 个字符。 |
| amount | string | 是 | 代付金额,最多保留 6 位小数。 |
| deleteSourceAfterSuccess | boolean | 否 | 可选 JSON boolean,默认 false。true 仅本笔链上确认成功后软删除来源地址,失败或取消不删除。付款不强制预留余额。 |
| merchantOrderNo | string | 是 | 商户侧订单号。 最长 128 个字符。 |
| notifyUrl | string | 是 | 该订单专用的回调通知地址,建议传商户自己的公网 HTTPS 地址。 最长 500 个字符;禁止私网、回环和不安全目标。 |
请求示例
{
"chainCode": "TRON",
"tokenSymbol": "USDT",
"fromAddress": "TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY",
"toAddress": "TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S",
"amount": "35.50",
"deleteSourceAfterSuccess": false,
"merchantOrderNo": "PO202604150001",
"notifyUrl": "https://merchant.example.com/api/uugate/payout-notify"
}接口返回
| 字段名称 | 字段类型 | 是否必填 | 说明 |
|---|---|---|---|
| orderNo | string | 是 | 平台代付订单号。 |
| merchantOrderNo | string | null | 是 | 商户订单号。 |
| fromAddress | string | null | 是 | 实际付款地址。 |
| toAddress | string | 是 | 收款地址。 |
| amount | string | 是 | 代付金额。 |
| txHash | string | null | 是 | 已广播时返回链上哈希。 |
| status | string | 是 | submitted / broadcasted / confirmed / failed。 |
| submittedAt | string | null | 是 | 提交时间。 |
| broadcastedAt | string | null | 是 | 广播时间。 |
| confirmedAt | string | null | 是 | 确认时间。 |
| failedAt | string | null | 是 | 失败时间。 |
| createdAt | string | null | 是 | 创建时间。 |
返回示例
{
"orderNo": "PO1776193200A1B2C3D4",
"merchantOrderNo": "PO202604150001",
"fromAddress": "TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY",
"toAddress": "TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S",
"amount": "35.500000",
"txHash": "b3d4fb0cb11eb86bb906d1e0ee3b84e17f40d5441d1e4c651ea46920a48a9a01",
"status": "broadcasted",
"submittedAt": "2026-04-15T03:00:00Z",
"broadcastedAt": "2026-04-15T03:00:08Z",
"confirmedAt": null,
"failedAt": null,
"createdAt": "2026-04-15T03:00:00Z"
}代码示例
运行:Java 保存为 UUGateExample.java(回调为 UUGateCallback.java),使用 java 文件名;PHP 保存为 example.php,使用 php example.php;Go 保存为 example.go,使用 go run example.go;Node.js 保存为 example.mjs,使用 node example.mjs。回调原文从标准输入读取。
import { createHmac, randomBytes } from 'node:crypto';
function required(name) {
const value = process.env[name]?.trim();
if (!value) throw new Error(name + ' is required');
return value;
}
const uid = required("UUGATE_MERCHANT_UID"), key = required("UUGATE_API_KEY");
const timestamp = String(Date.now()), nonce = randomBytes(16).toString("hex");
const method = 'POST', path = '/openapi/payout/orders';
const canonicalQuery = '', canonicalBody = "{\"amount\":\"35.50\",\"chainCode\":\"TRON\",\"deleteSourceAfterSuccess\":false,\"fromAddress\":\"TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY\",\"merchantOrderNo\":\"PO202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payout-notify\",\"toAddress\":\"TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S\",\"tokenSymbol\":\"USDT\"}";
const payload = [uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody].join('\n');
const signature = createHmac('sha256', key).update(payload, 'utf8').digest('hex');
const base = (process.env.UUGATE_BASE_URL || 'https://api.uugate.com').replace(/\/+$/, '');
const response = await fetch(base + path, {
method, redirect: 'error', signal: AbortSignal.timeout(30000),
headers: { 'Content-Type': 'application/json', 'x-api-key': key, 'x-merchant-uid': uid,
'x-timestamp': timestamp, 'x-nonce': nonce, 'x-signature': signature },
...(canonicalBody ? { body: canonicalBody } : {}),
});
const raw = await response.text();
if (!response.ok) throw new Error('HTTP ' + response.status + ': ' + (raw || 'empty response body'));
JSON.parse(raw);
console.log(raw);import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class UUGateExample {
static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) throw new IllegalArgumentException(name + " is required");
return value.trim();
}
public static void main(String[] args) throws Exception {
String uid = required("UUGATE_MERCHANT_UID"), key = required("UUGATE_API_KEY");
String timestamp = Long.toString(System.currentTimeMillis());
byte[] random = new byte[16]; new SecureRandom().nextBytes(random);
String nonce = HexFormat.of().formatHex(random);
String method = "POST", path = "/openapi/payout/orders";
String canonicalQuery = "", canonicalBody = "{\"amount\":\"35.50\",\"chainCode\":\"TRON\",\"deleteSourceAfterSuccess\":false,\"fromAddress\":\"TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY\",\"merchantOrderNo\":\"PO202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payout-notify\",\"toAddress\":\"TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S\",\"tokenSymbol\":\"USDT\"}";
String payload = String.join("\n", uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = HexFormat.of().formatHex(mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));
String base = System.getenv().getOrDefault("UUGATE_BASE_URL", "https://api.uugate.com").replaceAll("/+$", "");
HttpRequest request = HttpRequest.newBuilder(URI.create(base + path)).timeout(Duration.ofSeconds(30))
.header("Content-Type", "application/json").header("x-api-key", key).header("x-merchant-uid", uid)
.header("x-timestamp", timestamp).header("x-nonce", nonce).header("x-signature", signature)
.method(method, canonicalBody.isEmpty() ? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(canonicalBody, StandardCharsets.UTF_8)).build();
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode() + ": "
+ (response.body().isEmpty() ? "empty response body" : response.body()));
if (response.body().isBlank()) throw new IllegalStateException("Empty success response");
System.out.println(response.body());
}
}<?php
function required(string $name): string {
$value = trim((string) getenv($name));
if ($value === '') throw new RuntimeException($name . ' is required');
return $value;
}
$uid = required("UUGATE_MERCHANT_UID"); $key = required("UUGATE_API_KEY");
$timestamp = (string) (int) floor(microtime(true) * 1000);
$nonce = bin2hex(random_bytes(16));
$method = 'POST'; $path = '/openapi/payout/orders';
$canonicalQuery = ''; $canonicalBody = '{"amount":"35.50","chainCode":"TRON","deleteSourceAfterSuccess":false,"fromAddress":"TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY","merchantOrderNo":"PO202604150001","notifyUrl":"https://merchant.example.com/api/uugate/payout-notify","toAddress":"TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S","tokenSymbol":"USDT"}';
$payload = implode("\n", [$uid, $timestamp, $nonce, $method, $path, $canonicalQuery, $canonicalBody]);
$signature = hash_hmac('sha256', $payload, $key);
$base = rtrim(getenv('UUGATE_BASE_URL') ?: 'https://api.uugate.com', '/');
$curl = curl_init($base . $path);
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, CURLOPT_FOLLOWLOCATION => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'x-api-key: ' . $key,
'x-merchant-uid: ' . $uid, 'x-timestamp: ' . $timestamp,
'x-nonce: ' . $nonce, 'x-signature: ' . $signature],
]);
if ($canonicalBody !== '') curl_setopt($curl, CURLOPT_POSTFIELDS, $canonicalBody);
$raw = curl_exec($curl);
if ($raw === false) throw new RuntimeException('Transport error: ' . curl_error($curl));
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status < 200 || $status >= 300)
throw new RuntimeException('HTTP ' . $status . ': ' . ($raw === '' ? 'empty response body' : $raw));
json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
echo $raw, PHP_EOL;package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/rand"
"encoding/hex"
"fmt"
"encoding/json"
"io"
"net/http"
"time"
"os"
"strings"
)
func required(name string) string {
value := strings.TrimSpace(os.Getenv(name))
if value == "" { panic(name + " is required") }
return value
}
func main() {
uid, key := required("UUGATE_MERCHANT_UID"), required("UUGATE_API_KEY")
timestamp := fmt.Sprint(time.Now().UnixMilli())
random := make([]byte, 16); if _, err := rand.Read(random); err != nil { panic(err) }
nonce := hex.EncodeToString(random)
method, path := "POST", "/openapi/payout/orders"
canonicalQuery, canonicalBody := "", "{\"amount\":\"35.50\",\"chainCode\":\"TRON\",\"deleteSourceAfterSuccess\":false,\"fromAddress\":\"TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY\",\"merchantOrderNo\":\"PO202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payout-notify\",\"toAddress\":\"TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S\",\"tokenSymbol\":\"USDT\"}"
payload := strings.Join([]string{uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody}, "\n")
mac := hmac.New(sha256.New, []byte(key)); mac.Write([]byte(payload))
signature := hex.EncodeToString(mac.Sum(nil))
base := strings.TrimRight(os.Getenv("UUGATE_BASE_URL"), "/")
if base == "" { base = "https://api.uugate.com" }
request, err := http.NewRequest(method, base + path, strings.NewReader(canonicalBody))
if err != nil { panic(err) }
for key, value := range map[string]string{"Content-Type": "application/json", "x-api-key": key,
"x-merchant-uid": uid, "x-timestamp": timestamp, "x-nonce": nonce, "x-signature": signature} {
request.Header.Set(key, value)
}
client := &http.Client{Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }}
response, err := client.Do(request); if err != nil { panic(err) }
defer response.Body.Close()
raw, err := io.ReadAll(response.Body); if err != nil { panic(err) }
if response.StatusCode < 200 || response.StatusCode >= 300 {
detail := string(raw); if detail == "" { detail = "empty response body" }
panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, detail))
}
if !json.Valid(raw) { panic("Invalid JSON success response") }
fmt.Println(string(raw))
}说明
- 公开 API 只提供单笔付款;批量付款仅在商户后台使用。
- 付款 10 时来源可用余额至少需要 10,不额外预留余额,与 deleteSourceAfterSuccess 的取值无关。付款金额不会自动扣减,费用账户仍须足额支付网络费。
- 该参数应传 true / false,不接受字符串。同一 merchantOrderNo 重试必须保持参数一致;存在其他未完成转账时不能选择删除来源。
- 广播或创建成功不等于链上确认成功。软删除保留历史订单、账目及密钥引用,失败或取消不删除。
- 创建接口会自动尝试提交链上;请以返回或后续查询中的 status 判断结果,不要把 HTTP 2xx 当作链上成功。
- 四种语言使用相同的接口路径、参数和返回合同。示例从环境变量 UUGATE_MERCHANT_UID、UUGATE_API_KEY 读取凭据;UUGATE_BASE_URL 默认 https://api.uugate.com。
- 示例中的 canonicalBody 已按字段名排序并压缩为 JSON;修改参数后须递归排序对象字段,数组顺序保持,使用 UTF-8 且不额外转义斜杠或 Unicode。金额使用字符串,boolean 使用 true / false。
- 成功响应直接返回业务对象,没有统一的 code / data 外层。先判断 HTTP 状态,再处理正文;普通业务错误返回 code、message、data、requestId,安全拒绝可能只有 HTTP 401 / 403 和空正文。
- 网络超时或 HTTP 5xx 后先查询订单,不自动重复创建或提交付款;每次新请求重新生成 timestamp、nonce 和签名。