alipay

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 21 Imported by: 0

README

alipay-gosdk

支付宝开放平台(中国站)OpenAPI V3 协议的 Go SDK,RSA2 签名,证书模式与密钥模式均支持,零外部依赖(全标准库)。

本项目 100% 由 AI 开发。

目前实现了商家自动打款链路的四个接口:

接口 V3 端点
alipay.system.oauth.token POST /v3/alipay/system/oauth/token
alipay.user.info.share POST /v3/alipay/user/info/share
alipay.fund.trans.uni.transfer POST /v3/alipay/fund/trans/uni/transfer
alipay.fund.trans.common.query GET /v3/alipay/fund/trans/common/query

V3 网关层(签名、验签、加解密、证书管理)是通用的,加新接口只需在对应功能包里照现有模式对 PostJSON / GetWithQuery 加一层薄封装。

用法

根包 alipay 是 V3 协议核心;每个业务功能一个子包,用什么功能就 import 那个包。

import (
    alipay "github.com/simonxlong/alipay-gosdk"
    "github.com/simonxlong/alipay-gosdk/oauth"
    "github.com/simonxlong/alipay-gosdk/transfer"
    "github.com/simonxlong/alipay-gosdk/notify"
)
初始化

两种加签方式二选一(一个 APPID 只能配一种)。

// 证书模式(资金支出类接口必须用它)
c, err := alipay.New(alipay.Config{
    AppID:         "2021...",
    PrivateKey:    string(privateKeyPEM),   // PKCS1 或 PKCS8
    AppCertPEM:    string(appCertPEM),
    AlipayCertPEM: string(alipayCertPEM),
    RootCertPEM:   string(rootCertPEM),
    // EncryptKey: "base64...",     // AES 密钥,配了才启用内容加密
    // Sandbox:    true,            // 走沙箱网关
})

// 密钥模式(除资金支出类外都可用)
c, err := alipay.New(alipay.Config{
    AppID:           "2021...",
    PrivateKey:      string(privateKeyPEM),
    AlipayPublicKey: string(alipayPublicKey), // 控制台「支付宝公钥」
})

// 仅验签客户端(回调服务器不需要持有私钥)
c, err := alipay.New(alipay.Config{
    AppID:           "2021...",
    AlipayPublicKey: string(alipayPublicKey),
    // PrivateKey 留空,调 API 会返回 ErrNoPrivateKey
})

New 之后 Client 并发安全。

OAuth
tok, err := oauth.Token(ctx, c, "authorization_code", authCode)
// tok.OpenID / tok.AccessToken / tok.RefreshToken

tok, err = oauth.Token(ctx, c, "refresh_token", tok.RefreshToken)

// 需 scope=auth_user 授权;auth_base 静默授权只拿得到 open_id,
// 查会员信息会返回 oauth.ErrNoUserInfo
info, err := oauth.UserInfo(ctx, c, tok.AccessToken)
转账
resp, err := transfer.Send(ctx, c, transfer.Req{
    OutBizNo: "WR-123", TransAmount: "1.00",
    ProductCode: "TRANS_ACCOUNT_NO_PWD", BizScene: "DIRECT_TRANSFER",
    PayeeInfo: transfer.PayeeInfo{
        IdentityType: "ALIPAY_OPEN_ID",
        Identity:     tok.OpenID,
        Name:         "张三",
    },
})

q, err := transfer.Query(ctx, c, transfer.QueryReq{
    OutBizNo: "WR-123", ProductCode: "TRANS_ACCOUNT_NO_PWD", BizScene: "DIRECT_TRANSFER",
})
异步通知

通知是 V2 form 格式,与 API 协议版本无关。

err := notify.Verify(c, params) // params: 已 url-decode 的通知参数

// 资金单据状态变更:验签 + 解析一步到位
chg, err := notify.ParseFundOrderChanged(c, params)
// chg.OutBizNo / chg.Status / chg.TransAmount

处理成功后应答纯文本 success,否则支付宝会重投。同一 notify_id 可能重复投递,幂等由调用方负责。

资金单据变更通知走「消息服务」管线,需在控制台「开发设置 - 消息服务 - From 平台」订阅,投递到应用网关地址,不支持 notify_url 动态地址。

