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
|
|||
|
}
|