From 4b9b76c53b5313c1e4f0bc1432524a29543ef004 Mon Sep 17 00:00:00 2001 From: Jerry <85411418@qq.com> Date: Thu, 5 Oct 2023 02:47:37 -0500 Subject: [PATCH] Feature/xhttp update (#356) * update xhttp --- alipay/client.go | 356 +++------------------------ alipay/client_test.go | 3 + alipay/common_api.go | 10 +- alipay/customs.go | 5 +- alipay/request.go | 301 ++++++++++++++++++++++ alipay/sign.go | 19 +- allinpay/client.go | 23 +- allinpay/sign.go | 12 +- apple/client.go | 20 +- apple/verify.go | 2 +- apple/verify_model.go | 2 +- doc/alipay.md | 3 + lakala/client.go | 66 +++-- paypal/access_token.go | 16 +- paypal/client.go | 144 +---------- paypal/request.go | 129 ++++++++++ pkg/xhttp/client.go | 356 +++------------------------ pkg/xhttp/client_test.go | 12 +- pkg/xhttp/request.go | 268 ++++++++++++++++++++ pkg/xhttp/transport_default_js.go | 17 ++ pkg/xhttp/transport_default_other.go | 17 ++ qq/client.go | 144 +++++++---- qq/oplatform_api.go | 6 +- qq/param.go | 40 +-- qq/red.go | 13 +- wechat/base_api.go | 27 +- wechat/client.go | 160 +++++++++--- wechat/customs.go | 6 +- wechat/merchant.go | 72 ++---- wechat/oplatform_api.go | 8 +- wechat/papay.go | 10 +- wechat/param.go | 36 ++- wechat/payment_api.go | 2 +- wechat/red.go | 28 +-- wechat/v3/cert.go | 12 +- wechat/v3/client.go | 247 +------------------ wechat/v3/request.go | 232 +++++++++++++++++ wechat/v3/sign.go | 20 +- 38 files changed, 1479 insertions(+), 1365 deletions(-) create mode 100644 alipay/request.go create mode 100644 paypal/request.go create mode 100644 pkg/xhttp/request.go create mode 100644 pkg/xhttp/transport_default_js.go create mode 100644 pkg/xhttp/transport_default_other.go create mode 100644 wechat/v3/request.go diff --git a/alipay/client.go b/alipay/client.go index 6d1c5058..17d0d8da 100644 --- a/alipay/client.go +++ b/alipay/client.go @@ -1,13 +1,18 @@ package alipay import ( - "context" "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "encoding/base64" "encoding/json" "fmt" + "hash" + "sync" "time" "github.com/go-pay/gopay" + "github.com/go-pay/gopay/pkg/aes" "github.com/go-pay/gopay/pkg/util" "github.com/go-pay/gopay/pkg/xhttp" "github.com/go-pay/gopay/pkg/xlog" @@ -26,12 +31,17 @@ type Client struct { SignType string AppAuthToken string IsProd bool - bodySize int // http response body size(MB), default is 10MB + aesKey string // biz_content 加密的 AES KEY + ivKey []byte privateKey *rsa.PrivateKey aliPayPublicKey *rsa.PublicKey // 支付宝证书公钥内容 alipayPublicCert.crt autoSign bool DebugSwitch gopay.DebugSwitch location *time.Location + hc *xhttp.Client + sha1Hash hash.Hash + sha256Hash hash.Hash + mu sync.Mutex } // 初始化支付宝客户端 @@ -55,6 +65,9 @@ func NewClient(appid, privateKey string, isProd bool) (client *Client, err error IsProd: isProd, privateKey: priKey, DebugSwitch: gopay.DebugOff, + hc: xhttp.NewClient(), + sha1Hash: sha1.New(), + sha256Hash: sha256.New(), } return client, nil } @@ -76,22 +89,14 @@ func (a *Client) AutoVerifySign(alipayPublicKeyContent []byte) { // SetBodySize 设置http response body size(MB) func (a *Client) SetBodySize(sizeMB int) { if sizeMB > 0 { - a.bodySize = sizeMB + a.hc.SetBodySize(sizeMB) } } -// Deprecated -// 推荐使用 PostAliPayAPISelfV2() -// 示例:请参考 client_test.go 的 TestClient_PostAliPayAPISelf() 方法 -func (a *Client) PostAliPayAPISelf(ctx context.Context, bm gopay.BodyMap, method string, aliRsp any) (err error) { - var bs []byte - if bs, err = a.doAliPay(ctx, bm, method); err != nil { - return err - } - if err = json.Unmarshal(bs, aliRsp); err != nil { - return err - } - return nil +// SetAESKey 设置 biz_content 的AES加密key,设置此参数默认开启 biz_content 参数加密 +func (a *Client) SetAESKey(aesKey string) { + a.aesKey = aesKey + a.ivKey = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} } // Deprecated @@ -123,7 +128,7 @@ func (a *Client) RequestParam(bm gopay.BodyMap, method string) (string, error) { // check sign if bm.GetString("sign") == "" { - sign, err = a.getRsaSign(bm, bm.GetString("sign_type"), a.privateKey) + sign, err = a.getRsaSign(bm, bm.GetString("sign_type")) if err != nil { return "", fmt.Errorf("GetRsaSign Error: %w", err) } @@ -136,229 +141,6 @@ func (a *Client) RequestParam(bm gopay.BodyMap, method string) (string, error) { return bm.EncodeURLParams(), nil } -// PostAliPayAPISelfV2 支付宝接口自行实现方法 -// 注意:biz_content 需要自行通过bm.SetBodyMap()设置,不设置则没有此参数 -// 示例:请参考 client_test.go 的 TestClient_PostAliPayAPISelfV2() 方法 -func (a *Client) PostAliPayAPISelfV2(ctx context.Context, bm gopay.BodyMap, method string, aliRsp any) (err error) { - var ( - bs, bodyBs []byte - ) - // check if there is biz_content - bz := bm.GetInterface("biz_content") - if bzBody, ok := bz.(gopay.BodyMap); ok { - if bodyBs, err = json.Marshal(bzBody); err != nil { - return fmt.Errorf("json.Marshal(%v):%w", bzBody, err) - } - bm.Set("biz_content", string(bodyBs)) - } - - if bs, err = a.doAliPaySelf(ctx, bm, method); err != nil { - return err - } - if err = json.Unmarshal(bs, aliRsp); err != nil { - return err - } - return nil -} - -// 向支付宝发送自定义请求 -func (a *Client) doAliPaySelf(ctx context.Context, bm gopay.BodyMap, method string) (bs []byte, err error) { - var ( - url, sign string - ) - bm.Set("method", method) - // check public parameter - a.checkPublicParam(bm) - // check sign - if bm.GetString("sign") == "" { - sign, err = a.getRsaSign(bm, bm.GetString("sign_type"), a.privateKey) - if err != nil { - return nil, fmt.Errorf("GetRsaSign Error: %w", err) - } - bm.Set("sign", sign) - } - if a.DebugSwitch == gopay.DebugOn { - xlog.Debugf("Alipay_Request: %s", bm.JsonBody()) - } - - httpClient := xhttp.NewClient() - if a.bodySize > 0 { - httpClient.SetBodySize(a.bodySize) - } - if a.IsProd { - url = baseUrlUtf8 - } else { - url = sandboxBaseUrlUtf8 - } - res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendString(bm.EncodeURLParams()).EndBytes(ctx) - if err != nil { - return nil, err - } - if a.DebugSwitch == gopay.DebugOn { - xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) - } - if res.StatusCode != 200 { - return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) - } - return bs, nil -} - -// 向支付宝发送请求 -func (a *Client) doAliPay(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (bs []byte, err error) { - var ( - bizContent, url string - bodyBs []byte - ) - if bm != nil { - _, has := appAuthTokenInBizContent[method] - if has { - if bodyBs, err = json.Marshal(bm); err != nil { - return nil, fmt.Errorf("json.Marshal:%w", err) - } - bizContent = string(bodyBs) - bm.Remove("app_auth_token") - } else { - aat := bm.GetString("app_auth_token") - bm.Remove("app_auth_token") - if bodyBs, err = json.Marshal(bm); err != nil { - return nil, fmt.Errorf("json.Marshal:%w", err) - } - bizContent = string(bodyBs) - bm.Set("app_auth_token", aat) - } - } - // 处理公共参数 - param, err := a.pubParamsHandle(bm, method, bizContent, authToken...) - if err != nil { - return nil, err - } - switch method { - case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze": - return []byte(param), nil - case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify": - if !a.IsProd { - return []byte(sandboxBaseUrl + "?" + param), nil - } - return []byte(baseUrl + "?" + param), nil - default: - httpClient := xhttp.NewClient() - if a.bodySize > 0 { - httpClient.SetBodySize(a.bodySize) - } - url = baseUrlUtf8 - if !a.IsProd { - url = sandboxBaseUrlUtf8 - } - res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendString(param).EndBytes(ctx) - if err != nil { - return nil, err - } - if a.DebugSwitch == gopay.DebugOn { - xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) - } - if res.StatusCode != 200 { - return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) - } - return bs, nil - } -} - -// 向支付宝发送请求 -func (a *Client) DoAliPay(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (bs []byte, err error) { - var ( - bizContent, url string - bodyBs []byte - ) - if bm != nil { - _, has := appAuthTokenInBizContent[method] - if has { - if bodyBs, err = json.Marshal(bm); err != nil { - return nil, fmt.Errorf("json.Marshal:%w", err) - } - bizContent = string(bodyBs) - bm.Remove("app_auth_token") - } else { - aat := bm.GetString("app_auth_token") - bm.Remove("app_auth_token") - if bodyBs, err = json.Marshal(bm); err != nil { - return nil, fmt.Errorf("json.Marshal:%w", err) - } - bizContent = string(bodyBs) - bm.Set("app_auth_token", aat) - } - } - // 处理公共参数 - param, err := a.pubParamsHandle(bm, method, bizContent, authToken...) - if err != nil { - return nil, err - } - switch method { - case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze": - return []byte(param), nil - case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify": - if !a.IsProd { - return []byte(sandboxBaseUrl + "?" + param), nil - } - return []byte(baseUrl + "?" + param), nil - default: - httpClient := xhttp.NewClient() - if a.bodySize > 0 { - httpClient.SetBodySize(a.bodySize) - } - url = baseUrlUtf8 - if !a.IsProd { - url = sandboxBaseUrlUtf8 - } - res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendString(param).EndBytes(ctx) - if err != nil { - return nil, err - } - if a.DebugSwitch == gopay.DebugOn { - xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) - } - if res.StatusCode != 200 { - return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) - } - return bs, nil - } -} - -// 保持和官方 SDK 命名方式一致 -func (a *Client) PageExecute(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (url string, err error) { - var ( - bizContent string - bodyBs []byte - ) - if bm != nil { - _, has := appAuthTokenInBizContent[method] - if has { - if bodyBs, err = json.Marshal(bm); err != nil { - return "", fmt.Errorf("json.Marshal:%w", err) - } - bizContent = string(bodyBs) - bm.Remove("app_auth_token") - } else { - aat := bm.GetString("app_auth_token") - bm.Remove("app_auth_token") - if bodyBs, err = json.Marshal(bm); err != nil { - return "", fmt.Errorf("json.Marshal:%w", err) - } - bizContent = string(bodyBs) - bm.Set("app_auth_token", aat) - } - } - // 处理公共参数 - param, err := a.pubParamsHandle(bm, method, bizContent, authToken...) - if err != nil { - return "", err - } - - if !a.IsProd { - return sandboxBaseUrl + "?" + param, nil - } - return baseUrl + "?" + param, nil -} - // 公共参数处理 func (a *Client) pubParamsHandle(bm gopay.BodyMap, method, bizContent string, authToken ...string) (param string, err error) { pubBody := make(gopay.BodyMap) @@ -409,10 +191,23 @@ func (a *Client) pubParamsHandle(bm gopay.BodyMap, method, bizContent string, au pubBody.Set("auth_token", authToken[0]) } if bizContent != util.NULL { - pubBody.Set("biz_content", bizContent) + if a.aesKey == util.NULL { + pubBody.Set("biz_content", bizContent) + } else { + // AES Encrypt biz_content + encryptBizContent, err := a.encryptBizContent(bizContent) + if err != nil { + return "", fmt.Errorf("EncryptBizContent Error: %w", err) + } + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Origin_BizContent: %s", bizContent) + xlog.Debugf("Alipay_Encrypt_BizContent: %s", encryptBizContent) + } + pubBody.Set("biz_content", encryptBizContent) + } } // sign - sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type"), a.privateKey) + sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type")) if err != nil { return "", fmt.Errorf("GetRsaSign Error: %w", err) } @@ -455,81 +250,10 @@ func (a *Client) checkPublicParam(bm gopay.BodyMap) { } } -// 文件上传 -func (a *Client) FileRequest(ctx context.Context, bm gopay.BodyMap, file *util.File, method string) (bs []byte, err error) { - var ( - bodyStr string - bodyBs []byte - aat string - ) - if bm != nil { - aat = bm.GetString("app_auth_token") - bm.Remove("app_auth_token") - if bodyBs, err = json.Marshal(bm); err != nil { - return nil, fmt.Errorf("json.Marshal:%w", err) - } - bodyStr = string(bodyBs) - } - pubBody := make(gopay.BodyMap) - pubBody.Set("app_id", a.AppId). - Set("method", method). - Set("format", "JSON"). - Set("charset", a.Charset). - Set("sign_type", a.SignType). - Set("version", "1.0"). - Set("scene", "SYNC_ORDER"). - Set("timestamp", time.Now().Format(util.TimeLayout)) - - if a.AppCertSN != util.NULL { - pubBody.Set("app_cert_sn", a.AppCertSN) - } - if a.AliPayRootCertSN != util.NULL { - pubBody.Set("alipay_root_cert_sn", a.AliPayRootCertSN) - } - if a.ReturnUrl != util.NULL { - pubBody.Set("return_url", a.ReturnUrl) - } - if a.location != nil { - pubBody.Set("timestamp", time.Now().In(a.location).Format(util.TimeLayout)) - } - if a.NotifyUrl != util.NULL { //如果返回url为空,传过来的返回url不为空 - //fmt.Println("url不为空?", a.NotifyUrl) - pubBody.Set("notify_url", a.NotifyUrl) - } - //fmt.Println("notify,", pubBody.JsonBody()) - if a.AppAuthToken != util.NULL { - pubBody.Set("app_auth_token", a.AppAuthToken) - } - if aat != util.NULL { - pubBody.Set("app_auth_token", aat) - } - if bodyStr != util.NULL { - pubBody.Set("biz_content", bodyStr) - } - sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type"), a.privateKey) - if err != nil { - return nil, fmt.Errorf("GetRsaSign Error: %w", err) - } - //pubBody.Set("file_content", file.Content) - pubBody.Set("sign", sign) - if a.DebugSwitch == gopay.DebugOn { - xlog.Debugf("Alipay_Request: %s", pubBody.JsonBody()) - } - param := pubBody.EncodeURLParams() - url := baseUrlUtf8 + "&" + param - bm.Reset() - bm.SetFormFile("file_content", file) - httpClient := xhttp.NewClient() - res, bs, err := httpClient.Type(xhttp.TypeMultipartFormData).Post(url). - SendMultipartBodyMap(bm).EndBytes(ctx) +func (a *Client) encryptBizContent(originData string) (string, error) { + encryptData, err := aes.CBCEncrypt([]byte(originData), []byte(a.aesKey), a.ivKey) if err != nil { - return nil, err - } - if a.DebugSwitch == gopay.DebugOn { - xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) - } - if res.StatusCode != 200 { - return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + return "", err } - return bs, nil + return base64.StdEncoding.EncodeToString(encryptData), nil } diff --git a/alipay/client_test.go b/alipay/client_test.go index 887db588..a6885a4f 100644 --- a/alipay/client_test.go +++ b/alipay/client_test.go @@ -42,6 +42,9 @@ func TestMain(m *testing.M) { SetReturnUrl("https://www.fmm.ink"). SetNotifyUrl("https://www.fmm.ink") + // 设置biz_content加密KEY,设置此参数默认开启加密(目前未测试成功) + //client.SetAESKey("KvKUTqSVZX2fUgmxnFyMaQ==") + // 自动同步验签(只支持证书模式) // 传入 支付宝公钥证书 alipayPublicCert.crt 内容 client.AutoVerifySign(cert.AlipayPublicContentRSA2) diff --git a/alipay/common_api.go b/alipay/common_api.go index 70564543..2dc3cd99 100644 --- a/alipay/common_api.go +++ b/alipay/common_api.go @@ -34,8 +34,7 @@ func FormatURLParam(body gopay.BodyMap) (urlParam string) { // encryptedData:包括敏感数据在内的完整用户信息的加密数据 // secretKey:AES密钥,支付宝管理平台配置 // beanPtr:需要解析到的结构体指针 -// 文档:https://opendocs.alipay.com/mini/introduce/aes -// 文档:https://opendocs.alipay.com/open/common/104567 +// 文档:https://opendocs.alipay.com/common/02mse3 func DecryptOpenDataToStruct(encryptedData, secretKey string, beanPtr any) (err error) { if encryptedData == util.NULL || secretKey == util.NULL { return errors.New("encryptedData or secretKey is null") @@ -76,8 +75,7 @@ func DecryptOpenDataToStruct(encryptedData, secretKey string, beanPtr any) (err // DecryptOpenDataToBodyMap 解密支付宝开放数据到 BodyMap // encryptedData:包括敏感数据在内的完整用户信息的加密数据 // secretKey:AES密钥,支付宝管理平台配置 -// 文档:https://opendocs.alipay.com/mini/introduce/aes -// 文档:https://opendocs.alipay.com/open/common/104567 +// 文档:https://opendocs.alipay.com/common/02mse3 func DecryptOpenDataToBodyMap(encryptedData, secretKey string) (bm gopay.BodyMap, err error) { if encryptedData == util.NULL || secretKey == util.NULL { return nil, errors.New("encryptedData or secretKey is null") @@ -180,7 +178,7 @@ func systemOauthToken(ctx context.Context, appId string, privateKey *rsa.Private if !isProd { baseUrl = sandboxBaseUrlUtf8 } - _, bs, err = xhttp.NewClient().Type(xhttp.TypeForm).Post(baseUrl).SendString(bm.EncodeURLParams()).EndBytes(ctx) + _, bs, err = xhttp.NewClient().Req(xhttp.TypeForm).Post(baseUrl).SendString(bm.EncodeURLParams()).EndBytes(ctx) if err != nil { return nil, err } @@ -220,7 +218,7 @@ func MonitorHeartbeatSyn(ctx context.Context, appId string, privateKey, signType } bm.Set("sign", sign) - _, bs, err = xhttp.NewClient().Type(xhttp.TypeForm).Post(baseUrlUtf8).SendString(bm.EncodeURLParams()).EndBytes(ctx) + _, bs, err = xhttp.NewClient().Req(xhttp.TypeForm).Post(baseUrlUtf8).SendString(bm.EncodeURLParams()).EndBytes(ctx) if err != nil { return nil, err } diff --git a/alipay/customs.go b/alipay/customs.go index 679221b8..7cfcdf65 100644 --- a/alipay/customs.go +++ b/alipay/customs.go @@ -69,7 +69,7 @@ func (a *Client) doAliPayCustoms(ctx context.Context, bm gopay.BodyMap, service bm.Remove("sign_type") bm.Remove("sign") - sign, err := a.getRsaSign(bm, RSA, a.privateKey) + sign, err := a.getRsaSign(bm, RSA) if err != nil { return nil, fmt.Errorf("GetRsaSign Error: %v", err) } @@ -79,8 +79,7 @@ func (a *Client) doAliPayCustoms(ctx context.Context, bm gopay.BodyMap, service xlog.Debugf("Alipay_Request: %s", bm.JsonBody()) } // request - httpClient := xhttp.NewClient() - res, bs, err := httpClient.Type(xhttp.TypeForm).Post("https://mapi.alipay.com/gateway.do").SendString(bm.EncodeURLParams()).EndBytes(ctx) + res, bs, err := a.hc.Req(xhttp.TypeForm).Post("https://mapi.alipay.com/gateway.do").SendString(bm.EncodeURLParams()).EndBytes(ctx) if err != nil { return nil, err } diff --git a/alipay/request.go b/alipay/request.go new file mode 100644 index 00000000..a19b40f5 --- /dev/null +++ b/alipay/request.go @@ -0,0 +1,301 @@ +package alipay + +import ( + "context" + "encoding/json" + "fmt" + "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" +) + +// PostAliPayAPISelfV2 支付宝接口自行实现方法 +// 注意:biz_content 需要自行通过bm.SetBodyMap()设置,不设置则没有此参数 +// 示例:请参考 client_test.go 的 TestClient_PostAliPayAPISelfV2() 方法 +func (a *Client) PostAliPayAPISelfV2(ctx context.Context, bm gopay.BodyMap, method string, aliRsp any) (err error) { + var ( + bs, bodyBs []byte + ) + // check if there is biz_content + bz := bm.GetInterface("biz_content") + if bzBody, ok := bz.(gopay.BodyMap); ok { + if bodyBs, err = json.Marshal(bzBody); err != nil { + return fmt.Errorf("json.Marshal(%v):%w", bzBody, err) + } + bm.Set("biz_content", string(bodyBs)) + } + + if bs, err = a.doAliPaySelf(ctx, bm, method); err != nil { + return err + } + if err = json.Unmarshal(bs, aliRsp); err != nil { + return err + } + return nil +} + +// 向支付宝发送自定义请求 +func (a *Client) doAliPaySelf(ctx context.Context, bm gopay.BodyMap, method string) (bs []byte, err error) { + var ( + url, sign string + ) + bm.Set("method", method) + // check public parameter + a.checkPublicParam(bm) + // check sign + if bm.GetString("sign") == "" { + sign, err = a.getRsaSign(bm, bm.GetString("sign_type")) + if err != nil { + return nil, fmt.Errorf("GetRsaSign Error: %w", err) + } + bm.Set("sign", sign) + } + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Request: %s", bm.JsonBody()) + } + if a.IsProd { + url = baseUrlUtf8 + } else { + url = sandboxBaseUrlUtf8 + } + res, bs, err := a.hc.Req(xhttp.TypeForm).Post(url).SendString(bm.EncodeURLParams()).EndBytes(ctx) + if err != nil { + return nil, err + } + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + return bs, nil +} + +// 向支付宝发送请求 +func (a *Client) doAliPay(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (bs []byte, err error) { + var ( + bizContent, url string + bodyBs []byte + ) + if bm != nil { + _, has := appAuthTokenInBizContent[method] + if has { + if bodyBs, err = json.Marshal(bm); err != nil { + return nil, fmt.Errorf("json.Marshal:%w", err) + } + bizContent = string(bodyBs) + bm.Remove("app_auth_token") + } else { + aat := bm.GetString("app_auth_token") + bm.Remove("app_auth_token") + if bodyBs, err = json.Marshal(bm); err != nil { + return nil, fmt.Errorf("json.Marshal:%w", err) + } + bizContent = string(bodyBs) + bm.Set("app_auth_token", aat) + } + } + // 处理公共参数 + param, err := a.pubParamsHandle(bm, method, bizContent, authToken...) + if err != nil { + return nil, err + } + switch method { + case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze": + return []byte(param), nil + case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify": + if !a.IsProd { + return []byte(sandboxBaseUrl + "?" + param), nil + } + return []byte(baseUrl + "?" + param), nil + default: + url = baseUrlUtf8 + if !a.IsProd { + url = sandboxBaseUrlUtf8 + } + res, bs, err := a.hc.Req(xhttp.TypeForm).Post(url).SendString(param).EndBytes(ctx) + if err != nil { + return nil, err + } + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + return bs, nil + } +} + +// 向支付宝发送请求 +func (a *Client) DoAliPay(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (bs []byte, err error) { + var ( + bizContent, url string + bodyBs []byte + ) + if bm != nil { + _, has := appAuthTokenInBizContent[method] + if has { + if bodyBs, err = json.Marshal(bm); err != nil { + return nil, fmt.Errorf("json.Marshal:%w", err) + } + bizContent = string(bodyBs) + bm.Remove("app_auth_token") + } else { + aat := bm.GetString("app_auth_token") + bm.Remove("app_auth_token") + if bodyBs, err = json.Marshal(bm); err != nil { + return nil, fmt.Errorf("json.Marshal:%w", err) + } + bizContent = string(bodyBs) + bm.Set("app_auth_token", aat) + } + } + // 处理公共参数 + param, err := a.pubParamsHandle(bm, method, bizContent, authToken...) + if err != nil { + return nil, err + } + switch method { + case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze": + return []byte(param), nil + case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify": + if !a.IsProd { + return []byte(sandboxBaseUrl + "?" + param), nil + } + return []byte(baseUrl + "?" + param), nil + default: + url = baseUrlUtf8 + if !a.IsProd { + url = sandboxBaseUrlUtf8 + } + res, bs, err := a.hc.Req(xhttp.TypeForm).Post(url).SendString(param).EndBytes(ctx) + if err != nil { + return nil, err + } + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + return bs, nil + } +} + +// 保持和官方 SDK 命名方式一致 +func (a *Client) PageExecute(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (url string, err error) { + var ( + bizContent string + bodyBs []byte + ) + if bm != nil { + _, has := appAuthTokenInBizContent[method] + if has { + if bodyBs, err = json.Marshal(bm); err != nil { + return "", fmt.Errorf("json.Marshal:%w", err) + } + bizContent = string(bodyBs) + bm.Remove("app_auth_token") + } else { + aat := bm.GetString("app_auth_token") + bm.Remove("app_auth_token") + if bodyBs, err = json.Marshal(bm); err != nil { + return "", fmt.Errorf("json.Marshal:%w", err) + } + bizContent = string(bodyBs) + bm.Set("app_auth_token", aat) + } + } + // 处理公共参数 + param, err := a.pubParamsHandle(bm, method, bizContent, authToken...) + if err != nil { + return "", err + } + + if !a.IsProd { + return sandboxBaseUrl + "?" + param, nil + } + return baseUrl + "?" + param, nil +} + +// 文件上传 +func (a *Client) FileRequest(ctx context.Context, bm gopay.BodyMap, file *util.File, method string) (bs []byte, err error) { + var ( + bodyStr string + bodyBs []byte + aat string + ) + if bm != nil { + aat = bm.GetString("app_auth_token") + bm.Remove("app_auth_token") + if bodyBs, err = json.Marshal(bm); err != nil { + return nil, fmt.Errorf("json.Marshal:%w", err) + } + bodyStr = string(bodyBs) + } + pubBody := make(gopay.BodyMap) + pubBody.Set("app_id", a.AppId). + Set("method", method). + Set("format", "JSON"). + Set("charset", a.Charset). + Set("sign_type", a.SignType). + Set("version", "1.0"). + Set("scene", "SYNC_ORDER"). + Set("timestamp", time.Now().Format(util.TimeLayout)) + + if a.AppCertSN != util.NULL { + pubBody.Set("app_cert_sn", a.AppCertSN) + } + if a.AliPayRootCertSN != util.NULL { + pubBody.Set("alipay_root_cert_sn", a.AliPayRootCertSN) + } + if a.ReturnUrl != util.NULL { + pubBody.Set("return_url", a.ReturnUrl) + } + if a.location != nil { + pubBody.Set("timestamp", time.Now().In(a.location).Format(util.TimeLayout)) + } + if a.NotifyUrl != util.NULL { //如果返回url为空,传过来的返回url不为空 + //fmt.Println("url不为空?", a.NotifyUrl) + pubBody.Set("notify_url", a.NotifyUrl) + } + //fmt.Println("notify,", pubBody.JsonBody()) + if a.AppAuthToken != util.NULL { + pubBody.Set("app_auth_token", a.AppAuthToken) + } + if aat != util.NULL { + pubBody.Set("app_auth_token", aat) + } + if bodyStr != util.NULL { + pubBody.Set("biz_content", bodyStr) + } + sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type")) + if err != nil { + return nil, fmt.Errorf("GetRsaSign Error: %w", err) + } + //pubBody.Set("file_content", file.Content) + pubBody.Set("sign", sign) + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Request: %s", pubBody.JsonBody()) + } + param := pubBody.EncodeURLParams() + url := baseUrlUtf8 + "&" + param + bm.Reset() + bm.SetFormFile("file_content", file) + res, bs, err := a.hc.Req(xhttp.TypeMultipartFormData).Post(url). + SendMultipartBodyMap(bm).EndBytes(ctx) + if err != nil { + return nil, err + } + if a.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + return bs, nil +} diff --git a/alipay/sign.go b/alipay/sign.go index 1bc5e8bf..dba8d1b2 100644 --- a/alipay/sign.go +++ b/alipay/sign.go @@ -173,7 +173,7 @@ func GetRsaSign(bm gopay.BodyMap, signType string, privateKey *rsa.PrivateKey) ( return } -func (a *Client) getRsaSign(bm gopay.BodyMap, signType string, privateKey *rsa.PrivateKey) (sign string, err error) { +func (a *Client) getRsaSign(bm gopay.BodyMap, signType string) (sign string, err error) { var ( h hash.Hash hashs crypto.Hash @@ -182,23 +182,26 @@ func (a *Client) getRsaSign(bm gopay.BodyMap, signType string, privateKey *rsa.P switch signType { case RSA: - h = sha1.New() + h = a.sha1Hash hashs = crypto.SHA1 case RSA2: - h = sha256.New() + h = a.sha256Hash hashs = crypto.SHA256 default: - h = sha256.New() + h = a.sha256Hash hashs = crypto.SHA256 } signParams := bm.EncodeAliPaySignParams() if a.DebugSwitch == gopay.DebugOn { xlog.Debugf("Alipay_Request_SignStr: %s", signParams) } - if _, err = h.Write([]byte(signParams)); err != nil { - return - } - if encryptedBytes, err = rsa.SignPKCS1v15(rand.Reader, privateKey, hashs, h.Sum(nil)); err != nil { + a.mu.Lock() + defer func() { + h.Reset() + a.mu.Unlock() + }() + h.Write([]byte(signParams)) + if encryptedBytes, err = rsa.SignPKCS1v15(rand.Reader, a.privateKey, hashs, h.Sum(nil)); err != nil { return util.NULL, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err) } sign = base64.StdEncoding.EncodeToString(encryptedBytes) diff --git a/allinpay/client.go b/allinpay/client.go index 695bc32d..82a9e36e 100644 --- a/allinpay/client.go +++ b/allinpay/client.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "hash" + "sync" "github.com/go-pay/gopay" "github.com/go-pay/gopay/pkg/util" @@ -26,6 +27,9 @@ type Client struct { isProd bool // 是否正式环境 privateKey *rsa.PrivateKey // 商户的RSA私钥 publicKey *rsa.PublicKey // 通联的公钥 + hc *xhttp.Client + mu sync.Mutex + sha1Hash hash.Hash } // NewClient 初始化通联客户端 @@ -50,6 +54,8 @@ func NewClient(cusId, appId, privateKey, publicKey string, isProd bool) (*Client isProd: isProd, privateKey: prk, publicKey: puk, + hc: xhttp.NewClient(), + sha1Hash: sha1.New(), }, nil } @@ -62,25 +68,25 @@ func (c *Client) SetOrgId(id string) *Client { // getRsaSign 获取签名字符串 func (c *Client) getRsaSign(bm gopay.BodyMap, signType string, privateKey *rsa.PrivateKey) (sign string, err error) { var ( - h hash.Hash hashs crypto.Hash encryptedBytes []byte ) switch signType { case RSA: - h = sha1.New() hashs = crypto.SHA1 case SM2: return "", errors.New("暂不支持SM2加密") default: - h = sha1.New() hashs = crypto.SHA1 } signParams := bm.EncodeAliPaySignParams() - if _, err = h.Write([]byte(signParams)); err != nil { - return - } - if encryptedBytes, err = rsa.SignPKCS1v15(rand.Reader, privateKey, hashs, h.Sum(nil)); err != nil { + c.mu.Lock() + defer func() { + c.sha1Hash.Reset() + c.mu.Unlock() + }() + c.sha1Hash.Write([]byte(signParams)) + if encryptedBytes, err = rsa.SignPKCS1v15(rand.Reader, privateKey, hashs, c.sha1Hash.Sum(nil)); err != nil { return util.NULL, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err) } sign = base64.StdEncoding.EncodeToString(encryptedBytes) @@ -117,12 +123,11 @@ func (c *Client) doPost(ctx context.Context, path string, bm gopay.BodyMap) (bs if err != nil { return nil, err } - httpClient := xhttp.NewClient() url := baseUrl if !c.isProd { url = sandboxBaseUrl } - res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url + path).SendString(param).EndBytes(ctx) + res, bs, err := c.hc.Req(xhttp.TypeForm).Post(url + path).SendString(param).EndBytes(ctx) if err != nil { return nil, err } diff --git a/allinpay/sign.go b/allinpay/sign.go index 9117eb6b..f715f2cc 100644 --- a/allinpay/sign.go +++ b/allinpay/sign.go @@ -13,7 +13,7 @@ import ( // verifySign 验证响应签名 func (c *Client) verifySign(bs []byte) (err error) { bm := gopay.BodyMap{} - if err := json.Unmarshal(bs, &bm); err != nil { + if err = json.Unmarshal(bs, &bm); err != nil { return err } sign := bm.Get("sign") @@ -21,9 +21,13 @@ func (c *Client) verifySign(bs []byte) (err error) { signData := bm.EncodeAliPaySignParams() signBytes, _ := base64.StdEncoding.DecodeString(sign) hashs := crypto.SHA1 - h := hashs.New() - h.Write([]byte(signData)) - if err = rsa.VerifyPKCS1v15(c.publicKey, hashs, h.Sum(nil), signBytes); err != nil { + c.mu.Lock() + defer func() { + c.sha1Hash.Reset() + c.mu.Unlock() + }() + c.sha1Hash.Write([]byte(signData)) + if err = rsa.VerifyPKCS1v15(c.publicKey, hashs, c.sha1Hash.Sum(nil), signBytes); err != nil { return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err) } return nil diff --git a/apple/client.go b/apple/client.go index 4740a845..15a894f4 100644 --- a/apple/client.go +++ b/apple/client.go @@ -17,6 +17,7 @@ type Client struct { kid string // Your private key ID from App Store Connect (Ex: 2X9R4HXF34) isProd bool // 是否是正式环境 privateKey *ecdsa.PrivateKey + hc *xhttp.Client } // NewClient 初始化Apple客户端 @@ -43,6 +44,7 @@ func NewClient(iss, bid, kid, privateKey string, isProd bool) (client *Client, e kid: kid, privateKey: ecPrivateKey, isProd: isProd, + hc: xhttp.NewClient(), } return client, nil } @@ -56,9 +58,9 @@ func (c *Client) doRequestGet(ctx context.Context, path string) (res *http.Respo if err != nil { return nil, nil, err } - cli := xhttp.NewClient() - cli.Header.Set("Authorization", "Bearer "+token) - res, bs, err = cli.Type(xhttp.TypeJSON).Get(uri).EndBytes(ctx) + req := c.hc.Req() + req.Header.Set("Authorization", "Bearer "+token) + res, bs, err = req.Get(uri).EndBytes(ctx) if err != nil { return nil, nil, err } @@ -74,9 +76,9 @@ func (c *Client) doRequestPost(ctx context.Context, path string, bm gopay.BodyMa if err != nil { return nil, nil, err } - cli := xhttp.NewClient() - cli.Header.Set("Authorization", "Bearer "+token) - res, bs, err = cli.Type(xhttp.TypeJSON).Post(uri).SendBodyMap(bm).EndBytes(ctx) + req := c.hc.Req() + req.Header.Set("Authorization", "Bearer "+token) + res, bs, err = req.Post(uri).SendBodyMap(bm).EndBytes(ctx) if err != nil { return nil, nil, err } @@ -92,9 +94,9 @@ func (c *Client) doRequestPut(ctx context.Context, path string, bm gopay.BodyMap if err != nil { return nil, nil, err } - cli := xhttp.NewClient() - cli.Header.Set("Authorization", "Bearer "+token) - res, bs, err = cli.Type(xhttp.TypeJSON).Put(uri).SendBodyMap(bm).EndBytes(ctx) + req := c.hc.Req() + req.Header.Set("Authorization", "Bearer "+token) + res, bs, err = req.Put(uri).SendBodyMap(bm).EndBytes(ctx) if err != nil { return nil, nil, err } diff --git a/apple/verify.go b/apple/verify.go index 1787b8ca..c7e45311 100644 --- a/apple/verify.go +++ b/apple/verify.go @@ -20,7 +20,7 @@ const ( func VerifyReceipt(ctx context.Context, url, pwd, receipt string) (rsp *VerifyResponse, err error) { req := &VerifyRequest{Receipt: receipt, Password: pwd} rsp = new(VerifyResponse) - _, err = xhttp.NewClient().Type(xhttp.TypeJSON).Post(url).SendStruct(req).EndStruct(ctx, rsp) + _, err = xhttp.NewClient().Req(xhttp.TypeJSON).Post(url).SendStruct(req).EndStruct(ctx, rsp) if err != nil { return nil, err } diff --git a/apple/verify_model.go b/apple/verify_model.go index 9b3eb49a..7673ebda 100644 --- a/apple/verify_model.go +++ b/apple/verify_model.go @@ -7,7 +7,7 @@ type VerifyRequest struct { Receipt string `json:"receipt-data"` // Password App的秘钥 - Password string `json:"password"` + Password string `json:"password,omitempty"` // ExcludeOldTranscations Set this value to true for the response to include only the latest renewal transaction for any subscriptions. Use this field only for app receipts that contain auto-renewable subscriptions. ExcludeOldTranscations bool `json:"exclude-old-transactions"` diff --git a/doc/alipay.md b/doc/alipay.md index 3aa85e8c..88aae8fd 100644 --- a/doc/alipay.md +++ b/doc/alipay.md @@ -49,6 +49,9 @@ SetReturnUrl("https://www.fmm.ink"). // 设置返回URL SetNotifyUrl("https://www.fmm.ink"). // 设置异步通知URL SetAppAuthToken() // 设置第三方应用授权 +// 设置biz_content加密KEY,设置此参数默认开启加密 +client.SetAESKey("1234567890123456") + // 自动同步验签(只支持证书模式) // 传入 alipayPublicCert.crt 内容 client.AutoVerifySign([]byte("alipayPublicCert.crt bytes")) diff --git a/lakala/client.go b/lakala/client.go index 37ae524e..cb8c3681 100644 --- a/lakala/client.go +++ b/lakala/client.go @@ -5,13 +5,16 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "hash" "net/http" "strings" + "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" ) // Client lakala @@ -19,9 +22,11 @@ type Client struct { ctx context.Context // 上下文 PartnerCode string // partner_code:商户编码,由4~6位大写字母或数字构成 credentialCode string // credential_code:系统为商户分配的开发校验码,请妥善保管,不要在公开场合泄露 - bodySize int // http response body size(MB), default is 10MB IsProd bool // 是否生产环境 DebugSwitch gopay.DebugSwitch // 调试开关,是否打印日志 + hc *xhttp.Client + sha256Hash hash.Hash + mu sync.Mutex } // NewClient 初始化lakala户端 @@ -38,6 +43,8 @@ func NewClient(partnerCode, credentialCode string, isProd bool) (client *Client, credentialCode: credentialCode, IsProd: isProd, DebugSwitch: gopay.DebugOff, + hc: xhttp.NewClient(), + sha256Hash: sha256.New(), } return client, nil } @@ -45,7 +52,7 @@ func NewClient(partnerCode, credentialCode string, isProd bool) (client *Client, // SetBodySize 设置http response body size(MB) func (c *Client) SetBodySize(sizeMB int) { if sizeMB > 0 { - c.bodySize = sizeMB + c.hc.SetBodySize(sizeMB) } } @@ -87,27 +94,32 @@ func (c *Client) getRsaSign(bm gopay.BodyMap) (sign string, err error) { return "", fmt.Errorf("签名缺少必要的参数") } validStr := fmt.Sprintf("%v&%v&%v&%v", partnerCode, ts, nonceStr, credentialCode) - h := sha256.New() - h.Write([]byte(validStr)) - sign = strings.ToLower(hex.EncodeToString(h.Sum(nil))) + c.mu.Lock() + defer func() { + c.sha256Hash.Reset() + c.mu.Unlock() + }() + c.sha256Hash.Write([]byte(validStr)) + sign = strings.ToLower(hex.EncodeToString(c.sha256Hash.Sum(nil))) return } // PUT 发起请求 func (c *Client) doPut(ctx context.Context, path string, bm gopay.BodyMap) (bs []byte, err error) { - httpClient := xhttp.NewClient().Type(xhttp.TypeJSON) - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - httpClient.Header.Add("Content-Type", "application/json") - httpClient.Header.Add("Accept", "application/json") var url = baseUrlProd + path param, err := c.pubParamsHandle() if err != nil { return nil, err } + req := c.hc.Req() + req.Header.Add("Accept", "application/json") uri := url + "?" + param - res, bs, err := httpClient.Put(uri).SendBodyMap(bm).EndBytes(ctx) + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Lakala_Url: %s", uri) + xlog.Debugf("Lakala_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Lakala_Req_Headers: %#v", req.Header) + } + res, bs, err := req.Put(uri).SendBodyMap(bm).EndBytes(ctx) if err != nil { return nil, err } @@ -119,19 +131,20 @@ func (c *Client) doPut(ctx context.Context, path string, bm gopay.BodyMap) (bs [ // PUT 发起请求 func (c *Client) doPost(ctx context.Context, path string, bm gopay.BodyMap) (bs []byte, err error) { - httpClient := xhttp.NewClient().Type(xhttp.TypeJSON) - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - httpClient.Header.Add("Content-Type", "application/json") - httpClient.Header.Add("Accept", "application/json") var url = baseUrlProd + path param, err := c.pubParamsHandle() if err != nil { return nil, err } + req := c.hc.Req() + req.Header.Add("Accept", "application/json") uri := url + "?" + param - res, bs, err := httpClient.Post(uri).SendBodyMap(bm).EndBytes(ctx) + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Lakala_Url: %s", uri) + xlog.Debugf("Lakala_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Lakala_Req_Headers: %#v", req.Header) + } + res, bs, err := req.Post(uri).SendBodyMap(bm).EndBytes(ctx) if err != nil { return nil, err } @@ -143,13 +156,6 @@ func (c *Client) doPost(ctx context.Context, path string, bm gopay.BodyMap) (bs // GET 发起请求 func (c *Client) doGet(ctx context.Context, path, queryParams string) (bs []byte, err error) { - httpClient := xhttp.NewClient().Type(xhttp.TypeJSON) - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - httpClient.Header.Add("Content-Type", "application/json") - httpClient.Header.Add("Accept", "application/json") - var url = baseUrlProd + path param, err := c.pubParamsHandle() if err != nil { @@ -158,8 +164,14 @@ func (c *Client) doGet(ctx context.Context, path, queryParams string) (bs []byte if queryParams != "" { param = param + "&" + queryParams } + req := c.hc.Req() + req.Header.Add("Accept", "application/json") uri := url + "?" + param - res, bs, err := httpClient.Get(uri).EndBytes(ctx) + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Lakala_Url: %s", uri) + xlog.Debugf("Lakala_Req_Headers: %#v", req.Header) + } + res, bs, err := req.Get(uri).EndBytes(ctx) if err != nil { return nil, err } diff --git a/paypal/access_token.go b/paypal/access_token.go index 16de4942..0243d0f1 100644 --- a/paypal/access_token.go +++ b/paypal/access_token.go @@ -24,24 +24,24 @@ func (c *Client) GetAccessToken() (token *AccessToken, err error) { url = baseUrl + getAccessToken // Authorization authHeader := AuthorizationPrefixBasic + base64.StdEncoding.EncodeToString([]byte(c.Clientid+":"+c.Secret)) - // Request - httpClient := xhttp.NewClient() - httpClient.Header.Add(HeaderAuthorization, authHeader) - httpClient.Header.Add("Accept", "*/*") + req := c.hc.Req(xhttp.TypeForm) + req.Header.Add(HeaderAuthorization, authHeader) + req.Header.Add("Accept", "*/*") // Body bm := make(gopay.BodyMap) bm.Set("grant_type", "client_credentials") if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_RequestBody: %s", bm.JsonBody()) - xlog.Debugf("PayPal_Authorization: %s", authHeader) + xlog.Debugf("PayPal_Url: %s", url) + xlog.Debugf("PayPal_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("PayPal_Req_Headers: %#v", req.Header) } - res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendBodyMap(bm).EndBytes(c.ctx) + res, bs, err := req.Post(url).SendBodyMap(bm).EndBytes(c.ctx) if err != nil { return nil, err } if c.DebugSwitch == gopay.DebugOn { xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) - xlog.Debugf("PayPal_Headers: %#v", res.Header) + xlog.Debugf("PayPal_Rsp_Headers: %#v", res.Header) } if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) diff --git a/paypal/client.go b/paypal/client.go index f8ed73c7..7ebe884d 100644 --- a/paypal/client.go +++ b/paypal/client.go @@ -2,13 +2,10 @@ package paypal import ( "context" - "encoding/json" - "net/http" "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" ) // Client PayPal支付客户端 @@ -18,10 +15,10 @@ type Client struct { Appid string AccessToken string ExpiresIn int - bodySize int // http response body size(MB), default is 10MB IsProd bool ctx context.Context DebugSwitch gopay.DebugSwitch + hc *xhttp.Client } // NewClient 初始化PayPal支付客户端 @@ -35,6 +32,7 @@ func NewClient(clientid, secret string, isProd bool) (client *Client, err error) IsProd: isProd, ctx: context.Background(), DebugSwitch: gopay.DebugOff, + hc: xhttp.NewClient(), } _, err = client.GetAccessToken() if err != nil { @@ -46,142 +44,6 @@ func NewClient(clientid, secret string, isProd bool) (client *Client, err error) // SetBodySize 设置http response body size(MB) func (c *Client) SetBodySize(sizeMB int) { if sizeMB > 0 { - c.bodySize = sizeMB + c.hc.SetBodySize(sizeMB) } } - -func (c *Client) doPayPalGet(ctx context.Context, uri string) (res *http.Response, bs []byte, err error) { - var url = baseUrlProd + uri - if !c.IsProd { - url = baseUrlSandbox + uri - } - httpClient := xhttp.NewClient() - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - authHeader := AuthorizationPrefixBearer + c.AccessToken - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Url: %s", url) - xlog.Debugf("PayPal_Authorization: %s", authHeader) - } - httpClient.Header.Add(HeaderAuthorization, authHeader) - httpClient.Header.Add("Accept", "*/*") - res, bs, err = httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx) - if err != nil { - return nil, nil, err - } - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) - xlog.Debugf("PayPal_Headers: %#v", res.Header) - } - return res, bs, nil -} - -func (c *Client) doPayPalPost(ctx context.Context, bm gopay.BodyMap, path string) (res *http.Response, bs []byte, err error) { - var url = baseUrlProd + path - if !c.IsProd { - url = baseUrlSandbox + path - } - httpClient := xhttp.NewClient() - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - authHeader := AuthorizationPrefixBearer + c.AccessToken - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_RequestBody: %s", bm.JsonBody()) - xlog.Debugf("PayPal_Authorization: %s", authHeader) - } - httpClient.Header.Add(HeaderAuthorization, authHeader) - httpClient.Header.Add("Accept", "*/*") - res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx) - if err != nil { - return nil, nil, err - } - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) - xlog.Debugf("PayPal_Headers: %#v", res.Header) - } - return res, bs, nil -} - -func (c *Client) doPayPalPut(ctx context.Context, bm gopay.BodyMap, path string) (res *http.Response, bs []byte, err error) { - var url = baseUrlProd + path - if !c.IsProd { - url = baseUrlSandbox + path - } - httpClient := xhttp.NewClient() - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - authHeader := AuthorizationPrefixBearer + c.AccessToken - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_RequestBody: %s", bm.JsonBody()) - xlog.Debugf("PayPal_Authorization: %s", authHeader) - } - httpClient.Header.Add(HeaderAuthorization, authHeader) - httpClient.Header.Add("Accept", "*/*") - res, bs, err = httpClient.Type(xhttp.TypeJSON).Put(url).SendBodyMap(bm).EndBytes(ctx) - if err != nil { - return nil, nil, err - } - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) - xlog.Debugf("PayPal_Headers: %#v", res.Header) - } - return res, bs, nil -} - -func (c *Client) doPayPalPatch(ctx context.Context, patchs []*Patch, path string) (res *http.Response, bs []byte, err error) { - var url = baseUrlProd + path - if !c.IsProd { - url = baseUrlSandbox + path - } - httpClient := xhttp.NewClient() - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - authHeader := AuthorizationPrefixBearer + c.AccessToken - if c.DebugSwitch == gopay.DebugOn { - jb, _ := json.Marshal(patchs) - xlog.Debugf("PayPal_RequestBody: %s", string(jb)) - xlog.Debugf("PayPal_Authorization: %s", authHeader) - } - httpClient.Header.Add(HeaderAuthorization, authHeader) - httpClient.Header.Add("Accept", "*/*") - res, bs, err = httpClient.Type(xhttp.TypeJSON).Patch(url).SendStruct(patchs).EndBytes(ctx) - if err != nil { - return nil, nil, err - } - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) - xlog.Debugf("PayPal_Headers: %#v", res.Header) - } - return res, bs, nil -} - -func (c *Client) doPayPalDelete(ctx context.Context, path string) (res *http.Response, bs []byte, err error) { - var url = baseUrlProd + path - if !c.IsProd { - url = baseUrlSandbox + path - } - httpClient := xhttp.NewClient() - if c.bodySize > 0 { - httpClient.SetBodySize(c.bodySize) - } - authHeader := AuthorizationPrefixBearer + c.AccessToken - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Url: %s", url) - xlog.Debugf("PayPal_Authorization: %s", authHeader) - } - httpClient.Header.Add(HeaderAuthorization, authHeader) - httpClient.Header.Add("Accept", "*/*") - res, bs, err = httpClient.Type(xhttp.TypeJSON).Patch(url).EndBytes(ctx) - if err != nil { - return nil, nil, err - } - if c.DebugSwitch == gopay.DebugOn { - xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) - xlog.Debugf("PayPal_Headers: %#v", res.Header) - } - return res, bs, nil -} diff --git a/paypal/request.go b/paypal/request.go new file mode 100644 index 00000000..05dc031d --- /dev/null +++ b/paypal/request.go @@ -0,0 +1,129 @@ +package paypal + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/pkg/xlog" +) + +func (c *Client) doPayPalGet(ctx context.Context, uri string) (res *http.Response, bs []byte, err error) { + var url = baseUrlProd + uri + if !c.IsProd { + url = baseUrlSandbox + uri + } + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, AuthorizationPrefixBearer+c.AccessToken) + req.Header.Add("Accept", "*/*") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Url: %s", url) + xlog.Debugf("PayPal_Req_Headers: %#v", req.Header) + } + res, bs, err = req.Get(url).EndBytes(ctx) + if err != nil { + return nil, nil, err + } + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("PayPal_Rsp_Headers: %#v", res.Header) + } + return res, bs, nil +} + +func (c *Client) doPayPalPost(ctx context.Context, bm gopay.BodyMap, path string) (res *http.Response, bs []byte, err error) { + var url = baseUrlProd + path + if !c.IsProd { + url = baseUrlSandbox + path + } + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, AuthorizationPrefixBearer+c.AccessToken) + req.Header.Add("Accept", "*/*") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Url: %s", url) + xlog.Debugf("PayPal_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("PayPal_Req_Headers: %#v", req.Header) + } + res, bs, err = req.Post(url).SendBodyMap(bm).EndBytes(ctx) + if err != nil { + return nil, nil, err + } + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("PayPal_Rsp_Headers: %#v", res.Header) + } + return res, bs, nil +} + +func (c *Client) doPayPalPut(ctx context.Context, bm gopay.BodyMap, path string) (res *http.Response, bs []byte, err error) { + var url = baseUrlProd + path + if !c.IsProd { + url = baseUrlSandbox + path + } + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, AuthorizationPrefixBearer+c.AccessToken) + req.Header.Add("Accept", "*/*") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Url: %s", url) + xlog.Debugf("PayPal_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("PayPal_Req_Headers: %#v", req.Header) + } + res, bs, err = req.Put(url).SendBodyMap(bm).EndBytes(ctx) + if err != nil { + return nil, nil, err + } + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("PayPal_Rsp_Headers: %#v", res.Header) + } + return res, bs, nil +} + +func (c *Client) doPayPalPatch(ctx context.Context, patchs []*Patch, path string) (res *http.Response, bs []byte, err error) { + var url = baseUrlProd + path + if !c.IsProd { + url = baseUrlSandbox + path + } + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, AuthorizationPrefixBearer+c.AccessToken) + req.Header.Add("Accept", "*/*") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Url: %s", url) + body, _ := json.Marshal(patchs) + xlog.Debugf("PayPal_Req_Body: %s", string(body)) + xlog.Debugf("PayPal_Req_Headers: %#v", req.Header) + } + res, bs, err = req.Patch(url).SendStruct(patchs).EndBytes(ctx) + if err != nil { + return nil, nil, err + } + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("PayPal_Headers: %#v", res.Header) + } + return res, bs, nil +} + +func (c *Client) doPayPalDelete(ctx context.Context, path string) (res *http.Response, bs []byte, err error) { + var url = baseUrlProd + path + if !c.IsProd { + url = baseUrlSandbox + path + } + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, AuthorizationPrefixBearer+c.AccessToken) + req.Header.Add("Accept", "*/*") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Url: %s", url) + xlog.Debugf("PayPal_Req_Headers: %#v", req.Header) + } + res, bs, err = req.Delete(url).EndBytes(ctx) + if err != nil { + return nil, nil, err + } + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("PayPal_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("PayPal_Rsp_Headers: %#v", res.Header) + } + return res, bs, nil +} diff --git a/pkg/xhttp/client.go b/pkg/xhttp/client.go index 5e3967a1..93962f93 100644 --- a/pkg/xhttp/client.go +++ b/pkg/xhttp/client.go @@ -1,41 +1,15 @@ package xhttp import ( - "bytes" - "context" "crypto/tls" - "encoding/json" - "encoding/xml" - "errors" - "fmt" - "io" - "mime/multipart" + "net" "net/http" - "net/url" - "sort" - "strings" "time" - - "github.com/go-pay/gopay" - "github.com/go-pay/gopay/pkg/util" ) type Client struct { - HttpClient *http.Client - Transport *http.Transport - Header http.Header - Timeout time.Duration - Host string - bodySize int // body size limit(MB), default is 10MB - url string - method string - requestType RequestType - FormString string - ContentType string - unmarshalType string - multipartBodyMap map[string]any - jsonByte []byte - err error + HttpClient *http.Client + bodySize int // body size limit(MB), default is 10MB } // NewClient , default tls.Config{InsecureSkipVerify: true} @@ -44,37 +18,38 @@ func NewClient() (client *Client) { HttpClient: &http.Client{ Timeout: 60 * time.Second, Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - DisableKeepAlives: true, - Proxy: http.ProxyFromEnvironment, + Proxy: http.ProxyFromEnvironment, + DialContext: defaultTransportDialContext(&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }), + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + DisableKeepAlives: true, + ForceAttemptHTTP2: true, }, }, - Transport: nil, - Header: make(http.Header), - bodySize: 10, // default is 10MB - requestType: TypeJSON, - unmarshalType: string(TypeJSON), + bodySize: 10, // default is 10MB } return client } func (c *Client) SetTransport(transport *http.Transport) (client *Client) { - c.Transport = transport + c.HttpClient.Transport = transport return c } func (c *Client) SetTLSConfig(tlsCfg *tls.Config) (client *Client) { - c.Transport = &http.Transport{TLSClientConfig: tlsCfg, DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment} + c.HttpClient.Transport.(*http.Transport).TLSClientConfig = tlsCfg return c } func (c *Client) SetTimeout(timeout time.Duration) (client *Client) { - c.Timeout = timeout - return c -} - -func (c *Client) SetHost(host string) (client *Client) { - c.Host = host + c.HttpClient.Timeout = timeout return c } @@ -84,285 +59,20 @@ func (c *Client) SetBodySize(sizeMB int) (client *Client) { return c } -func (c *Client) Type(typeStr RequestType) (client *Client) { - if _, ok := types[typeStr]; ok { - c.requestType = typeStr - } - return c -} - -func (c *Client) Get(url string) (client *Client) { - c.method = GET - c.url = url - return c -} - -func (c *Client) Post(url string) (client *Client) { - c.method = POST - c.url = url - return c -} - -func (c *Client) Put(url string) (client *Client) { - c.method = PUT - c.url = url - return c -} - -func (c *Client) Delete(url string) (client *Client) { - c.method = DELETE - c.url = url - return c -} - -func (c *Client) Patch(url string) (client *Client) { - c.method = PATCH - c.url = url - return c -} - -func (c *Client) SendStruct(v any) (client *Client) { - if v == nil { - return c - } - bs, err := json.Marshal(v) - if err != nil { - c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, v) - return c - } - switch c.requestType { - case TypeJSON: - c.jsonByte = bs - case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: - body := make(map[string]any) - if err = json.Unmarshal(bs, &body); err != nil { - c.err = fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs)) - return c - } - c.FormString = FormatURLParam(body) - } - return c -} - -func (c *Client) SendBodyMap(bm map[string]any) (client *Client) { - if bm == nil { - return c - } - switch c.requestType { - case TypeJSON: - bs, err := json.Marshal(bm) - if err != nil { - c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm) - return c - } - c.jsonByte = bs - case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: - c.FormString = FormatURLParam(bm) - } - return c -} - -func (c *Client) SendMultipartBodyMap(bm map[string]any) (client *Client) { - if bm == nil { - return c - } - switch c.requestType { - case TypeJSON: - bs, err := json.Marshal(bm) - if err != nil { - c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm) - return c - } - c.jsonByte = bs - case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: - c.FormString = FormatURLParam(bm) - case TypeMultipartFormData: - c.multipartBodyMap = bm - } - return c -} - -// encodeStr: url.Values.Encode() or jsonBody -func (c *Client) SendString(encodeStr string) (client *Client) { - switch c.requestType { - case TypeJSON: - c.jsonByte = []byte(encodeStr) - case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: - c.FormString = encodeStr - } - return c -} - -func (c *Client) EndStruct(ctx context.Context, v any) (res *http.Response, err error) { - res, bs, err := c.EndBytes(ctx) - if err != nil { - return nil, err - } - if res.StatusCode != http.StatusOK { - return res, fmt.Errorf("StatusCode(%d) != 200", res.StatusCode) - } - - switch c.unmarshalType { - case string(TypeJSON): - err = json.Unmarshal(bs, &v) - if err != nil { - return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs)) - } - return res, nil - case string(TypeXML): - err = xml.Unmarshal(bs, &v) - if err != nil { - return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs)) +func (c *Client) Req(typeStr ...RequestType) *Request { + tp := TypeJSON + if len(typeStr) == 1 { + tpp := typeStr[0] + if _, ok := types[tpp]; ok { + tp = tpp } - return res, nil - default: - return nil, errors.New("unmarshalType Type Wrong") - } -} - -func (c *Client) EndBytes(ctx context.Context) (res *http.Response, bs []byte, err error) { - if c.err != nil { - return nil, nil, c.err } - var ( - body io.Reader - bw *multipart.Writer - ) - // multipart-form-data - if c.requestType == TypeMultipartFormData { - body = &bytes.Buffer{} - bw = multipart.NewWriter(body.(io.Writer)) - } - - reqFunc := func() (err error) { - switch c.method { - case GET: - switch c.requestType { - case TypeJSON: - c.ContentType = types[TypeJSON] - case TypeForm, TypeFormData, TypeUrlencoded: - c.ContentType = types[TypeForm] - case TypeMultipartFormData: - c.ContentType = bw.FormDataContentType() - case TypeXML: - c.ContentType = types[TypeXML] - c.unmarshalType = string(TypeXML) - default: - return errors.New("Request type Error ") - } - case POST, PUT, DELETE, PATCH: - switch c.requestType { - case TypeJSON: - if c.jsonByte != nil { - body = strings.NewReader(string(c.jsonByte)) - } - c.ContentType = types[TypeJSON] - case TypeForm, TypeFormData, TypeUrlencoded: - body = strings.NewReader(c.FormString) - c.ContentType = types[TypeForm] - case TypeMultipartFormData: - for k, v := range c.multipartBodyMap { - // file 参数 - if file, ok := v.(*util.File); ok { - fw, err := bw.CreateFormFile(k, file.Name) - if err != nil { - return err - } - _, _ = fw.Write(file.Content) - continue - } - // text 参数 - vs, ok2 := v.(string) - if ok2 { - _ = bw.WriteField(k, vs) - } else if ss := util.ConvertToString(v); ss != "" { - _ = bw.WriteField(k, ss) - } - } - _ = bw.Close() - c.ContentType = bw.FormDataContentType() - case TypeXML: - body = strings.NewReader(c.FormString) - c.ContentType = types[TypeXML] - c.unmarshalType = string(TypeXML) - default: - return errors.New("Request type Error ") - } - default: - return errors.New("Only support GET and POST and PUT and DELETE ") - } - - req, err := http.NewRequestWithContext(ctx, c.method, c.url, body) - if err != nil { - return err - } - req.Header = c.Header - req.Header.Set("Content-Type", c.ContentType) - if c.Transport != nil { - c.HttpClient.Transport = c.Transport - } - if c.Host != "" { - req.Host = c.Host - } - if c.Timeout > 0 { - c.HttpClient.Timeout = c.Timeout - } - res, err = c.HttpClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - bs, err = io.ReadAll(io.LimitReader(res.Body, int64(c.bodySize<<20))) // default 10MB change the size you want - if err != nil { - return err - } - return nil - } - - if err = reqFunc(); err != nil { - return nil, nil, err - } - return res, bs, nil -} - -func FormatURLParam(body map[string]any) (urlParam string) { - var ( - buf strings.Builder - keys []string - ) - for k := range body { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - v, ok := body[k].(string) - if !ok { - v = convertToString(body[k]) - } - if v != "" { - buf.WriteString(url.QueryEscape(k)) - buf.WriteByte('=') - buf.WriteString(url.QueryEscape(v)) - buf.WriteByte('&') - } - } - if buf.Len() <= 0 { - return "" - } - return buf.String()[:buf.Len()-1] -} - -func convertToString(v any) (str string) { - if v == nil { - return "" - } - var ( - bs []byte - err error - ) - if bs, err = json.Marshal(v); err != nil { - return "" + r := &Request{ + client: c, + Header: make(http.Header), + requestType: tp, + unmarshalType: string(tp), } - str = string(bs) - return + r.Header.Set("Content-Type", types[tp]) + return r } diff --git a/pkg/xhttp/client_test.go b/pkg/xhttp/client_test.go index f38727be..93369068 100644 --- a/pkg/xhttp/client_test.go +++ b/pkg/xhttp/client_test.go @@ -2,13 +2,11 @@ package xhttp import ( "context" - "os" - "testing" - "time" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/pkg/util" "github.com/go-pay/gopay/pkg/xlog" + "os" + "testing" ) type HttpGet struct { @@ -21,9 +19,8 @@ var ctx = context.Background() func TestHttpGet(t *testing.T) { client := NewClient() - client.Timeout = 10 * time.Second // test - _, bs, err := client.Get("http://www.baidu.com").EndBytes(ctx) + _, bs, err := client.Req().Get("http://www.baidu.com").EndBytes(ctx) if err != nil { xlog.Error(err) return @@ -54,10 +51,9 @@ func TestHttpUploadFile(t *testing.T) { }).SetFormFile("image", &util.File{Name: "logo.png", Content: fileContent}) client := NewClient() - client.Timeout = 10 * time.Second rsp := new(HttpGet) - _, err = client.Type(TypeMultipartFormData). + _, err = client.Req(TypeMultipartFormData). Post("http://localhost:2233/admin/v1/oss/uploadImage"). SendMultipartBodyMap(bm). EndStruct(ctx, rsp) diff --git a/pkg/xhttp/request.go b/pkg/xhttp/request.go new file mode 100644 index 00000000..36db2c1a --- /dev/null +++ b/pkg/xhttp/request.go @@ -0,0 +1,268 @@ +package xhttp + +import ( + "bytes" + "context" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "sort" + "strings" + + "github.com/go-pay/gopay/pkg/util" +) + +type Request struct { + client *Client + Header http.Header + formString string + jsonByte []byte + url string + method string + requestType RequestType + unmarshalType string + multipartBodyMap map[string]any + err error +} + +func (r *Request) Get(url string) *Request { + r.method = GET + r.url = url + return r +} + +func (r *Request) Post(url string) *Request { + r.method = POST + r.url = url + return r +} + +func (r *Request) Put(url string) *Request { + r.method = PUT + r.url = url + return r +} + +func (r *Request) Delete(url string) *Request { + r.method = DELETE + r.url = url + return r +} + +func (r *Request) Patch(url string) *Request { + r.method = PATCH + r.url = url + return r +} + +// ===================================================================================================================== + +func (r *Request) SendStruct(v any) (c *Request) { + if v == nil { + return r + } + bs, err := json.Marshal(v) + if err != nil { + r.err = fmt.Errorf("json.Marshal(%+v):%w", v, err) + return r + } + switch r.requestType { + case TypeJSON: + r.jsonByte = bs + case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: + body := make(map[string]any) + if err = json.Unmarshal(bs, &body); err != nil { + r.err = fmt.Errorf("json.Unmarshal(%s, %+v):%w", string(bs), body, err) + return r + } + r.formString = FormatURLParam(body) + } + return r +} + +func (r *Request) SendBodyMap(bm map[string]any) (client *Request) { + if bm == nil { + return r + } + switch r.requestType { + case TypeJSON: + bs, err := json.Marshal(bm) + if err != nil { + r.err = fmt.Errorf("json.Marshal(%+v):%w", bm, err) + return r + } + r.jsonByte = bs + case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: + r.formString = FormatURLParam(bm) + } + return r +} + +func (r *Request) SendMultipartBodyMap(bm map[string]any) (client *Request) { + if bm == nil { + return r + } + switch r.requestType { + case TypeJSON: + bs, err := json.Marshal(bm) + if err != nil { + r.err = fmt.Errorf("json.Marshal(%+v):%w", bm, err) + return r + } + r.jsonByte = bs + case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: + r.formString = FormatURLParam(bm) + case TypeMultipartFormData: + r.multipartBodyMap = bm + } + return r +} + +// encodeStr: url.Values.Encode() or jsonBody +func (r *Request) SendString(encodeStr string) (client *Request) { + switch r.requestType { + case TypeJSON: + r.jsonByte = []byte(encodeStr) + case TypeXML, TypeUrlencoded, TypeForm, TypeFormData: + r.formString = encodeStr + } + return r +} + +// ===================================================================================================================== + +func (r *Request) EndBytes(ctx context.Context) (res *http.Response, bs []byte, err error) { + if r.err != nil { + return nil, nil, r.err + } + var ( + body io.Reader + bw *multipart.Writer + ) + // multipart-form-data + if r.requestType == TypeMultipartFormData { + body = &bytes.Buffer{} + bw = multipart.NewWriter(body.(io.Writer)) + } + + switch r.method { + case GET: + // do nothing + case POST, PUT, DELETE, PATCH: + switch r.requestType { + case TypeJSON: + if r.jsonByte != nil { + body = strings.NewReader(string(r.jsonByte)) + } + case TypeForm, TypeFormData, TypeUrlencoded: + if r.formString != "" { + body = strings.NewReader(r.formString) + } + case TypeMultipartFormData: + for k, v := range r.multipartBodyMap { + // file 参数 + if file, ok := v.(*util.File); ok { + fw, e := bw.CreateFormFile(k, file.Name) + if e != nil { + return nil, nil, e + } + _, _ = fw.Write(file.Content) + continue + } + // text 参数 + vs, ok2 := v.(string) + if ok2 { + _ = bw.WriteField(k, vs) + } else if ss := util.ConvertToString(v); ss != "" { + _ = bw.WriteField(k, ss) + } + } + _ = bw.Close() + r.Header.Set("Content-Type", bw.FormDataContentType()) + case TypeXML: + if r.formString != "" { + body = strings.NewReader(r.formString) + } + default: + return nil, nil, errors.New("Request type Error ") + } + default: + return nil, nil, errors.New("Only support GET and POST and PUT and DELETE ") + } + + // request + req, err := http.NewRequestWithContext(ctx, r.method, r.url, body) + if err != nil { + return nil, nil, err + } + req.Header = r.Header + res, err = r.client.HttpClient.Do(req) + if err != nil { + return nil, nil, err + } + defer res.Body.Close() + bs, err = io.ReadAll(io.LimitReader(res.Body, int64(r.client.bodySize<<20))) // default 10MB change the size you want + if err != nil { + return nil, nil, err + } + return res, bs, nil +} + +func (r *Request) EndStruct(ctx context.Context, v any) (res *http.Response, err error) { + res, bs, err := r.EndBytes(ctx) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + return res, fmt.Errorf("StatusCode(%d) != 200", res.StatusCode) + } + + switch r.unmarshalType { + case string(TypeJSON): + err = json.Unmarshal(bs, &v) + if err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s, %+v):%w", string(bs), v, err) + } + return res, nil + case string(TypeXML): + err = xml.Unmarshal(bs, &v) + if err != nil { + return nil, fmt.Errorf("xml.Unmarshal(%s, %+v):%w", string(bs), v, err) + } + return res, nil + default: + return nil, errors.New("unmarshalType Type Wrong") + } +} + +func FormatURLParam(body map[string]any) (urlParam string) { + var ( + buf strings.Builder + keys []string + ) + for k := range body { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v, ok := body[k].(string) + if !ok { + v = util.ConvertToString(body[k]) + } + if v != "" { + buf.WriteString(url.QueryEscape(k)) + buf.WriteByte('=') + buf.WriteString(url.QueryEscape(v)) + buf.WriteByte('&') + } + } + if buf.Len() <= 0 { + return "" + } + return buf.String()[:buf.Len()-1] +} diff --git a/pkg/xhttp/transport_default_js.go b/pkg/xhttp/transport_default_js.go new file mode 100644 index 00000000..c07d35ef --- /dev/null +++ b/pkg/xhttp/transport_default_js.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build js && wasm +// +build js,wasm + +package http + +import ( + "context" + "net" +) + +func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return nil +} diff --git a/pkg/xhttp/transport_default_other.go b/pkg/xhttp/transport_default_other.go new file mode 100644 index 00000000..a67fd465 --- /dev/null +++ b/pkg/xhttp/transport_default_other.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !(js && wasm) +// +build !js !wasm + +package xhttp + +import ( + "context" + "net" +) + +func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return dialer.DialContext +} diff --git a/qq/client.go b/qq/client.go index f64df556..0b9b97bd 100644 --- a/qq/client.go +++ b/qq/client.go @@ -2,10 +2,14 @@ package qq import ( "context" + "crypto/hmac" + "crypto/md5" + "crypto/sha256" "crypto/tls" "encoding/xml" "errors" "fmt" + "hash" "strings" "sync" @@ -19,10 +23,12 @@ type Client struct { MchId string ApiKey string IsProd bool - bodySize int // http response body size(MB), default is 10MB DebugSwitch gopay.DebugSwitch - certificate *tls.Certificate mu sync.RWMutex + sha256Hash hash.Hash + md5Hash hash.Hash + hc *xhttp.Client + tlsHc *xhttp.Client } // 初始化QQ客户端(正式环境) @@ -33,13 +39,17 @@ func NewClient(mchId, apiKey string) (client *Client) { MchId: mchId, ApiKey: apiKey, DebugSwitch: gopay.DebugOff, + sha256Hash: hmac.New(sha256.New, []byte(apiKey)), + md5Hash: md5.New(), + hc: xhttp.NewClient(), + tlsHc: xhttp.NewClient(), } } // SetBodySize 设置http response body size(MB) func (q *Client) SetBodySize(sizeMB int) { if sizeMB > 0 { - q.bodySize = sizeMB + q.hc.SetBodySize(sizeMB) } } @@ -48,7 +58,38 @@ func (q *Client) SetBodySize(sizeMB int) { // url:完整url地址,例如:https://qpay.qq.com/cgi-bin/pay/qpay_unified_order.cgi // tlsConfig:tls配置,如无需证书请求,传nil func (q *Client) PostQQAPISelf(ctx context.Context, bm gopay.BodyMap, url string, tlsConfig *tls.Config) (bs []byte, err error) { - return q.doQQ(ctx, bm, url, tlsConfig) + if bm.GetString("mch_id") == util.NULL { + bm.Set("mch_id", q.MchId) + } + if bm.GetString("fee_type") == util.NULL { + bm.Set("fee_type", "CNY") + } + if bm.GetString("sign") == util.NULL { + sign := GetReleaseSign(q.ApiKey, bm.GetString("sign_type"), bm) + bm.Set("sign", sign) + } + req := GenerateXml(bm) + if q.DebugSwitch == gopay.DebugOn { + xlog.Debugf("QQ_Request: %s", req) + } + httpClient := xhttp.NewClient() + if q.IsProd && tlsConfig != nil { + httpClient.SetTLSConfig(tlsConfig) + } + res, bs, err := httpClient.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + if err != nil { + return nil, err + } + if q.DebugSwitch == gopay.DebugOn { + xlog.Debugf("QQ_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + if strings.Contains(string(bs), "HTML") { + return nil, errors.New(string(bs)) + } + return bs, nil } // 提交付款码支付 @@ -59,7 +100,7 @@ func (q *Client) MicroPay(ctx context.Context, bm gopay.BodyMap) (qqRsp *MicroPa return nil, err } bm.Set("trade_type", TradeType_MicroPay) - bs, err := q.doQQ(ctx, bm, microPay, nil) + bs, err := q.doQQPost(ctx, bm, microPay) if err != nil { return nil, err } @@ -77,7 +118,7 @@ func (q *Client) Reverse(ctx context.Context, bm gopay.BodyMap) (qqRsp *ReverseR if err != nil { return nil, err } - bs, err := q.doQQ(ctx, bm, reverse, nil) + bs, err := q.doQQPost(ctx, bm, reverse) if err != nil { return nil, err } @@ -95,7 +136,7 @@ func (q *Client) UnifiedOrder(ctx context.Context, bm gopay.BodyMap) (qqRsp *Uni if err != nil { return nil, err } - bs, err := q.doQQ(ctx, bm, unifiedOrder, nil) + bs, err := q.doQQPost(ctx, bm, unifiedOrder) if err != nil { return nil, err } @@ -116,7 +157,7 @@ func (q *Client) OrderQuery(ctx context.Context, bm gopay.BodyMap) (qqRsp *Order if bm.GetString("out_trade_no") == util.NULL && bm.GetString("transaction_id") == util.NULL { return nil, errors.New("out_trade_no and transaction_id are not allowed to be null at the same time") } - bs, err := q.doQQ(ctx, bm, orderQuery, nil) + bs, err := q.doQQPost(ctx, bm, orderQuery) if err != nil { return nil, err } @@ -134,7 +175,7 @@ func (q *Client) CloseOrder(ctx context.Context, bm gopay.BodyMap) (qqRsp *Close if err != nil { return nil, err } - bs, err := q.doQQ(ctx, bm, orderClose, nil) + bs, err := q.doQQPost(ctx, bm, orderClose) if err != nil { return nil, err } @@ -159,11 +200,7 @@ func (q *Client) Refund(ctx context.Context, bm gopay.BodyMap, certFilePath, key if bm.GetString("out_trade_no") == util.NULL && bm.GetString("transaction_id") == util.NULL { return nil, errors.New("out_trade_no and transaction_id are not allowed to be null at the same time") } - tlsConfig, err := q.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath) - if err != nil { - return nil, err - } - bs, err := q.doQQ(ctx, bm, refund, tlsConfig) + bs, err := q.doQQPostTLS(ctx, bm, refund) if err != nil { return nil, err } @@ -184,7 +221,7 @@ func (q *Client) RefundQuery(ctx context.Context, bm gopay.BodyMap) (qqRsp *Refu if bm.GetString("refund_id") == util.NULL && bm.GetString("out_refund_no") == util.NULL && bm.GetString("transaction_id") == util.NULL && bm.GetString("out_trade_no") == util.NULL { return nil, errors.New("refund_id, out_refund_no, out_trade_no, transaction_id are not allowed to be null at the same time") } - bs, err := q.doQQ(ctx, bm, refundQuery, nil) + bs, err := q.doQQPost(ctx, bm, refundQuery) if err != nil { return nil, err } @@ -206,7 +243,7 @@ func (q *Client) StatementDown(ctx context.Context, bm gopay.BodyMap) (qqRsp str if billType != "ALL" && billType != "SUCCESS" && billType != "REFUND" && billType != "RECHAR" { return util.NULL, errors.New("bill_type error, please reference: https://qpay.qq.com/buss/wiki/38/1209") } - bs, err := q.doQQ(ctx, bm, statementDown, nil) + bs, err := q.doQQPost(ctx, bm, statementDown) if err != nil { return util.NULL, err } @@ -224,7 +261,7 @@ func (q *Client) AccRoll(ctx context.Context, bm gopay.BodyMap) (qqRsp string, e if accType != "CASH" && accType != "MARKETING" { return util.NULL, errors.New("acc_type error, please reference: https://qpay.qq.com/buss/wiki/38/3089") } - bs, err := q.doQQ(ctx, bm, accRoll, nil) + bs, err := q.doQQPost(ctx, bm, accRoll) if err != nil { return util.NULL, err } @@ -232,31 +269,54 @@ func (q *Client) AccRoll(ctx context.Context, bm gopay.BodyMap) (qqRsp string, e } // 向QQ发送请求 -func (q *Client) doQQ(ctx context.Context, bm gopay.BodyMap, url string, tlsConfig *tls.Config) (bs []byte, err error) { - +func (q *Client) doQQPost(ctx context.Context, bm gopay.BodyMap, url string) (bs []byte, err error) { if bm.GetString("mch_id") == util.NULL { bm.Set("mch_id", q.MchId) } if bm.GetString("fee_type") == util.NULL { bm.Set("fee_type", "CNY") } - if bm.GetString("sign") == util.NULL { - sign := GetReleaseSign(q.ApiKey, bm.GetString("sign_type"), bm) + sign := q.getReleaseSign(q.ApiKey, bm.GetString("sign_type"), bm) bm.Set("sign", sign) } + req := GenerateXml(bm) + if q.DebugSwitch == gopay.DebugOn { + xlog.Debugf("QQ_Request: %s", req) + } + res, bs, err := q.hc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + if err != nil { + return nil, err + } + if q.DebugSwitch == gopay.DebugOn { + xlog.Debugf("QQ_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + if strings.Contains(string(bs), "HTML") { + return nil, errors.New(string(bs)) + } + return bs, nil +} - httpClient := xhttp.NewClient() - if q.bodySize > 0 { - httpClient.SetBodySize(q.bodySize) +// 向QQ发送请求 TLS +func (q *Client) doQQPostTLS(ctx context.Context, bm gopay.BodyMap, url string) (bs []byte, err error) { + if bm.GetString("mch_id") == util.NULL { + bm.Set("mch_id", q.MchId) } - if tlsConfig != nil { - httpClient.SetTLSConfig(tlsConfig) + if bm.GetString("fee_type") == util.NULL { + bm.Set("fee_type", "CNY") + } + if bm.GetString("sign") == util.NULL { + sign := q.getReleaseSign(q.ApiKey, bm.GetString("sign_type"), bm) + bm.Set("sign", sign) } + req := GenerateXml(bm) if q.DebugSwitch == gopay.DebugOn { - xlog.Debugf("QQ_Request: %s", bm.JsonBody()) + xlog.Debugf("QQ_Request: %s", req) } - res, bs, err := httpClient.Type(xhttp.TypeXML).Post(url).SendString(generateXml(bm)).EndBytes(ctx) + res, bs, err := q.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -278,20 +338,14 @@ func (q *Client) doQQGet(ctx context.Context, bm gopay.BodyMap, url, signType st bm.Set("mch_id", q.MchId) } bm.Remove("sign") - sign := GetReleaseSign(q.ApiKey, signType, bm) + sign := q.getReleaseSign(q.ApiKey, signType, bm) bm.Set("sign", sign) - if q.DebugSwitch == gopay.DebugOn { xlog.Debugf("QQ_Request: %s", bm.JsonBody()) } param := bm.EncodeURLParams() - url = url + "?" + param - - httpClient := xhttp.NewClient() - if q.bodySize > 0 { - httpClient.SetBodySize(q.bodySize) - } - res, bs, err := httpClient.Get(url).EndBytes(ctx) + uri := url + "?" + param + res, bs, err := q.hc.Req(xhttp.TypeXML).Get(uri).EndBytes(ctx) if err != nil { return nil, err } @@ -307,8 +361,7 @@ func (q *Client) doQQGet(ctx context.Context, bm gopay.BodyMap, url, signType st return bs, nil } -func (q *Client) doQQRed(ctx context.Context, bm gopay.BodyMap, url string, tlsConfig *tls.Config) (bs []byte, err error) { - +func (q *Client) doQQRed(ctx context.Context, bm gopay.BodyMap, url string) (bs []byte, err error) { if bm.GetString("mch_id") == util.NULL { bm.Set("mch_id", q.MchId) } @@ -316,18 +369,11 @@ func (q *Client) doQQRed(ctx context.Context, bm gopay.BodyMap, url string, tlsC sign := GetReleaseSign(q.ApiKey, SignType_MD5, bm) bm.Set("sign", sign) } - - httpClient := xhttp.NewClient() - if tlsConfig != nil { - httpClient.SetTLSConfig(tlsConfig) - } - if q.bodySize > 0 { - httpClient.SetBodySize(q.bodySize) - } + req := GenerateXml(bm) if q.DebugSwitch == gopay.DebugOn { - xlog.Debugf("QQ_Request: %s", bm.JsonBody()) + xlog.Debugf("QQ_Request: %s", req) } - res, bs, err := httpClient.Type(xhttp.TypeXML).Post(url).SendString(generateXml(bm)).EndBytes(ctx) + res, bs, err := q.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } diff --git a/qq/oplatform_api.go b/qq/oplatform_api.go index a17b1031..311d227c 100644 --- a/qq/oplatform_api.go +++ b/qq/oplatform_api.go @@ -79,7 +79,7 @@ func GetAccessToken(ctx context.Context, appId, appSecret, code, redirectUri str accessToken = new(AccessToken) url := "https://graph.qq.com/oauth2.0/token?client_id=" + appId + "&client_secret=" + appSecret + "&code=" + code + "&redirect_uri=" + redirectUri + "&fmt=json" + "&grant_type=authorization_code" - _, err = xhttp.NewClient().Get(url).EndStruct(ctx, accessToken) + _, err = xhttp.NewClient().Req().Get(url).EndStruct(ctx, accessToken) if err != nil { return nil, err } @@ -98,7 +98,7 @@ func GetOpenId(ctx context.Context, accessToken string, lang ...string) (openid if len(lang) == 1 { url += "&lang=" + lang[0] } - _, bs, err := xhttp.NewClient().Get(url).EndBytes(ctx) + _, bs, err := xhttp.NewClient().Req().Get(url).EndBytes(ctx) if err != nil { return nil, err } @@ -124,7 +124,7 @@ func GetUserInfo(ctx context.Context, accessToken, openId string, oauthConsumerK if len(lang) == 1 { url += "&lang=" + lang[0] } - _, err = xhttp.NewClient().Get(url).EndStruct(ctx, userInfo) + _, err = xhttp.NewClient().Req().Get(url).EndStruct(ctx, userInfo) if err != nil { return nil, err } diff --git a/qq/param.go b/qq/param.go index d1772f94..27ccbce7 100644 --- a/qq/param.go +++ b/qq/param.go @@ -16,6 +16,7 @@ import ( "github.com/go-pay/gopay" "github.com/go-pay/gopay/pkg/util" + "github.com/go-pay/gopay/pkg/xlog" "golang.org/x/crypto/pkcs12" ) @@ -28,13 +29,11 @@ func (q *Client) AddCertFilePath(certFilePath, keyFilePath, pkcs12FilePath any) if err = checkCertFilePathOrContent(certFilePath, keyFilePath, pkcs12FilePath); err != nil { return err } - var config *tls.Config - if config, err = q.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath); err != nil { + config, err := q.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath) + if err != nil { return } - q.mu.Lock() - q.certificate = &config.Certificates[0] - q.mu.Unlock() + q.tlsHc.SetTLSConfig(config) return nil } @@ -88,7 +87,7 @@ func checkCertFilePathOrContent(certFile, keyFile, pkcs12File any) error { } // 生成请求XML的Body体 -func generateXml(bm gopay.BodyMap) (reqXml string) { +func GenerateXml(bm gopay.BodyMap) (reqXml string) { bs, err := xml.Marshal(bm) if err != nil { return util.NULL @@ -108,17 +107,28 @@ func GetReleaseSign(apiKey string, signType string, bm gopay.BodyMap) (sign stri return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) } +func (q *Client) getReleaseSign(apiKey string, signType string, bm gopay.BodyMap) (sign string) { + signParams := bm.EncodeWeChatSignParams(apiKey) + if q.DebugSwitch == gopay.DebugOn { + xlog.Debugf("QQ_Request_SignStr: %s", signParams) + } + var h hash.Hash + if signType == SignType_HMAC_SHA256 { + h = q.sha256Hash + } else { + h = q.md5Hash + } + q.mu.Lock() + defer func() { + h.Reset() + q.mu.Unlock() + }() + h.Write([]byte(signParams)) + return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) +} + func (q *Client) addCertConfig(certFile, keyFile, pkcs12File any) (tlsConfig *tls.Config, err error) { if certFile == nil && keyFile == nil && pkcs12File == nil { - q.mu.RLock() - defer q.mu.RUnlock() - if q.certificate != nil { - tlsConfig = &tls.Config{ - Certificates: []tls.Certificate{*q.certificate}, - InsecureSkipVerify: true, - } - return tlsConfig, nil - } return nil, errors.New("cert parse failed") } diff --git a/qq/red.go b/qq/red.go index 8fafd525..40e6572c 100644 --- a/qq/red.go +++ b/qq/red.go @@ -17,20 +17,13 @@ import ( // SendCashRed 创建现金红包 // 注意:如已使用client.AddCertFilePath()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传 nil,否则,3证书Path均不可空 // 文档:https://qpay.qq.com/buss/wiki/221/1220 -func (q *Client) SendCashRed(ctx context.Context, bm gopay.BodyMap, certFilePath, keyFilePath, pkcs12FilePath any) (qqRsp *SendCashRedResponse, err error) { - if err = checkCertFilePathOrContent(certFilePath, keyFilePath, pkcs12FilePath); err != nil { - return nil, err - } +func (q *Client) SendCashRed(ctx context.Context, bm gopay.BodyMap) (qqRsp *SendCashRedResponse, err error) { err = bm.CheckEmptyError("charset", "nonce_str", "mch_billno", "mch_name", "re_openid", "total_amount", "total_num", "wishing", "act_name", "icon_id", "min_value", "max_value") if err != nil { return nil, err } - tlsConfig, err := q.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath) - if err != nil { - return nil, err - } - bs, err := q.doQQRed(ctx, bm, createCashRed, tlsConfig) + bs, err := q.doQQRed(ctx, bm, createCashRed) if err != nil { return nil, err } @@ -65,7 +58,7 @@ func (q *Client) QueryRedInfo(ctx context.Context, bm gopay.BodyMap) (qqRsp *Que if err != nil { return nil, err } - bs, err := q.doQQRed(ctx, bm, queryRedInfo, nil) + bs, err := q.doQQRed(ctx, bm, queryRedInfo) if err != nil { return nil, err } diff --git a/wechat/base_api.go b/wechat/base_api.go index a847eab7..ccf292a9 100644 --- a/wechat/base_api.go +++ b/wechat/base_api.go @@ -2,7 +2,6 @@ package wechat import ( "context" - "crypto/tls" "encoding/xml" "errors" "fmt" @@ -21,7 +20,7 @@ func (w *Client) UnifiedOrder(ctx context.Context, bm gopay.BodyMap) (wxRsp *Uni } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, unifiedOrder, nil) + bs, err = w.doProdPost(ctx, bm, unifiedOrder) } else { bm.Set("total_fee", 101) bs, err = w.doSanBoxPost(ctx, bm, sandboxUnifiedOrder) @@ -46,7 +45,7 @@ func (w *Client) Micropay(ctx context.Context, bm gopay.BodyMap) (wxRsp *Micropa } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, microPay, nil) + bs, err = w.doProdPost(ctx, bm, microPay) } else { bm.Set("total_fee", 1) bs, err = w.doSanBoxPost(ctx, bm, sandboxMicroPay) @@ -74,7 +73,7 @@ func (w *Client) QueryOrder(ctx context.Context, bm gopay.BodyMap) (wxRsp *Query } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, orderQuery, nil) + bs, err = w.doProdPost(ctx, bm, orderQuery) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxOrderQuery) } @@ -102,7 +101,7 @@ func (w *Client) CloseOrder(ctx context.Context, bm gopay.BodyMap) (wxRsp *Close } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, closeOrder, nil) + bs, err = w.doProdPost(ctx, bm, closeOrder) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxCloseOrder) } @@ -129,14 +128,10 @@ func (w *Client) Refund(ctx context.Context, bm gopay.BodyMap) (wxRsp *RefundRes return nil, nil, errors.New("out_trade_no and transaction_id are not allowed to be null at the same time") } var ( - bs []byte - tlsConfig *tls.Config + bs []byte ) if w.IsProd { - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, nil, err - } - bs, err = w.doProdPost(ctx, bm, refund, tlsConfig) + bs, err = w.doProdPostTLS(ctx, bm, refund) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxRefund) } @@ -167,7 +162,7 @@ func (w *Client) QueryRefund(ctx context.Context, bm gopay.BodyMap) (wxRsp *Quer } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, refundQuery, nil) + bs, err = w.doProdPost(ctx, bm, refundQuery) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxRefundQuery) } @@ -195,14 +190,10 @@ func (w *Client) Reverse(ctx context.Context, bm gopay.BodyMap) (wxRsp *ReverseR return nil, err } var ( - bs []byte - tlsConfig *tls.Config + bs []byte ) if w.IsProd { - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, err - } - bs, err = w.doProdPost(ctx, bm, reverse, tlsConfig) + bs, err = w.doProdPostTLS(ctx, bm, reverse) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxReverse) } diff --git a/wechat/client.go b/wechat/client.go index 3d56c3a0..c5e3706a 100644 --- a/wechat/client.go +++ b/wechat/client.go @@ -2,10 +2,14 @@ package wechat import ( "context" + "crypto/hmac" + "crypto/md5" + "crypto/sha256" "crypto/tls" "encoding/xml" "errors" "fmt" + "hash" "strings" "sync" @@ -21,10 +25,12 @@ type Client struct { ApiKey string BaseURL string IsProd bool - bodySize int // http response body size(MB), default is 10MB DebugSwitch gopay.DebugSwitch - Certificate *tls.Certificate mu sync.RWMutex + sha256Hash hash.Hash + md5Hash hash.Hash + hc *xhttp.Client + tlsHc *xhttp.Client } // 初始化微信客户端 V2 @@ -39,13 +45,17 @@ func NewClient(appId, mchId, apiKey string, isProd bool) (client *Client) { ApiKey: apiKey, IsProd: isProd, DebugSwitch: gopay.DebugOff, + sha256Hash: hmac.New(sha256.New, []byte(apiKey)), + md5Hash: md5.New(), + hc: xhttp.NewClient(), + tlsHc: xhttp.NewClient(), } } // SetBodySize 设置http response body size(MB) func (w *Client) SetBodySize(sizeMB int) { if sizeMB > 0 { - w.bodySize = sizeMB + w.hc.SetBodySize(sizeMB) } } @@ -54,7 +64,7 @@ func (w *Client) SetBodySize(sizeMB int) { // path:接口地址去掉baseURL的path,例如:url为https://api.mch.weixin.qq.com/pay/micropay,只需传 pay/micropay // tlsConfig:tls配置,如无需证书请求,传nil func (w *Client) PostWeChatAPISelf(ctx context.Context, bm gopay.BodyMap, path string, tlsConfig *tls.Config) (bs []byte, err error) { - return w.doProdPost(ctx, bm, path, tlsConfig) + return w.doProdPostSelf(ctx, bm, path, tlsConfig) } // 授权码查询openid(正式) @@ -65,7 +75,7 @@ func (w *Client) AuthCodeToOpenId(ctx context.Context, bm gopay.BodyMap) (wxRsp return nil, err } - bs, err := w.doProdPost(ctx, bm, authCodeToOpenid, nil) + bs, err := w.doProdPost(ctx, bm, authCodeToOpenid) if err != nil { return nil, err } @@ -89,7 +99,7 @@ func (w *Client) DownloadBill(ctx context.Context, bm gopay.BodyMap) (wxRsp stri } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, downloadBill, nil) + bs, err = w.doProdPost(ctx, bm, downloadBill) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxDownloadBill) } @@ -113,11 +123,7 @@ func (w *Client) DownloadFundFlow(ctx context.Context, bm gopay.BodyMap) (wxRsp return util.NULL, errors.New("account_type error, please reference: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_18&index=7") } bm.Set("sign_type", SignType_HMAC_SHA256) - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return util.NULL, err - } - bs, err := w.doProdPost(ctx, bm, downloadFundFlow, tlsConfig) + bs, err := w.doProdPostTLS(ctx, bm, downloadFundFlow) if err != nil { return util.NULL, err } @@ -139,7 +145,7 @@ func (w *Client) Report(ctx context.Context, bm gopay.BodyMap) (wxRsp *ReportRes } var bs []byte if w.IsProd { - bs, err = w.doProdPost(ctx, bm, report, nil) + bs, err = w.doProdPost(ctx, bm, report) } else { bs, err = w.doSanBoxPost(ctx, bm, sandboxReport) } @@ -163,11 +169,7 @@ func (w *Client) BatchQueryComment(ctx context.Context, bm gopay.BodyMap) (wxRsp return util.NULL, err } bm.Set("sign_type", SignType_HMAC_SHA256) - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return util.NULL, err - } - bs, err := w.doProdPost(ctx, bm, batchQueryComment, tlsConfig) + bs, err := w.doProdPostTLS(ctx, bm, batchQueryComment) if err != nil { return util.NULL, err } @@ -196,11 +198,7 @@ func (w *Client) doSanBoxPost(ctx context.Context, bm gopay.BodyMap, path string if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - httpClient := xhttp.NewClient().Type(xhttp.TypeXML) - if w.bodySize > 0 { - httpClient.SetBodySize(w.bodySize) - } - res, bs, err := httpClient.Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.hc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -217,7 +215,7 @@ func (w *Client) doSanBoxPost(ctx context.Context, bm gopay.BodyMap, path string } // Post请求、正式 -func (w *Client) doProdPost(ctx context.Context, bm gopay.BodyMap, path string, tlsConfig *tls.Config) (bs []byte, err error) { +func (w *Client) doProdPostSelf(ctx context.Context, bm gopay.BodyMap, path string, tlsConfig *tls.Config) (bs []byte, err error) { var url = baseUrlCh + path if bm.GetString("appid") == util.NULL { bm.Set("appid", w.AppId) @@ -229,13 +227,45 @@ func (w *Client) doProdPost(ctx context.Context, bm gopay.BodyMap, path string, sign := w.getReleaseSign(w.ApiKey, bm.GetString("sign_type"), bm) bm.Set("sign", sign) } - + if w.BaseURL != util.NULL { + url = w.BaseURL + path + } + req := GenerateXml(bm) + if w.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_Request: %s", req) + } httpClient := xhttp.NewClient() if w.IsProd && tlsConfig != nil { httpClient.SetTLSConfig(tlsConfig) } - if w.bodySize > 0 { - httpClient.SetBodySize(w.bodySize) + res, bs, err := httpClient.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + if err != nil { + return nil, err + } + if w.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + if strings.Contains(string(bs), "HTML") || strings.Contains(string(bs), "html") { + return nil, errors.New(string(bs)) + } + return bs, nil +} + +// Post请求、正式 +func (w *Client) doProdPost(ctx context.Context, bm gopay.BodyMap, path string) (bs []byte, err error) { + var url = baseUrlCh + path + if bm.GetString("appid") == util.NULL { + bm.Set("appid", w.AppId) + } + if bm.GetString("mch_id") == util.NULL { + bm.Set("mch_id", w.MchId) + } + if bm.GetString("sign") == util.NULL { + sign := w.getReleaseSign(w.ApiKey, bm.GetString("sign_type"), bm) + bm.Set("sign", sign) } if w.BaseURL != util.NULL { url = w.BaseURL + path @@ -244,7 +274,7 @@ func (w *Client) doProdPost(ctx context.Context, bm gopay.BodyMap, path string, if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Type(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.hc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -260,15 +290,68 @@ func (w *Client) doProdPost(ctx context.Context, bm gopay.BodyMap, path string, return bs, nil } -func (w *Client) doProdPostPure(ctx context.Context, bm gopay.BodyMap, path string, tlsConfig *tls.Config) (bs []byte, err error) { +func (w *Client) doProdPostTLS(ctx context.Context, bm gopay.BodyMap, path string) (bs []byte, err error) { var url = baseUrlCh + path - httpClient := xhttp.NewClient() - if w.IsProd && tlsConfig != nil { - httpClient.SetTLSConfig(tlsConfig) + if bm.GetString("appid") == util.NULL { + bm.Set("appid", w.AppId) } - if w.bodySize > 0 { - httpClient.SetBodySize(w.bodySize) + if bm.GetString("mch_id") == util.NULL { + bm.Set("mch_id", w.MchId) } + if bm.GetString("sign") == util.NULL { + sign := w.getReleaseSign(w.ApiKey, bm.GetString("sign_type"), bm) + bm.Set("sign", sign) + } + if w.BaseURL != util.NULL { + url = w.BaseURL + path + } + req := GenerateXml(bm) + if w.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_Request: %s", req) + } + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + if err != nil { + return nil, err + } + if w.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + if strings.Contains(string(bs), "HTML") || strings.Contains(string(bs), "html") { + return nil, errors.New(string(bs)) + } + return bs, nil +} + +func (w *Client) doProdPostPure(ctx context.Context, bm gopay.BodyMap, path string) (bs []byte, err error) { + var url = baseUrlCh + path + if w.BaseURL != util.NULL { + url = w.BaseURL + path + } + req := GenerateXml(bm) + if w.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_Request: %s", req) + } + res, bs, err := w.hc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + if err != nil { + return nil, err + } + if w.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs)) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + if strings.Contains(string(bs), "HTML") || strings.Contains(string(bs), "html") { + return nil, errors.New(string(bs)) + } + return bs, nil +} + +func (w *Client) doProdPostPureTLS(ctx context.Context, bm gopay.BodyMap, path string) (bs []byte, err error) { + var url = baseUrlCh + path if w.BaseURL != util.NULL { url = w.BaseURL + path } @@ -276,7 +359,7 @@ func (w *Client) doProdPostPure(ctx context.Context, bm gopay.BodyMap, path stri if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Type(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -307,17 +390,12 @@ func (w *Client) doProdGet(ctx context.Context, bm gopay.BodyMap, path, signType if w.BaseURL != util.NULL { url = w.BaseURL + path } - if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", bm.JsonBody()) } param := bm.EncodeURLParams() - url = url + "?" + param - httpClient := xhttp.NewClient() - if w.bodySize > 0 { - httpClient.SetBodySize(w.bodySize) - } - res, bs, err := httpClient.Get(url).EndBytes(ctx) + uri := url + "?" + param + res, bs, err := w.hc.Req(xhttp.TypeXML).Get(uri).EndBytes(ctx) if err != nil { return nil, err } diff --git a/wechat/customs.go b/wechat/customs.go index 44490879..bb2a2475 100644 --- a/wechat/customs.go +++ b/wechat/customs.go @@ -16,7 +16,7 @@ func (w *Client) CustomsDeclareOrder(ctx context.Context, bm gopay.BodyMap) (wxR return nil, err } bm.Set("sign_type", SignType_MD5) - bs, err := w.doProdPost(ctx, bm, customsDeclareOrder, nil) + bs, err := w.doProdPost(ctx, bm, customsDeclareOrder) if err != nil { return nil, err } @@ -35,7 +35,7 @@ func (w *Client) CustomsDeclareQuery(ctx context.Context, bm gopay.BodyMap) (wxR return nil, err } bm.Set("sign_type", SignType_MD5) - bs, err := w.doProdPost(ctx, bm, customsDeclareQuery, nil) + bs, err := w.doProdPost(ctx, bm, customsDeclareQuery) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func (w *Client) CustomsReDeclareOrder(ctx context.Context, bm gopay.BodyMap) (w return nil, err } bm.Set("sign_type", SignType_MD5) - bs, err := w.doProdPost(ctx, bm, customsReDeclareOrder, nil) + bs, err := w.doProdPost(ctx, bm, customsReDeclareOrder) if err != nil { return nil, err } diff --git a/wechat/merchant.go b/wechat/merchant.go index 9ed3fccb..976b8db9 100644 --- a/wechat/merchant.go +++ b/wechat/merchant.go @@ -2,7 +2,6 @@ package wechat import ( "context" - "crypto/tls" "encoding/xml" "errors" "fmt" @@ -24,15 +23,10 @@ func (w *Client) Transfer(ctx context.Context, bm gopay.BodyMap) (wxRsp *Transfe bm.Set("mch_appid", w.AppId) bm.Set("mchid", w.MchId) var ( - tlsConfig *tls.Config - url = baseUrlCh + transfers + url = baseUrlCh + transfers ) - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, err - } bm.Set("sign", w.getReleaseSign(w.ApiKey, SignType_MD5, bm)) - httpClient := xhttp.NewClient().SetTLSConfig(tlsConfig).Type(xhttp.TypeXML) if w.BaseURL != util.NULL { w.mu.RLock() url = w.BaseURL + transfers @@ -42,7 +36,7 @@ func (w *Client) Transfer(ctx context.Context, bm gopay.BodyMap) (wxRsp *Transfe if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -70,15 +64,10 @@ func (w *Client) GetTransferInfo(ctx context.Context, bm gopay.BodyMap) (wxRsp * bm.Set("appid", w.AppId) bm.Set("mch_id", w.MchId) var ( - tlsConfig *tls.Config - url = baseUrlCh + getTransferInfo + url = baseUrlCh + getTransferInfo ) - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, err - } bm.Set("sign", w.getReleaseSign(w.ApiKey, SignType_MD5, bm)) - httpClient := xhttp.NewClient().SetTLSConfig(tlsConfig).Type(xhttp.TypeXML) if w.BaseURL != util.NULL { w.mu.RLock() url = w.BaseURL + getTransferInfo @@ -88,7 +77,7 @@ func (w *Client) GetTransferInfo(ctx context.Context, bm gopay.BodyMap) (wxRsp * if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -118,15 +107,10 @@ func (w *Client) PayBank(ctx context.Context, bm gopay.BodyMap) (wxRsp *PayBankR } bm.Set("mch_id", w.MchId) var ( - tlsConfig *tls.Config - url = baseUrlCh + payBank + url = baseUrlCh + payBank ) - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, err - } bm.Set("sign", w.getReleaseSign(w.ApiKey, SignType_MD5, bm)) - httpClient := xhttp.NewClient().SetTLSConfig(tlsConfig).Type(xhttp.TypeXML) if w.BaseURL != util.NULL { w.mu.RLock() url = w.BaseURL + payBank @@ -136,7 +120,7 @@ func (w *Client) PayBank(ctx context.Context, bm gopay.BodyMap) (wxRsp *PayBankR if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -162,15 +146,10 @@ func (w *Client) QueryBank(ctx context.Context, bm gopay.BodyMap) (wxRsp *QueryB } bm.Set("mch_id", w.MchId) var ( - tlsConfig *tls.Config - url = baseUrlCh + queryBank + url = baseUrlCh + queryBank ) - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, err - } bm.Set("sign", w.getReleaseSign(w.ApiKey, SignType_MD5, bm)) - httpClient := xhttp.NewClient().SetTLSConfig(tlsConfig).Type(xhttp.TypeXML) if w.BaseURL != util.NULL { w.mu.RLock() url = w.BaseURL + queryBank @@ -180,7 +159,7 @@ func (w *Client) QueryBank(ctx context.Context, bm gopay.BodyMap) (wxRsp *QueryB if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -206,20 +185,15 @@ func (w *Client) GetRSAPublicKey(ctx context.Context, bm gopay.BodyMap) (wxRsp * } bm.Set("mch_id", w.MchId) var ( - tlsConfig *tls.Config - url = getPublicKey + url = getPublicKey ) - if tlsConfig, err = w.addCertConfig(nil, nil, nil); err != nil { - return nil, err - } bm.Set("sign", w.getReleaseSign(w.ApiKey, bm.GetString("sign_type"), bm)) - httpClient := xhttp.NewClient().SetTLSConfig(tlsConfig).Type(xhttp.TypeXML) req := GenerateXml(bm) if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request: %s", req) } - res, bs, err := httpClient.Post(url).SendString(req).EndBytes(ctx) + res, bs, err := w.tlsHc.Req(xhttp.TypeXML).Post(url).SendString(req).EndBytes(ctx) if err != nil { return nil, err } @@ -265,11 +239,7 @@ func (w *Client) profitSharing(ctx context.Context, bm gopay.BodyMap, uri string // 设置签名类型,官方文档此接口只支持 HMAC_SHA256 bm.Set("sign_type", SignType_HMAC_SHA256) - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - bs, err := w.doProdPost(ctx, bm, uri, tlsConfig) + bs, err := w.doProdPostTLS(ctx, bm, uri) if err != nil { return nil, err } @@ -295,7 +265,7 @@ func (w *Client) ProfitSharingQuery(ctx context.Context, bm gopay.BodyMap) (wxRs sign := w.getReleaseSign(w.ApiKey, bm.GetString("sign_type"), bm) bm.Set("sign", sign) } - bs, err := w.doProdPostPure(ctx, bm, profitSharingQuery, nil) + bs, err := w.doProdPostPure(ctx, bm, profitSharingQuery) if err != nil { return nil, err } @@ -316,7 +286,7 @@ func (w *Client) ProfitSharingAddReceiver(ctx context.Context, bm gopay.BodyMap) } // 设置签名类型,官方文档此接口只支持 HMAC_SHA256 bm.Set("sign_type", SignType_HMAC_SHA256) - bs, err := w.doProdPost(ctx, bm, profitSharingAddReceiver, nil) + bs, err := w.doProdPost(ctx, bm, profitSharingAddReceiver) if err != nil { return nil, err } @@ -337,7 +307,7 @@ func (w *Client) ProfitSharingRemoveReceiver(ctx context.Context, bm gopay.BodyM } // 设置签名类型,官方文档此接口只支持 HMAC_SHA256 bm.Set("sign_type", SignType_HMAC_SHA256) - bs, err := w.doProdPost(ctx, bm, profitSharingRemoveReceiver, nil) + bs, err := w.doProdPost(ctx, bm, profitSharingRemoveReceiver) if err != nil { return nil, err } @@ -361,11 +331,7 @@ func (w *Client) ProfitSharingFinish(ctx context.Context, bm gopay.BodyMap) (wxR } // 设置签名类型,官方文档此接口只支持 HMAC_SHA256 bm.Set("sign_type", SignType_HMAC_SHA256) - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - bs, err := w.doProdPost(ctx, bm, profitSharingFinish, tlsConfig) + bs, err := w.doProdPostTLS(ctx, bm, profitSharingFinish) if err != nil { return nil, err } @@ -394,11 +360,7 @@ func (w *Client) ProfitSharingReturn(ctx context.Context, bm gopay.BodyMap) (wxR } // 设置签名类型,官方文档此接口只支持 HMAC_SHA256 bm.Set("sign_type", SignType_HMAC_SHA256) - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - bs, err := w.doProdPost(ctx, bm, profitSharingReturn, tlsConfig) + bs, err := w.doProdPostTLS(ctx, bm, profitSharingReturn) if err != nil { return nil, err } @@ -424,7 +386,7 @@ func (w *Client) ProfitSharingReturnQuery(ctx context.Context, bm gopay.BodyMap) } // 设置签名类型,官方文档此接口只支持 HMAC_SHA256 bm.Set("sign_type", SignType_HMAC_SHA256) - bs, err := w.doProdPost(ctx, bm, profitSharingReturnQuery, nil) + bs, err := w.doProdPost(ctx, bm, profitSharingReturnQuery) if err != nil { return nil, err } diff --git a/wechat/oplatform_api.go b/wechat/oplatform_api.go index 25571fa6..2f3f21fa 100644 --- a/wechat/oplatform_api.go +++ b/wechat/oplatform_api.go @@ -21,7 +21,7 @@ import ( func GetOauth2AccessToken(ctx context.Context, appId, appSecret, code string) (accessToken *Oauth2AccessToken, err error) { accessToken = new(Oauth2AccessToken) url := "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code" - _, err = xhttp.NewClient().Get(url).EndStruct(ctx, accessToken) + _, err = xhttp.NewClient().Req().Get(url).EndStruct(ctx, accessToken) if err != nil { return nil, err } @@ -35,7 +35,7 @@ func GetOauth2AccessToken(ctx context.Context, appId, appSecret, code string) (a func RefreshOauth2AccessToken(ctx context.Context, appId, refreshToken string) (accessToken *Oauth2AccessToken, err error) { accessToken = new(Oauth2AccessToken) url := "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + appId + "&grant_type=refresh_token&refresh_token=" + refreshToken - _, err = xhttp.NewClient().Get(url).EndStruct(ctx, accessToken) + _, err = xhttp.NewClient().Req().Get(url).EndStruct(ctx, accessToken) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func RefreshOauth2AccessToken(ctx context.Context, appId, refreshToken string) ( func CheckOauth2AccessToken(ctx context.Context, accessToken, openid string) (result *CheckAccessTokenRsp, err error) { result = new(CheckAccessTokenRsp) url := "https://api.weixin.qq.com/sns/auth?access_token=" + accessToken + "&openid=" + openid - _, err = xhttp.NewClient().Get(url).EndStruct(ctx, result) + _, err = xhttp.NewClient().Req().Get(url).EndStruct(ctx, result) if err != nil { return nil, err } @@ -67,7 +67,7 @@ func GetOauth2UserInfo(ctx context.Context, accessToken, openId string, lang ... if len(lang) == 1 { url += "&lang=" + lang[0] } - _, err = xhttp.NewClient().Get(url).EndStruct(ctx, userInfo) + _, err = xhttp.NewClient().Req().Get(url).EndStruct(ctx, userInfo) if err != nil { return nil, err } diff --git a/wechat/papay.go b/wechat/papay.go index 09c049d2..85143358 100644 --- a/wechat/papay.go +++ b/wechat/papay.go @@ -33,7 +33,7 @@ func (w *Client) EntrustAppPre(ctx context.Context, bm gopay.BodyMap) (wxRsp *En if err != nil { return nil, err } - bs, err := w.doProdPost(ctx, bm, entrustApp, nil) + bs, err := w.doProdPost(ctx, bm, entrustApp) if err != nil { return nil, err } @@ -72,7 +72,7 @@ func (w *Client) EntrustPaying(ctx context.Context, bm gopay.BodyMap) (wxRsp *En if err != nil { return nil, err } - bs, err := w.doProdPost(ctx, bm, entrustPaying, nil) + bs, err := w.doProdPost(ctx, bm, entrustPaying) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func (w *Client) EntrustApplyPay(ctx context.Context, bm gopay.BodyMap) (wxRsp * if err != nil { return nil, err } - bs, err := w.doProdPost(ctx, bm, entrustApplyPay, nil) + bs, err := w.doProdPost(ctx, bm, entrustApplyPay) if err != nil { return nil, err } @@ -108,7 +108,7 @@ func (w *Client) EntrustDelete(ctx context.Context, bm gopay.BodyMap) (wxRsp *En if err != nil { return nil, err } - bs, err := w.doProdPost(ctx, bm, entrustDelete, nil) + bs, err := w.doProdPost(ctx, bm, entrustDelete) if err != nil { return nil, err } @@ -126,7 +126,7 @@ func (w *Client) EntrustQuery(ctx context.Context, bm gopay.BodyMap) (wxRsp *Ent if err != nil { return nil, err } - bs, err := w.doProdPost(ctx, bm, entrustQuery, nil) + bs, err := w.doProdPost(ctx, bm, entrustQuery) if err != nil { return nil, err } diff --git a/wechat/param.go b/wechat/param.go index 7b737347..9d406c5a 100644 --- a/wechat/param.go +++ b/wechat/param.go @@ -77,27 +77,16 @@ func (w *Client) addCertFileContentOrPath(certFile, keyFile, pkcs12File any) (er if err = checkCertFilePathOrContent(certFile, keyFile, pkcs12File); err != nil { return } - var config *tls.Config - if config, err = w.addCertConfig(certFile, keyFile, pkcs12File); err != nil { + config, err := w.addCertConfig(certFile, keyFile, pkcs12File) + if err != nil { return } - w.mu.Lock() - w.Certificate = &config.Certificates[0] - w.mu.Unlock() + w.tlsHc.SetTLSConfig(config) return } func (w *Client) addCertConfig(certFile, keyFile, pkcs12File any) (tlsConfig *tls.Config, err error) { if certFile == nil && keyFile == nil && pkcs12File == nil { - w.mu.RLock() - defer w.mu.RUnlock() - if w.Certificate != nil { - tlsConfig = &tls.Config{ - Certificates: []tls.Certificate{*w.Certificate}, - InsecureSkipVerify: true, - } - return tlsConfig, nil - } return nil, errors.New("cert parse failed or nil") } @@ -204,16 +193,21 @@ func GetReleaseSign(apiKey string, signType string, bm gopay.BodyMap) (sign stri // 获取微信支付正式环境Sign值 func (w *Client) getReleaseSign(apiKey string, signType string, bm gopay.BodyMap) (sign string) { - var h hash.Hash - if signType == SignType_HMAC_SHA256 { - h = hmac.New(sha256.New, []byte(apiKey)) - } else { - h = md5.New() - } signParams := bm.EncodeWeChatSignParams(apiKey) if w.DebugSwitch == gopay.DebugOn { xlog.Debugf("Wechat_Request_SignStr: %s", signParams) } + var h hash.Hash + if signType == SignType_HMAC_SHA256 { + h = w.sha256Hash + } else { + h = w.md5Hash + } + w.mu.Lock() + defer func() { + h.Reset() + w.mu.Unlock() + }() h.Write([]byte(signParams)) return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) } @@ -272,7 +266,7 @@ func getSanBoxSignKey(ctx context.Context, mchId, nonceStr, sign string) (key st reqs.Set("sign", sign) keyResponse := new(getSignKeyResponse) - _, err = xhttp.NewClient().Type(xhttp.TypeXML).Post(sandboxGetSignKey).SendString(GenerateXml(reqs)).EndStruct(ctx, keyResponse) + _, err = xhttp.NewClient().Req(xhttp.TypeXML).Post(sandboxGetSignKey).SendString(GenerateXml(reqs)).EndStruct(ctx, keyResponse) if err != nil { return util.NULL, err } diff --git a/wechat/payment_api.go b/wechat/payment_api.go index 29510d2c..8086d39d 100644 --- a/wechat/payment_api.go +++ b/wechat/payment_api.go @@ -162,7 +162,7 @@ func GetOpenIdByAuthCode(ctx context.Context, appId, mchId, apiKey, authCode, no bm.Set("sign", GetReleaseSign(apiKey, SignType_MD5, bm)) openIdRsp = new(OpenIdByAuthCodeRsp) - _, err = xhttp.NewClient().Type(xhttp.TypeXML).Post(url).SendString(GenerateXml(bm)).EndStruct(ctx, openIdRsp) + _, err = xhttp.NewClient().Req(xhttp.TypeXML).Post(url).SendString(GenerateXml(bm)).EndStruct(ctx, openIdRsp) if err != nil { return nil, err } diff --git a/wechat/red.go b/wechat/red.go index f8f8c970..e22ec0a6 100644 --- a/wechat/red.go +++ b/wechat/red.go @@ -34,12 +34,7 @@ func (w *Client) SendCashRed(ctx context.Context, bm gopay.BodyMap) (wxRsp *Send bm.Set("sign", sign) } - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - - bs, err := w.doProdPostPure(ctx, bm, sendCashRed, tlsConfig) + bs, err := w.doProdPostPureTLS(ctx, bm, sendCashRed) if err != nil { return nil, err } @@ -71,12 +66,7 @@ func (w *Client) SendGroupCashRed(ctx context.Context, bm gopay.BodyMap) (wxRsp bm.Set("sign", sign) } - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - - bs, err := w.doProdPostPure(ctx, bm, sendGroupCashRed, tlsConfig) + bs, err := w.doProdPostPureTLS(ctx, bm, sendGroupCashRed) if err != nil { return nil, err } @@ -108,12 +98,7 @@ func (w *Client) SendAppletRed(ctx context.Context, bm gopay.BodyMap) (wxRsp *Se bm.Set("sign", sign) } - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - - bs, err := w.doProdPostPure(ctx, bm, sendAppletRed, tlsConfig) + bs, err := w.doProdPostPureTLS(ctx, bm, sendAppletRed) if err != nil { return nil, err } @@ -145,12 +130,7 @@ func (w *Client) QueryRedRecord(ctx context.Context, bm gopay.BodyMap) (wxRsp *Q bm.Set("sign", sign) } - tlsConfig, err := w.addCertConfig(nil, nil, nil) - if err != nil { - return nil, err - } - - bs, err := w.doProdPostPure(ctx, bm, getRedRecord, tlsConfig) + bs, err := w.doProdPostPureTLS(ctx, bm, getRedRecord) if err != nil { return nil, err } diff --git a/wechat/v3/cert.go b/wechat/v3/cert.go index 9df8f68b..05c56c14 100644 --- a/wechat/v3/cert.go +++ b/wechat/v3/cert.go @@ -68,12 +68,12 @@ func GetPlatformCerts(ctx context.Context, mchid, apiV3Key, serialNo, privateKey authorization := Authorization + ` mchid="` + mchid + `",nonce_str="` + nonceStr + `",timestamp="` + ts + `",serial_no="` + serialNo + `",signature="` + sign + `"` // Request var url = v3BaseUrlCh + uri - httpClient := xhttp.NewClient() - httpClient.Header.Add(HeaderAuthorization, authorization) - httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) - httpClient.Header.Add(HeaderSerial, serialNo) - httpClient.Header.Add("Accept", "*/*") - res, bs, err := httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx) + hc := xhttp.NewClient().Req() + hc.Header.Add(HeaderAuthorization, authorization) + hc.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + hc.Header.Add(HeaderSerial, serialNo) + hc.Header.Add("Accept", "application/json") + res, bs, err := hc.Get(url).EndBytes(ctx) if err != nil { return nil, err } diff --git a/wechat/v3/client.go b/wechat/v3/client.go index 67547f0c..f795d473 100644 --- a/wechat/v3/client.go +++ b/wechat/v3/client.go @@ -3,15 +3,13 @@ package wechat import ( "context" "crypto/rsa" - "fmt" - "net/http" + "crypto/sha256" + "hash" "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" ) @@ -22,8 +20,9 @@ type ClientV3 struct { SerialNo string WxSerialNo string autoSign bool - bodySize int // http response body size(MB), default is 10MB rwMu sync.RWMutex + sha256Hash hash.Hash + hc *xhttp.Client privateKey *rsa.PrivateKey wxPublicKey *rsa.PublicKey ctx context.Context @@ -49,8 +48,10 @@ func NewClientV3(mchid, serialNo, apiV3Key, privateKey string) (client *ClientV3 SerialNo: serialNo, ApiV3Key: []byte(apiV3Key), privateKey: priKey, + sha256Hash: sha256.New(), ctx: context.Background(), DebugSwitch: gopay.DebugOff, + hc: xhttp.NewClient(), } return client, nil } @@ -86,240 +87,6 @@ func (c *ClientV3) AutoVerifySign(autoRefresh ...bool) (err error) { // SetBodySize 设置http response body size(MB) func (c *ClientV3) SetBodySize(sizeMB int) { if sizeMB > 0 { - c.bodySize = sizeMB + c.hc.SetBodySize(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 -} diff --git a/wechat/v3/request.go b/wechat/v3/request.go new file mode 100644 index 00000000..ce07c32a --- /dev/null +++ b/wechat/v3/request.go @@ -0,0 +1,232 @@ +package wechat + +import ( + "context" + "fmt" + "net/http" + "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" +) + +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 + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + for k, v := range headerMap { + req.Header.Add(k, v) + } + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_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 + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_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 + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_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 + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_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 + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_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 + req := c.hc.Req(xhttp.TypeMultipartFormData) + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_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 + req := c.hc.Req() // default json + req.Header.Add(HeaderAuthorization, authorization) + req.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + req.Header.Add(HeaderSerial, c.WxSerialNo) + req.Header.Add("Accept", "application/json") + if c.DebugSwitch == gopay.DebugOn { + xlog.Debugf("Wechat_V3_Url: %s", url) + xlog.Debugf("Wechat_V3_Req_Body: %s", bm.JsonBody()) + xlog.Debugf("Wechat_V3_Req_Headers: %#v", req.Header) + } + res, bs, err = req.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_V3_Response: %d > %s", res.StatusCode, string(bs)) + xlog.Debugf("Wechat_V3_Rsp_Headers: %#v", res.Header) + xlog.Debugf("Wechat_V3_SignInfo: %#v", si) + } + return res, si, bs, nil +} diff --git a/wechat/v3/sign.go b/wechat/v3/sign.go index eeccd1ce..e04ff063 100644 --- a/wechat/v3/sign.go +++ b/wechat/v3/sign.go @@ -254,9 +254,13 @@ func (c *ClientV3) rsaSign(str string) (string, error) { if c.privateKey == nil { return "", errors.New("privateKey can't be nil") } - h := sha256.New() - h.Write([]byte(str)) - result, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, h.Sum(nil)) + c.rwMu.Lock() + defer func() { + c.sha256Hash.Reset() + c.rwMu.Unlock() + }() + c.sha256Hash.Write([]byte(str)) + result, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, c.sha256Hash.Sum(nil)) if err != nil { return util.NULL, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err) } @@ -288,9 +292,13 @@ func (c *ClientV3) verifySyncSign(si *SignInfo) (err error) { } str := si.HeaderTimestamp + "\n" + si.HeaderNonce + "\n" + si.SignBody + "\n" signBytes, _ := base64.StdEncoding.DecodeString(si.HeaderSignature) - h := sha256.New() - h.Write([]byte(str)) - if err = rsa.VerifyPKCS1v15(wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil { + c.rwMu.Lock() + defer func() { + c.sha256Hash.Reset() + c.rwMu.Unlock() + }() + c.sha256Hash.Write([]byte(str)) + if err = rsa.VerifyPKCS1v15(wxPublicKey, crypto.SHA256, c.sha256Hash.Sum(nil), signBytes); err != nil { return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err) } return nil