直接调用未封装的接口
var out MyResp
err := c.PostJSON(ctx, "/v3/alipay/some/api", reqBody, &out)
err := c.GetWithQuery(ctx, "/v3/alipay/some/api", url.Values{"k": {"v"}}, &out)

错误处理

// 业务/网关失败(HTTP 非 2xx,标准错误 JSON)
if e, ok := errors.AsType[*alipay.APIError](err); ok {
    // e.HTTPStatus / e.Code / e.Message / e.TraceID
}

// 非 JSON 错误响应(网关 5xx HTML 页等),Unwrap 到 ErrBadResponse
if e, ok := errors.AsType[*alipay.HTTPError](err); ok {
    // e.StatusCode / e.Body
}

// 5xx / 429 可重试判断
if alipay.Retriable(err) {
    // 转账重试必须复用同一 out_biz_no,换新单号会变成第二笔转账
}

根包不自动重试——请求可能已被服务端执行,重试是调用方的业务决策。

哨兵错误:ErrBadResponseErrNoSignatureErrSignatureInvalidErrNoPrivateKeyErrAlipayCertMismatch

参考

V3 协议规范:

实现参考:

源码

路径 内容
client.go Config / Client / New
transport.go PostJSON / GetWithQuery + 内部收发/验签/解码
sign.go 请求签名、响应验签 VerifyRSA
cert.go 证书 SN、私钥解析
encoding.go AES 加解密、JSON 封装
errors.go APIError / HTTPError / Retriable / 哨兵错误
oauth/ 换令牌、会员信息查询
transfer/ 转账、单据查询
notify/ 异步通知验签、资金单据变更通知解析
internal/testkit/ 测试基建(不进公共 API)
cmd/sandboxcheck/ 沙箱逐接口探测器

测试

go test -race -cover ./...

License

MIT

Documentation

Overview

Package alipay 是支付宝开放平台(中国站)证书模式(RSA2)Go 客户端的 V3 协议核心: Config / Client / New、传输原语(PostJSON / GetWithQuery)、签名与验签(VerifyRSA)、 证书 SN、内容加密。所有业务功能包共享它。

按功能分包,用户想要什么功能就 import 那个包:

  • oauth alipay.system.oauth.token(auth_code / refresh_token 换 open_id + token)
  • transfer alipay.fund.trans.uni.transfer(转账)+ alipay.fund.trans.order.query(单据查询)
  • notify 异步通知(服务端回调)验签

根包导出的传输/验签原语也可用于直接调用尚未封装的 V3 接口。

