Créer un ordre de paiement sortant
Crée un ordre de paiement. En cas de succès, la plateforme gèle le montant du paiement et les frais de réseau, puis renvoie les détails de la commande.
Si fromAddress n’est pas fourni, le système sélectionne automatiquement une adresse avec un solde suffisant. Si aucun n’est disponible, le solde d’adresses est insuffisant.
En-têtes
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| x-api-key | string | Oui | Marchand API Key. Utilisez la valeur commençant par mch_ de la page des paramètres API du marchand. |
| x-merchant-uid | string | Oui | Merchant UID. |
| x-timestamp | string | Oui | Milliseconde à 13 chiffres timestamp. La dérive d'horloge autorisée par défaut est de 5 minutes. |
| x-nonce | string | Oui | Chaîne aléatoire unique pour chaque requête, jusqu'à 128 caractères. La réutilisation est rejetée. |
| x-signature | string | Oui | Signature hexadécimale générée avec HMAC-SHA256. La charge utile contient Merchant UID, timestamp, nonce, la méthode, le chemin, le query canonique et le body canonique. |
Paramètres
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| chainCode | string | Oui | Code de chaîne. Valeurs prises en charge : TRON / BSC / SOL. |
| tokenSymbol | string | Oui | Symbole de jeton. |
| fromAddress | string | Non | Adresse de l'expéditeur spécifiée. En cas d'omission, le système en attribue un automatiquement. Maximum 128 caractères. |
| toAddress | string | Oui | Adresse de réception. Maximum 128 caractères. |
| amount | string | Oui | Montant du paiement, jusqu'à 6 décimales. |
| deleteSourceAfterSuccess | boolean | Non | Booléen JSON facultatif, false par défaut. Si true, supprime logiquement la source uniquement après confirmation de ce paiement sur la blockchain ; aucun effacement en cas d’échec ou d’annulation. Les paiements n’exigent aucune réserve de solde. |
| merchantOrderNo | string | Oui | Numéro de commande côté marchand. Maximum 128 caractères. |
| notifyUrl | string | Oui | Rappel spécifique à la commande URL. Un HTTPS URL public appartenant au commerçant est recommandé. Maximum 500 caractères ; les destinations privées, loopback ou non sécurisées sont interdites. |
Exemple de requête
{
"chainCode": "TRON",
"tokenSymbol": "USDT",
"fromAddress": "TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY",
"toAddress": "TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S",
"amount": "35.50",
"deleteSourceAfterSuccess": false,
"merchantOrderNo": "PO202604150001",
"notifyUrl": "https://merchant.example.com/api/uugate/payout-notify"
}Champs de réponse
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| orderNo | string | Oui | Numéro d’ordre de paiement de la plateforme. |
| merchantOrderNo | string | null | Oui | Numéro de commande du commerçant. |
| fromAddress | string | null | Oui | Adresse réelle de l'expéditeur. |
| toAddress | string | Oui | Adresse de réception. |
| amount | string | Oui | Montant du paiement. |
| txHash | string | null | Oui | Le hachage en chaîne est revenu après la diffusion. |
| status | string | Oui | soumis / diffusé / confirmed / failed. |
| submittedAt | string | null | Oui | Heure soumise. |
| broadcastedAt | string | null | Oui | Heure de diffusion. |
| confirmedAt | string | null | Oui | Heure de confirmation. |
| failedAt | string | null | Oui | Temps raté. |
| createdAt | string | null | Oui | Temps créé. |
Exemple de réponse
{
"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"
}Exemples de code
Exécution : enregistrez Java sous UUGateExample.java (UUGateCallback.java pour les callbacks), puis utilisez java nom du fichier ; PHP : php example.php ; Go : go run example.go ; Node.js : node example.mjs. Les vérificateurs de callback lisent le corps brut sur l’entrée standard.
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))
}Notes
- L API publique prend uniquement en charge les paiements unitaires. Les paiements par lot sont disponibles uniquement dans le tableau de bord marchand.
- Un paiement de 10 nécessite au moins 10 de solde disponible à la source, sans réserve supplémentaire, quelle que soit la valeur de deleteSourceAfterSuccess. Le montant n’est pas réduit ; le compte de frais doit couvrir les frais réseau.
- Transmettez true / false, pas des chaines. Les nouvelles tentatives avec le meme merchantOrderNo doivent conserver ce parametre. La suppression est impossible si d autres transferts sont inacheves.
- La creation ou la diffusion reussie ne vaut pas confirmation sur la blockchain. La suppression logique conserve historique, comptabilite et references de cles. L echec ou l annulation conserve la source.
- The create API automatically attempts submission. Use status from the response or a later query; an HTTP 2xx alone is not on-chain success.
- Les quatre langages utilisent les mêmes chemins, paramètres et réponses. Les identifiants proviennent de UUGATE_MERCHANT_UID et UUGATE_API_KEY ; UUGATE_BASE_URL vaut https://api.uugate.com par défaut.
- canonicalBody est un JSON compact aux clés triées. Après modification, triez les objets récursivement, conservez l’ordre des tableaux et utilisez UTF-8 sans échappement supplémentaire des barres obliques ou Unicode. Les montants sont des chaînes, les booléens true / false.
- Une réponse réussie renvoie directement l’objet métier, sans enveloppe code / data. Vérifiez d’abord le statut HTTP. Les erreurs métier renvoient code, message, data et requestId ; les refus de sécurité peuvent se limiter à HTTP 401 / 403 avec un corps vide.
- Après un timeout ou HTTP 5xx, consultez la commande avant de répéter sa création ou son envoi. Générez un nouveau timestamp, nonce et une nouvelle signature pour chaque requête.