Go
更新时间:2025.05.29一、概述
本工具类 wxpay_utility 为使用 Go 接入微信支付的开发者提供了一系列实用的功能,包括 JSON 处理、密钥加载、加密签名、请求头构建、响应验证等。通过使用这个工具类,开发者可以更方便地完成与微信支付相关的开发工作。
二、环境要求
Go 1.16+
三、必需的证书和密钥
运行 SDK 必需以下的商户身份信息,用于构造请求的签名和验证应答的签名:
四、工具类代码
1package wxpay_utility 2 3import ( 4 "bytes" 5 "crypto" 6 "crypto/rand" 7 "crypto/rsa" 8 "crypto/sha1" 9 "crypto/sha256" 10 "crypto/x509" 11 "encoding/base64" 12 "encoding/pem" 13 "errors" 14 "fmt" 15 "io" 16 "net/http" 17 "os" 18 "strconv" 19 "time" 20) 21 22// MchConfig 商户信息配置,用于调用商户API 23type MchConfig struct { 24 mchId string 25 certificateSerialNo string 26 privateKeyFilePath string 27 wechatPayPublicKeyId string 28 wechatPayPublicKeyFilePath string 29 privateKey *rsa.PrivateKey 30 wechatPayPublicKey *rsa.PublicKey 31} 32 33// MchId 商户号 34func (c *MchConfig) MchId() string { 35 return c.mchId 36} 37 38// CertificateSerialNo 商户API证书序列号 39func (c *MchConfig) CertificateSerialNo() string { 40 return c.certificateSerialNo 41} 42 43// PrivateKey 商户API证书对应的私钥 44func (c *MchConfig) PrivateKey() *rsa.PrivateKey { 45 return c.privateKey 46} 47 48// WechatPayPublicKeyId 微信支付公钥ID 49func (c *MchConfig) WechatPayPublicKeyId() string { 50 return c.wechatPayPublicKeyId 51} 52 53// WechatPayPublicKey 微信支付公钥 54func (c *MchConfig) WechatPayPublicKey() *rsa.PublicKey { 55 return c.wechatPayPublicKey 56} 57 58// CreateMchConfig MchConfig 构造函数 59func CreateMchConfig( 60 mchId string, 61 certificateSerialNo string, 62 privateKeyFilePath string, 63 wechatPayPublicKeyId string, 64 wechatPayPublicKeyFilePath string, 65) (*MchConfig, error) { 66 mchConfig := &MchConfig{ 67 mchId: mchId, 68 certificateSerialNo: certificateSerialNo, 69 privateKeyFilePath: privateKeyFilePath, 70 wechatPayPublicKeyId: wechatPayPublicKeyId, 71 wechatPayPublicKeyFilePath: wechatPayPublicKeyFilePath, 72 } 73 privateKey, err := LoadPrivateKeyWithPath(mchConfig.privateKeyFilePath) 74 if err != nil { 75 return nil, err 76 } 77 mchConfig.privateKey = privateKey 78 wechatPayPublicKey, err := LoadPublicKeyWithPath(mchConfig.wechatPayPublicKeyFilePath) 79 if err != nil { 80 return nil, err 81 } 82 mchConfig.wechatPayPublicKey = wechatPayPublicKey 83 return mchConfig, nil 84} 85 86// LoadPrivateKey 通过私钥的文本内容加载私钥 87func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) { 88 block, _ := pem.Decode([]byte(privateKeyStr)) 89 if block == nil { 90 return nil, fmt.Errorf("decode private key err") 91 } 92 if block.Type != "PRIVATE KEY" { 93 return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY") 94 } 95 key, err := x509.ParsePKCS8PrivateKey(block.Bytes) 96 if err != nil { 97 return nil, fmt.Errorf("parse private key err:%s", err.Error()) 98 } 99 privateKey, ok := key.(*rsa.PrivateKey) 100 if !ok { 101 return nil, fmt.Errorf("not a RSA private key") 102 } 103 return privateKey, nil 104} 105 106// LoadPublicKey 通过公钥的文本内容加载公钥 107func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) { 108 block, _ := pem.Decode([]byte(publicKeyStr)) 109 if block == nil { 110 return nil, errors.New("decode public key error") 111 } 112 if block.Type != "PUBLIC KEY" { 113 return nil, fmt.Errorf("the kind of PEM should be PUBLIC KEY") 114 } 115 key, err := x509.ParsePKIXPublicKey(block.Bytes) 116 if err != nil { 117 return nil, fmt.Errorf("parse public key err:%s", err.Error()) 118 } 119 publicKey, ok := key.(*rsa.PublicKey) 120 if !ok { 121 return nil, fmt.Errorf("%s is not rsa public key", publicKeyStr) 122 } 123 return publicKey, nil 124} 125 126// LoadPrivateKeyWithPath 通过私钥的文件路径内容加载私钥 127func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) { 128 privateKeyBytes, err := os.ReadFile(path) 129 if err != nil { 130 return nil, fmt.Errorf("read private pem file err:%s", err.Error()) 131 } 132 return LoadPrivateKey(string(privateKeyBytes)) 133} 134 135// LoadPublicKeyWithPath 通过公钥的文件路径加载公钥 136func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) { 137 publicKeyBytes, err := os.ReadFile(path) 138 if err != nil { 139 return nil, fmt.Errorf("read certificate pem file err:%s", err.Error()) 140 } 141 return LoadPublicKey(string(publicKeyBytes)) 142} 143 144// EncryptOAEPWithPublicKey 使用 OAEP padding方式用公钥进行加密 145func EncryptOAEPWithPublicKey(message string, publicKey *rsa.PublicKey) (ciphertext string, err error) { 146 if publicKey == nil { 147 return "", fmt.Errorf("you should input *rsa.PublicKey") 148 } 149 ciphertextByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(message), nil) 150 if err != nil { 151 return "", fmt.Errorf("encrypt message with public key err:%s", err.Error()) 152 } 153 ciphertext = base64.StdEncoding.EncodeToString(ciphertextByte) 154 return ciphertext, nil 155} 156 157// SignSHA256WithRSA 通过私钥对字符串以 SHA256WithRSA 算法生成签名信息 158func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) { 159 if privateKey == nil { 160 return "", fmt.Errorf("private key should not be nil") 161 } 162 h := crypto.Hash.New(crypto.SHA256) 163 _, err = h.Write([]byte(source)) 164 if err != nil { 165 return "", nil 166 } 167 hashed := h.Sum(nil) 168 signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed) 169 if err != nil { 170 return "", err 171 } 172 return base64.StdEncoding.EncodeToString(signatureByte), nil 173} 174 175// VerifySHA256WithRSA 通过公钥对字符串和签名结果以 SHA256WithRSA 验证签名有效性 176func VerifySHA256WithRSA(source string, signature string, publicKey *rsa.PublicKey) error { 177 if publicKey == nil { 178 return fmt.Errorf("public key should not be nil") 179 } 180 181 sigBytes, err := base64.StdEncoding.DecodeString(signature) 182 if err != nil { 183 return fmt.Errorf("verify failed: signature is not base64 encoded") 184 } 185 hashed := sha256.Sum256([]byte(source)) 186 err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], sigBytes) 187 if err != nil { 188 return fmt.Errorf("verify signature with public key error:%s", err.Error()) 189 } 190 return nil 191} 192 193// GenerateNonce 生成一个长度为 NonceLength 的随机字符串(只包含大小写字母与数字) 194func GenerateNonce() (string, error) { 195 const ( 196 // NonceSymbols 随机字符串可用字符集 197 NonceSymbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 198 // NonceLength 随机字符串的长度 199 NonceLength = 32 200 ) 201 202 bytes := make([]byte, NonceLength) 203 _, err := rand.Read(bytes) 204 if err != nil { 205 return "", err 206 } 207 symbolsByteLength := byte(len(NonceSymbols)) 208 for i, b := range bytes { 209 bytes[i] = NonceSymbols[b%symbolsByteLength] 210 } 211 return string(bytes), nil 212} 213 214// BuildAuthorization 构建请求头中的 Authorization 信息 215func BuildAuthorization( 216 mchid string, 217 certificateSerialNo string, 218 privateKey *rsa.PrivateKey, 219 method string, 220 canonicalURL string, 221 body []byte, 222) (string, error) { 223 const ( 224 SignatureMessageFormat = "%s\n%s\n%d\n%s\n%s\n" // 数字签名原文格式 225 // HeaderAuthorizationFormat 请求头中的 Authorization 拼接格式 226 HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"" 227 ) 228 229 nonce, err := GenerateNonce() 230 if err != nil { 231 return "", err 232 } 233 timestamp := time.Now().Unix() 234 message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body) 235 signature, err := SignSHA256WithRSA(message, privateKey) 236 if err != nil { 237 return "", err 238 } 239 authorization := fmt.Sprintf( 240 HeaderAuthorizationFormat, 241 mchid, nonce, timestamp, certificateSerialNo, signature, 242 ) 243 return authorization, nil 244} 245 246// ExtractResponseBody 提取应答报文的 Body 247func ExtractResponseBody(response *http.Response) ([]byte, error) { 248 if response.Body == nil { 249 return nil, nil 250 } 251 252 body, err := io.ReadAll(response.Body) 253 if err != nil { 254 return nil, fmt.Errorf("read response body err:[%s]", err.Error()) 255 } 256 response.Body = io.NopCloser(bytes.NewBuffer(body)) 257 return body, nil 258} 259 260const ( 261 WechatPayTimestamp = "Wechatpay-Timestamp" // 微信支付回包时间戳 262 WechatPayNonce = "Wechatpay-Nonce" // 微信支付回包随机字符串 263 WechatPaySignature = "Wechatpay-Signature" // 微信支付回包签名信息 264 WechatPaySerial = "Wechatpay-Serial" // 微信支付回包平台序列号 265 RequestID = "Request-Id" // 微信支付回包请求ID 266) 267 268// ValidateResponse 验证微信支付回包的签名信息 269func ValidateResponse( 270 wechatpayPublicKeyId string, 271 wechatpayPublicKey *rsa.PublicKey, 272 headers *http.Header, 273 body []byte, 274) error { 275 requestID := headers.Get(RequestID) 276 timestampStr := headers.Get(WechatPayTimestamp) 277 serialNo := headers.Get(WechatPaySerial) 278 signature := headers.Get(WechatPaySignature) 279 nonce := headers.Get(WechatPayNonce) 280 281 // 拒绝过期请求 282 timestamp, err := strconv.ParseInt(timestampStr, 10, 64) 283 if err != nil { 284 return fmt.Errorf("invalid timestamp: %v", err) 285 } 286 if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute { 287 return errors.New("invalid timestamp") 288 } 289 290 if serialNo != wechatpayPublicKeyId { 291 return fmt.Errorf( 292 "serial-no mismatch: got %s, expected %s, request-id: %s", 293 serialNo, 294 wechatpayPublicKeyId, 295 requestID, 296 ) 297 } 298 299 message := fmt.Sprintf("%s\n%s\n%s\n", timestampStr, nonce, body) 300 if err := VerifySHA256WithRSA(message, signature, wechatpayPublicKey); err != nil { 301 return fmt.Errorf("invalid signature: %v, request-id: %s", err, requestID) 302 } 303 304 return nil 305} 306 307// ApiException 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 308type ApiException struct { 309 StatusCode int // 应答报文的 HTTP 状态码 310 Header http.Header // 应答报文的 Header 信息 311 Body []byte // 应答报文的 Body 原文 312} 313 314func (c *ApiException) Error() string { 315 return fmt.Sprintf("API Error:status code: %d, body: %s, header: %v", c.StatusCode, string(c.Body), c.Header) 316} 317 318// Time 复制 time.Time 对象,并返回复制体的指针 319func Time(t time.Time) *time.Time { 320 return &t 321} 322 323// String 复制 string 对象,并返回复制体的指针 324func String(s string) *string { 325 return &s 326} 327 328// Bool 复制 bool 对象,并返回复制体的指针 329func Bool(b bool) *bool { 330 return &b 331} 332 333// Float64 复制 float64 对象,并返回复制体的指针 334func Float64(f float64) *float64 { 335 return &f 336} 337 338// Float32 复制 float32 对象,并返回复制体的指针 339func Float32(f float32) *float32 { 340 return &f 341} 342 343// Int64 复制 int64 对象,并返回复制体的指针 344func Int64(i int64) *int64 { 345 return &i 346} 347 348// Int32 复制 int64 对象,并返回复制体的指针 349func Int32(i int32) *int32 { 350 return &i 351} 352
文档是否有帮助