Crear orden de pago saliente
Crea una orden de pago. Después del éxito, la plataforma congela el monto del pago y la tarifa de la red y luego devuelve los detalles del pedido.
Si no se proporciona fromAddress, el sistema selecciona automáticamente una dirección con saldo suficiente. Si no hay ninguno disponible, devuelve un saldo de dirección insuficiente.
Encabezados
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| x-api-key | string | Sí | Comerciante API Key. Utilice el valor que comienza con mch_ de la página de configuración del comerciante API. |
| x-merchant-uid | string | Sí | Merchant UID. |
| x-timestamp | string | Sí | Milisegundo de 13 dígitos timestamp. La desviación del reloj permitida por defecto es de 5 minutos. |
| x-nonce | string | Sí | Cadena aleatoria única para cada solicitud, hasta 128 caracteres. Se rechaza la reutilización. |
| x-signature | string | Sí | Firma hexadecimal generada con HMAC-SHA256. La carga útil contiene Merchant UID, timestamp, nonce, método, ruta, query canónico y body canónico. |
Parámetros
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| chainCode | string | Sí | Código de cadena. Valores admitidos: TRON / BSC / SOL. |
| tokenSymbol | string | Sí | Símbolo simbólico. |
| fromAddress | string | No | Dirección del remitente especificada. Si se omite, el sistema asigna uno automáticamente. Máximo 128 caracteres. |
| toAddress | string | Sí | Dirección de recepción. Máximo 128 caracteres. |
| amount | string | Sí | Importe del pago, hasta 6 decimales. |
| deleteSourceAfterSuccess | boolean | No | Booleano JSON opcional, false por defecto. Si es true, elimina lógicamente el origen solo tras confirmar este pago en cadena; un fallo o cancelación no lo elimina. Los pagos no exigen reserva de saldo. |
| merchantOrderNo | string | Sí | Número de pedido del lado comerciante. Máximo 128 caracteres. |
| notifyUrl | string | Sí | Devolución de llamada específica del pedido URL. Se recomienda un HTTPS URL público propiedad del comerciante. Máximo 500 caracteres; destinos privados, loopback o inseguros están prohibidos. |
Ejemplo de solicitud
{
"chainCode": "TRON",
"tokenSymbol": "USDT",
"fromAddress": "TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY",
"toAddress": "TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S",
"amount": "35.50",
"deleteSourceAfterSuccess": false,
"merchantOrderNo": "PO202604150001",
"notifyUrl": "https://merchant.example.com/api/uugate/payout-notify"
}Campos de respuesta
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| orderNo | string | Sí | Número de orden de pago de la plataforma. |
| merchantOrderNo | string | null | Sí | Número de pedido del comerciante. |
| fromAddress | string | null | Sí | Dirección real del remitente. |
| toAddress | string | Sí | Dirección de recepción. |
| amount | string | Sí | Monto del pago. |
| txHash | string | null | Sí | El hash en cadena se devolvió después de la transmisión. |
| status | string | Sí | enviado / transmitido / confirmed / failed. |
| submittedAt | string | null | Sí | Hora de envío. |
| broadcastedAt | string | null | Sí | Hora de transmisión. |
| confirmedAt | string | null | Sí | Hora de confirmación. |
| failedAt | string | null | Sí | Tiempo fallido. |
| createdAt | string | null | Sí | Tiempo creado. |
Ejemplo de respuesta
{
"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"
}Ejemplos de código
Ejecución: guarda Java como UUGateExample.java (UUGateCallback.java para callbacks) y usa java nombre de archivo; PHP: php example.php; Go: go run example.go; Node.js: node example.mjs. La verificación de callbacks lee el cuerpo original desde la entrada estándar.
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))
}Notas
- La API publica solo admite pagos individuales. Los pagos por lote estan disponibles solo en el panel del comercio.
- Un pago de 10 requiere al menos 10 de saldo disponible en el origen, sin reserva adicional, independientemente de deleteSourceAfterSuccess. El importe no se reduce; la cuenta de tarifas debe cubrir la tarifa de red.
- Envia true / false, no cadenas. Los reintentos con el mismo merchantOrderNo deben conservar el parametro. No se puede borrar el origen si hay otras transferencias sin finalizar.
- Crear o transmitir con exito no equivale a confirmacion en cadena. La eliminacion logica conserva historial, registros y referencias de claves. Un fallo o cancelacion conserva el origen.
- The create API automatically attempts submission. Use status from the response or a later query; an HTTP 2xx alone is not on-chain success.
- Los cuatro lenguajes comparten rutas, parámetros y respuestas. Las credenciales se leen de UUGATE_MERCHANT_UID y UUGATE_API_KEY; UUGATE_BASE_URL usa https://api.uugate.com por defecto.
- canonicalBody es JSON compacto con claves ordenadas. Al modificar parámetros, ordena objetos recursivamente, conserva el orden de arrays y usa UTF-8 sin escapes adicionales de barras ni Unicode. Los importes son cadenas y los booleanos true / false.
- El éxito devuelve directamente el objeto de negocio, sin envoltorio code / data. Comprueba primero el estado HTTP. Los errores devuelven code, message, data y requestId; un rechazo de seguridad puede ser solo HTTP 401 / 403 sin cuerpo.
- Tras un timeout o HTTP 5xx, consulta el pedido antes de repetir su creación o envío. Genera un timestamp, nonce y firma nuevos en cada solicitud.