设计取舍(对标官方 alipay-sdk-java V3 与文档《API 调用规则》):

  • 只做转账链路必需的接口,不铺 70+ API。V3 网关签名/验签/编码/加密是通用的,需要别的接口在 对应功能包里照现有模式对 PostJSON / GetWithQuery 加一层薄封装即可。
  • V3 协议远比 V2 简单:请求签名是 authString + method + uri + body 的固定格式拼接, 不需要参数排序、form 编码、biz_content 嵌套;响应验签直接对 body 整体做,不需要字节级 节点提取,也不涉及 GBK(V3 强制 UTF-8)。
  • 业务成功走 HTTP 2xx(body 即业务 JSON);业务/网关失败走非 2xx(body 为 code/message), 统一返回 *APIError。调用方用 errors.AsType[*alipay.APIError] 提取错误码判定。
  • 支持接口内容加密(AES/CBC/PKCS7,Config.EncryptKey 配了才启用)。
  • 响应验签固定用 Config.AlipayCertPEM 里的支付宝公钥。响应头 alipay-sn 带支付宝签名所用 证书序列号,与 AlipayCertPEM 不符时报 ErrAlipayCertMismatch(对应官方 Java SDK 的 「支付宝公钥证书已过期,请重新下载」)。只做比对不做轮换/下载——支付宝换证书前会先通知商户, 由人工更新 AlipayCertPEM。
  • 零外部依赖:全部标准库 crypto/*、net/http、encoding/*。

并发安全:New 之后 Client 字段只读,可并发调用各接口方法。

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrBadResponse 响应体无法解析为业务结构体或错误结构体。
	ErrBadResponse = errors.New("alipay: malformed response body")

	// ErrNoSignature 需要验签的响应缺少 alipay-signature 头,或异步通知缺 sign。
	ErrNoSignature = errors.New("alipay: response signature missing")

	// ErrSignatureInvalid 响应验签失败(内容被篡改,或用错了支付宝公钥)。
	// 若支付宝更换了公钥证书,请更新 Config.AlipayCertPEM。
	ErrSignatureInvalid = errors.New("alipay: response signature verification failed")

	// ErrNoPrivateKey 客户端构造时未提供 Config.PrivateKey,却发起了需要签名的请求。
	// 仅验签用途(notify 包)可以不配私钥,但那样的客户端不能调 API。
	ErrNoPrivateKey = errors.New("alipay: Config.PrivateKey is required to sign requests")

	// ErrAlipayCertMismatch 响应头 alipay-sn 与 Config.AlipayCertPEM 的证书 SN 不符:
	// 支付宝已换用新的公钥证书签名,需下载最新支付宝公钥证书替换 AlipayCertPEM。
	ErrAlipayCertMismatch = errors.New("alipay: response cert sn does not match configured AlipayCertPEM")
)

传输/协议层哨兵错误。业务/网关失败(非 2xx)用 *APIError。

Functions

func Retriable

func Retriable(err error) bool

Retriable 判断错误是否值得重试:HTTP 5xx(服务端/网关故障)与 429(限流)。

**本包不自动重试**——请求可能已被服务端执行,重试与否是调用方的业务决策。资金类写操作 (转账)重试前务必确认幂等口径:支付宝以 out_biz_no 幂等,**必须用同一个 out_biz_no 重发**, 换新单号会变成第二笔真实转账。读操作(查询)可放心重试。

注意 4xx(除 429)一律不可重试:那是请求本身的问题,重发多少次都一样。

Types

type APIError

type APIError struct {
	HTTPStatus int    `json:"-"`       // HTTP 状态码,如 400
	Code       string `json:"code"`    // 业务/网关错误码,如 "BALANCE_IS_NOT_ENOUGH"
	Message    string `json:"message"` // 错误描述
	TraceID    string `json:"-"`       // 响应头 alipay-trace-id,报支付宝技术支持排障用
}

APIError 支付宝 V3 返回的错误(HTTP 非 2xx,body 为 {"code":...,"message":...})。 V2 的 sub_code 业务错误码(如 BALANCE_IS_NOT_ENOUGH、ORDER_NOT_EXIST)在 V3 直接是 Code。 用 errors.AsType 提取判定:

if e, ok := errors.AsType[*alipay.APIError](err); ok {
	switch e.Code { case "PAYEE_NOT_EXIST": ... }
}

func (*APIError) Error

func (e *APIError) Error() string

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client 支付宝 V3 客户端。零值不可用,须经 New 构造。

func New

func New(cfg Config) (*Client, error)

New 解析私钥与加签凭据(证书模式三张证书 / 密钥模式支付宝公钥)、校验配置,构造客户端。 任一步失败即 fail-fast。

func (*Client) GetWithQuery

func (c *Client) GetWithQuery(ctx context.Context, path string, q url.Values, out any) error

GetWithQuery 以 GET 调用 V3 接口。查询参数进入 URI(也进入签名),无请求体。 URI 只构造一次,签名与发送用同一字节串,保证二者逐字节一致。

导出为传输原语:功能子包与使用者可用它直接调用尚未封装的 V3 GET 接口。

func (*Client) PostJSON

func (c *Client) PostJSON(ctx context.Context, path string, reqBody any, out any) error

PostJSON 序列化请求体(按需 AES 加密)后以 POST 调用 V3 接口。

导出为传输原语:功能子包(oauth/transfer 等)经它调用各自的接口;使用者也可用它直接 调用本 SDK 尚未封装的 V3 POST 接口,无需等待封装。

path    接口 path,如 "/v3/alipay/fund/trans/uni/transfer"
reqBody 请求体结构体或 map,序列化为 JSON
out     业务响应结构体指针(HTTP 2xx 时反序列化到它)

func (*Client) VerifyRSA

func (c *Client) VerifyRSA(content []byte, signBase64 string) error

VerifyRSA 用配置的支付宝公钥(Config.AlipayCertPEM)对 content 做 SHA256withRSA 验签。

导出供 notify 包与高级用户使用:对本 SDK 尚未封装的 V3 响应或异步回调,可自行构造待验签串后 调用本方法。content 为待验签串原文,signBase64 为 base64 编码的签名。

type Config

type Config struct {
	AppID string // 支付宝分配的应用 ID(必填)

	// PrivateKey 应用私钥,PKCS1 或 PKCS8,带不带 PEM 头都可。调用任何 API 都必填。
	//
	// 只做异步通知验签(notify 包)时可留空——验签只用支付宝公钥。这样回调服务器不必持有私钥,
	// 少一个暴露面。留空后任何发起请求的操作会返回 ErrNoPrivateKey。
	PrivateKey string

	AppCertPEM    string // 证书模式:应用公钥证书 appCertPublicKey_xxxx.crt
	AlipayCertPEM string // 证书模式:支付宝公钥证书 alipayCertPublicKey_RSA2.crt(响应验签用)
	RootCertPEM   string // 证书模式:支付宝根证书 alipayRootCert.crt,可含多张(算 root cert SN)

	// AlipayPublicKey 密钥模式:控制台「支付宝公钥」,X.509/PKIX 裸 base64 或带 PEM 头(响应验签用)。
	// 与上面三张证书互斥,只能填一边。
	AlipayPublicKey string

	EncryptKey string       // 接口内容加密的 AES 密钥(base64);空则不加密
	Sandbox    bool         // true 走沙箱网关
	Gateway    string       // 覆盖默认网关基址,一般留空
	HTTPClient *http.Client // 覆盖默认 http.Client(默认 30s 超时),一般留空
}

Config 客户端配置。证书/密钥以 PEM 或 base64 字符串传入(读文件由调用方负责,本包不碰文件系统)。

加签方式二选一(《接口加签方式》:一个 APPID 只能配置一种):

  • 证书模式:填 AppCertPEM + AlipayCertPEM + RootCertPEM。红包、转账到支付宝账户等资金支出类接口必须用它。
  • 密钥模式:填 AlipayPublicKey。除资金支出类外都可用。

type HTTPError

type HTTPError struct {
	StatusCode int
	Body       string // 响应体原文(已截断),排障用
}

HTTPError 非 2xx 响应,且响应体不是标准的 {"code","message"} 错误 JSON——典型是网关自身的 5xx HTML 错误页(沙箱 504 高发)。包装 ErrBadResponse 以兼容 errors.Is 判断,同时把状态码 单独留出来,供调用方做重试决策(见 Retriable)。

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

Unwrap 让既有的 errors.Is(err, ErrBadResponse) 判断继续成立。

Directories

Path Synopsis
cmd
sandboxcheck command
sandboxcheck 沙箱逐接口探测器:用真实沙箱网关调用 SDK 各功能,打印结果与原始错误码。
sandboxcheck 沙箱逐接口探测器:用真实沙箱网关调用 SDK 各功能,打印结果与原始错误码。
internal
testkit
Package testkit 是跨包共享的测试基建:测试证书/密钥生成、RSA 签名、V3 假网关。
Package testkit 是跨包共享的测试基建:测试证书/密钥生成、RSA 签名、V3 假网关。
Package notify 校验支付宝异步通知(服务端回调)签名,并解析资金单据状态变更通知。
Package notify 校验支付宝异步通知(服务端回调)签名,并解析资金单据状态变更通知。
Package oauth 封装用户授权相关接口:用 auth_code / refresh_token 换取 open_id 与访问令牌, 以及用访问令牌查询会员授权信息。
Package oauth 封装用户授权相关接口:用 auth_code / refresh_token 换取 open_id 与访问令牌, 以及用访问令牌查询会员授权信息。
Package transfer 封装商家转账链路两接口:
Package transfer 封装商家转账链路两接口:

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL