付款 API 文件

下載 Markdown 原始檔

付款 API 文件

付款服務會依付款訂單將 USDT 下發至指定的 TRON 地址,並在鏈上結果確認後通知商戶。商戶請使用下列 API 路徑建立與查詢付款訂單;取消付款訂單請在管理後台操作。

商戶須先在後台登記 RSA 公鑰,並使用配對私鑰對每個請求簽章;私鑰不得提供給前端或第三方。

1. 創建付款訂單

介面

POST /payout/create

請求參數

參數 類型 必填 說明
mid string 商戶 ID。
orderid string 商戶付款訂單號;同一商戶下必須唯一。
amount decimal 法幣金額,必須大於 0,最多 2 位小數。
toaddress string TRON/TRC20 收款地址,必須為 34 位、以 T 開頭。
notifyurl string 付款結果通知地址,必須是有效的 httphttps URL。
sign string 請求簽章,規則見下文。

服務會在創建時按照當前系統匯率換算 uamount,並將該值固定到訂單中;之後即使修改匯率,也不會影響已創建訂單。

成功回應

{
  "status": "ok",
  "orderid": "PAYOUT_20260726_001",
  "amount": 100,
  "uamount": 13.888889,
  "toaddress": "TVArfDmKDux1LSQYavBTEgnyhaF5Ta3xBU",
  "created_time": 1785050247
}

失敗回應

{
  "status": "error",
  "message": "商戶訂單號已存在"
}

常見失敗原因:參數缺失、TRON 地址格式錯誤、通知地址格式錯誤、簽章錯誤、來源 IP 不在商戶付款 API 白名單內、當前匯率不可用,或同一商戶訂單號重複。

2. 查詢付款訂單

介面

POST /payout/checkorder

請求參數

參數 類型 必填 說明
mid string 商戶 ID。
orderid string 創建付款訂單時使用的商戶訂單號。
sign string 請求簽章。

成功回應

{
  "status": "ok",
  "orderid": "PAYOUT_20260726_001",
  "status_code": 0,
  "amount": 100,
  "uamount": 13.888889,
  "toaddress": "TVArfDmKDux1LSQYavBTEgnyhaF5Ta3xBU",
  "updatetime": 1785050247
}

status_code 含義:

狀態 updatetime
0 待下發 創建時間
1 已發送 鏈上交易成功時間。
2 已取消 後台取消時間。
3 下發中 後台服務領取訂單的時間。
4 已送出 交易已廣播、正在等待鏈上回執的時間。
5 下發失敗 廣播失敗或鏈上執行失敗時間。
6 待人工核查 下發請求結果無法判定(如逾時或連接中斷);不會回調,須人工確認鏈上結果。

付款結果回調

付款訂單進入終態後,payoutworker 會以 application/x-www-form-urlencoded 向創建訂單時傳入的 notifyurl 發起 HTTP POST 回調。終態包括已發送、已取消和下發失敗。狀態 6(待人工核查)不會發起回調,避免在下發結果無法判定時錯誤通知商戶。

回調會按原入款通知服務相同的規則重試:HTTP 狀態碼為 200 即視為成功;其他狀態碼或網路錯誤會指數退避重試,最多 9 次。

回調參數

參數 類型 說明
orderid string 商戶付款訂單號。
amount string 法幣訂單金額,固定 2 位小數。
uamount string 實際下發 USDT 金額,固定 2 位小數。
created_time string 訂單創建時間,Unix 秒級時間戳。
success_time string 訂單進入終態的時間,Unix 秒級時間戳。欄位名為兼容原通知服務而保留。
status string 終態:1 已發送、2 已取消、5 下發失敗。
txid string TRON 交易 ID;取消或送出前失敗時為空。
sign string 回調簽章,見下方規則。

回調簽章

sign 外的所有回調參數按參數名 ASCII 升序排列,拼接為 key=value&...(不做 URL 编碼),平台使用 RSA PKCS#1 v1.5 + SHA-256 簽章後將標準 Base64 結果放入 sign。商戶用後台獲取的平台 RSA 公鑰驗證簽章。

簽章原文示例(已發送):

amount=100.00&created_time=1785050247&orderid=PAYOUT_20260726_001&status=1&success_time=1785050301&txid=交易ID&uamount=13.89

