Research
Security News
Kill Switch Hidden in npm Packages Typosquatting Chalk and Chokidar
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
github.com/wechatpay-apiv3/wechatpay-go
微信支付 APIv3 官方Go语言客户端代码库。
core.Client
,支持请求签名和应答验签。如果 SDK 未支持你需要的接口,请用此客户端发起请求。core/notify
,支持微信支付回调通知的验签和解密。详见 回调通知验签与解密。当前版本为测试版本,微信支付会尽量保持向后兼容。但可能因为可用性或易用性,同历史版本存在不兼容。如果你使用版本 <= v0.2.2
,升级前请参考 升级指南。
如果你的项目还不是使用 Go Modules 做依赖管理,在项目根目录下执行:
go mod init
go get -u github.com/wechatpay-apiv3/wechatpay-go
来添加依赖,完成 go.mod
修改与 SDK 下载。
先初始化一个 core.Client
实例,再向微信支付发送请求。
package main
import (
"context"
"log"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/certificates"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
)
func main() {
var (
mchID string = "190000****" // 商户号
mchCertificateSerialNumber string = "3775B6A45ACD588826D15E583A95F5DD********" // 商户证书序列号
mchAPIv3Key string = "2ab9****************************" // 商户APIv3密钥
)
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem")
if err != nil {
log.Fatal("load merchant private key error")
}
ctx := context.Background()
// 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
}
client, err := core.NewClient(ctx, opts...)
if err != nil {
log.Fatalf("new wechat pay client err:%s", err)
}
// 发送请求,以下载微信支付平台证书为例
// https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml
svc := certificates.CertificatesApiService{Client: client}
resp, result, err := svc.DownloadCertificates(ctx)
log.Printf("status=%d resp=%s", result.Response.StatusCode, resp)
}
resp
是反序列化(UnmarshalJSON)后的应答。上例中是 services/certificates
包中的 *certificates.Certificate
。
result
是 *core.APIResult
实例,包含了完整的请求报文 *http.Request
和应答报文 *http.Response
。
商户 API 证书,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称 CA)签发,以防证书被伪造或篡改。如何获取请见 商户 API 证书 。
商户 API 私钥。商户申请商户 API 证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
:warning: 不要把私钥文件暴露在公共场合,如上传到 Github,写在客户端代码等。
AES-256-GCM
加密时使用的对称加密密钥。import (
"log"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
)
svc := jsapi.JsapiApiService{Client: client}
// 得到prepay_id,以及调起支付所需的参数和签名
resp, result, err := svc.PrepayWithRequestPayment(ctx,
jsapi.PrepayRequest{
Appid: core.String("wxd678efh567hg6787"),
Mchid: core.String("1900009191"),
Description: core.String("Image形象店-深圳腾大-QQ公仔"),
OutTradeNo: core.String("1217752501201407033233368018"),
Attach: core.String("自定义数据说明"),
NotifyUrl: core.String("https://www.weixin.qq.com/wxpay/pay.php"),
Amount: &jsapi.Amount{
Total: core.Int64(100),
},
Payer: &jsapi.Payer{
Openid: core.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
},
},
)
if err == nil {
log.Println(resp)
} else {
log.Println(err)
}
import (
"log"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
)
svc := jsapi.JsapiApiService{Client: client}
resp, result, err := svc.QueryOrderById(ctx,
jsapi.QueryOrderByIdRequest{
TransactionId: core.String("4200000985202103031441826014"),
Mchid: core.String("1900009191"),
},
)
if err == nil {
log.Println(resp)
} else {
log.Println(err)
}
import (
"os"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/consts"
"github.com/wechatpay-apiv3/wechatpay-go/services/fileuploader"
)
file, err := os.Open("resource/demo.jpg")
defer file.Close()
if err != nil {
return err
}
svc := fileuploader.ImageUploader{Client: client}
resp, result, err := svc.Upload(ctx, file, "demo.jpg", consts.ImageJPG)
为了方便开发者快速上手,微信支付给每个服务生成了示例代码 api_xx_example_test.go
。请按需查阅。例如:
如果 SDK 还未支持你需要的接口,使用 core.Client
的 GET
、POST
等方法发送 HTTP 请求,而不用关注签名、验签等逻辑。
以 下载微信支付平台证书 为例:
result, err := client.Get(ctx, "https://api.mch.weixin.qq.com/v3/certificates")
使用 core.Client
发送 HTTP 请求后会得到 *core.APIResult
实例。
以下情况,SDK 发送请求会返回 error
:
2xx
HTTP 状态码为了方便使用,SDK 将服务器返回的 4xx
和 5xx
错误,转换成了 APIError
。
// 错误处理示例
result, err := client.Get(ctx, "https://api.mch.weixin.qq.com/v3/certificates")
if err != nil {
if core.IsAPIError(err, "INVALID_REQUEST") {
// 处理无效请求
}
// 处理的其他错误
}
notify.Handler
handler.ParseNotifyRequest
验签,并解密报文。适用场景: 仅需要对回调通知验证签名并解密的场景。例如,基础支付的回调通知。
ctx := context.Background()
// 1. 使用 `RegisterDownloaderWithPrivateKey` 注册下载器
err := downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIV3Key)
// 2. 获取商户号对应的微信支付平台证书访问器
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
// 3. 使用证书访问器初始化 `notify.Handler`
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
WithWechatPayAutoAuthCipher
初始化 core.Client
,然后再用client进行接口调用。适用场景:需要对回调通知验证签名并解密,并且后续需要使用 Client 的场景。例如,电子发票的回调通知,验签与解密后还需要通过 Client 调用用户填写抬头接口。
ctx := context.Background()
// 1. 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
}
client, err := core.NewClient(ctx, opts...)
// 2. 获取商户号对应的微信支付平台证书访问器
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
// 3. 使用证书访问器初始化 `notify.Handler`
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
// 4. 使用client进行接口调用
// ...
Handler
。适用场景:首次通过工具下载平台证书到本地,后续使用本地管理的平台证书进行验签与解密。
// 1. 初始化商户API v3 Key及微信支付平台证书
mchAPIv3Key := "<your apiv3 key>"
wechatPayCert, err := utils.LoadCertificate("<your wechat pay certificate>")
// 2. 使用本地管理的微信支付平台证书获取微信支付平台证书访问器
certificateVisitor := core.NewCertificateMapWithList([]*x509.Certificate{wechatPayCert})
// 3. 使用apiv3 key、证书访问器初始化 `notify.Handler`
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
建议:为了正确使用平台证书下载管理器,你应阅读并理解 如何使用平台证书下载管理器。
将支付回调通知中的内容,解析为 payments.Transaction
。
transaction := new(payments.Transaction)
notifyReq, err := handler.ParseNotifyRequest(context.Background(), request, transaction)
// 如果验签未通过,或者解密失败
if err != nil {
fmt.Println(err)
return
}
// 处理通知内容
fmt.Println(notifyReq.Summary)
fmt.Println(transaction.TransactionId)
将 SDK 未支持的回调消息体,解析至 map[string]interface{}
。
content := make(map[string]interface{})
notifyReq, err := handler.ParseNotifyRequest(context.Background(), request, &content)
// 如果验签未通过,或者解密失败
if err != nil {
fmt.Println(err)
return
}
// 处理通知内容
fmt.Println(notifyReq.Summary)
fmt.Println(content)
为了保证通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等)的机密性,
详见 接口规则 - 敏感信息加解密。
敏感信息加解密器 cipher.Cipher
能根据 API 契约自动处理敏感信息:
Wechatpay-Serial
请求头使用敏感信息加解密器,只需通过 option.WithWechatPayCipher
为 core.Client
添加加解密器:
client, err := core.NewClient(
context.Background(),
// 一次性设置 签名/验签/敏感字段加解密,并注册 平台证书下载器,自动定时获取最新的平台证书
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
option.WithWechatPayCipher(
encryptors.NewWechatPayEncryptor(downloader.MgrInstance().GetCertificateVisitor(mchID)),
decryptors.NewWechatPayDecryptor(mchPrivateKey),
),
)
请求的敏感信息,使用微信支付平台证书中的公钥加密。推荐 使用平台证书下载管理器 获取微信支付平台证书,或者 下载平台证书。
使用工具包 utils 中的函数,手动对敏感信息加解密。
package utils
// EncryptOAEPWithPublicKey 使用公钥加密
func EncryptOAEPWithPublicKey(message string, publicKey *rsa.PublicKey) (ciphertext string, err error)
// EncryptOAEPWithCertificate 使用证书中的公钥加密
func EncryptOAEPWithCertificate(message string, certificate *x509.Certificate) (ciphertext string, err error)
// DecryptOAEP 使用私钥解密
func DecryptOAEP(ciphertext string, privateKey *rsa.PrivateKey) (message string, err error)
rsa_crypto_test.go 中演示了如何使用以上函数做敏感信息加解密。
Wechatpay-Serial
请求头请求的敏感信息加密后,在 HTTP 请求头中添加微信支付平台证书序列号 Wechatpay-Serial
。该序列号用于告知微信支付加密使用的证书。
使用 core.Client
的 Request
方法来传输自定义 HTTPHeader。
// Request 向微信支付发送请求
//
// 相比于 Get / Post / Put / Patch / Delete 方法,本方法支持设置更多内容
// 特别地,如果需要为当前请求设置 Header,应使用本方法
func (client *Client) Request(
ctx context.Context,
method, requestPath string,
headerParams http.Header,
queryParams url.Values,
postBody interface{},
contentType string,
) (result *APIResult, err error)
// 示例代码
// 微信支付平台证书序列号,对应加密使用的私钥
header.Add("Wechatpay-Serial", "5157F09EFDC096DE15EBE81A47057A72*******")
result, err := client.Request(
ctx,
"POST",
"https://api.mch.weixin.qq.com/v3/profitsharing/receivers/add",
header,
nil,
body,
"application/json")
当默认的本地签名和验签方式不适合你的系统时,实现 Signer
或者 Verifier
来定制签名和验签。
比如,你把商户私钥集中存储,业务系统通过远程调用获得请求签名。
// 签名器
type CustomSigner struct {
}
func (s *CustomSigner) Sign(ctx context.Context, message string) (*auth.SignatureResult, error) {
// TODO: 远程调用获取签名信息
return &auth.SignatureResult{MchID: "xxx", MchCertificateSerialNo: "xxx", Signature: "xxx"}, nil
}
// 校验器
type CustomVerifier struct {
}
func (v *CustomVerifier) Verify(ctx context.Context, serial, message, signature string) error {
// TODO: 远程调用验签
return nil
}
当你需要使用自定的签名器和校验器时,这样创建客户端
package core_test
import (
"context"
"path/to/your/custom_signer"
"path/to/your/custom_verifier"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/credentials"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/validators"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
)
func NewCustomClient(ctx context.Context, mchID string) (*core.Client, error) {
signer := &custom_signer.CustomSigner{
// ...
}
verifier := &custom_verifier.CustomVerifier{
// ...
}
opts := []core.ClientOption{
option.WithSigner(signer),
option.WithVerifier(verifier),
}
return core.NewClient(ctx, opts...)
}
如果你的商户是全新入驻,且仅可使用微信支付的公钥验证应答和回调的签名,请使用微信支付公钥和公钥 ID 初始化。
var (
wechatpayPublicKeyID string = "00000000000000000000000000000000" // 微信支付公钥ID
)
wechatpayPublicKey, err = utils.LoadPublicKeyWithPath("/path/to/wechatpay/pub_key.pem")
if err != nil {
panic(fmt.Errorf("load wechatpay public key err:%s", err.Error()))
}
// 初始化 Client
opts := []core.ClientOption{
option.WithWechatPayPublicKeyAuthCipher(
mchID,
mchCertificateSerialNumber, mchPrivateKey,
wechatpayPublicKeyID, wechatpayPublicKey),
}
client, err := core.NewClient(ctx, opts...)
// 初始化 notify.Handler
handler := notify.NewNotifyHandler(
mchAPIv3Key,
verifiers.NewSHA256WithRSAPubkeyVerifier(wechatpayPublicKeyID, *wechatPayPublicKey))
如果你既有微信支付平台证书,又有公钥。那么,你可以在商户平台自助地从微信支付平台证书切换到公私钥,或者反过来。 在切换期间,回调要同时支持使用平台证书和公钥的验签。
请参考下文,使用微信平台证书访问器和公钥一起初始化 NotifyHandler
。
// 初始化 notify.Handler
handler := notify.NewNotifyHandler(
mchAPIv3Key,
verifiers.NewSHA256WithRSACombinedVerifier(certificateVisitor, wechatpayPublicKeyID, *wechatPayPublicKey))
常见问题请见 FAQ.md。
微信支付欢迎来自社区的开发者贡献你们的想法和代码。请你在提交 PR 之前,先提一个对应的 issue 说明以下内容:
#35 是一个很好的参考。
开发者提交的代码,应能通过本 SDK 所有的测试用例。
SDK 在单元测试中使用了 agiledragon/gomonkey 和 stretchr/testify,测试前请确认相关的依赖。使用以下命令获取所有的依赖。
go get -t -v
由于 gomonkey
的原因,在执行测试用例时需要携带参数 -gcflags=all=-l
。使用以下命令发起测试。
go test -gcflags=all=-l ./...
如果你发现了 BUG,或者需要的功能还未支持,或者有任何疑问、建议,欢迎通过 issue 反馈。
也欢迎访问微信支付的 开发者社区。
为了向广大开发者提供更好的使用体验,微信支付诚挚邀请您反馈使用微信支付 APIv3 SDK中的感受。 您的反馈将对改进 SDK 大有帮助,点击参与问卷调查。
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.