Buat Dompet Eksklusif
Membuat alamat penerima eksklusif untuk bindKey. Setoran mendatang ke alamat ini secara permanen menjadi milik bindKey tersebut.
Jika notifyUrl disediakan, pesanan isi ulang yang dibuat secara otomatis di masa mendatang untuk alamat eksklusif ini akan melakukan panggilan balik ke URL tersebut terlebih dahulu. Ini berguna ketika setiap situs atau aplikasi menerima notifikasi isi ulang anggotanya sendiri.
Header
| Kolom | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| x-api-key | string | Ya | Pedagang API Key. Gunakan nilai yang diawali dengan mch_ dari halaman pengaturan API pedagang. |
| x-merchant-uid | string | Ya | Merchant UID. |
| x-timestamp | string | Ya | 13 digit milidetik timestamp. Penyimpangan jam yang diperbolehkan secara default adalah 5 menit. |
| x-nonce | string | Ya | String acak unik untuk setiap permintaan, hingga 128 karakter. Penggunaan kembali ditolak. |
| x-signature | string | Ya | Tanda tangan hex dihasilkan dengan HMAC-SHA256. Payload berisi Merchant UID, timestamp, nonce, metode, jalur, query kanonik, dan body kanonik. |
Parameter
| Kolom | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| chainCode | string | Ya | Kode rantai. Nilai yang didukung: TRON / BSC / SOL. |
| tokenSymbol | string | Ya | Simbol token. |
| bindKey | string | Ya | Kunci pengikatan unik di sisi pedagang. Maksimal 128 karakter. |
| label | string | TIDAK | Komentar yang mengikat. Maksimal 100 karakter. |
| notifyUrl | string | TIDAK | Panggilan balik independen URL untuk alamat eksklusif ini. Pesanan yang dibuat secara otomatis dari setoran masa depan akan dipanggil kembali ke sini terlebih dahulu. Maksimal 500 karakter; tujuan jaringan privat, loopback, dan tidak aman dilarang. |
Contoh permintaan
{
"chainCode": "TRON",
"tokenSymbol": "USDT",
"bindKey": "USER_90001",
"label": "VIP User 90001",
"notifyUrl": "https://merchant.example.com/api/uugate/member-topup-notify"
}Kolom respons
| Kolom | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| bindingId | number | Ya | Mengikat ID rekaman. |
| chainCode | string | Ya | Kode rantai. |
| tokenSymbol | string | Ya | Simbol token. |
| bindKey | string | Ya | Kunci pengikat. |
| addressId | number | Ya | ID Alamat. |
| address | string | Ya | Alamat penerima eksklusif. |
| addressType | string | Ya | Selalu eksklusif. |
| notifyUrl | string | null | Ya | URL callback eksklusif yang digunakan oleh binding ini. |
| status | string | Ya | Mengikat status. |
Contoh respons
{
"bindingId": 70031,
"chainCode": "TRON",
"tokenSymbol": "USDT",
"bindKey": "USER_90001",
"addressId": 31088,
"address": "TQ3sCY8k9h7G9UsApY3JPt5VDpNKr2kJ1E",
"addressType": "exclusive",
"notifyUrl": "https://merchant.example.com/api/uugate/member-topup-notify",
"status": "active"
}Contoh kode
Menjalankan: simpan Java sebagai UUGateExample.java (UUGateCallback.java untuk callback), lalu gunakan java nama file; PHP: php example.php; Go: go run example.go; Node.js: node example.mjs. Verifikasi callback membaca isi asli dari input standar.
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/payin/exclusive-bindings';
const canonicalQuery = '', canonicalBody = "{\"bindKey\":\"USER_90001\",\"chainCode\":\"TRON\",\"label\":\"VIP User 90001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/member-topup-notify\",\"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/payin/exclusive-bindings";
String canonicalQuery = "", canonicalBody = "{\"bindKey\":\"USER_90001\",\"chainCode\":\"TRON\",\"label\":\"VIP User 90001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/member-topup-notify\",\"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/payin/exclusive-bindings';
$canonicalQuery = ''; $canonicalBody = '{"bindKey":"USER_90001","chainCode":"TRON","label":"VIP User 90001","notifyUrl":"https://merchant.example.com/api/uugate/member-topup-notify","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/payin/exclusive-bindings"
canonicalQuery, canonicalBody := "", "{\"bindKey\":\"USER_90001\",\"chainCode\":\"TRON\",\"label\":\"VIP User 90001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/member-topup-notify\",\"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))
}Catatan
- Jika bindKey yang sama dibuat lagi, platform secara idempoten mengembalikan pengikatan dan alamat yang ada, yang berfungsi dengan baik untuk alur dapatkan atau buat.
- Untuk isi ulang anggota, lewati notifyUrl pada waktu yang mengikat sehingga setoran dalam jumlah berapa pun ke alamat eksklusif akan dipanggil kembali langsung ke situs atau aplikasi terkait.
- Keempat bahasa memakai jalur, parameter, dan kontrak respons yang sama. Kredensial dibaca dari UUGATE_MERCHANT_UID dan UUGATE_API_KEY; UUGATE_BASE_URL secara default adalah https://api.uugate.com.
- canonicalBody adalah JSON ringkas dengan nama kolom terurut. Setelah mengubah parameter, urutkan key objek secara rekursif, pertahankan urutan array, dan gunakan UTF-8 tanpa escape tambahan untuk slash atau Unicode. Nominal berupa string; boolean true / false.
- Respons sukses langsung berisi objek bisnis tanpa pembungkus code / data. Periksa status HTTP sebelum isi. Error bisnis berisi code, message, data, requestId; penolakan keamanan dapat hanya mengirim HTTP 401 / 403 dengan isi kosong.
- Setelah timeout atau HTTP 5xx, periksa pesanan sebelum mengulang pembuatan atau pengiriman. Buat timestamp, nonce, dan tanda tangan baru untuk setiap permintaan.