商戶收到並驗簽成功後應盡快回傳 HTTP 200;無需等待鏈上確認,因為付款服務僅會在鏈上回執已確認終態後發送此回調。

3. 簽章規則

簽章邏輯與收款 API 相同:

  1. 取請求內除 sign 外的所有參數。
  2. 按參數名 ASCII 升序排序並拼成 key=value&key=value不要 URL 编碼
  3. 用商戶 RSA 私鑰執行 SHA256withRSA(RSA PKCS#1 v1.5),將結果標準 Base64 编碼為 sign
  4. 平台用後台登記的商戶 RSA 公鑰驗簽;回調由平台私鑰簽章,商戶用平台公鑰驗簽。

創建訂單時待簽章原文示例:

amount=100.00&mid=1001&notifyurl=https://merchant.example.com/payout/notify&orderid=PAYOUT_20260726_001&toaddress=TVArfDmKDux1LSQYavBTEgnyhaF5Ta3xBU

查詢訂單時待簽章原文示例:

mid=1001&orderid=PAYOUT_20260726_001

簽章核心邏輯

以下示例從創建付款訂單的業務欄位構建排序後的簽章原文;請求使用商戶私鑰簽章,付款回調使用平台公鑰驗簽。

$params = ['mid'=>'1001', 'orderid'=>'PAYOUT_001', 'amount'=>'100.00', 'toaddress'=>'T...', 'notifyurl'=>'https://merchant.example/notify'];
ksort($params, SORT_STRING);
$source = implode('&', array_map(fn($k) => $k.'='.$params[$k], array_keys($params)));
openssl_sign($source, $raw, $privateKey, OPENSSL_ALGO_SHA256);
$params['sign'] = base64_encode($raw);
params := map[string]string{"mid":"1001", "orderid":"PAYOUT_001", "amount":"100.00", "toaddress":"T...", "notifyurl":"https://merchant.example/notify"}
keys := make([]string, 0, len(params)); for k := range params { keys = append(keys, k) }; sort.Strings(keys)
parts := make([]string, 0, len(keys)); for _, k := range keys { parts = append(parts, k+"="+params[k]) }; source := strings.Join(parts, "&")
digest := sha256.Sum256([]byte(source))
raw, _ := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, digest[:])
params["sign"] = base64.StdEncoding.EncodeToString(raw)
Map<String,String> params = new TreeMap<>();
params.put("mid", "1001"); params.put("orderid", "PAYOUT_001"); params.put("amount", "100.00"); params.put("toaddress", "T..."); params.put("notifyurl", "https://merchant.example/notify");
String source = params.entrySet().stream().map(e -> e.getKey()+"="+e.getValue()).collect(Collectors.joining("&"));
Signature s = Signature.getInstance("SHA256withRSA");
s.initSign(privateKey); s.update(source.getBytes(StandardCharsets.UTF_8));
params.put("sign", Base64.getEncoder().encodeToString(s.sign()));

回調驗簽核心邏輯

取出回調中的 sign 並從參數集合移除,其餘參數排序後拼接。platformPublicKey 為後台展示的平台公鑰。

$signature = base64_decode($params['sign']); unset($params['sign']); ksort($params, SORT_STRING);
$source = implode('&', array_map(fn($k) => $k.'='.$params[$k], array_keys($params)));
$valid = openssl_verify($source, $signature, $platformPublicKey, OPENSSL_ALGO_SHA256) === 1;
signature, _ := base64.StdEncoding.DecodeString(params["sign"]); delete(params, "sign")
keys := make([]string, 0, len(params)); for k := range params { keys = append(keys, k) }; sort.Strings(keys)
parts := make([]string, 0, len(keys)); for _, k := range keys { parts = append(parts, k+"="+params[k]) }; digest := sha256.Sum256([]byte(strings.Join(parts, "&")))
err := rsa.VerifyPKCS1v15(platformPublicKey, crypto.SHA256, digest[:], signature)
byte[] signature = Base64.getDecoder().decode(params.remove("sign"));
String source = params.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(e -> e.getKey()+"="+e.getValue()).collect(Collectors.joining("&"));
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(platformPublicKey); verifier.update(source.getBytes(StandardCharsets.UTF_8));
boolean valid = verifier.verify(signature);