Callback de paiement sortant
Les rappels de paiement utilisent les mêmes en-têtes de signature et le même mécanisme de nouvelle tentative que les rappels de paiement.
The platform calls back only when a payout reaches the confirmed or failed final state.
En-têtes
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| content-type | application/json | Oui | Toujours application/json. |
| x-callback-timestamp | string | Oui | Rappel timestamp en millisecondes. |
| x-callback-nonce | string | Oui | Rappel nonce. |
| x-callback-signature | string | Oui | HMAC-SHA256(apiKey, timestamp + "." + nonce + "." + rawBody)。 |
| x-callback-signature-algorithm | string | Oui | Toujours HMAC-SHA256. |
| x-callback-signature-version | string | Oui | Toujours v1. |
Paramètres
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| merchantUid | string | Oui | Merchant UID. |
| orderNo | string | Oui | Numéro d’ordre de paiement de la plateforme. |
| merchantOrderNo | string | null | Oui | Numéro de commande du commerçant. |
| chainCode | string | Oui | Code de chaîne. |
| tokenSymbol | string | Oui | Symbole de jeton. |
| fromAddress | string | null | Oui | Adresse de l'expéditeur. |
| toAddress | string | Oui | Adresse de réception. |
| txHash | string | null | Non | Hachage en chaîne. |
| amount | string | Oui | Montant du paiement. |
| status | string | Oui | Final payout state: confirmed or failed. |
| failedStage | string | Non | Returned only when status=failed. |
Exemple de requête
{
"merchantUid": "880001",
"orderNo": "PO1776193200A1B2C3D4",
"merchantOrderNo": "PO202604150001",
"chainCode": "TRON",
"tokenSymbol": "USDT",
"fromAddress": "TNjBrNq2a9FK2vwu4W9C1QdLKSZ42Yx5YY",
"toAddress": "TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S",
"txHash": "b3d4fb0cb11eb86bb906d1e0ee3b84e17f40d5441d1e4c651ea46920a48a9a01",
"amount": "35.500000",
"status": "confirmed"
}Champs de réponse
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| HTTP Status | 200-299 | Oui | Toute réponse 2xx est considérée comme un succès. |
| Response Body | string | json | Non | La réponse body peut être personnalisée. |
Exemple de réponse
HTTP/1.1 200 OK
Content-Type: text/plain
successExemples 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, timingSafeEqual } from 'node:crypto';
function required(name) {
const value = process.env[name]?.trim();
if (!value) throw new Error(name + ' is required');
return value;
}
const key = required('UUGATE_API_KEY');
const timestamp = required('UUGATE_CALLBACK_TIMESTAMP'), nonce = required('UUGATE_CALLBACK_NONCE');
const signature = required('UUGATE_CALLBACK_SIGNATURE');
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const rawBody = Buffer.concat(chunks);
const expected = createHmac('sha256', key).update(timestamp + '.' + nonce + '.', 'utf8').update(rawBody).digest();
const actual = Buffer.from(signature, 'hex');
if (!/^[a-fA-F0-9]{64}$/.test(signature) || actual.length !== expected.length || !timingSafeEqual(expected, actual))
throw new Error('Invalid callback signature');
console.log('VERIFIED');import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class UUGateCallback {
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 key = required("UUGATE_API_KEY");
String timestamp = required("UUGATE_CALLBACK_TIMESTAMP"), nonce = required("UUGATE_CALLBACK_NONCE");
String signature = required("UUGATE_CALLBACK_SIGNATURE");
byte[] rawBody = System.in.readAllBytes();
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((timestamp + "." + nonce + ".").getBytes(StandardCharsets.UTF_8));
byte[] expected = mac.doFinal(rawBody);
if (!signature.matches("[a-fA-F0-9]{64}") ||
!MessageDigest.isEqual(expected, HexFormat.of().parseHex(signature)))
throw new IllegalArgumentException("Invalid callback signature");
System.out.println("VERIFIED");
}
}<?php
function required(string $name): string {
$value = trim((string) getenv($name));
if ($value === '') throw new RuntimeException($name . ' is required');
return $value;
}
$key = required('UUGATE_API_KEY');
$timestamp = required('UUGATE_CALLBACK_TIMESTAMP'); $nonce = required('UUGATE_CALLBACK_NONCE');
$signature = required('UUGATE_CALLBACK_SIGNATURE');
$rawBody = file_get_contents('php://stdin');
if ($rawBody === false) throw new RuntimeException('Cannot read raw body');
$expected = hash_hmac('sha256', $timestamp . '.' . $nonce . '.' . $rawBody, $key);
if (!preg_match('/^[a-fA-F0-9]{64}$/D', $signature) || !hash_equals($expected, strtolower($signature)))
throw new RuntimeException('Invalid callback signature');
echo 'VERIFIED', PHP_EOL;package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"strings"
)
func required(name string) string {
value := strings.TrimSpace(os.Getenv(name))
if value == "" { panic(name + " is required") }
return value
}
func main() {
key := required("UUGATE_API_KEY")
timestamp, nonce := required("UUGATE_CALLBACK_TIMESTAMP"), required("UUGATE_CALLBACK_NONCE")
signature := required("UUGATE_CALLBACK_SIGNATURE")
rawBody, err := io.ReadAll(os.Stdin); if err != nil { panic(err) }
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(timestamp + "." + nonce + ".")); mac.Write(rawBody)
actual, err := hex.DecodeString(signature)
if err != nil || !hmac.Equal(mac.Sum(nil), actual) { panic("Invalid callback signature") }
fmt.Println("VERIFIED")
}Notes
- Handle callbacks idempotently by orderNo; only confirmed or failed final states are sent automatically.
- Ce code vérifie uniquement les callbacks. Transmettez le corps brut à l’entrée standard et mappez les en-têtes vers UUGATE_CALLBACK_TIMESTAMP, UUGATE_CALLBACK_NONCE et UUGATE_CALLBACK_SIGNATURE. UUGATE_API_KEY doit être la clé utilisée pour créer la commande. Validez ensuite la commande et persistez-la de façon idempotente dans une transaction avant de renvoyer HTTP 2xx.