Contoh Permintaan dan Verifikasi Tanda Tangan
Metode permintaan: API terutama memakai `GET` / `POST`; permintaan JSON memakai `application/json`.
Autentikasi: permintaan OpenAPI diverifikasi dengan header dan tanda tangan HMAC-SHA256.
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. |
Aturan tanda tangan
- Permintaan menggunakan application/json. Untuk GET APIs tanpa body, bagian body di tanda tangan adalah string kosong.
- Jalur tanda tangan harus berupa jalur API mentah, misalnya /openapi/payin/orders, tanpa domain.
- Parameter kueri diurutkan berdasarkan kunci; Kunci JSON body diurutkan sebelum serialisasi.
- Kegagalan tanda tangan yang umum mencakup timestamp yang kedaluwarsa, nilai nonce yang berulang, urutan bidang yang tidak konsisten, atau spasi tambahan.
Timestamp, nonce, dan kredensial tetap di bawah adalah nilai uji tanda tangan offline. Untuk permintaan nyata, gunakan timestamp milidetik saat ini dan nonce acak baru. Jalur API tidak memiliki awalan versi; v1 hanya merujuk ke tanda tangan callback.
Contoh payload tanda tangan
880001
1776193200000
2f5c7b147c3748f0a8b3d9bb38aa91a4
POST
/openapi/payin/orders
{"amount":"100.00","chainCode":"TRON","merchantOrderNo":"M202604150001","notifyUrl":"https://merchant.example.com/api/uugate/payin-notify","tokenSymbol":"USDT"}Contoh permintaan lengkap
POST /openapi/payin/orders HTTP/1.1
Host: api.uugate.com
Content-Type: application/json
x-api-key: mch_xxxxxxxxxxxxxxxxxxxx
x-merchant-uid: 880001
x-timestamp: 1776193200000
x-nonce: 2f5c7b147c3748f0a8b3d9bb38aa91a4
x-signature: 6d7e96fdbf3ec37c1ec515540f46c6c19b08754973c43293e54644c2da910838
{
"chainCode": "TRON",
"tokenSymbol": "USDT",
"merchantOrderNo": "M202604150001",
"amount": "100.00",
"notifyUrl": "https://merchant.example.com/api/uugate/payin-notify"
}Contoh kode tanda tangan
- 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.
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 = "880001", key = "mch_xxxxxxxxxxxxxxxxxxxx";
const timestamp = "1776193200000", nonce = "2f5c7b147c3748f0a8b3d9bb38aa91a4";
const method = 'POST', path = '/openapi/payin/orders';
const canonicalQuery = '', canonicalBody = "{\"amount\":\"100.00\",\"chainCode\":\"TRON\",\"merchantOrderNo\":\"M202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payin-notify\",\"tokenSymbol\":\"USDT\"}";
const payload = [uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody].join('\n');
const signature = createHmac('sha256', key).update(payload, 'utf8').digest('hex');
console.log(signature);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 = "880001", key = "mch_xxxxxxxxxxxxxxxxxxxx";
String timestamp = "1776193200000", nonce = "2f5c7b147c3748f0a8b3d9bb38aa91a4";
String method = "POST", path = "/openapi/payin/orders";
String canonicalQuery = "", canonicalBody = "{\"amount\":\"100.00\",\"chainCode\":\"TRON\",\"merchantOrderNo\":\"M202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payin-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)));
System.out.println(signature);
}
}<?php
function required(string $name): string {
$value = trim((string) getenv($name));
if ($value === '') throw new RuntimeException($name . ' is required');
return $value;
}
$uid = "880001"; $key = "mch_xxxxxxxxxxxxxxxxxxxx";
$timestamp = "1776193200000"; $nonce = "2f5c7b147c3748f0a8b3d9bb38aa91a4";
$method = 'POST'; $path = '/openapi/payin/orders';
$canonicalQuery = ''; $canonicalBody = '{"amount":"100.00","chainCode":"TRON","merchantOrderNo":"M202604150001","notifyUrl":"https://merchant.example.com/api/uugate/payin-notify","tokenSymbol":"USDT"}';
$payload = implode("\n", [$uid, $timestamp, $nonce, $method, $path, $canonicalQuery, $canonicalBody]);
$signature = hash_hmac('sha256', $payload, $key);
echo $signature, PHP_EOL;package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"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 := "880001", "mch_xxxxxxxxxxxxxxxxxxxx"
timestamp, nonce := "1776193200000", "2f5c7b147c3748f0a8b3d9bb38aa91a4"
method, path := "POST", "/openapi/payin/orders"
canonicalQuery, canonicalBody := "", "{\"amount\":\"100.00\",\"chainCode\":\"TRON\",\"merchantOrderNo\":\"M202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payin-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))
fmt.Println(signature)
}Error bisnis mengembalikan JSON berisi code, message, data, dan requestId. Key, tanda tangan, timestamp, nonce yang tidak valid atau pembatasan keamanan dapat mengembalikan HTTP 401 / 403 dengan isi kosong tanpa kode bisnis. Periksa status HTTP dan isi sebelum mengurai JSON. Jangan mengambil keputusan dari teks message yang diterjemahkan.
Kolom error
| Kolom | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| code | number | Ya | Kode kesalahan bisnis. |
| message | string | Ya | Pesan kesalahan. |
| data | object | null | Ya | Data kesalahan tambahan. |
| requestId | string | null | Ya | ID jejak permintaan server. |
Contoh error
HTTP/1.1 401 Unauthorized
Cache-Control: no-store
Content-Length: 0