Jerry 6 lat temu
rodzic
commit
8c6f9a58bb
10 zmienionych plików z 186 dodań i 139 usunięć
  1. 21 6
      README.md
  2. 2 0
      alipay_client.go
  3. 16 0
      alipay_params.go
  4. 2 2
      alipay_service_api.go
  5. 1 1
      constant.go
  6. 16 19
      examples/wechat/wx_Transfer.go
  7. 12 0
      release_note.txt
  8. 2 2
      util.go
  9. 45 95
      wechat_client.go
  10. 69 14
      wechat_params.go

+ 21 - 6
README.md

@@ -94,7 +94,8 @@
 * gopay.AliPaySystemOauthToken() => 换取授权访问令牌(得到access_token,user_id等信息)
 * gopay.FormatPrivateKey() => 格式化应用私钥
 * gopay.FormatAliPayPublicKey() => 格式化支付宝公钥
-* gopay.ParseAliPayNotifyResult() => 解析并返回支付宝支付异步通知的参数
+* gopay.ParseAliPayNotifyResult() => 解析支付宝支付异步通知的参数到Struct
+* gopay.ParseAliPayNotifyResultToBodyMap() => 解析支付宝支付异步通知的参数到BodyMap
 * gopay.VerifyAliPaySign() => 支付宝同步返回参数验签或异步通知参数验签
 * gopay.DecryptAliPayOpenDataToStruct() => 支付宝小程序敏感加密数据解析到结构体
 
@@ -145,28 +146,42 @@ QQ群:
 
 ---
 
-## 1、初始化GoPay客户端并做配置(所有https请求,已忽略https双向认证:InsecureSkipVerify: true)
+## 1、初始化GoPay客户端并做配置(HTTP请求均设置tls.Config{InsecureSkipVerify: true})
 
-* #### 微信客户端,如无需更改Appid、Mchid和ApiKey,推荐在 init()方法中初始化,全局适用
+* #### 微信客户端,如无需更改Appid、Mchid和ApiKey等参数,可在init()方法中初始化,全局适用
 
 微信官方文档:[官方文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)
 ```go
-//初始化微信客户端
+// 初始化微信客户端
 //    appId:应用ID
 //    mchId:商户ID
 //    apiKey:API秘钥值
 //    isProd:是否是正式环境
 client := gopay.NewWeChatClient("wxdaa2ab9ef87b5497", mchId, apiKey, false)
 
-//设置国家:不设置默认 中国国内
+// 设置国家:不设置默认 中国国内
 //    gopay.China:中国国内
 //    gopay.China2:中国国内备用
 //    gopay.SoutheastAsia:东南亚
 //    gopay.Other:其他国家
 client.SetCountry(gopay.China)
+
+// 添加微信证书 Byte 数组
+//    certFile:apiclient_cert.pem byte数组
+//    keyFile:apiclient_key.pem byte数组
+//    pkcs12File:apiclient_cert.p12 byte数组
+client.AddCertFileByte()
+
+// 添加微信证书 Path 路径
+//    certFilePath:apiclient_cert.pem 路径
+//    keyFilePath:apiclient_key.pem 路径
+//    pkcs12FilePath:apiclient_cert.p12 路径
+//    返回err
+client.AddCertFilePath()
+
 ```
 
