Go SDK 参考#
Go SDK 参考(适用于 exact、aggr_deferred)#
包#
| 包 | 描述 |
|---|---|
github.com/okx/payments/go/x402 | 核心包:客户端、服务端、facilitator、类型定义 |
.../go/x402/mechanisms/evm | EVM 机制:exact、aggr_deferred |
.../go/x402/mechanisms/svm | Solana 机制:exact |
.../go/x402/http/gin | Gin 中间件(卖方) |
.../go/x402/http/echo | Echo 中间件(卖方) |
.../go/x402/http/nethttp | net/http 中间件(卖方) |
.../go/x402/http | HTTP 客户端封装(买方) |
核心类型#
Network#
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#
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#
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#
type VerifyResponse struct {
IsValid bool `json:"isValid"`
InvalidReason string `json:"invalidReason,omitempty"`
InvalidMessage string `json:"invalidMessage,omitempty"`
Payer string `json:"payer,omitempty"`
}
SettleResponse#
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#
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#
type SettlementOverrides struct {
Amount string `json:"amount,omitempty"` // Atomic units, percentage, or dollar
}
视图接口#
用于 V1/V2 支付数据的统一视图:
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)#
构造函数#
import x402 "github.com/okx/payments/go/x402"
client := x402.Newx402Client(opts ...ClientOption)
ClientOption 函数:
| 选项 | 描述 |
|---|---|
WithPaymentSelector(selector) | 自定义支付需求选择器 |
WithPolicy(policy) | 添加支付过滤策略 |
注册方法(可链式调用)#
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
支付创建#
// 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)
生命周期钩子(可链式调用)#
func (c *x402Client) OnBeforePaymentCreation(hook BeforePaymentCreationHook) *x402Client
func (c *x402Client) OnAfterPaymentCreation(hook AfterPaymentCreationHook) *x402Client
func (c *x402Client) OnPaymentCreationFailure(hook OnPaymentCreationFailureHook) *x402Client
策略与选择器类型#
type PaymentRequirementsSelector func(requirements []PaymentRequirementsView) PaymentRequirementsView
type PaymentPolicy func(requirements []PaymentRequirementsView) []PaymentRequirementsView
func DefaultPaymentSelector(requirements []PaymentRequirementsView) PaymentRequirementsView
服务端 API(x402ResourceServer)#
构造函数#
server := x402.Newx402ResourceServer(opts ...ResourceServerOption)
ResourceServerOption 函数:
| 选项 | 描述 |
|---|---|
WithFacilitatorClient(client) | 设置 facilitator 客户端 |
WithSchemeServer(network, server) | 注册一个 scheme |
WithCacheTTL(ttl) | 设置 supported-kinds 缓存 TTL |
注册方法(可链式调用)#
func (s *x402ResourceServer) Register(network Network, server SchemeNetworkServer) *x402ResourceServer
func (s *x402ResourceServer) RegisterExtension(ext types.ResourceServerExtension) *x402ResourceServer
初始化#
func (s *x402ResourceServer) Initialize(ctx context.Context) error
支付操作#
// 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
服务端生命周期钩子(可链式调用)#
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(买方)#
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(卖方)#
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#
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(网络边界)#
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#
type ClientExtension interface {
Key() string
EnrichPaymentPayload(ctx context.Context, payload types.PaymentPayload, required types.PaymentRequired) (types.PaymentPayload, error)
}
MoneyParser#
type MoneyParser func(amount float64, network Network) (*AssetAmount, error)
中间件参考#
所有 Go 中间件包提供三个构建层级以及构建器快捷方式。
Gin(go/x402/http/gin)#
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 结构体(构建器模式):
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 类型:
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:
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 客户端(买方)#
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(客户端)#
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 接口:
type ClientEvmSigner interface {
Address() string
SignTypedData(ctx context.Context, domain TypedDataDomain, types map[string][]TypedDataField, primaryType string, message map[string]interface{}) ([]byte, error)
}
ExactEvmScheme(服务端)#
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)#
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 类型#
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(客户端)#
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(服务端)#
import svmserver "github.com/okx/payments/go/x402/mechanisms/svm/exact/server"
scheme := svmserver.NewExactSvmScheme()
scheme.RegisterMoneyParser(customParser)
Facilitator 钩子#
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 参考(适用于 charge、session)#
Packages#
| Package | 导入路径 | 描述 |
|---|---|---|
server | github.com/okx/payments/go/mpp/server | 高层 MPP 协调器:Mpp、EVMConfig、ChargeRouteConfig、SessionRouteConfig |
evm | github.com/okx/payments/go/mpp/evm | EVM 支付方法:EVMChargeMethod / EVMSessionMethod、EIP-712 签名/验签、Voucher、Authorization |
protocol | github.com/okx/payments/go/mpp/protocol | 协议层:challenge / credential / receipt 解析与格式化、ChargeVerifier / SessionVerifier 接口、错误码 |
saclient | github.com/okx/payments/go/mpp/saclient | SA-API 客户端:SAClient 接口与 OKXSAClient 实现,HMAC-SHA256 认证 |
store | github.com/okx/payments/go/mpp/store | 泛型 key-value store:Store[T] 接口、MemoryStore、FileStore、ChannelState |
http/gin | github.com/okx/payments/go/mpp/http/gin | Gin 框架中间件:ChargeMiddleware / SessionMiddleware |
http/echo | github.com/okx/payments/go/mpp/http/echo | Echo 框架中间件:ChargeMiddleware / SessionMiddleware |
http/nethttp | github.com/okx/payments/go/mpp/http/nethttp | 标准库 net/http 中间件:ChargeMiddleware / SessionMiddleware |
adapters | github.com/okx/payments/go/mpp/adapters | Payment Router 适配器:MppAdapter 实现 paymentrouter.ProtocolAdapter |
sse | github.com/okx/payments/go/mpp/sse | Server-Sent Events 辅助工具:Event 格式化/解析、Serve 流式推送 |
errors | github.com/okx/payments/go/mpp/errors | 错误类型:MppError、MppErrorCode、RFC 9457 PaymentErrorDetails |
核心类型(evm package)#
常量#
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 中的分账项(绝对金额)。
type Split struct {
Amount string `json:"amount"`
Memo *string `json:"memo,omitempty"`
Recipient string `json:"recipient"`
}
EVMMethodDetails#
Charge 请求的 methodDetails 字段(嵌入 challenge 的 request base64url JSON)。
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 分账项(基点比例)。
// 约束: 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 字段。
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 特有字段。
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 类型#
// 基础 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 接口#
// Signer 签名任意消息哈希和 EIP-712 类型化数据。
type Signer interface {
Sign(hash []byte) ([]byte, error)
SignTypedData(typedData apitypes.TypedData) ([]byte, error)
Address() common.Address
}
PrivateKeySigner#
type PrivateKeySigner struct { /* private */ }
func NewPrivateKeySigner(key *ecdsa.PrivateKey) *PrivateKeySigner
func NewPrivateKeySignerFromHex(hexKey string) (*PrivateKeySigner, error)
签名输出 65 字节 r||s||v,其中 v = 27 或 28。
NonceProvider 接口#
// 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。
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 链上 broadcasttransferWithAuthorization)"hash"--VerifyHash(client 已自行 broadcast,SA-API 验证 tx hash)"proof"-- 验证 proof source,然后调Settle
Session -- EVMSessionMethod#
实现 protocol.SessionVerifier。维护本地 channel state、支持 voucher 本地验签 + 累计扣费、商户主动 settle/close。
配置与构造#
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 接口实现#
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
业务方法#
// 获取底层 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#
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 串起来。
type Mpp struct { /* private */ }
func NewMpp(cfg EVMConfig, charge protocol.ChargeVerifier, session protocol.SessionVerifier) *Mpp
两个 verifier 可以为 nil(只需一种 intent 时)。
Charge 相关方法#
// 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 相关方法#
// 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#
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#
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#
type ChargeOptions struct {
EVMConfig EVMConfig
Description string
Realm string
}
type SessionChallengeOptions struct {
EVMConfig EVMConfig
Description string
Realm string
}
ReceiptContextKey#
const ReceiptContextKey = "mpp_payment_receipt"
所有框架适配器(gin/echo/nethttp)在验证成功后将 *protocol.Receipt 存入此 context key。
protocol package#
常量#
const IntentCharge = "charge"
const IntentSession = "session"
const WWWAuthenticateHeader = "WWW-Authenticate"
const AuthorizationHeader = "Authorization"
const PaymentReceiptHeader = "Payment-Receipt"
const PaymentScheme = "Payment"
PayloadType#
type PayloadType string
const (
PayloadTypeTransaction PayloadType = "transaction"
PayloadTypeHash PayloadType = "hash"
PayloadTypeProof PayloadType = "proof"
)
PaymentChallenge#
解析后的 WWW-Authenticate: Payment challenge。
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 中。
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。
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#
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#
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#
type SessionVerifyResult struct {
Receipt *Receipt
// ManagementResponse 非 nil 时,说明这是管理操作 (open/topUp/close),
// 调用方应把它作为响应体返回而不是继续处理请求。
// 为 nil 时 (voucher/replay),调用方应继续提供资源。
ManagementResponse any
}
ChargeRequest / SessionRequest#
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 接口#
type ChargeVerifier interface {
Method() string
PrepareRequest(request ChargeRequest, cred *PaymentCredential) ChargeRequest
Verify(ctx context.Context, cred *PaymentCredential, request *ChargeRequest) (*Receipt, error)
}
SessionVerifier 接口#
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 解析/格式化#
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
序列化辅助#
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#
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#
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"
)
便捷错误构造器:
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。
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#
type OKXSAClient struct { /* private */ }
func NewOKXSAClient(baseURL, apiKey, secretKey, passphrase string, opts ...Option) *OKXSAClient
Option 函数:
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#
type SAResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
}
HMAC 认证头常量#
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))
泛型请求包装#
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 类型#
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 类型#
// 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"`
}
响应类型#
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 错误码#
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] 接口#
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#
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#
// 原子扣费:available = HighestVoucherAmount - Spent; 不足则返 error。
// 调用方必须持有必要的锁以保证原子性。
func DeductFromChannel(ctx context.Context, s Store[ChannelState], channelID string, amount *big.Int) (*ChannelState, error)
MemoryStore#
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#
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)#
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)#
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)#
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
中间件行为#
- 无 Authorization header:生成 challenge,返回
402 Payment Required+WWW-Authenticateheader - 有 Authorization header:
- 从 Authorization 头重建 challenge header
- 调
Mpp.VerifyCredential(charge) 或Mpp.VerifySession(session) 验证 - 验证成功:设置
Payment-Receiptheader,将 receipt 存入 context - Session 管理操作(open/topUp/close):直接返回 JSON 响应,不继续执行 handler
- Voucher:继续执行后续 handler(提供资源)
- 验证失败:返回对应 HTTP 错误码(400/402/410)
adapters package -- Payment Router 集成#
MppAdapter#
实现 paymentrouter.ProtocolAdapter,让 Payment Router 支持 MPP 协议。
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 路由级配置,用于 GetChallenge 的 cfg 参数。
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#
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;
}// 签名 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;
}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 辅助工具。
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#
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)#
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 | 配置无效 |
Http | HTTP 请求失败 |
ChainIdMismatch | 链 ID 不匹配 |
Json | JSON 序列化/反序列化失败 |
HexDecode | Hex 解码失败 |
Base64Decode | Base64 解码失败 |
UnsupportedPaymentMethod | 不支持的支付方式 |
MissingHeader | 缺少必需头 |
InvalidBase64Url | 无效 Base64URL |
MalformedCredential | 凭证格式错误 |
InvalidChallenge | Challenge 无效 |
VerificationFailed | 验证失败 |
PaymentExpired | 支付已过期 |
PaymentRequired | 需要支付 |
InvalidPayload | Payload 无效 |
BadRequest | 请求错误 |
InsufficientBalance | 余额不足 |
InvalidSignature | 签名无效 |
SignerMismatch | 签名者不匹配 |
AmountExceedsDeposit | 金额超过存款 |
DeltaTooSmall | 增量过小 |
ChannelNotFound | Channel 不存在 |
ChannelClosed | Channel 已关闭 |
Io | IO 错误 |
InvalidUtf8 | 无效 UTF-8 |
SystemTime | 系统时间错误 |
Internal | 内部错误 |
SA-API 错误码映射表#
| SA code | 含义 | 映射 MppErrorCode |
|---|---|---|
| 8000 | API 服务内部错误 | Internal |
| 70001 | 链不在支持列表 | Internal |
| 70002 | 付款方在黑名单 | MalformedCredential |
| 70003 | source 缺失、feePayer=true 不支持 hash 模式、或 txHash 已被使用 | MalformedCredential |
| 70004 | 签名验证失败 | InvalidSignature |
| 70005 | 余额不足 | InsufficientBalance |
| 70006 | 金额超过存款 | AmountExceedsDeposit |
| 70007 | 交易未在链上确认 | Internal |
| 70008 | channelId 不存在 | ChannelNotFound |
| 70009 | channel 已关闭 | ChannelClosed |
| 70010 | delta 过小 | DeltaTooSmall |
| 70011 | Escrow 合约 grace period 配置不达标,拒绝开通 | Internal |
| 70012 | 签名者不匹配 | SignerMismatch |
| 70013 | voucher 递增量低于 minVoucherDelta | DeltaTooSmall |
| 70014 | channel 处于 CLOSING 状态,不接受新 Voucher | ChannelClosed |
- Go SDK 参考(适用于 exact、aggr_deferred)包核心类型视图接口客户端 API(x402Client)服务端 API(x402ResourceServer)Scheme 接口中间件参考HTTP 客户端(买方)EVM 机制SVM 机制Facilitator 钩子Go SDK 参考(适用于 charge、session)Packages核心类型(evm package)Signer 接口NonceProvider 接口Charge -- EVMChargeMethodSession -- EVMSessionMethodserver packageprotocol packagesaclient packagestore packageHTTP 中间件adapters package -- Payment Router 集成EIP-712 签名 (evm package)sse package错误类型 (errors package)SA-API 错误码映射表