Skip to content

接口调用示例 ​

几分钟内创建您的第一笔支付订单。

前置条件 ​

  • 拥有 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

手写签名时有三个地方最容易踩:

  1. 签名用的 body 必须和实际发出的字节完全一致。不要签名一次、发送时再序列化一次—— 键顺序或空格差一个字符,验签就会失败。上面的写法是先生成 body 字符串,再原样发出去。
  2. hashnut-request-uuid 每次请求都必须不同。服务端会按 accessKey + uuid 做去重, 重复的 uuid 会被当成重放直接拒绝。
  3. 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 拼坏。

下一步 ​