Skip to content

Go SDK ​

HashNut 官方 Go SDK,为 HashNut 支付 API 提供便捷的封装。

安装 ​

bash
go get github.com/nuttybounty/hashnut-sdk-go/v4

初始化客户端 ​

go
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)
go
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 ​

创建新的支付订单。

go
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 ​

查询订单的当前状态。三个字段都是必填的,缺任何一个服务端都会报错。

go
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 读状态。

go
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。

go
err := client.CancelOrder(&model.CancelOrderRequest{
	PayOrderID: order.PayOrderID,
})
if err != nil {
	log.Fatal(err)
}
// 取消后订单状态变为 -3(CANCELED),可用 QueryOrder 复核

QueryChains ​

查询所有支持的区块链网络。无参数。

go
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 过滤。

go
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:

go
_, 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() 进行数值转换。

完整示例 ​

go
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 通知而非轮询。此处使用轮询仅为演示方便。