Go SDK 参考#

Go SDK 参考(适用于 exactaggr_deferred#

#

描述
github.com/okx/payments/go/x402核心包:客户端、服务端、facilitator、类型定义
.../go/x402/mechanisms/evmEVM 机制:exact、aggr_deferred
.../go/x402/mechanisms/svmSolana 机制:exact
.../go/x402/http/ginGin 中间件(卖方)
.../go/x402/http/echoEcho 中间件(卖方)
.../go/x402/http/nethttpnet/http 中间件(卖方)
.../go/x402/httpHTTP 客户端封装(买方)

核心类型#

Network#

go
type Network string
// CAIP-2 format, e.g., "eip155:196", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"

func (n Network) Parse() (namespace, reference string, err error)
func (n Network) Match(pattern Network) bool

Price / AssetAmount#

go
type Price interface{}
// Can be string ("$0.01"), number (0.01), or map (AssetAmount)

type AssetAmount struct {
    Asset  string                 `json:"asset"`    // Token contract address
    Amount string                 `json:"amount"`   // Smallest unit amount
    Extra  map[string]interface{} `json:"extra,omitempty"`
}

ResourceConfig#

go
type ResourceConfig struct {
    Scheme            string                 `json:"scheme"`            // "exact" | "aggr_deferred" | "upto"
    PayTo             string                 `json:"payTo"`             // Recipient wallet
    Price             Price                  `json:"price"`             // "$0.01" or AssetAmount
    Network           Network                `json:"network"`           // "eip155:196"
    MaxTimeoutSeconds int                    `json:"maxTimeoutSeconds,omitempty"` // Default: 60
    Extra             map[string]interface{} `json:"extra,omitempty"`
}

VerifyResponse#

go
type VerifyResponse struct {
    IsValid        bool   `json:"isValid"`
    InvalidReason  string `json:"invalidReason,omitempty"`
    InvalidMessage string `json:"invalidMessage,omitempty"`
    Payer          string `json:"payer,omitempty"`
}

SettleResponse#

go
type SettleResponse struct {
    Success      bool    `json:"success"`
    ErrorReason  string  `json:"errorReason,omitempty"`
    ErrorMessage string  `json:"errorMessage,omitempty"`
    Payer        string  `json:"payer,omitempty"`
    Transaction  string  `json:"transaction"`
    Network      Network `json:"network"`
    Status       string  `json:"status,omitempty"`   // OKX: "pending" | "success" | "timeout"
}

SupportedResponse#

go
type SupportedKind struct {
    X402Version int                    `json:"x402Version"`
    Scheme      string                 `json:"scheme"`
    Network     Network                `json:"network"`
    Extra       map[string]interface{} `json:"extra,omitempty"`
}

type SupportedResponse struct {
    Kinds      []SupportedKind            `json:"kinds"`
    Extensions []string                   `json:"extensions"`
    Signers    map[string][]string        `json:"signers"` // CAIP family → addresses
}

SettlementOverrides#

go
type SettlementOverrides struct {
    Amount string `json:"amount,omitempty"` // Atomic units, percentage, or dollar
}

视图接口#

用于 V1/V2 支付数据的统一视图:

go
type PaymentRequirementsView interface {
    GetScheme() string
    GetNetwork() string
    GetAsset() string
    GetAmount() string
    GetPayTo() string
    GetMaxTimeoutSeconds() int
    GetExtra() map[string]interface{}
}

type PaymentPayloadView interface {
    GetVersion() int
    GetScheme() string
    GetNetwork() string
    GetPayload() map[string]interface{}
}

客户端 API(x402Client#

构造函数#

go
import x402 "github.com/okx/payments/go/x402"

client := x402.Newx402Client(opts ...ClientOption)

ClientOption 函数:

选项描述
WithPaymentSelector(selector)自定义支付需求选择器
WithPolicy(policy)添加支付过滤策略

注册方法(可链式调用)#

go
func (c *x402Client) Register(network Network, client SchemeNetworkClient) *x402Client
func (c *x402Client) RegisterV1(network Network, client SchemeNetworkClientV1) *x402Client
func (c *x402Client) RegisterPolicy(policy PaymentPolicy) *x402Client
func (c *x402Client) RegisterExtension(ext ClientExtension) *x402Client

支付创建#

go
// Select best payment option from requirements list
func (c *x402Client) SelectPaymentRequirements(
    requirements []types.PaymentRequirements,
) (types.PaymentRequirements, error)

// Create signed payment payload
func (c *x402Client) CreatePaymentPayload(
    ctx context.Context,
    requirements types.PaymentRequirements,
    resource *types.ResourceInfo,
    extensions map[string]interface{},
) (types.PaymentPayload, error)

生命周期钩子(可链式调用)#

go
func (c *x402Client) OnBeforePaymentCreation(hook BeforePaymentCreationHook) *x402Client
func (c *x402Client) OnAfterPaymentCreation(hook AfterPaymentCreationHook) *x402Client
func (c *x402Client) OnPaymentCreationFailure(hook OnPaymentCreationFailureHook) *x402Client

策略与选择器类型#

go
type PaymentRequirementsSelector func(requirements []PaymentRequirementsView) PaymentRequirementsView
type PaymentPolicy func(requirements []PaymentRequirementsView) []PaymentRequirementsView

func DefaultPaymentSelector(requirements []PaymentRequirementsView) PaymentRequirementsView

服务端 API(x402ResourceServer#

构造函数#

go
server := x402.Newx402ResourceServer(opts ...ResourceServerOption)

ResourceServerOption 函数:

选项描述
WithFacilitatorClient(client)设置 facilitator 客户端
WithSchemeServer(network, server)注册一个 scheme
WithCacheTTL(ttl)设置 supported-kinds 缓存 TTL

注册方法(可链式调用)#

go
func (s *x402ResourceServer) Register(network Network, server SchemeNetworkServer) *x402ResourceServer
func (s *x402ResourceServer) RegisterExtension(ext types.ResourceServerExtension) *x402ResourceServer

初始化#

go
func (s *x402ResourceServer) Initialize(ctx context.Context) error

支付操作#

go
// Build requirements from config
func (s *x402ResourceServer) BuildPaymentRequirementsFromConfig(
    ctx context.Context,
    config ResourceConfig,
) ([]types.PaymentRequirements, error)

// Build single requirement
func (s *x402ResourceServer) BuildPaymentRequirements(
    ctx context.Context,
    config ResourceConfig,
    supportedKind types.SupportedKind,
    extensions []string,
) (types.PaymentRequirements, error)

// Create 402 response
func (s *x402ResourceServer) CreatePaymentRequiredResponse(
    requirements []types.PaymentRequirements,
    resourceInfo *types.ResourceInfo,
    errorMsg string,
    extensions map[string]interface{},
) types.PaymentRequired

// Find matching requirements for a payment payload
func (s *x402ResourceServer) FindMatchingRequirements(
    available []types.PaymentRequirements,
    payload types.PaymentPayload,
) *types.PaymentRequirements

// Verify payment signature
func (s *x402ResourceServer) VerifyPayment(
    ctx context.Context,
    payload types.PaymentPayload,
    requirements types.PaymentRequirements,
) (*VerifyResponse, error)

// Settle payment on-chain
func (s *x402ResourceServer) SettlePayment(
    ctx context.Context,
    payload types.PaymentPayload,
    requirements types.PaymentRequirements,
    overrides *SettlementOverrides,
) (*SettleResponse, error)

// Check registered support
func (s *x402ResourceServer) HasRegisteredScheme(network Network, scheme string) bool
func (s *x402ResourceServer) HasFacilitatorSupport(network Network, scheme string) bool

服务端生命周期钩子(可链式调用)#

go
func (s *x402ResourceServer) OnBeforeVerify(hook BeforeVerifyHook) *x402ResourceServer
func (s *x402ResourceServer) OnAfterVerify(hook AfterVerifyHook) *x402ResourceServer
func (s *x402ResourceServer) OnVerifyFailure(hook OnVerifyFailureHook) *x402ResourceServer
func (s *x402ResourceServer) OnBeforeSettle(hook BeforeSettleHook) *x402ResourceServer
func (s *x402ResourceServer) OnAfterSettle(hook AfterSettleHook) *x402ResourceServer
func (s *x402ResourceServer) OnSettleFailure(hook OnSettleFailureHook) *x402ResourceServer

Scheme 接口#

SchemeNetworkClient(买方)#

go
type SchemeNetworkClient interface {
    Scheme() string
    CreatePaymentPayload(ctx context.Context, requirements types.PaymentRequirements) (types.PaymentPayload, error)
}

type ExtensionAwareClient interface {
    SchemeNetworkClient
    CreatePaymentPayloadWithExtensions(ctx context.Context, requirements types.PaymentRequirements, extensions map[string]interface{}) (types.PaymentPayload, error)
}

SchemeNetworkServer(卖方)#

go
type SchemeNetworkServer interface {
    Scheme() string
    ParsePrice(price Price, network Network) (AssetAmount, error)
    EnhancePaymentRequirements(ctx context.Context, requirements types.PaymentRequirements, supportedKind types.SupportedKind, extensions []string) (types.PaymentRequirements, error)
}

SchemeNetworkFacilitator#

go
type SchemeNetworkFacilitator interface {
    Scheme() string
    CaipFamily() string
    GetExtra(network Network) map[string]interface{}
    GetSigners(network Network) []string
    Verify(ctx context.Context, payload types.PaymentPayload, requirements types.PaymentRequirements, fctx *FacilitatorContext) (*VerifyResponse, error)
    Settle(ctx context.Context, payload types.PaymentPayload, requirements types.PaymentRequirements, fctx *FacilitatorContext) (*SettleResponse, error)
}

FacilitatorClient(网络边界)#

go
type FacilitatorClient interface {
    Verify(ctx context.Context, payloadBytes []byte, requirementsBytes []byte) (*VerifyResponse, error)
    Settle(ctx context.Context, payloadBytes []byte, requirementsBytes []byte) (*SettleResponse, error)
    GetSupported(ctx context.Context) (SupportedResponse, error)
}

ClientExtension#

go
type ClientExtension interface {
    Key() string
    EnrichPaymentPayload(ctx context.Context, payload types.PaymentPayload, required types.PaymentRequired) (types.PaymentPayload, error)
}

MoneyParser#

go
type MoneyParser func(amount float64, network Network) (*AssetAmount, error)

中间件参考#

所有 Go 中间件包提供三个构建层级以及构建器快捷方式。

Gin(go/x402/http/gin#

go
import x402gin "github.com/okx/payments/go/x402/http/gin"

// Standard (recommended)
r.Use(x402gin.PaymentMiddleware(routes, server, opts ...MiddlewareOption))

// From HTTP server
r.Use(x402gin.PaymentMiddlewareFromHTTPServer(httpServer, opts ...MiddlewareOption))

// From config
r.Use(x402gin.PaymentMiddlewareFromConfig(routes, opts ...MiddlewareOption))

// Builder shortcut
r.Use(x402gin.X402Payment(Config{ Routes: routes, Facilitator: facilitator, Schemes: schemes }))

// Simple one-liner
r.Use(x402gin.SimpleX402Payment(payTo, price, network, facilitatorURL))

MiddlewareOption 函数:

选项类型描述
WithFacilitatorClient(client)FacilitatorClient用于验证 / 结算的 facilitator
WithScheme(network, server)Network, SchemeNetworkServer注册一个 scheme
WithPaywallConfig(config)*PaywallConfig浏览器付费墙设置
WithSyncFacilitatorOnStart(bool)bool启动时获取支持的 kinds
WithErrorHandler(fn)func(*gin.Context, error)自定义错误处理器
WithSettlementHandler(fn)func(*gin.Context, *SettleResponse)自定义结算处理器
WithTimeout(duration)time.Duration请求超时时间

Config 结构体(构建器模式):

go
type Config struct {
    Routes                 x402http.RoutesConfig
    Facilitator            x402.FacilitatorClient
    Facilitators           []x402.FacilitatorClient
    Schemes                []SchemeConfig
    PaywallConfig          *x402http.PaywallConfig
    SyncFacilitatorOnStart bool
    Timeout                time.Duration
    ErrorHandler           func(*gin.Context, error)
    SettlementHandler      func(*gin.Context, *x402.SettleResponse)
}

type SchemeConfig struct {
    Network x402.Network
    Server  x402.SchemeNetworkServer
}

Echo(go/x402/http/echo#

与 Gin 相同的 API 接口,但使用 echo.Context 类型:

go
import x402echo "github.com/okx/payments/go/x402/http/echo"

e.Use(x402echo.PaymentMiddleware(routes, server, opts ...MiddlewareOption))
e.Use(x402echo.X402Payment(Config{ ... }))
e.Use(x402echo.SimpleX402Payment(payTo, price, network, facilitatorURL))

net/http(go/x402/http/nethttp#

返回 func(http.Handler) http.Handler

go
import x402nethttp "github.com/okx/payments/go/x402/http/nethttp"

handler := x402nethttp.PaymentMiddleware(routes, server, opts ...MiddlewareOption)(yourHandler)
handler := x402nethttp.X402Payment(Config{ ... })(yourHandler)
handler := x402nethttp.SimpleX402Payment(payTo, price, network, facilitatorURL)(yourHandler)

http.ListenAndServe(":4021", handler)

HTTP 客户端(买方)#

go
import x402http "github.com/okx/payments/go/x402/http"

// Create HTTP client with payment support
httpClient := x402http.Newx402HTTPClient(x402Client)

// Wrap existing http.Client
wrappedClient := x402http.WrapHTTPClientWithPayment(http.DefaultClient, httpClient)

// Direct methods
resp, err := httpClient.GetWithPayment(ctx, url)
resp, err := httpClient.PostWithPayment(ctx, url, body)
resp, err := httpClient.DoWithPayment(ctx, req)

// Convenience functions
resp, err := x402http.Get(ctx, url, httpClient)
resp, err := x402http.Post(ctx, url, body, httpClient)
resp, err := x402http.Do(ctx, req, httpClient)

EVM 机制#

ExactEvmScheme(客户端)#

go
import (
    "github.com/okx/payments/go/x402/mechanisms/evm"
    evmsigners "github.com/okx/payments/go/x402/mechanisms/evm/signers"
    evmclient "github.com/okx/payments/go/x402/mechanisms/evm/exact/client"
)

signer, err := evmsigners.NewClientSignerFromPrivateKey("0x...")
scheme := evmclient.NewExactEvmScheme(signer, config)
scheme.Scheme()  // "exact"

ClientEvmSigner 接口:

go
type ClientEvmSigner interface {
    Address() string
    SignTypedData(ctx context.Context, domain TypedDataDomain, types map[string][]TypedDataField, primaryType string, message map[string]interface{}) ([]byte, error)
}

ExactEvmScheme(服务端)#

go
import evmserver "github.com/okx/payments/go/x402/mechanisms/evm/exact/server"

scheme := evmserver.NewExactEvmScheme()
scheme.RegisterMoneyParser(customParser)  // Optional: custom price parsing
scheme.Scheme()  // "exact"

ExactEvmScheme(Facilitator)#

go
import evmfacilitator "github.com/okx/payments/go/x402/mechanisms/evm/exact/facilitator"

scheme := evmfacilitator.NewExactEvmScheme(signer, &ExactEvmSchemeConfig{
    DeployERC4337WithEIP6492: false,  // Auto-deploy smart wallets
    SimulateInSettle:         false,  // Re-simulate during settle
})

EVM 类型#

go
type TypedDataDomain struct {
    Name              string   `json:"name"`
    Version           string   `json:"version"`
    ChainID           *big.Int `json:"chainId"`
    VerifyingContract string   `json:"verifyingContract"`
}

type TypedDataField struct {
    Name string `json:"name"`
    Type string `json:"type"`
}

type AssetTransferMethod string
const (
    AssetTransferMethodEIP3009 AssetTransferMethod = "eip3009"
    AssetTransferMethodPermit2 AssetTransferMethod = "permit2"
)

type AssetInfo struct {
    Address             string
    Name                string
    Version             string
    Decimals            int
    AssetTransferMethod AssetTransferMethod
    SupportsEip2612     bool
}

SVM 机制#

ExactSvmScheme(客户端)#

go
import (
    svmsigners "github.com/okx/payments/go/x402/mechanisms/svm/signers"
    svmclient "github.com/okx/payments/go/x402/mechanisms/svm/exact/client"
)

signer, err := svmsigners.NewClientSignerFromPrivateKey(solanaPrivateKey)
scheme := svmclient.NewExactSvmScheme(signer, config)

ExactSvmScheme(服务端)#

go
import svmserver "github.com/okx/payments/go/x402/mechanisms/svm/exact/server"

scheme := svmserver.NewExactSvmScheme()
scheme.RegisterMoneyParser(customParser)

Facilitator 钩子#

go
type FacilitatorVerifyContext struct {
    Ctx               context.Context
    Payload           PaymentPayloadView
    Requirements      PaymentRequirementsView
    PayloadBytes      []byte
    RequirementsBytes []byte
}

type FacilitatorBeforeHookResult struct {
    Abort   bool
    Reason  string
    Message string
}

type FacilitatorBeforeVerifyHook    func(FacilitatorVerifyContext) (*FacilitatorBeforeHookResult, error)
type FacilitatorAfterVerifyHook     func(FacilitatorVerifyResultContext) error
type FacilitatorOnVerifyFailureHook func(FacilitatorVerifyFailureContext) (*FacilitatorVerifyFailureHookResult, error)
type FacilitatorBeforeSettleHook    func(FacilitatorSettleContext) (*FacilitatorBeforeHookResult, error)
type FacilitatorAfterSettleHook     func(FacilitatorSettleResultContext) error
type FacilitatorOnSettleFailureHook func(FacilitatorSettleFailureContext) (*FacilitatorSettleFailureHookResult, error)

Go SDK 参考(适用于 chargesession#

Packages#

Package导入路径描述
servergithub.com/okx/payments/go/mpp/server高层 MPP 协调器:MppEVMConfigChargeRouteConfigSessionRouteConfig
evmgithub.com/okx/payments/go/mpp/evmEVM 支付方法:EVMChargeMethod / EVMSessionMethod、EIP-712 签名/验签、Voucher、Authorization
protocolgithub.com/okx/payments/go/mpp/protocol协议层:challenge / credential / receipt 解析与格式化、ChargeVerifier / SessionVerifier 接口、错误码
saclientgithub.com/okx/payments/go/mpp/saclientSA-API 客户端:SAClient 接口与 OKXSAClient 实现,HMAC-SHA256 认证
storegithub.com/okx/payments/go/mpp/store泛型 key-value store:Store[T] 接口、MemoryStoreFileStoreChannelState
http/gingithub.com/okx/payments/go/mpp/http/ginGin 框架中间件:ChargeMiddleware / SessionMiddleware
http/echogithub.com/okx/payments/go/mpp/http/echoEcho 框架中间件:ChargeMiddleware / SessionMiddleware
http/nethttpgithub.com/okx/payments/go/mpp/http/nethttp标准库 net/http 中间件:ChargeMiddleware / SessionMiddleware
adaptersgithub.com/okx/payments/go/mpp/adaptersPayment Router 适配器:MppAdapter 实现 paymentrouter.ProtocolAdapter
ssegithub.com/okx/payments/go/mpp/sseServer-Sent Events 辅助工具:Event 格式化/解析、Serve 流式推送
errorsgithub.com/okx/payments/go/mpp/errors错误类型:MppErrorMppErrorCode、RFC 9457 PaymentErrorDetails

核心类型(evm package)#

常量#

go
const DefaultExpiresMinutes = 5
const MethodNameEVM         = "evm"
const MaxSplits             = 10

// 默认 EIP-712 domain 常量
const DefaultDomainName     = "EVM Payment Channel"
const DefaultDomainVersion  = "1"

// 默认 Escrow 合约地址 (X Layer)
const DefaultEscrowContract = "0x5E550002e64FaF79B41D89fE8439eEb1be66CE3b"

// EIP-712 proof domain 常量
const ProofDomainName       = "MPP"
const ProofDomainVersion    = "1"

// X Layer 链 ID
const XLayerChainID uint64  = 196

// Session action 常量
const (
    ActionOpen    = "open"
    ActionTopUp   = "topUp"
    ActionVoucher = "voucher"
    ActionClose   = "close"
    ActionSettle  = "settle"
)

// Session receipt 状态常量
const (
    StatusOpen   = "open"
    StatusClosed = "closed"
)

Split#

Charge challenge 中的分账项(绝对金额)。

go
type Split struct {
    Amount    string  `json:"amount"`
    Memo      *string `json:"memo,omitempty"`
    Recipient string  `json:"recipient"`
}

EVMMethodDetails#

Charge 请求的 methodDetails 字段(嵌入 challenge 的 request base64url JSON)。

go
type EVMMethodDetails struct {
    ChainID  *uint64 `json:"chainId,omitempty"`
    FeePayer *bool   `json:"feePayer,omitempty"`
    Memo     *string `json:"memo,omitempty"`
    Splits   []Split `json:"splits,omitempty"`
}

func (d *EVMMethodDetails) IsFeePayer() bool

SessionSplit#

Session 分账项(基点比例)。

go
// 约束: bps in [1, 9999]; sum(splits[].bps) < 10000。
type SessionSplit struct {
    Recipient string  `json:"recipient"`
    Bps       uint32  `json:"bps"`
    Memo      *string `json:"memo,omitempty"`
}

EVMSessionMethodDetails#

Session 请求的 methodDetails 字段。

go
type EVMSessionMethodDetails struct {
    EscrowContract  string         `json:"escrowContract"`
    ChannelID       *string        `json:"channelId,omitempty"`
    MinVoucherDelta *string        `json:"minVoucherDelta,omitempty"`
    ChainID         *uint64        `json:"chainId,omitempty"`
    FeePayer        *bool          `json:"feePayer,omitempty"`
    Splits          []SessionSplit `json:"splits,omitempty"`
}

func (d *EVMSessionMethodDetails) IsFeePayer() bool

SessionReceipt (EVM)#

EVM 层的 session receipt,包含 channel 特有字段。

go
type SessionReceipt struct {
    ChannelID        string `json:"channelId"`
    CumulativeAmount string `json:"cumulativeAmount"`
    EscrowContract   string `json:"escrowContract"`
    Status           string `json:"status"`
    Reference        string `json:"reference,omitempty"`
}

func NewSessionReceipt(channelID, cumulativeAmount, escrowContract, status, reference string) *SessionReceipt
func (r *SessionReceipt) ToBaseReceipt(challengeID string) (*protocol.Receipt, error)

Payload 类型#

go
// 基础 payload 结构
type Payload struct {
    Action string `json:"action"`
}

type OpenPayload struct {
    Payload
    Type             string             `json:"type"`              // "transaction" | "hash"
    ChannelID        string             `json:"channelId"`
    Salt             string             `json:"salt"`
    CumulativeAmount string             `json:"cumulativeAmount"`
    Signature        string             `json:"signature"`
    Authorization    *OpenAuthorization `json:"authorization,omitempty"`
    Hash             string             `json:"hash,omitempty"`
    VoucherSignature string             `json:"voucherSignature,omitempty"`
    AuthorizedSigner string             `json:"authorizedSigner,omitempty"`
    Deposit          string             `json:"deposit,omitempty"`
}

type TopUpPayload struct {
    Payload
    Type              string             `json:"type"`
    ChannelID         string             `json:"channelId"`
    AdditionalDeposit string             `json:"additionalDeposit"`
    Authorization     *OpenAuthorization `json:"authorization,omitempty"`
    Hash              string             `json:"hash,omitempty"`
    Signature         string             `json:"signature,omitempty"`
    TopUpSalt         string             `json:"topUpSalt,omitempty"`
}

type VoucherPayload struct {
    Payload
    ChannelID        string `json:"channelId"`
    CumulativeAmount string `json:"cumulativeAmount"`
    Signature        string `json:"signature"`
}

type ClosePayload struct {
    Payload
    ChannelID        string `json:"channelId"`
    CumulativeAmount string `json:"cumulativeAmount"`
    Signature        string `json:"signature"`
}

每种 payload 都有 Validate() error 方法做必填字段检查。


Signer 接口#

go
// Signer 签名任意消息哈希和 EIP-712 类型化数据。
type Signer interface {
    Sign(hash []byte) ([]byte, error)
    SignTypedData(typedData apitypes.TypedData) ([]byte, error)
    Address() common.Address
}

PrivateKeySigner#

go
type PrivateKeySigner struct { /* private */ }

func NewPrivateKeySigner(key *ecdsa.PrivateKey) *PrivateKeySigner
func NewPrivateKeySignerFromHex(hexKey string) (*PrivateKeySigner, error)

签名输出 65 字节 r||s||v,其中 v = 27 或 28。


NonceProvider 接口#

go
// NonceProvider 为 settle/close authorization 分配 nonce。
type NonceProvider interface {
    Allocate(payee common.Address, channelID [32]byte) (*big.Int, error)
}

// UuidNonceProvider 默认实现:UUID v4 转 U256(128-bit 随机,无状态,多实例/重启安全)。
type UuidNonceProvider struct{}

func NewUuidNonceProvider() *UuidNonceProvider

合约层 nonce 已用集 key = (payee, channelId, nonce),重复使用以 NonceAlreadyUsed revert。SDK 只负责分配「大概率没用过」的 nonce。


Charge -- EVMChargeMethod#

实现 protocol.ChargeVerifier,把 credential 透传给 SA-API。

go
type EVMChargeMethod struct { /* private */ }

func NewEVMChargeMethod() *EVMChargeMethod

// Builder 方法(链式调用)
func (m *EVMChargeMethod) WithChainID(chainID uint64) *EVMChargeMethod
func (m *EVMChargeMethod) WithRecipient(recipient string) *EVMChargeMethod
func (m *EVMChargeMethod) WithFeePayer(feePayer bool) *EVMChargeMethod
func (m *EVMChargeMethod) WithSAClient(c saclient.SAClient) *EVMChargeMethod

// ChargeVerifier 接口实现
func (m *EVMChargeMethod) Method() string
func (m *EVMChargeMethod) PrepareRequest(request protocol.ChargeRequest, _ *protocol.PaymentCredential) protocol.ChargeRequest
func (m *EVMChargeMethod) Verify(ctx context.Context, cred *protocol.PaymentCredential, request *protocol.ChargeRequest) (*protocol.Receipt, error)

payload.type 路由:

  • "transaction" -- Settle(SA-API 链上 broadcast transferWithAuthorization)
  • "hash" -- VerifyHash(client 已自行 broadcast,SA-API 验证 tx hash)
  • "proof" -- 验证 proof source,然后调 Settle

Session -- EVMSessionMethod#

实现 protocol.SessionVerifier。维护本地 channel state、支持 voucher 本地验签 + 累计扣费、商户主动 settle/close。

配置与构造#

go
type EVMSessionMethodConfig struct {
    // 必填
    Recipient string            // Payee 钱包地址
    SAClient  saclient.SAClient // SA API 客户端

    // 可选(零值 = 使用默认值)
    ChainID         uint64                          // 默认: 196 (X Layer)
    EscrowContract  string                          // 默认: DefaultEscrowContract
    Signer          Signer                          // Payee 签名器(settle/close 必需),nil = 禁用 settle/close
    Store           store.Store[store.ChannelState]  // 默认: MemoryStore
    PerRequestCost  *big.Int                        // 每次请求扣费金额
    MinVoucherDelta *big.Int                        // 连续 voucher 最小递增量
    NonceProvider   NonceProvider                   // 默认: UuidNonceProvider
    Deadline        *big.Int                        // 默认: U256 MAX(不过期)
    DomainName      string                          // 默认: "EVM Payment Channel"
    DomainVersion   string                          // 默认: "1"
    FeePayer        bool                            // payee 是否代付 gas
}

func NewEVMSessionMethod(cfg EVMSessionMethodConfig) (*EVMSessionMethod, error)

SessionVerifier 接口实现#

go
func (m *EVMSessionMethod) Method() string
func (m *EVMSessionMethod) VerifySession(ctx context.Context, cred *protocol.PaymentCredential, request *protocol.SessionRequest) (*protocol.Receipt, error)
func (m *EVMSessionMethod) ChallengeMethodDetails() json.RawMessage
func (m *EVMSessionMethod) Respond(cred *protocol.PaymentCredential, receipt *protocol.Receipt) any

业务方法#

go
// 获取底层 channel store。
func (m *EVMSessionMethod) ChannelStore() store.Store[store.ChannelState]

// 从 channel 可用余额中扣减。
func (m *EVMSessionMethod) DeductFromSession(ctx context.Context, channelID string, amount *big.Int) (*store.ChannelState, error)

// 中间结算:读取 store 最高 voucher -> 签 SettleAuthorization -> 调 SA API。
func (m *EVMSessionMethod) SettleWithAuthorization(ctx context.Context, channelID string) (*saclient.SessionReceipt, error)

// 关闭 channel:签 CloseAuthorization -> 调 SA API -> 成功后从 store 删除。
func (m *EVMSessionMethod) CloseWithAuthorization(ctx context.Context, channelID string) (*saclient.SessionReceipt, error)

Session action 路由(VerifySession 内部)#

payload.action行为
"open"验证 payload -> 调 SA session/open -> 写本地 store
"topUp"验证 -> 调 SA session/topUp -> 累加本地 deposit
"voucher"本地验签 + 升 highest -> deduct perRequestCost
"close"验证 voucher sig -> 签 CloseAuthorization -> 调 SA /session/close

server package#

EVMConfig#

go
type EVMConfig struct {
    ChainID   uint64  // EVM 链 ID (例: 196 = X Layer)
    Recipient string  // 收款地址(hex, 有/无 0x 前缀)
    SecretKey string  // HMAC 密钥,用于签名和验证 challenge ID
    Realm     string  // WWW-Authenticate 保护域,默认 "mpp"
}

Mpp#

高层支付协调器,把 EVMConfig、charge verifier、session verifier 串起来。

go
type Mpp struct { /* private */ }

func NewMpp(cfg EVMConfig, charge protocol.ChargeVerifier, session protocol.SessionVerifier) *Mpp

两个 verifier 可以为 nil(只需一种 intent 时)。

Charge 相关方法#

go
// Charge 根据 ChargeRouteConfig 生成 WWW-Authenticate challenge header。
// Amount 为人类可读十进制,Decimals 为 token 小数位数。
func (m *Mpp) Charge(ctx context.Context, cfg ChargeRouteConfig) (string, error)

// ChargeWithOptions 低层 API,直接传 ChargeRequest + ChargeOptions。
func (m *Mpp) ChargeWithOptions(ctx context.Context, req protocol.ChargeRequest, opts ChargeOptions) (string, error)

// VerifyCredential 验证 charge credential,成功返回 Receipt。
func (m *Mpp) VerifyCredential(ctx context.Context, challengeHeader string, authHeader string) (*protocol.Receipt, error)

Session 相关方法#

go
// SessionChallenge 根据 SessionRouteConfig 生成 session challenge header。
func (m *Mpp) SessionChallenge(ctx context.Context, cfg SessionRouteConfig) (string, error)

// SessionChallengeWithDetails 低层 API,直接传 SessionRequest + SessionChallengeOptions。
func (m *Mpp) SessionChallengeWithDetails(ctx context.Context, req protocol.SessionRequest, opts SessionChallengeOptions) (string, error)

// VerifySession 验证 session credential,成功返回 SessionVerifyResult。
func (m *Mpp) VerifySession(ctx context.Context, challengeHeader string, authHeader string) (*protocol.SessionVerifyResult, error)

ChargeRouteConfig#

go
type ChargeRouteConfig struct {
    Amount      string     // 人类可读十进制 (如 "0.01")
    Currency    string     // ERC-20 token 合约地址
    Decimals    uint32     // token 小数位 (如 USDC=6)
    Description string     // 可选:人类可读描述
    ExternalID  string     // 可选:调用方标识符
    Splits      []evm.Split // 分账项,最多 10 条
}

SessionRouteConfig#

go
type SessionRouteConfig struct {
    Amount           string // 人类可读十进制 (如 "0.001")
    Currency         string // ERC-20 token 合约地址
    Decimals         uint32 // token 小数位
    Description      string // 可选:人类可读描述
    ExternalID       string // 可选:调用方标识符
    UnitType         string // 计费单位 (如 "request", "second", "byte")
    SuggestedDeposit string // 建议初始 deposit (base units)
}

ChargeOptions / SessionChallengeOptions#

go
type ChargeOptions struct {
    EVMConfig   EVMConfig
    Description string
    Realm       string
}

type SessionChallengeOptions struct {
    EVMConfig   EVMConfig
    Description string
    Realm       string
}

ReceiptContextKey#

go
const ReceiptContextKey = "mpp_payment_receipt"

所有框架适配器(gin/echo/nethttp)在验证成功后将 *protocol.Receipt 存入此 context key。


protocol package#

常量#

go
const IntentCharge  = "charge"
const IntentSession = "session"

const WWWAuthenticateHeader = "WWW-Authenticate"
const AuthorizationHeader   = "Authorization"
const PaymentReceiptHeader  = "Payment-Receipt"
const PaymentScheme         = "Payment"

PayloadType#

go
type PayloadType string

const (
    PayloadTypeTransaction PayloadType = "transaction"
    PayloadTypeHash        PayloadType = "hash"
    PayloadTypeProof       PayloadType = "proof"
)

PaymentChallenge#

解析后的 WWW-Authenticate: Payment challenge。

go
type PaymentChallenge struct {
    ID          string `json:"id"`
    Realm       string `json:"realm"`
    Method      string `json:"method"`
    Intent      string `json:"intent"`
    Request     string `json:"request"`      // base64url-encoded JSON
    Expires     uint64 `json:"expires,omitempty"`
    Description string `json:"description,omitempty"`
    Digest      string `json:"digest,omitempty"`
    Opaque      string `json:"opaque,omitempty"`
    SecretKey   string `json:"-"`            // 不序列化
}

func NewPaymentChallenge(realm, method, intent, request string) *PaymentChallenge
func PaymentChallengeFromHeader(header string) (*PaymentChallenge, error)

func (c *PaymentChallenge) WithSecretKey(key string) *PaymentChallenge
func (c *PaymentChallenge) WithExpires(expires uint64) *PaymentChallenge
func (c *PaymentChallenge) WithDescription(desc string) *PaymentChallenge
func (c *PaymentChallenge) ToHeader() (string, error)
func (c *PaymentChallenge) Verify(cred *PaymentCredential) error
func (c *PaymentChallenge) IsExpired() bool
func (c *PaymentChallenge) ValidateForCharge() error
func (c *PaymentChallenge) ValidateForSession() error

ChallengeEcho#

Challenge 的回声副本,嵌在 Authorization header 中。

go
type ChallengeEcho struct {
    ID          string `json:"id"`
    Realm       string `json:"realm"`
    Method      string `json:"method"`
    Intent      string `json:"intent"`
    Request     string `json:"request"`
    Expires     string `json:"expires,omitempty"`
    Description string `json:"description,omitempty"`
    Digest      string `json:"digest,omitempty"`
    Opaque      string `json:"opaque,omitempty"`
}

func (e *ChallengeEcho) ParseExpiresUnix() uint64

PaymentCredential#

Authorization: Payment <base64url-encoded JSON> 中的 credential。

go
type PaymentCredential struct {
    Echo    *ChallengeEcho  `json:"echo"`
    Source  string          `json:"source,omitempty"`
    Payload *PaymentPayload `json:"payload"`
}

func NewPaymentCredential(echo *ChallengeEcho, payload *PaymentPayload) *PaymentCredential
func NewPaymentCredentialWithSource(echo *ChallengeEcho, source string, payload *PaymentPayload) *PaymentCredential

PaymentPayload#

go
type PaymentPayload struct {
    Type    PayloadType `json:"payload_type"`
    Payload string      `json:"payload"`    // raw JSON string
}

func NewTransactionPayload(payload string) *PaymentPayload
func NewHashPayload(payload string) *PaymentPayload
func NewProofPayload(payload string) *PaymentPayload

Receipt#

go
type Receipt struct {
    ID         string        `json:"id"`
    Status     ReceiptStatus `json:"status"`
    Method     MethodName    `json:"method"`
    Intent     IntentName    `json:"intent"`
    Settlement string        `json:"settlement"`
}

func NewSuccessReceipt(id string, method MethodName, intent IntentName, settlement string) *Receipt
func (r *Receipt) ToHeader() (string, error)

SessionVerifyResult#

go
type SessionVerifyResult struct {
    Receipt            *Receipt
    // ManagementResponse 非 nil 时,说明这是管理操作 (open/topUp/close),
    // 调用方应把它作为响应体返回而不是继续处理请求。
    // 为 nil 时 (voucher/replay),调用方应继续提供资源。
    ManagementResponse any
}

ChargeRequest / SessionRequest#

go
type ChargeRequest struct {
    Amount        string          `json:"amount"`
    Currency      string          `json:"currency"`
    Decimals      *uint8          `json:"decimals,omitempty"`
    Recipient     *string         `json:"recipient,omitempty"`
    Description   *string         `json:"description,omitempty"`
    ExternalID    *string         `json:"externalId,omitempty"`
    MethodDetails json.RawMessage `json:"methodDetails,omitempty"`
}

func (r *ChargeRequest) WithBaseUnits() (*ChargeRequest, error)
func (r *ChargeRequest) ParseAmountBigInt() (*big.Int, error)
func (r *ChargeRequest) ValidateMaxAmount(max string) error

type SessionRequest struct {
    Amount           string          `json:"amount"`
    UnitType         *string         `json:"unitType,omitempty"`
    Currency         string          `json:"currency"`
    Decimals         *uint8          `json:"decimals,omitempty"`
    Recipient        *string         `json:"recipient,omitempty"`
    SuggestedDeposit *string         `json:"suggestedDeposit,omitempty"`
    Description      *string         `json:"description,omitempty"`
    ExternalID       *string         `json:"externalId,omitempty"`
    MethodDetails    json.RawMessage `json:"methodDetails,omitempty"`
}

func (r *SessionRequest) WithBaseUnits() (*SessionRequest, error)
func (r *SessionRequest) ValidateMaxAmount(max string) error

ChargeVerifier 接口#

go
type ChargeVerifier interface {
    Method() string
    PrepareRequest(request ChargeRequest, cred *PaymentCredential) ChargeRequest
    Verify(ctx context.Context, cred *PaymentCredential, request *ChargeRequest) (*Receipt, error)
}

SessionVerifier 接口#

go
type SessionVerifier interface {
    Method() string
    VerifySession(ctx context.Context, cred *PaymentCredential, request *SessionRequest) (*Receipt, error)
    ChallengeMethodDetails() json.RawMessage
    Respond(cred *PaymentCredential, receipt *Receipt) any
}

Header 解析/格式化#

go
func ParseWWWAuthenticate(header string) (*PaymentChallenge, error)
func FormatWWWAuthenticate(c *PaymentChallenge) (string, error)
func ParseAuthorization(header string) (*PaymentCredential, error)
func FormatAuthorization(c *PaymentCredential) (string, error)
func ParseReceipt(header string) (*Receipt, error)
func FormatReceipt(r *Receipt) (string, error)
func ComputeChallengeID(secretKey, realm, method, intent, request string, expires uint64, digest, opaque string) string

序列化辅助#

go
func SerializeRequest(v interface{}) (string, error)
func DeserializeRequest(encoded string) (json.RawMessage, error)
func DeserializeRequestTyped(encoded string, v interface{}) error
func RequestFromChallenge(c *PaymentChallenge) (json.RawMessage, error)
func RequestFromChallengeTyped(c *PaymentChallenge, v interface{}) error
func Base64URLEncode(data []byte) string
func Base64URLDecode(input string) ([]byte, error)
func ParseUnits(amount string, decimals uint8) (string, error)

VerificationError#

go
type VerificationError struct {
    Message   string    `json:"message"`
    Code      ErrorCode `json:"code,omitempty"`
    Retryable bool      `json:"retryable"`
}

func (e *VerificationError) Error() string
func (e *VerificationError) HTTPStatus() int  // 400 | 402 | 410
func (e *VerificationError) WithRetryable() *VerificationError

ErrorCode#

go
type ErrorCode string

const (
    ErrorCodeExpired              ErrorCode = "expired"
    ErrorCodeInvalidAmount        ErrorCode = "invalid-amount"
    ErrorCodeInvalidRecipient     ErrorCode = "invalid-recipient"
    ErrorCodeTransactionFailed    ErrorCode = "transaction-failed"
    ErrorCodeNotFound             ErrorCode = "not-found"
    ErrorCodeInvalidCredential    ErrorCode = "invalid-credential"
    ErrorCodeNetworkError         ErrorCode = "network-error"
    ErrorCodeChainIdMismatch      ErrorCode = "chain-id-mismatch"
    ErrorCodeCredentialMismatch   ErrorCode = "credential-mismatch"
    ErrorCodeChannelNotFound      ErrorCode = "channel-not-found"
    ErrorCodeChannelClosed        ErrorCode = "channel-closed"
    ErrorCodeInsufficientBalance  ErrorCode = "insufficient-balance"
    ErrorCodeInvalidPayload       ErrorCode = "invalid-payload"
    ErrorCodeInvalidSignature     ErrorCode = "invalid-signature"
    ErrorCodeAmountExceedsDeposit ErrorCode = "amount-exceeds-deposit"
    ErrorCodeDeltaTooSmall        ErrorCode = "delta-too-small"
)

便捷错误构造器:

go
func ErrCredential(msg string) *VerificationError
func ErrPayload(msg string) *VerificationError
func ErrSig(msg string) *VerificationError
func ErrAmount(msg string) *VerificationError
func ErrNetwork(msg string) *VerificationError
func ErrChannelNotFound(channelID string) *VerificationError
func ErrChannelClosed() *VerificationError
func ErrExceedsDeposit(amount, deposit string) *VerificationError
func ErrDeltaTooSmall(delta, min string) *VerificationError
func ErrInsufficientBalance(available, requested string) *VerificationError
func ErrChainMismatch(expected, got uint64) *VerificationError

saclient package#

SAClient 接口#

可插拔 SA-API client 接口,默认实现 OKXSAClient

go
type SAClient interface {
    // Charge (client-facing -- forward credential)
    Settle(ctx context.Context, req *ChargeSettleRequest) (*ChargeReceipt, error)
    VerifyHash(ctx context.Context, req *ChargeVerifyHashRequest) (*ChargeReceipt, error)

    // Session (client-facing -- forward credential)
    SessionOpen(ctx context.Context, req *SessionOpenRequest) (*SessionReceipt, error)
    SessionTopUp(ctx context.Context, req *SessionTopUpRequest) (*SessionReceipt, error)

    // Session (merchant-facing -- server builds request)
    SessionSettle(ctx context.Context, req *SessionSettleRequest) (*SessionReceipt, error)
    SessionClose(ctx context.Context, req *SessionCloseRequest) (*SessionReceipt, error)

    // Session (read-only)
    SessionStatus(ctx context.Context, channelID string) (*SessionStatus, error)
}

OKXSAClient#

go
type OKXSAClient struct { /* private */ }

func NewOKXSAClient(baseURL, apiKey, secretKey, passphrase string, opts ...Option) *OKXSAClient

Option 函数:

go
func WithHTTPClient(c *http.Client) Option

调用的端点#

SAClient 方法OKX 路径
Settle()POST /api/v6/pay/mpp/charge/settle
VerifyHash()POST /api/v6/pay/mpp/charge/verifyHash
SessionOpen()POST /api/v6/pay/mpp/session/open
SessionTopUp()POST /api/v6/pay/mpp/session/topUp
SessionSettle()POST /api/v6/pay/mpp/session/settle
SessionClose()POST /api/v6/pay/mpp/session/close
SessionStatus()GET /api/v6/pay/mpp/session/status?channelId=...

OKX 响应被包装在 {"code": 0, "data": {...}, "msg": ""} 中,客户端会自动解包。

SAResponse#

go
type SAResponse struct {
    Code int             `json:"code"`
    Msg  string          `json:"msg"`
    Data json.RawMessage `json:"data"`
}

HMAC 认证头常量#

go
const headerAPIKey      = "OK-ACCESS-KEY"
const headerSign        = "OK-ACCESS-SIGN"
const headerTimestamp   = "OK-ACCESS-TIMESTAMP"
const headerPassphrase  = "OK-ACCESS-PASSPHRASE"

签名算法:Base64(HMAC-SHA256(secretKey, timestamp + METHOD + requestPath + body))

泛型请求包装#

go
type CredentialRequest[P any] struct {
    Challenge *protocol.ChallengeEcho `json:"challenge,omitempty"`
    Payload   P                       `json:"payload"`
    Source    string                  `json:"source,omitempty"`
}

// 类型别名
type ChargeSettleRequest    = CredentialRequest[ChargeTransactionPayload]
type ChargeVerifyHashRequest = CredentialRequest[ChargeHashPayload]
type SessionOpenRequest     = CredentialRequest[SessionOpenPayload]
type SessionTopUpRequest    = CredentialRequest[SessionTopUpPayload]
type SessionSettleRequest   = CredentialRequest[SessionSettlePayload]
type SessionCloseRequest    = CredentialRequest[SessionClosePayload]

Charge Payload 类型#

go
type Eip3009Authorization struct {
    Type        string                 `json:"type"`    // "eip-3009"
    From        string                 `json:"from"`
    To          string                 `json:"to"`
    Value       string                 `json:"value"`
    ValidAfter  string                 `json:"validAfter"`
    ValidBefore string                 `json:"validBefore"`
    Nonce       string                 `json:"nonce"`
    Signature   string                 `json:"signature,omitempty"`
    Splits      []Eip3009Authorization `json:"splits,omitempty"`
}

type ChargeTransactionPayload struct {
    Type          string               `json:"type"`    // "transaction"
    Authorization Eip3009Authorization `json:"authorization"`
}

type ChargeHashPayload struct {
    Type string `json:"type"`    // "hash"
    Hash string `json:"hash"`
}

Session Payload 类型#

go
// Client-facing (forward credential)
type SessionOpenPayload struct {
    Action           string                `json:"action"`
    Type             string                `json:"type"`
    ChannelID        string                `json:"channelId"`
    Authorization    *Eip3009Authorization `json:"authorization,omitempty"`
    Signature        string                `json:"signature,omitempty"`
    Hash             string                `json:"hash,omitempty"`
    Salt             string                `json:"salt"`
    AuthorizedSigner string                `json:"authorizedSigner,omitempty"`
}

type SessionTopUpPayload struct {
    Action            string                `json:"action"`
    Type              string                `json:"type"`
    ChannelID         string                `json:"channelId"`
    Authorization     *Eip3009Authorization `json:"authorization,omitempty"`
    Signature         string                `json:"signature,omitempty"`
    Hash              string                `json:"hash,omitempty"`
    AdditionalDeposit string                `json:"additionalDeposit"`
    TopUpSalt         string                `json:"topUpSalt,omitempty"`
}

// Merchant-facing (server builds request)
type SessionSettlePayload struct {
    Action           string `json:"action,omitempty"`
    ChannelID        string `json:"channelId"`
    CumulativeAmount string `json:"cumulativeAmount"`
    VoucherSignature string `json:"voucherSignature"`
    PayeeSignature   string `json:"payeeSignature"`
    Nonce            string `json:"nonce"`
    Deadline         string `json:"deadline"`
}

type SessionClosePayload struct {
    Action           string `json:"action,omitempty"`
    ChannelID        string `json:"channelId"`
    CumulativeAmount string `json:"cumulativeAmount"`
    VoucherSignature string `json:"voucherSignature"`
    PayeeSignature   string `json:"payeeSignature"`
    Nonce            string `json:"nonce"`
    Deadline         string `json:"deadline"`
}

响应类型#

go
type ChargeReceipt struct {
    Method      string `json:"method"`
    Reference   string `json:"reference"`
    Status      string `json:"status"`
    Timestamp   string `json:"timestamp"`
    ChainID     uint64 `json:"chainId"`
    ChallengeID string `json:"challengeId"`
    ExternalID  string `json:"externalId"`
}

type SessionReceipt struct {
    Method    string `json:"method"`
    Intent    string `json:"intent"`
    Status    string `json:"status"`
    Timestamp string `json:"timestamp"`
    ChannelID string `json:"channelId"`
    ChainID   uint64 `json:"chainId"`
    Reference string `json:"reference"`
    Deposit   string `json:"deposit"`
}

type SessionStatus struct {
    ChannelID        string `json:"channelId"`
    Payer            string `json:"payer"`
    Payee            string `json:"payee"`
    Token            string `json:"token"`
    Deposit          string `json:"deposit"`
    CumulativeAmount string `json:"cumulativeAmount"`
    SettledOnChain   string `json:"settledOnChain"`
    RemainingBalance string `json:"remainingBalance"`
    SessionStatus    string `json:"sessionStatus"` // OPEN, CLOSING, CLOSED
}

SA 错误码#

go
type SAErrorCode int

const (
    SACodeSuccess              SAErrorCode = 0
    SACodeUnsupportedChain     SAErrorCode = 70001
    SACodePayerBlocked         SAErrorCode = 70002
    SACodeInvalidCredential    SAErrorCode = 70003
    SACodeInvalidSignature     SAErrorCode = 70004
    SACodeInsufficientBalance  SAErrorCode = 70005
    SACodeAmountExceedsDeposit SAErrorCode = 70006
    SACodeTxNotConfirmed       SAErrorCode = 70007
    SACodeChannelNotFound      SAErrorCode = 70008
    SACodeChannelClosed        SAErrorCode = 70009
    SACodeDeltaTooSmall        SAErrorCode = 70010
    SACodeGracePeriodTooShort  SAErrorCode = 70011
    SACodeSignerMismatch       SAErrorCode = 70012
    SACodeDeltaBelowMinimum    SAErrorCode = 70013
    SACodeChannelClosing       SAErrorCode = 70014
    SACodeInternalError        SAErrorCode = 8000
)

store package#

Store[T] 接口#

go
type Store[T any] interface {
    Get(ctx context.Context, key string) (*T, error)
    Put(ctx context.Context, key string, value *T) error
    Delete(ctx context.Context, key string) error
}

ChannelState#

go
type ChannelState struct {
    ChannelID               string   `json:"channelId"`
    ChainID                 uint64   `json:"chainId"`
    EscrowContract          string   `json:"escrowContract"`
    Payer                   string   `json:"payer"`
    Payee                   string   `json:"payee"`
    Token                   string   `json:"token"`
    AuthorizedSigner        string   `json:"authorizedSigner"`
    Deposit                 *big.Int `json:"deposit"`
    HighestVoucherAmount    *big.Int `json:"highestVoucherAmount"`
    HighestVoucherSignature []byte   `json:"highestVoucherSignature,omitempty"`
    MinVoucherDelta         *big.Int `json:"minVoucherDelta,omitempty"`
    Spent                   *big.Int `json:"spent"`
    Units                   uint64   `json:"units"`
    Finalized               bool     `json:"finalized"`
    CloseRequestedAt        uint64   `json:"closeRequestedAt"`
    CreatedAt               string   `json:"createdAt"`
}

不变式:

  • HighestVoucherAmount 单调非递减
  • Spent 单调非递减
  • available = HighestVoucherAmount - Spent

DeductFromChannel#

go
// 原子扣费:available = HighestVoucherAmount - Spent; 不足则返 error。
// 调用方必须持有必要的锁以保证原子性。
func DeductFromChannel(ctx context.Context, s Store[ChannelState], channelID string, amount *big.Int) (*ChannelState, error)

MemoryStore#

go
type MemoryStore[T any] struct { /* private */ }

func NewMemoryStore[T any]() *MemoryStore[T]

进程内 HashMap,适合大多数 single-process 部署。值通过 JSON round-trip 深拷贝以防止调用方篡改。两个注意事项:

  • 重启即丢:进程重启 / crash 丢失所有 channel state。如果业务不能接受,可自实现持久化 store 注入。
  • abandoned channel 累积:payer 不调 close 时记录会一直留着,商户应有 cleanup 策略。

FileStore#

go
type FileStore[T any] struct { /* private */ }

func NewFileStore[T any](dir string) (*FileStore[T], error)

基于文件系统的 Store,每个 key 对应一个 JSON 文件。

  • 创建时自动 mkdir -p
  • Per-key mutex 保证并发安全
  • Key 自动 sanitize(仅保留字母/数字/-/_,其余替换为 _)
  • 适合本地开发、测试或少量 channel 场景
  • 数据格式化为 pretty-printed JSON

HTTP 中间件#

三个框架的中间件签名一致:接收 *server.Mpp + 路由级配置,返回对应框架的 middleware 类型。

Gin (http/gin)#

go
func ChargeMiddleware(m *server.Mpp, cfg server.ChargeRouteConfig) gin.HandlerFunc
func SessionMiddleware(m *server.Mpp, cfg server.SessionRouteConfig) gin.HandlerFunc
func GetReceipt(c *gin.Context) *protocol.Receipt

Echo (http/echo)#

go
func ChargeMiddleware(m *server.Mpp, cfg server.ChargeRouteConfig) echo.MiddlewareFunc
func SessionMiddleware(m *server.Mpp, cfg server.SessionRouteConfig) echo.MiddlewareFunc
func GetReceipt(c echo.Context) *protocol.Receipt

net/http (http/nethttp)#

go
func ChargeMiddleware(m *server.Mpp, cfg server.ChargeRouteConfig) func(http.Handler) http.Handler
func SessionMiddleware(m *server.Mpp, cfg server.SessionRouteConfig) func(http.Handler) http.Handler
func GetReceipt(r *http.Request) *protocol.Receipt

中间件行为#

  1. 无 Authorization header:生成 challenge,返回 402 Payment Required + WWW-Authenticate header
  2. 有 Authorization header:
    • 从 Authorization 头重建 challenge header
    • Mpp.VerifyCredential(charge) 或 Mpp.VerifySession(session) 验证
    • 验证成功:设置 Payment-Receipt header,将 receipt 存入 context
    • Session 管理操作(open/topUp/close):直接返回 JSON 响应,不继续执行 handler
    • Voucher:继续执行后续 handler(提供资源)
  3. 验证失败:返回对应 HTTP 错误码(400/402/410)

adapters package -- Payment Router 集成#

MppAdapter#

实现 paymentrouter.ProtocolAdapter,让 Payment Router 支持 MPP 协议。

go
type MppAdapter struct { /* private */ }

func NewMppAdapter(mpp *server.Mpp) *MppAdapter

func (a *MppAdapter) Name() string       // "mpp"
func (a *MppAdapter) Priority() int      // 10
func (a *MppAdapter) Detect(r *http.Request) bool
func (a *MppAdapter) GetChallenge(ctx context.Context, r *http.Request, cfg any) (http.Header, error)
func (a *MppAdapter) Handle(w http.ResponseWriter, r *http.Request, cfg any) error

MppRouteConfig#

Payment Router 路由级配置,用于 GetChallengecfg 参数。

go
type MppRouteConfig struct {
    Intent           string `json:"intent"`             // "charge" | "session"
    Amount           string `json:"amount"`
    Currency         string `json:"currency"`
    Decimals         uint32 `json:"decimals"`
    Description      string `json:"description,omitempty"`
    ExternalID       string `json:"externalId,omitempty"`
    Realm            string `json:"realm,omitempty"`
    UnitType         string `json:"unitType,omitempty"`
    SuggestedDeposit string `json:"suggestedDeposit,omitempty"`
}

EIP-712 签名 (evm package)#

Domain#

go
const DefaultDomainName    = "EVM Payment Channel"
const DefaultDomainVersion = "1"

EIP-712 domain 由 (name, version, chainId, verifyingContract) 组成,其中 verifyingContract 是 escrow 合约地址。

Voucher 签名 / 验签#

EIP-712 typed struct:

struct Voucher {
    bytes32 channelId;
    uint128 cumulativeAmount;
}
go
// 签名 voucher,返回 65-byte signature。
func SignVoucher(
    signer Signer,
    channelID [32]byte,
    cumulativeAmount *big.Int,
    escrowContract common.Address,
    chainID uint64,
    domainName, domainVersion string,
) ([]byte, error)

// 验签 voucher:EIP-712 digest + ecrecover + 地址比较。
func VerifyVoucher(
    escrowContract common.Address,
    chainID uint64,
    channelID [32]byte,
    cumulativeAmount *big.Int,
    sig []byte,
    expectedSigner common.Address,
    domainName, domainVersion string,
) bool

// 签名长度 + low-s 预检。
func ValidateVoucherSignature(sig []byte) error

// 计算确定性 channel ID: keccak256(abi.encode(payer, payee, token, salt, authorizedSigner, escrowContract, chainID))
func ComputeChannelID(
    payer, payee, token common.Address,
    salt [32]byte,
    authorizedSigner, escrowContract common.Address,
    chainID uint64,
) [32]byte

SettleAuthorization / CloseAuthorization 签名#

EIP-712 typed structs:

struct SettleAuthorization {
    bytes32 channelId;
    uint128 cumulativeAmount;
    uint256 nonce;
    uint256 deadline;
}

struct CloseAuthorization {
    bytes32 channelId;
    uint128 cumulativeAmount;
    uint256 nonce;
    uint256 deadline;
}
go
type SignedAuthorization struct {
    ChannelID        [32]byte
    CumulativeAmount *big.Int
    Nonce            *big.Int
    Deadline         *big.Int
    Signature        []byte    // 65-byte r||s||v
}

// primaryType 必须是 "SettleAuthorization" 或 "CloseAuthorization"。
func SignAuthorization(
    signer Signer,
    primaryType string,
    channelID [32]byte,
    cumulativeAmount *big.Int,
    nonce *big.Int,
    deadline *big.Int,
    escrowContract common.Address,
    chainID uint64,
    domainName, domainVersion string,
) (*SignedAuthorization, error)

sse package#

Server-Sent Events 辅助工具。

go
type Event struct {
    Name  string  // event type (SSE "event:" 字段)
    Data  any     // string / []byte / 可 JSON 序列化的值
    ID    string  // 事件 ID (SSE "id:" 字段)
    Retry int     // 重连间隔 (ms, SSE "retry:" 字段)
}

func (e Event) Format() (string, error)
func ParseEvent(block string) (*Event, error)

// HTTP response 辅助
func SetHeaders(w http.ResponseWriter)
func IsEventStream(r *http.Request) bool
func Serve(w http.ResponseWriter, r *http.Request, events <-chan Event)

Serve 持续从 channel 读取事件并写入 response(需要 http.Flusher),直到 channel 关闭或 client 断开。


错误类型 (errors package)#

MppError#

go
type MppErrorCode string

type MppError struct {
    Code    MppErrorCode `json:"code"`
    Message string       `json:"message"`
    Reason  string       `json:"reason,omitempty"`
}

func (e *MppError) Error() string
func (e *MppError) ToProblemDetails(challengeID string) *PaymentErrorDetails

PaymentErrorDetails (RFC 9457)#

go
type PaymentErrorDetails struct {
    ProblemType string `json:"type"`
    Title       string `json:"title"`
    Status      int    `json:"status"`
    Detail      string `json:"detail"`
    ChallengeID string `json:"challengeId,omitempty"`
}

func CorePaymentError(suffix string) *PaymentErrorDetails
func SessionPaymentError(suffix string) *PaymentErrorDetails

Problem type URI 前缀:

  • Core: https://payment-auth.org/problems/
  • Session: https://payment-auth.org/problems/session/

MppErrorCode 一览#

错误码含义
AmountExceedsMax金额超过最大值
InvalidAmount金额格式错误
InvalidConfig配置无效
HttpHTTP 请求失败
ChainIdMismatch链 ID 不匹配
JsonJSON 序列化/反序列化失败
HexDecodeHex 解码失败
Base64DecodeBase64 解码失败
UnsupportedPaymentMethod不支持的支付方式
MissingHeader缺少必需头
InvalidBase64Url无效 Base64URL
MalformedCredential凭证格式错误
InvalidChallengeChallenge 无效
VerificationFailed验证失败
PaymentExpired支付已过期
PaymentRequired需要支付
InvalidPayloadPayload 无效
BadRequest请求错误
InsufficientBalance余额不足
InvalidSignature签名无效
SignerMismatch签名者不匹配
AmountExceedsDeposit金额超过存款
DeltaTooSmall增量过小
ChannelNotFoundChannel 不存在
ChannelClosedChannel 已关闭
IoIO 错误
InvalidUtf8无效 UTF-8
SystemTime系统时间错误
Internal内部错误

SA-API 错误码映射表#

SA code含义映射 MppErrorCode
8000API 服务内部错误Internal
70001链不在支持列表Internal
70002付款方在黑名单MalformedCredential
70003source 缺失、feePayer=true 不支持 hash 模式、或 txHash 已被使用MalformedCredential
70004签名验证失败InvalidSignature
70005余额不足InsufficientBalance
70006金额超过存款AmountExceedsDeposit
70007交易未在链上确认Internal
70008channelId 不存在ChannelNotFound
70009channel 已关闭ChannelClosed
70010delta 过小DeltaTooSmall
70011Escrow 合约 grace period 配置不达标,拒绝开通Internal
70012签名者不匹配SignerMismatch
70013voucher 递增量低于 minVoucherDeltaDeltaTooSmall
70014channel 处于 CLOSING 状态,不接受新 VoucherChannelClosed