326 lines
11 KiB
Go
326 lines
11 KiB
Go
package wechat
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rsa"
|
||
"fmt"
|
||
"net/http"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/go-pay/gopay"
|
||
"github.com/go-pay/gopay/pkg/util"
|
||
"github.com/go-pay/gopay/pkg/xhttp"
|
||
"github.com/go-pay/gopay/pkg/xlog"
|
||
"github.com/go-pay/gopay/pkg/xpem"
|
||
)
|
||
|
||
// ClientV3 微信支付 V3
|
||
type ClientV3 struct {
|
||
Mchid string
|
||
ApiV3Key []byte
|
||
SerialNo string
|
||
WxSerialNo string
|
||
autoSign bool
|
||
bodySize int // http response body size(MB), default is 10MB
|
||
rwMu sync.RWMutex
|
||
privateKey *rsa.PrivateKey
|
||
wxPublicKey *rsa.PublicKey
|
||
ctx context.Context
|
||
DebugSwitch gopay.DebugSwitch
|
||
SnCertMap map[string]*rsa.PublicKey // key: serial_no
|
||
}
|
||
|
||
// NewClientV3 初始化微信客户端 V3
|
||
// mchid:商户ID 或者服务商模式的 sp_mchid
|
||
// serialNo:商户API证书的证书序列号
|
||
// apiV3Key:APIv3Key,商户平台获取
|
||
// privateKey:商户API证书下载后,私钥 apiclient_key.pem 读取后的字符串内容
|
||
func NewClientV3(mchid, serialNo, apiV3Key, privateKey string) (client *ClientV3, err error) {
|
||
if mchid == util.NULL || serialNo == util.NULL || apiV3Key == util.NULL || privateKey == util.NULL {
|
||
return nil, gopay.MissWechatInitParamErr
|
||
}
|
||
priKey, err := xpem.DecodePrivateKey([]byte(privateKey))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
client = &ClientV3{
|
||
Mchid: mchid,
|
||
SerialNo: serialNo,
|
||
ApiV3Key: []byte(apiV3Key),
|
||
privateKey: priKey,
|
||
ctx: context.Background(),
|
||
DebugSwitch: gopay.DebugOff,
|
||
}
|
||
return client, nil
|
||
}
|
||
|
||
// AutoVerifySign 开启请求完自动验签功能(默认不开启,推荐开启)
|
||
// 开启自动验签,自动开启每12小时一次轮询,请求最新证书操作
|
||
func (c *ClientV3) AutoVerifySign(autoRefresh ...bool) (err error) {
|
||
wxSerialNo, certMap, err := c.GetAndSelectNewestCert()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if len(c.SnCertMap) <= 0 {
|
||
c.SnCertMap = make(map[string]*rsa.PublicKey)
|
||
}
|
||
for sn, cert := range certMap {
|
||
// decode cert
|
||
pubKey, err := xpem.DecodePublicKey([]byte(cert))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
c.SnCertMap[sn] = pubKey
|
||
}
|
||
c.WxSerialNo = wxSerialNo
|
||
c.wxPublicKey = c.SnCertMap[wxSerialNo]
|
||
if len(autoRefresh) == 1 && !autoRefresh[0] {
|
||
return
|
||
}
|
||
c.autoSign = true
|
||
go c.autoCheckCertProc()
|
||
return
|
||
}
|
||
|
||
// SetBodySize 设置http response body size(MB)
|
||
func (c *ClientV3) SetBodySize(sizeMB int) {
|
||
if sizeMB > 0 {
|
||
c.bodySize = sizeMB
|
||
}
|
||
}
|
||
|
||
func (c *ClientV3) doProdPostWithHeader(ctx context.Context, headerMap map[string]string, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + path
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
for k, v := range headerMap {
|
||
httpClient.Header.Add(k, v)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|
||
|
||
func (c *ClientV3) doProdPost(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + path
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|
||
|
||
func (c *ClientV3) doProdGet(ctx context.Context, uri, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + uri
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_Url: %s", url)
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|
||
|
||
func (c *ClientV3) doProdPut(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + path
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Put(url).SendBodyMap(bm).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|
||
|
||
func (c *ClientV3) doProdDelete(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + path
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Delete(url).SendBodyMap(bm).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|
||
|
||
func (c *ClientV3) doProdPostFile(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + path
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.GetString("meta"))
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeMultipartFormData).Post(url).SendMultipartBodyMap(bm).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|
||
|
||
func (c *ClientV3) doProdPatch(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||
var url = v3BaseUrlCh + path
|
||
httpClient := xhttp.NewClient()
|
||
if c.bodySize > 0 {
|
||
httpClient.SetBodySize(c.bodySize)
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||
}
|
||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||
httpClient.Header.Add("Accept", "*/*")
|
||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Patch(url).SendBodyMap(bm).EndBytes(ctx)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
si = &SignInfo{
|
||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||
SignBody: string(bs),
|
||
}
|
||
if c.DebugSwitch == gopay.DebugOn {
|
||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||
}
|
||
return res, si, bs, nil
|
||
}
|