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