请求示例和验签说明

请求方式:接口以 `GET` / `POST` 为主,请求体统一使用 `application/json`。

鉴权方式:开放接口通过请求头 + HMAC-SHA256 签名完成认证。

请求头

字段名称字段类型是否必填说明
x-api-keystring是商户 API Key,使用商户后台 API 接入页面中的 mch_ 开头字符串。
x-merchant-uidstring是商户 UID。
x-timestampstring是13 位毫秒时间戳,默认允许和服务器时间相差 5 分钟。
x-noncestring是每次请求唯一的随机字符串,最长 128 位,重复使用会被拒绝。
x-signaturestring是使用 HMAC-SHA256 生成的十六进制签名,签名原文由 Merchant UID、时间戳、nonce、请求方法、路径、规范化 query 和规范化 body 组成。

签名规则

  • 请求体统一为 application/json;GET 接口没有 body 时,参与签名的 body 为空字符串。
  • 签名路径必须是纯接口路径,例如 /openapi/payin/orders,不要带域名。
  • query 会按 key 升序处理;body 会按 JSON key 升序后再序列化。
  • 签名失败常见原因是时间戳过期、nonce 重复、字段顺序不一致或多余空格。

下面的固定时间戳、nonce 和凭据仅为离线签名对照值,不可用于实际请求。正式请求使用当前毫秒时间戳和新的随机 nonce。接口路径没有版本前缀;v1 仅指回调签名版本。

签名原文示例

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"}

完整请求示例

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"
}

签名代码示例

  • 四种语言使用相同的接口路径、参数和返回合同。示例从环境变量 UUGATE_MERCHANT_UID、UUGATE_API_KEY 读取凭据;UUGATE_BASE_URL 默认 https://api.uugate.com。
  • 示例中的 canonicalBody 已按字段名排序并压缩为 JSON;修改参数后须递归排序对象字段,数组顺序保持,使用 UTF-8 且不额外转义斜杠或 Unicode。金额使用字符串,boolean 使用 true / false。
  • 成功响应直接返回业务对象,没有统一的 code / data 外层。先判断 HTTP 状态,再处理正文;普通业务错误返回 code、message、data、requestId,安全拒绝可能只有 HTTP 401 / 403 和空正文。
  • 网络超时或 HTTP 5xx 后先查询订单,不自动重复创建或提交付款;每次新请求重新生成 timestamp、nonce 和签名。

运行: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, 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);

普通业务错误返回 JSON:code、message、data、requestId。无效 Key、签名、时间戳、nonce 或安全准入拒绝可能返回 HTTP 401 / 403 和空正文,不保证有业务错误码。所有语言先检查 HTTP 状态及正文是否为空,再解析 JSON;message 可能随语言变化,不作为判断条件。

失败返回结构

字段名称字段类型是否必填说明
codenumber是业务错误码。
messagestring是错误描述。
dataobject | null是错误附加数据。
requestIdstring | null是服务端请求追踪 ID。

失败返回示例

HTTP/1.1 401 Unauthorized
Cache-Control: no-store
Content-Length: 0