接口调用示例
几分钟内创建您的第一笔支付订单。
前置条件
- 拥有 Access Key ID 和 Secret Key 的 HashNut 商户账号
- 已部署的 Splitter Address(分账合约地址)
两者都可以由 一键开户 一次生成。
创建支付订单
Go 和 Java 有官方 SDK,签名、URL 拼接、错误处理都封装好了;Python / Node.js 目前没有 SDK, 需要自己按 认证签名 手写,下面给出可直接运行的完整实现。
go
// go get github.com/nuttybounty/hashnut-sdk-go/v4
package main
import (
"fmt"
"log"
hashnut "github.com/nuttybounty/hashnut-sdk-go/v4"
"github.com/nuttybounty/hashnut-sdk-go/v4/model"
)
func main() {
client := hashnut.NewClient("your-secret-key", false) // 第二个参数固定 false
order, err := client.CreateOrder(&model.CreateOrderRequest{
AccessKeyID: "your-access-key-id",
MerchantOrderID: "aaf..-8",
BlockChain: "ETH",
TokenSymbol: "usdt",
Amount: "0.05",
SplitterAddress: "0xYourSplitterAddress",
Subject: "Test Product",
ExpireDuration: 60000, // 秒
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("订单号: %s\n", order.PayOrderID)
fmt.Printf("收款地址: %s\n", order.ReceiptAddress)
fmt.Printf("支付页面: %s\n", order.PayURL)
}java
// Maven 坐标与仓库配置见 Java SDK 页
import io.hashnut.client.HashNutClientImpl;
import io.hashnut.service.HashNutServiceImpl;
import io.hashnut.model.request.CreateOrderRequest;
import io.hashnut.model.response.CreateOrderResponse;
public class CreateOrder {
public static void main(String[] args) throws Exception {
var client = new HashNutClientImpl("your-secret-key", false); // 第二个参数固定 false
var service = new HashNutServiceImpl(client);
CreateOrderResponse resp = service.createOrder(new CreateOrderRequest.Builder()
.withAccessKeyId("your-access-key-id")
.withMerchantOrderId("aaf..-8")
.withBlockChain("ETH")
.withTokenSymbol("usdt")
.withAmount("0.05")
.withSplitterAddress("0xYourSplitterAddress")
.withSubject("Test Product")
.withExpireDuration(60000L) // 秒
.build());
System.out.println("订单号: " + resp.getData().getPayOrderId());
System.out.println("收款地址: " + resp.getData().getReceiptAddress());
System.out.println("支付页面: " + resp.getData().getPayUrl());
}
}python
import hmac, hashlib, base64, json, time, uuid
from urllib.parse import urlencode
import requests
SECRET_KEY = "your-secret-key"
BASE_URL = "https://defi.hashnut.io/api/v4.0.0"
PAY_PAGE = "https://defi.hashnut.io/pay"
# 注意:签名和发送必须是同一个字符串,所以这里先序列化成 body,后面直接用 data=body 发出去
body = json.dumps({
"accessKeyId": "your-access-key-id",
"merchantOrderId": "aaf..-8",
"blockChain": "ETH",
"tokenSymbol": "usdt",
"amount": "0.05",
"splitterAddress": "0xYourSplitterAddress",
"subject": "Test Product",
"expireDuration": 60000,
}, separators=(",", ":"))
# 签名串 = uuid + timestamp + body,HMAC-SHA256 后 base64
req_uuid = str(uuid.uuid4()) # 每次请求都要换,服务端会去重拒绝重放
timestamp = str(int(time.time() * 1000)) # Unix 毫秒
message = req_uuid + timestamp + body
signature = base64.b64encode(
hmac.new(SECRET_KEY.encode(), message.encode(), hashlib.sha256).digest()
).decode()
resp = requests.post(
f"{BASE_URL}/api/orders/create",
headers={
"Content-Type": "application/json",
"hashnut-request-uuid": req_uuid,
"hashnut-request-timestamp": timestamp,
"hashnut-request-sign": signature,
},
data=body,
)
order = resp.json()["data"]
# 响应里没有 payUrl,支付页地址要自己拼(官方 SDK 已经封装好了这一步)
pay_url = PAY_PAGE + "?" + urlencode({
"payOrderId": order["payOrderId"],
"merchantOrderId": order["merchantOrderId"],
"accessSign": order["accessSign"],
"blockChain": order["blockChain"],
"payApiVersion": "v4",
})
print("订单号: ", order["payOrderId"])
print("收款地址:", order["receiptAddress"])
print("支付页面:", pay_url)javascript
const crypto = require("crypto");
const SECRET_KEY = "your-secret-key";
const BASE_URL = "https://defi.hashnut.io/api/v4.0.0";
const PAY_PAGE = "https://defi.hashnut.io/pay";
// 注意:签名和发送必须是同一个字符串,所以这里先序列化成 body,后面直接把它当请求体发出去
const body = JSON.stringify({
accessKeyId: "your-access-key-id",
merchantOrderId: "aaf..-8",
blockChain: "ETH",
tokenSymbol: "usdt",
amount: "0.05",
splitterAddress: "0xYourSplitterAddress",
subject: "Test Product",
expireDuration: 60000,
});
// 签名串 = uuid + timestamp + body,HMAC-SHA256 后 base64
const uuid = crypto.randomUUID(); // 每次请求都要换,服务端会去重拒绝重放
const timestamp = Date.now().toString(); // Unix 毫秒
const message = uuid + timestamp + body;
const signature = crypto
.createHmac("sha256", SECRET_KEY)
.update(message)
.digest("base64");
fetch(`${BASE_URL}/api/orders/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"hashnut-request-uuid": uuid,
"hashnut-request-timestamp": timestamp,
"hashnut-request-sign": signature,
},
body,
})
.then((res) => res.json())
.then(({ data }) => {
// 响应里没有 payUrl,支付页地址要自己拼(官方 SDK 已经封装好了这一步)
const payUrl = `${PAY_PAGE}?${new URLSearchParams({
payOrderId: data.payOrderId,
merchantOrderId: data.merchantOrderId,
accessSign: data.accessSign,
blockChain: data.blockChain,
payApiVersion: "v4",
})}`;
console.log("订单号: ", data.payOrderId);
console.log("收款地址:", data.receiptAddress);
console.log("支付页面:", payUrl);
});WARNING
手写签名时有三个地方最容易踩:
- 签名用的 body 必须和实际发出的字节完全一致。不要签名一次、发送时再序列化一次—— 键顺序或空格差一个字符,验签就会失败。上面的写法是先生成
body字符串,再原样发出去。 hashnut-request-uuid每次请求都必须不同。服务端会按accessKey + uuid做去重, 重复的 uuid 会被当成重放直接拒绝。hashnut-request-timestamp是 Unix 毫秒,且与服务端时间的偏差不能超过 ±5 分钟, 否则请求会被拒。机器时钟不准是这个报错最常见的原因。
完整签名规则见 认证签名。
响应
json
{
"code": 0,
"msg": "success",
"data": {
"merchantAddress" : "0x17a...42",
"merchantChannel" : "default",
"blockChain" : "ETH",
"tokenSymbol" : "usdt",
"createChannel" : 1,
"merchantOrderId" : "aaf..-8",
"payOrderId" : "01K...H8",
"tokenAddress" : "0xdac17f958d2ee523a2206206994597c13d831ec7",
"receiptAddress" : "0x5...c4",
"amount" : 0.05,
"state" : 0,
"accessSign" : "AA377....F5",
"rate" : 80,
"obtainAmount" : 0.0496,
"platformFee" : 4.0E-4,
"expireDuration" : "60000",
"underPaid" : false,
"paidAmount" : 0.0,
"createTime" : 1785753243948,
}
}code 为 0 表示成功,非 0 时看 msg。state 为 0 表示待付款, 完整状态取值见 订单状态。 data 的全部字段见 创建订单。
NOTE
服务端把 Java 的 long / Long 字段统一序列化成 JSON 字符串(避免 JavaScript 超过 2^53 丢精度), 所以 expireDuration、confirmCount、chainId 这类字段返回的是 "600" 而不是 600。 自己解析 JSON 时要按字符串接,或者用能同时兼容两种写法的类型 (Go 用 json.Number,TypeScript 用 string | number)。官方 SDK 已经处理好了。
跳转支付页URL
订单创建后,跳转到支付页面的URL格式如下:
https://defi.hashnut.io/pay?accessSign=${accessSign}&merchantOrderId=${merchantOrderId}&payOrderId=${payOrderId}&blockChain=${blockChain}&payApiVersion=v4用官方 SDK 的话这一步已经封装好了,直接读 order.PayURL(Go)或 resp.getData().getPayUrl()(Java)。自己拼的时候记得对参数做 URL 编码—— merchantOrderId 是你自己给的值,含空格或 & 会把 URL 拼坏。