-* #### 支付宝,如无需更改Appid和PrivateKey,推荐在 init()方法中初始化,全局适用
+* #### 支付宝,如无需更改Appid和PrivateKey等参数,可在init()方法中初始化,全局适用
 
 支付宝官方文档:[官方文档](https://docs.open.alipay.com/catalog)
 

+ 2 - 0
alipay_client.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/parnurzeal/gorequest"
@@ -22,6 +23,7 @@ type AliPayClient struct {
 	AppAuthToken     string
 	AuthToken        string
 	IsProd           bool
+	mu               sync.RWMutex
 }
 
 // 初始化支付宝客户端

+ 16 - 0
alipay_params.go

@@ -39,58 +39,74 @@ type OpenApiRoyaltyDetailInfoPojo struct {
 // 设置 应用公钥证书SN
 //    appCertSN:应用公钥证书SN,通过 gopay.GetCertSN() 获取
 func (a *AliPayClient) SetAppCertSN(appCertSN string) (client *AliPayClient) {
+	a.mu.Lock()
 	a.AppCertSN = appCertSN
+	a.mu.Unlock()
 	return a
 }
 
 // 设置 支付宝根证书SN
 //    alipayRootCertSN:支付宝根证书SN,通过 gopay.GetCertSN() 获取
 func (a *AliPayClient) SetAliPayRootCertSN(alipayRootCertSN string) (client *AliPayClient) {
+	a.mu.Lock()
 	a.AlipayRootCertSN = alipayRootCertSN
+	a.mu.Unlock()
 	return a
 }
 
 // 设置支付后的ReturnUrl
 func (a *AliPayClient) SetReturnUrl(url string) (client *AliPayClient) {
+	a.mu.Lock()
 	a.ReturnUrl = url
+	a.mu.Unlock()
 	return a
 }
 
 // 设置支付宝服务器主动通知商户服务器里指定的页面http/https路径。
 func (a *AliPayClient) SetNotifyUrl(url string) (client *AliPayClient) {
+	a.mu.Lock()
 	a.NotifyUrl = url
+	a.mu.Unlock()
 	return a
 }
 
 // 设置编码格式,如utf-8,gbk,gb2312等,默认推荐使用 utf-8
 func (a *AliPayClient) SetCharset(charset string) (client *AliPayClient) {
+	a.mu.Lock()
 	if charset == null {
 		a.Charset = "utf-8"
 	} else {
 		a.Charset = charset
 	}
+	a.mu.Unlock()
 	return a
 }
 
 // 设置签名算法类型,目前支持RSA2和RSA,默认推荐使用 RSA2
 func (a *AliPayClient) SetSignType(signType string) (client *AliPayClient) {
+	a.mu.Lock()
 	if signType == null {
 		a.SignType = "RSA2"
 	} else {
 		a.SignType = signType
 	}
+	a.mu.Unlock()
 	return a
 }
 
 // 设置应用授权
 func (a *AliPayClient) SetAppAuthToken(appAuthToken string) (client *AliPayClient) {
+	a.mu.Lock()
 	a.AppAuthToken = appAuthToken
+	a.mu.Unlock()
 	return a
 }
 
 // 设置用户信息授权
 func (a *AliPayClient) SetAuthToken(authToken string) (client *AliPayClient) {
+	a.mu.Lock()
 	a.AuthToken = authToken
+	a.mu.Unlock()
 	return a
 }
 

+ 2 - 2
alipay_service_api.go

@@ -34,7 +34,7 @@ var allowSignatureAlgorithm = map[string]bool{
 	"SHA512-RSAPSS": true,
 }
 
-// 解析支付宝支付异步通知的结果到BodyMap
+// 解析支付宝支付异步通知的参数到BodyMap
 //    req:*http.Request
 //    返回参数bm:Notify请求的参数
 //    返回参数err:错误信息
@@ -53,7 +53,7 @@ func ParseAliPayNotifyResultToBodyMap(req *http.Request) (bm BodyMap, err error)
 	return
 }
 
-// ParseAliPayNotifyResult 解析支付宝支付完成后的Notify信息
+// 解析支付宝支付异步通知的参数到Struct
 //    req:*http.Request
 //    返回参数notifyReq:Notify请求的参数
 //    返回参数err:错误信息

+ 1 - 1
constant.go

@@ -4,7 +4,7 @@ const (
 	null       string = ""
 	TimeLayout string = "2006-01-02 15:04:05"
 	DateLayout string = "2006-01-02"
-	Version    string = "1.4.3"
+	Version    string = "1.4.4"
 	// 微信
 	// ===========================================================================================
 

+ 16 - 19
examples/wechat/wx_Transfer.go

@@ -1,8 +1,3 @@
-//==================================
-//  * Name:Jerry
-//  * DateTime:2019/8/21 21:02
-//  * Desc:
-//==================================
 package wechat
 
 import (
@@ -12,34 +7,36 @@ import (
 )
 
 func Transfer() {
-	//初始化微信客户端
+	// 初始化微信客户端
 	//    appId:应用ID
 	//    MchID:商户ID
 	//    ApiKey:Key值
 	//    isProd:是否是正式环境(企业转账到个人账户,默认正式环境)
-	client := gopay.NewWeChatClient("wxdaa2ab9ef87b5497", "1368139502", "GFDS8j98rewnmgl45wHTt980jg543abc", true)
+	client := gopay.NewWeChatClient("wxdaa2ab9ef87b5497", "1368139502", "GFDS8j98rewnmgl45wHTt980jg543abc", false)
 
-	nonceStr := gopay.GetRandomString(32)
-	partnerTradeNo := gopay.GetRandomString(32)
+	err := client.AddCertFilePath("iguiyu_cert/apiclient_cert.pem", "iguiyu_cert/apiclient_key.pem", "iguiyu_cert/apiclient_cert.p12")
+	if err != nil {
+		fmt.Println("client.AddCertFilePath err:", err)
+		return
+	}
 
-	fmt.Println("partnerTradeNo:", partnerTradeNo)
-	//初始化参数结构体
+	// 初始化参数结构体
 	body := make(gopay.BodyMap)
-	body.Set("nonce_str", nonceStr)
-	body.Set("partner_trade_no", partnerTradeNo)
-	body.Set("openid", "oMlss5F06l97UpwtB-8jvZd6Yabc")
+	body.Set("nonce_str", gopay.GetRandomString(32))
+	body.Set("partner_trade_no", gopay.GetRandomString(32))
+	body.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")
 	body.Set("check_name", "FORCE_CHECK") // NO_CHECK:不校验真实姓名 , FORCE_CHECK:强校验真实姓名
-	body.Set("re_user_name", "付明明")       //收款用户真实姓名。 如果check_name设置为FORCE_CHECK,则必填用户真实姓名
-	body.Set("amount", 30)                //企业付款金额,单位为分
-	body.Set("desc", "测试转账")              //企业付款备注,必填。注意:备注中的敏感词会被转成字符*
+	body.Set("re_user_name", "付明明")       // 收款用户真实姓名。 如果check_name设置为FORCE_CHECK,则必填用户真实姓名
+	body.Set("amount", 30)                // 企业付款金额,单位为分
+	body.Set("desc", "测试转账")              // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
 	body.Set("spbill_create_ip", "127.0.0.1")
 
-	//请求申请退款(沙箱环境下,证书路径参数可传空)
+	// 请求申请退款(沙箱环境下,证书路径参数可传空)
 	//    body:参数Body
 	//    certFilePath:cert证书路径
 	//    keyFilePath:Key证书路径
 	//    pkcs12FilePath:p12证书路径
-	wxRsp, err := client.Transfer(body, "iguiyu_cert/apiclient_cert.pem", "iguiyu_cert/apiclient_key.pem", "iguiyu_cert/apiclient_cert.p12")
+	wxRsp, err := client.Transfer(body, "", "", "")
 	if err != nil {
 		fmt.Println("Error:", err)
 		return

+ 12 - 0
release_note.txt

@@ -1,3 +1,15 @@
+版本号:Release 1.4.4
+发布时间:2019/11/16 15:56
+修改记录:
+   (1) 支付宝:新增公共API方法:gopay.ParseAliPayNotifyResultToBodyMap(),解析支付宝支付异步通知的参数到BodyMap
+   (2) 支付宝:修改公共API方法:gopay.VerifyAliPaySign(),支付宝异步验签支持传入 BodyMap
+   (3) 微信:新增Client方法:client.AddCertFileByte(),添加微信证书 Byte 数组
+   (4) 微信:新增Client方法:client.AddCertFilePath(),添加微信证书 Path 路径
+   (5) 微信:微信Client需要证书的方法,如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",如方法需单独使用证书,则传证书Path
+   (6) BodyMap 的Set方法去掉switch判断,直接赋值
+   (7) WeChatClient、AliPayClient 加锁
+   (8) 修改部分小问题和部分样式
+
 版本号:Release 1.4.3
 发布时间:2019/11/12 01:15
 修改记录:

+ 2 - 2
util.go

@@ -38,10 +38,10 @@ func (bm BodyMap) Get(key string) string {
 	if v, ok = value.(string); ok {
 		return v
 	}
-	return jsonToString(value)
+	return convertToString(value)
 }
 
-func jsonToString(v interface{}) (str string) {
+func convertToString(v interface{}) (str string) {
 	if v == nil {
 		return null
 	}

+ 45 - 95
wechat_client.go

@@ -2,12 +2,11 @@ package gopay
 
 import (
 	"crypto/tls"
-	"crypto/x509"
 	"encoding/xml"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"strings"
+	"sync"
 
 	"github.com/parnurzeal/gorequest"
 )
@@ -21,9 +20,10 @@ type WeChatClient struct {
 	KeyFile    []byte
 	Pkcs12File []byte
 	IsProd     bool
+	mu         sync.RWMutex
 }
 
-// 初始化微信客户端 ok
+// 初始化微信客户端
 //    appId:应用ID
 //    mchId:商户ID
 //    ApiKey:API秘钥值
@@ -36,7 +36,7 @@ func NewWeChatClient(appId, mchId, apiKey string, isProd bool) (client *WeChatCl
 		IsProd: isProd}
 }
 
-// 提交付款码支付 ok
+// 提交付款码支付
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
 func (w *WeChatClient) Micropay(body BodyMap) (wxRsp *WeChatMicropayResponse, err error) {
 	var bs []byte
@@ -55,7 +55,7 @@ func (w *WeChatClient) Micropay(body BodyMap) (wxRsp *WeChatMicropayResponse, er
 	return
 }
 
-// 统一下单 ok
+// 统一下单
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
 func (w *WeChatClient) UnifiedOrder(body BodyMap) (wxRsp *WeChatUnifiedOrderResponse, err error) {
 	var bs []byte
@@ -75,7 +75,7 @@ func (w *WeChatClient) UnifiedOrder(body BodyMap) (wxRsp *WeChatUnifiedOrderResp
 	return
 }
 
-// 查询订单 ok
+// 查询订单
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
 func (w *WeChatClient) QueryOrder(body BodyMap) (wxRsp *WeChatQueryOrderResponse, err error) {
 	var bs []byte
@@ -94,7 +94,7 @@ func (w *WeChatClient) QueryOrder(body BodyMap) (wxRsp *WeChatQueryOrderResponse
 	return
 }
 
-// 关闭订单 ok
+// 关闭订单
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
 func (w *WeChatClient) CloseOrder(body BodyMap) (wxRsp *WeChatCloseOrderResponse, err error) {
 	var bs []byte
@@ -113,28 +113,18 @@ func (w *WeChatClient) CloseOrder(body BodyMap) (wxRsp *WeChatCloseOrderResponse
 	return
 }
 
-// 撤销订单 ok
+// 撤销订单
+//    注意:如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",如方法需单独使用证书,则传证书Path
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3
 func (w *WeChatClient) Reverse(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp *WeChatReverseResponse, err error) {
 	var (
-		bs, pkcs    []byte
-		pkcsPool    *x509.CertPool
-		certificate tls.Certificate
-		tlsConfig   *tls.Config
+		bs        []byte
+		tlsConfig *tls.Config
 	)
 	if w.IsProd {
-		pkcsPool = x509.NewCertPool()
-		if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
-			return nil, fmt.Errorf("ioutil.ReadFile:%s", err.Error())
+		if tlsConfig, err = w.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath); err != nil {
+			return nil, err
 		}
-		pkcsPool.AppendCertsFromPEM(pkcs)
-		if certificate, err = tls.LoadX509KeyPair(certFilePath, keyFilePath); err != nil {
-			return nil, fmt.Errorf("tls.LoadX509KeyPair:%s", err.Error())
-		}
-		tlsConfig = &tls.Config{
-			Certificates:       []tls.Certificate{certificate},
-			RootCAs:            pkcsPool,
-			InsecureSkipVerify: true}
 		bs, err = w.doWeChat(body, wxReverse, tlsConfig)
 	} else {
 		bs, err = w.doWeChat(body, wxSandboxReverse)
@@ -149,28 +139,18 @@ func (w *WeChatClient) Reverse(body BodyMap, certFilePath, keyFilePath, pkcs12Fi
 	return
 }
 
-// 申请退款 ok
+// 申请退款
+//    注意:如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",如方法需单独使用证书,则传证书Path
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
 func (w *WeChatClient) Refund(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp *WeChatRefundResponse, err error) {
 	var (
-		bs, pkcs    []byte
-		pkcsPool    *x509.CertPool
-		certificate tls.Certificate
-		tlsConfig   *tls.Config
+		bs        []byte
+		tlsConfig *tls.Config
 	)
 	if w.IsProd {
-		pkcsPool = x509.NewCertPool()
-		if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
-			return nil, fmt.Errorf("ioutil.ReadFile:%s", err.Error())
-		}
-		pkcsPool.AppendCertsFromPEM(pkcs)
-		if certificate, err = tls.LoadX509KeyPair(certFilePath, keyFilePath); err != nil {
-			return nil, fmt.Errorf("tls.LoadX509KeyPair:%s", err.Error())
+		if tlsConfig, err = w.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath); err != nil {
+			return nil, err
 		}
-		tlsConfig = &tls.Config{
-			Certificates:       []tls.Certificate{certificate},
-			RootCAs:            pkcsPool,
-			InsecureSkipVerify: true}
 		bs, err = w.doWeChat(body, wxRefund, tlsConfig)
 	} else {
 		bs, err = w.doWeChat(body, wxSandboxRefund)
@@ -185,7 +165,7 @@ func (w *WeChatClient) Refund(body BodyMap, certFilePath, keyFilePath, pkcs12Fil
 	return
 }
 
-// 查询退款 ok
+// 查询退款
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
 func (w *WeChatClient) QueryRefund(body BodyMap) (wxRsp *WeChatQueryRefundResponse, err error) {
 	var bs []byte
@@ -204,7 +184,7 @@ func (w *WeChatClient) QueryRefund(body BodyMap) (wxRsp *WeChatQueryRefundRespon
 	return
 }
 
-// 下载对账单 ok
+// 下载对账单
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6
 func (w *WeChatClient) DownloadBill(body BodyMap) (wxRsp string, err error) {
 	var bs []byte
@@ -220,29 +200,19 @@ func (w *WeChatClient) DownloadBill(body BodyMap) (wxRsp string, err error) {
 	return
 }
 
-// 下载资金账单 ok
+// 下载资金账单
+//    注意:如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",如方法需单独使用证书,则传证书Path
+//    貌似不支持沙箱环境,因为沙箱环境默认需要用MD5签名,但是此接口仅支持HMAC-SHA256签名
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_18&index=7
-//    好像不支持沙箱环境,因为沙箱环境默认需要用MD5签名,但是此接口仅支持HMAC-SHA256签名
 func (w *WeChatClient) DownloadFundFlow(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp string, err error) {
 	var (
-		bs, pkcs    []byte
-		pkcsPool    *x509.CertPool
-		certificate tls.Certificate
-		tlsConfig   *tls.Config
+		bs        []byte
+		tlsConfig *tls.Config
 	)
 	if w.IsProd {
-		pkcsPool = x509.NewCertPool()
-		if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
-			return null, fmt.Errorf("ioutil.ReadFile:%s", err.Error())
+		if tlsConfig, err = w.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath); err != nil {
+			return null, err
 		}
-		pkcsPool.AppendCertsFromPEM(pkcs)
-		if certificate, err = tls.LoadX509KeyPair(certFilePath, keyFilePath); err != nil {
-			return null, fmt.Errorf("tls.LoadX509KeyPair:%s", err.Error())
-		}
-		tlsConfig = &tls.Config{
-			Certificates:       []tls.Certificate{certificate},
-			RootCAs:            pkcsPool,
-			InsecureSkipVerify: true}
 		bs, err = w.doWeChat(body, wxDownloadfundflow, tlsConfig)
 	} else {
 		bs, err = w.doWeChat(body, wxSandboxDownloadfundflow)
@@ -255,29 +225,19 @@ func (w *WeChatClient) DownloadFundFlow(body BodyMap, certFilePath, keyFilePath,
 }
 
 // 拉取订单评价数据
+//    注意:如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",如方法需单独使用证书,则传证书Path
+//    貌似不支持沙箱环境,因为沙箱环境默认需要用MD5签名,但是此接口仅支持HMAC-SHA256签名
 //    文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_17&index=11
-//    好像不支持沙箱环境,因为沙箱环境默认需要用MD5签名,但是此接口仅支持HMAC-SHA256签名
 func (w *WeChatClient) BatchQueryComment(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp string, err error) {
 	var (
-		bs, pkcs    []byte
-		pkcsPool    *x509.CertPool
-		certificate tls.Certificate
-		tlsConfig   *tls.Config
+		bs        []byte
+		tlsConfig *tls.Config
 	)
 	if w.IsProd {
 		body.Set("sign_type", SignType_HMAC_SHA256)
-		pkcsPool = x509.NewCertPool()
-		if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
-			return null, fmt.Errorf("ioutil.ReadFile:%s", err.Error())
-		}
-		pkcsPool.AppendCertsFromPEM(pkcs)
-		if certificate, err = tls.LoadX509KeyPair(certFilePath, keyFilePath); err != nil {
-			return null, fmt.Errorf("tls.LoadX509KeyPair:%s", err.Error())
+		if tlsConfig, err = w.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath); err != nil {
+			return null, err
 		}
-		tlsConfig = &tls.Config{
-			Certificates:       []tls.Certificate{certificate},
-			RootCAs:            pkcsPool,
-			InsecureSkipVerify: true}
 		bs, err = w.doWeChat(body, wxBatchquerycomment, tlsConfig)
 	} else {
 		bs, err = w.doWeChat(body, wxSandboxBatchquerycomment)
@@ -290,32 +250,22 @@ func (w *WeChatClient) BatchQueryComment(body BodyMap, certFilePath, keyFilePath
 }
 
 // 企业向微信用户个人付款
-//    文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
+//    注意:如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",如方法需单独使用证书,则传证书Path
 //    注意:此方法未支持沙箱环境,默认正式环境,转账请慎重
+//    文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
 func (w *WeChatClient) Transfer(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp *WeChatTransfersResponse, err error) {
 	body.Set("mch_appid", w.AppId)
 	body.Set("mchid", w.MchId)
 	var (
-		bs, pkcs    []byte
-		pkcsPool    *x509.CertPool
-		certificate tls.Certificate
-		tlsConfig   *tls.Config
-		agent       *gorequest.SuperAgent
-		errs        []error
-		res         gorequest.Response
+		bs        []byte
+		tlsConfig *tls.Config
+		agent     *gorequest.SuperAgent
+		errs      []error
+		res       gorequest.Response
 	)
-	pkcsPool = x509.NewCertPool()
-	if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
-		return nil, fmt.Errorf("ioutil.ReadFile:%s", err.Error())
-	}
-	pkcsPool.AppendCertsFromPEM(pkcs)
-	if certificate, err = tls.LoadX509KeyPair(certFilePath, keyFilePath); err != nil {
-		return nil, fmt.Errorf("tls.LoadX509KeyPair:%s", err.Error())
-	}
-	tlsConfig = &tls.Config{
-		Certificates:       []tls.Certificate{certificate},
-		RootCAs:            pkcsPool,
-		InsecureSkipVerify: true}
+	if tlsConfig, err = w.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath); err != nil {
+		return nil, err
+	}
 	body.Set("sign", getWeChatReleaseSign(w.ApiKey, SignType_MD5, body))
 	agent = HttpAgent().TLSClientConfig(tlsConfig)
 	if w.BaseURL != null {
@@ -344,7 +294,7 @@ func (w *WeChatClient) EntrustPublic(body BodyMap) (bs []byte, err error) {
 	return nil, nil
 }
 
-// 向微信发送请求 ok
+// 向微信发送请求
 func (w *WeChatClient) doWeChat(body BodyMap, path string, tlsConfig ...*tls.Config) (bytes []byte, err error) {
 	body.Set("appid", w.AppId)
 	body.Set("mch_id", w.MchId)

+ 69 - 14
wechat_params.go

@@ -4,9 +4,12 @@ import (
 	"crypto/hmac"
 	"crypto/md5"
 	"crypto/sha256"
+	"crypto/tls"
+	"crypto/x509"
 	"encoding/hex"
 	"encoding/xml"
 	"errors"
+	"fmt"
 	"hash"
 	"io/ioutil"
 	"strings"
@@ -18,6 +21,7 @@ type Country int
 //    根据支付地区情况设置国家
 //    country:<China:中国国内,China2:中国国内(冗灾方案),SoutheastAsia:东南亚,Other:其他国家>
 func (w *WeChatClient) SetCountry(country Country) (client *WeChatClient) {
+	w.mu.Lock()
 	switch country {
 	case China:
 		w.BaseURL = wxBaseUrlCh
@@ -30,27 +34,78 @@ func (w *WeChatClient) SetCountry(country Country) (client *WeChatClient) {
 	default:
 		w.BaseURL = wxBaseUrlCh
 	}
+	w.mu.Unlock()
 	return w
 }
 
-// 添加微信证书Bytes
-func (w *WeChatClient) AddCertFileBytes(certFile, keyFile, pkcs12File []byte) {
+// 添加微信证书 Byte 数组
+//    certFile:apiclient_cert.pem byte数组
+//    keyFile:apiclient_key.pem byte数组
+//    pkcs12File:apiclient_cert.p12 byte数组
+func (w *WeChatClient) AddCertFileByte(certFile, keyFile, pkcs12File []byte) {
+	w.mu.Lock()
 	w.CertFile = certFile
 	w.KeyFile = keyFile
 	w.Pkcs12File = pkcs12File
+	w.mu.Unlock()
 }
 
-// 添加微信证书Path路径
-func (w *WeChatClient) AddCertFilePath(certFilePath, keyFilePath, pkcs12FilePath string) {
-	if cert, err := ioutil.ReadFile(certFilePath); err == nil {
-		w.CertFile = cert
+// 添加微信证书 Path 路径
+//    certFilePath:apiclient_cert.pem 路径
+//    keyFilePath:apiclient_key.pem 路径
+//    pkcs12FilePath:apiclient_cert.p12 路径
+//    返回err
+func (w *WeChatClient) AddCertFilePath(certFilePath, keyFilePath, pkcs12FilePath string) (err error) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	var (
+		cert, key, pkcs []byte
+	)
+	if cert, err = ioutil.ReadFile(certFilePath); err != nil {
+		return
 	}
-	if key, err := ioutil.ReadFile(keyFilePath); err == nil {
-		w.KeyFile = key
+	if key, err = ioutil.ReadFile(keyFilePath); err != nil {
+		return
 	}
-	if pkcs, err := ioutil.ReadFile(pkcs12FilePath); err == nil {
-		w.Pkcs12File = pkcs
+	if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
+		return
 	}
+	w.CertFile = cert
+	w.KeyFile = key
+	w.Pkcs12File = pkcs
+	return
+}
+
+func (w *WeChatClient) addCertConfig(certFilePath, keyFilePath, pkcs12FilePath string) (tlsConfig *tls.Config, err error) {
+	var (
+		pkcs        []byte
+		certificate tls.Certificate
+		pkcsPool    = x509.NewCertPool()
+	)
+	if certFilePath != null && keyFilePath != null && pkcs12FilePath != null {
+		if pkcs, err = ioutil.ReadFile(pkcs12FilePath); err != nil {
+			return nil, fmt.Errorf("ioutil.ReadFile:%s", err.Error())
+		}
+		pkcsPool.AppendCertsFromPEM(pkcs)
+		if certificate, err = tls.LoadX509KeyPair(certFilePath, keyFilePath); err != nil {
+			return nil, fmt.Errorf("tls.LoadX509KeyPair:%s", err.Error())
+		}
+		tlsConfig = &tls.Config{
+			Certificates:       []tls.Certificate{certificate},
+			RootCAs:            pkcsPool,
+			InsecureSkipVerify: true}
+		return
+	}
+
+	pkcsPool.AppendCertsFromPEM(w.Pkcs12File)
+	if certificate, err = tls.X509KeyPair(w.CertFile, w.KeyFile); err != nil {
+		return nil, fmt.Errorf("tls.X509KeyPair:%s", err.Error())
+	}
+	tlsConfig = &tls.Config{
+		Certificates:       []tls.Certificate{certificate},
+		RootCAs:            pkcsPool,
+		InsecureSkipVerify: true}
+	return
 }
 
 // 获取微信支付正式环境Sign值
@@ -83,11 +138,11 @@ func getWeChatSignBoxSign(mchId, apiKey string, bm BodyMap) (sign string, err er
 
 // 从微信提供的接口获取:SandboxSignKey
 func getSanBoxKey(mchId, nonceStr, apiKey, signType string) (key string, err error) {
-	body := make(BodyMap)
-	body.Set("mch_id", mchId)
-	body.Set("nonce_str", nonceStr)
+	bm := make(BodyMap)
+	bm.Set("mch_id", mchId)
+	bm.Set("nonce_str", nonceStr)
 	//沙箱环境:获取沙箱环境ApiKey
-	if key, err = getSanBoxSignKey(mchId, nonceStr, getWeChatReleaseSign(apiKey, signType, body)); err != nil {
+	if key, err = getSanBoxSignKey(mchId, nonceStr, getWeChatReleaseSign(apiKey, signType, bm)); err != nil {
 		return
 	}
 	return