Go SDK
HashNut 官方 Go SDK,为 HashNut 支付 API 提供便捷的封装。
安装
go get github.com/nuttybounty/hashnut-sdk-go/v4初始化客户端
import hashnut "github.com/nuttybounty/hashnut-sdk-go/v4"
// 第二个参数固定传 false,即使用生产环境 (https://defi.hashnut.io/api/v4.0.0)
client := hashnut.NewClient("your-secret-key", false)import hashnut "github.com/nuttybounty/hashnut-sdk-go/v4"
// WithBaseURL 必须带上 /api/v4.0.0,SDK 内部拼的是相对路径
client := hashnut.NewClient(
"your-secret-key",
false,
hashnut.WithBaseURL("https://custom.endpoint.com/api/v4.0.0"),
)NOTE
SDK 只需要 secretKey。accessKeyId 是逐笔请求的参数(CreateOrderRequest.AccessKeyID),不在构造客户端时传入。
方法
CreateOrder
创建新的支付订单。
import "github.com/nuttybounty/hashnut-sdk-go/v4/model"
order, err := client.CreateOrder(&model.CreateOrderRequest{
AccessKeyID: "your-access-key-id",
MerchantOrderID: "ORDER-001",
BlockChain: "ETH",
TokenSymbol: "usdt",
Amount: "25.00",
SplitterAddress: "0xYourSplitterAddress",
// 可选字段
Subject: "Monthly Subscription",
ExpireDuration: 1800, // 秒
CallbackURL: "https://yoursite.com/payment-result",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Pay Order ID: %s\n", order.PayOrderID)
fmt.Printf("Receipt Address: %s\n", order.ReceiptAddress)
fmt.Printf("Pay URL: %s\n", order.PayURL)IMPORTANT
请把响应里的 order.AccessSign 连同 payOrderId 一起存进你自己的订单表。 后续的 QueryOrder / ConfirmPaid 都要带上它,且无法再重新计算—— 它是服务端用 secretKey 对 payOrderId + merchantOrderId 做的 HMAC。
QueryOrder
查询订单的当前状态。三个字段都是必填的,缺任何一个服务端都会报错。
result, err := client.QueryOrder(&model.QueryOrderRequest{
PayOrderID: order.PayOrderID,
MerchantOrderID: "ORDER-001",
AccessSign: order.AccessSign, // 来自创建订单的响应
})
if err != nil {
log.Fatal(err)
}
// State / Amount 等数值字段是 json.Number,不能直接当 int 用
fmt.Printf("State: %s\n", result.State.String())
fmt.Printf("Amount: %s\n", result.Amount.String())
fmt.Printf("Tx Hash: %s\n", result.PayTxID)ConfirmPaid
用交易哈希手动确认支付。只返回 error,不返回订单——确认后需要再调 QueryOrder 读状态。
err := client.ConfirmPaid(&model.ConfirmPaidRequest{
PayOrderID: order.PayOrderID,
MerchantOrderID: "ORDER-001",
AccessSign: order.AccessSign,
PayTxID: "0x9876543210fedcba...",
})
if err != nil {
log.Fatal(err)
}CancelOrder
取消未支付的订单。只需要 PayOrderID,同样只返回 error。
err := client.CancelOrder(&model.CancelOrderRequest{
PayOrderID: order.PayOrderID,
})
if err != nil {
log.Fatal(err)
}
// 取消后订单状态变为 -3(CANCELED),可用 QueryOrder 复核QueryChains
查询所有支持的区块链网络。无参数。
chains, err := client.QueryChains()
if err != nil {
log.Fatal(err)
}
for _, chain := range chains {
fmt.Printf("%s chainId=%d confirms=%d eip1559=%v\n",
chain.BlockChain, chain.ChainID, chain.TxConfirmCount, chain.EIP1559Support)
}QueryCoins
查询所有支持的代币。无参数——拿到全量列表后自己按 BlockChain 过滤。
coins, err := client.QueryCoins()
if err != nil {
log.Fatal(err)
}
for _, coin := range coins {
if coin.BlockChain != "ETH" {
continue
}
fmt.Printf("%s/%s decimals=%d contract=%s\n",
coin.BlockChain, coin.TokenSymbol, coin.Decimals, coin.ContractAddress)
}错误处理
所有方法的第二个返回值为 error。SDK 不导出结构化错误类型,API 业务错误会被包装成 error,消息形如 api error (code=1): xxx:
_, err := client.CreateOrder(&model.CreateOrderRequest{
// ...
})
if err != nil {
// 业务错误: api error (code=1): invalid request sign
// HTTP 错误: http 502: <body>
// 网络/解析错误: http request: ... / unmarshal response: ...
fmt.Printf("下单失败: %v\n", err)
return
}WARNING
不要用 errors.As 去断言具体错误类型——SDK 返回的是 fmt.Errorf 生成的普通 error。 需要按错误码分支处理时,请匹配错误消息,或直接改用 WithHTTPClient 自行接管请求。
WARNING
API 响应中的数值字段(如 amount 和 paidAmount)以 json.Number 类型返回,以兼容后端可能返回的字符串或数值格式。使用 .String() 读取值,或使用 .Int64() / .Float64() 进行数值转换。
完整示例
package main
import (
"fmt"
"log"
"time"
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: fmt.Sprintf("ORDER-%d", time.Now().Unix()),
BlockChain: "ETH",
TokenSymbol: "usdt",
Amount: "1.00",
SplitterAddress: "0xYourSplitterAddress",
Subject: "Test Order",
})
if err != nil {
log.Fatalf("create order: %v", err)
}
// 真实业务里要把 PayOrderID + AccessSign 落库,后续查询/确认都要用
fmt.Printf("订单创建成功: %s\n", order.PayOrderID)
fmt.Printf("请向该地址转账: %s usdt -> %s\n", order.Amount.String(), order.ReceiptAddress)
fmt.Printf("Pay URL: %s\n", order.PayURL)
// 轮询订单状态(生产环境请改用 webhook 通知)
for i := 0; i < 60; i++ {
time.Sleep(10 * time.Second)
result, err := client.QueryOrder(&model.QueryOrderRequest{
PayOrderID: order.PayOrderID,
MerchantOrderID: order.MerchantOrderID,
AccessSign: order.AccessSign,
})
if err != nil {
log.Printf("查询失败: %v", err)
continue
}
// State 是 json.Number:先转成 int64 再比较
state, err := result.State.Int64()
if err != nil {
log.Printf("bad state %q: %v", result.State.String(), err)
continue
}
fmt.Printf("状态: %d\n", state)
if state == 3 { // 3 = SUCCESS, 4 = FINISH
fmt.Println("支付成功!")
return
}
if state < 0 { // -1 FAILED / -2 EXPIRED / -3 CANCELED
log.Fatalf("order ended in state %d", state)
}
}
log.Println("轮询超时,订单仍未支付成功")
}TIP
在生产环境中,建议使用 Webhook 通知而非轮询。此处使用轮询仅为演示方便。