service/vendor/github.com/go-pay/gopay/wechat/client.go

335 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package wechat
import (
"context"
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"strings"
"sync"
"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"
)
type Client struct {
AppId string
MchId string
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
}
// 初始化微信客户端 V2
// appId应用ID
// mchId商户ID
// ApiKeyAPI秘钥值
// IsProd是否是正式环境
func NewClient(appId, mchId, apiKey string, isProd bool) (client *Client) {
return &Client{
AppId: appId,
MchId: mchId,
ApiKey: apiKey,
IsProd: isProd,
DebugSwitch: gopay.DebugOff,
}
}
// SetBodySize 设置http response body size(MB)
func (w *Client) SetBodySize(sizeMB int) {
if sizeMB > 0 {
w.bodySize = sizeMB
}
}
// 向微信发送Post请求对于本库未提供的微信API可自行实现通过此方法发送请求
// bm请求参数的BodyMap
// path接口地址去掉baseURL的path例如url为https://api.mch.weixin.qq.com/pay/micropay只需传 pay/micropay
// tlsConfigtls配置如无需证书请求传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)
}
// 授权码查询openid正式
// 文档地址https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter4_8.shtml
func (w *Client) AuthCodeToOpenId(ctx context.Context, bm gopay.BodyMap) (wxRsp *AuthCodeToOpenIdResponse, err error) {
err = bm.CheckEmptyError("nonce_str", "auth_code")
if err != nil {
return nil, err
}
bs, err := w.doProdPost(ctx, bm, authCodeToOpenid, nil)
if err != nil {
return nil, err
}
wxRsp = new(AuthCodeToOpenIdResponse)
if err = xml.Unmarshal(bs, wxRsp); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
return wxRsp, nil
}
// 下载对账单
// 文档地址https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter3_6.shtml
func (w *Client) DownloadBill(ctx context.Context, bm gopay.BodyMap) (wxRsp string, err error) {
err = bm.CheckEmptyError("nonce_str", "bill_date", "bill_type")
if err != nil {
return util.NULL, err
}
billType := bm.GetString("bill_type")
if billType != "ALL" && billType != "SUCCESS" && billType != "REFUND" && billType != "RECHARGE_REFUND" {
return util.NULL, errors.New("bill_type error, please reference: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6")
}
var bs []byte
if w.IsProd {
bs, err = w.doProdPost(ctx, bm, downloadBill, nil)
} else {
bs, err = w.doSanBoxPost(ctx, bm, sandboxDownloadBill)
}
if err != nil {
return util.NULL, err
}
return string(bs), nil
}
// 下载资金账单(正式)
// 注意请在初始化client时调用 client 添加证书的相关方法添加证书
// 不支持沙箱环境因为沙箱环境默认需要用MD5签名但是此接口仅支持HMAC-SHA256签名
// 文档地址https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter3_7.shtml
func (w *Client) DownloadFundFlow(ctx context.Context, bm gopay.BodyMap) (wxRsp string, err error) {
err = bm.CheckEmptyError("nonce_str", "bill_date", "account_type")
if err != nil {
return util.NULL, err
}
accountType := bm.GetString("account_type")
if accountType != "Basic" && accountType != "Operation" && accountType != "Fees" {
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)
if err != nil {
return util.NULL, err
}
wxRsp = string(bs)
return
}
// 交易保障
// 文档地址JSAPIhttps://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter3_9.shtml
// 文档地址付款码https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter4_9.shtml
// 文档地址Nativehttps://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter6_9.shtml
// 文档地址APPhttps://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter7_9.shtml
// 文档地址H5https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter8_9.shtml
// 文档地址微信小程序https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter5_9.shtml
func (w *Client) Report(ctx context.Context, bm gopay.BodyMap) (wxRsp *ReportResponse, err error) {
err = bm.CheckEmptyError("nonce_str", "interface_url", "execute_time", "return_code", "return_msg", "result_code", "user_ip")
if err != nil {
return nil, err
}
var bs []byte
if w.IsProd {
bs, err = w.doProdPost(ctx, bm, report, nil)
} else {
bs, err = w.doSanBoxPost(ctx, bm, sandboxReport)
}
if err != nil {
return nil, err
}
wxRsp = new(ReportResponse)
if err = xml.Unmarshal(bs, wxRsp); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
return wxRsp, nil
}
// 拉取订单评价数据(正式)
// 注意请在初始化client时调用 client 添加证书的相关方法添加证书
// 不支持沙箱环境因为沙箱环境默认需要用MD5签名但是此接口仅支持HMAC-SHA256签名
// 文档地址https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/open/chapter3_11.shtml
func (w *Client) BatchQueryComment(ctx context.Context, bm gopay.BodyMap) (wxRsp string, err error) {
err = bm.CheckEmptyError("nonce_str", "begin_time", "end_time", "offset")
if err != nil {
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)
if err != nil {
return util.NULL, err
}
return string(bs), nil
}
// doSanBoxPost sanbox环境post请求
func (w *Client) doSanBoxPost(ctx context.Context, bm gopay.BodyMap, path string) (bs []byte, err error) {
var url = baseUrlCh + path
bm.Set("appid", w.AppId)
bm.Set("mch_id", w.MchId)
if bm.GetString("sign") == util.NULL {
bm.Set("sign_type", SignType_MD5)
sign, err := w.getSandBoxSign(ctx, w.MchId, w.ApiKey, bm)
if err != nil {
return nil, err
}
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().Type(xhttp.TypeXML)
if w.bodySize > 0 {
httpClient.SetBodySize(w.bodySize)
}
res, bs, err := httpClient.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, tlsConfig *tls.Config) (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)
}
httpClient := xhttp.NewClient()
if w.IsProd && tlsConfig != nil {
httpClient.SetTLSConfig(tlsConfig)
}
if w.bodySize > 0 {
httpClient.SetBodySize(w.bodySize)
}
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 := httpClient.Type(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, tlsConfig *tls.Config) (bs []byte, err error) {
var url = baseUrlCh + path
httpClient := xhttp.NewClient()
if w.IsProd && tlsConfig != nil {
httpClient.SetTLSConfig(tlsConfig)
}
if w.bodySize > 0 {
httpClient.SetBodySize(w.bodySize)
}
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 := httpClient.Type(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
}
// Get请求、正式
func (w *Client) doProdGet(ctx context.Context, bm gopay.BodyMap, path, signType 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)
}
bm.Remove("sign")
sign := w.getReleaseSign(w.ApiKey, signType, bm)
bm.Set("sign", sign)
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)
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
}