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

 

 

反馈
咨询
目录
置顶