收款回调
收款订单成功完成后才会回调商户服务器,回调体固定为 status=completed。
系统仅推送收款、付款的最终结果,以 HTTP 2xx 作为成功标准并立即停止;首次失败后 1 分钟进行第 2 次投递,再失败则 5 分钟后进行第 3 次投递,总计最多 3 次。回调业务时间统一为北京时间 YYYY/MM/DD HH:mm:ss。
请求头
| 字段名称 | 字段类型 | 是否必填 | 说明 |
|---|---|---|---|
| content-type | application/json | 是 | 固定为 application/json。 |
| x-callback-timestamp | string | 是 | 回调发送时的毫秒时间戳。 |
| x-callback-nonce | string | 是 | 回调随机串。 |
| x-callback-signature | string | 是 | HMAC-SHA256(apiKey, timestamp + "." + nonce + "." + rawBody)。 |
| x-callback-signature-algorithm | string | 是 | 固定为 HMAC-SHA256。 |
| x-callback-signature-version | string | 是 | 固定为 v1。 |
接口参数
| 字段名称 | 字段类型 | 是否必填 | 说明 |
|---|---|---|---|
| merchantUid | string | 是 | Merchant UID。 |
| orderNo | string | 是 | 平台订单号。 |
| merchantOrderNo | string | null | 是 | 商户订单号。 |
| bindKey | string | null | 是 | 会员充值场景下的绑定键;共享收款订单通常为空。 |
| chainCode | string | null | 是 | 订单所属网络。 |
| tokenSymbol | string | null | 是 | 订单所属币种。 |
| txHash | string | null | 是 | 匹配的交易哈希,未检测到时为空。 |
| expectedAmount | string | null | 是 | 订单金额。 |
| paidAmount | string | 是 | 实际支付金额。 |
| fromAddress | string | null | 是 | 付款地址。 |
| status | string | 是 | 收款成功终态,固定为 completed。 |
| mode | string | 是 | 订单模式:shared 或 exclusive。 |
| businessType | string | 是 | 订单业务类型。 |
| serviceFeeUsd | string | 否 | 本次收款服务费。 |
| feeBalanceAfterUsd | string | 否 | 扣费后的费用账户余额。 |
| payerRiskStatus | string | 是 | 付款来源风控状态。 |
| payerRiskLabel | string | null | 是 | 付款来源风控标签。 |
请求示例
{
"merchantUid": "880001",
"orderNo": "PI1776193200123ABCD1234",
"merchantOrderNo": "M202604150001",
"bindKey": null,
"expectedAmount": "100.000000",
"paidAmount": "100.000000",
"chainCode": "TRON",
"tokenSymbol": "USDT",
"txHash": "f7f17891f52c35d0c93170f0d12bb347ac16ab34853b4bc8dcf7fd0a8c9aa321",
"fromAddress": "TS7b7iD8G2PaPqK1TqSmLJ9nrrYH4oKX1S",
"status": "completed",
"mode": "shared",
"businessType": "fixed",
"serviceFeeUsd": "0.000000",
"feeBalanceAfterUsd": "10.000000",
"payerRiskStatus": "low_risk",
"payerRiskLabel": "低风险"
}接口返回
| 字段名称 | 字段类型 | 是否必填 | 说明 |
|---|---|---|---|
| HTTP Status | 200-299 | 是 | 任意 2xx 都视为成功。 |
| Response Body | string | json | 否 | 内容可自定义。 |
返回示例
HTTP/1.1 200 OK
Content-Type: application/json
{
"ok": true
}代码示例
运行:Java 保存为 UUGateExample.java(回调为 UUGateCallback.java),使用 java 文件名;PHP 保存为 example.php,使用 php example.php;Go 保存为 example.go,使用 go run example.go;Node.js 保存为 example.mjs,使用 node example.mjs。回调原文从标准输入读取。
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")
}说明
- 回调验签使用 apiKey,而不是开放接口请求头里的 x-signature。
- 验签后核对商户、订单、币种、网络和金额,在事务中按平台订单号幂等记录付款与业务结果,成功持久化后才返回 2xx;重复通知不得重复发货或入账。
- 会员充值场景建议直接使用 bindKey 作为会员标识,不需要再额外维护 orderNo 到会员 ID 的映射表。
- 以下代码只演示回调验签:将原始请求体作为标准输入,回调头映射到 UUGATE_CALLBACK_TIMESTAMP、UUGATE_CALLBACK_NONCE、UUGATE_CALLBACK_SIGNATURE,UUGATE_API_KEY 使用创建该订单时的 API Key。验签后仍须核对订单并事务幂等持久化,完成后才返回 HTTP 2xx。