From cf8079778a14adc3f8aa20f91166ec9cb3485063 Mon Sep 17 00:00:00 2001 From: Jerry <85411418@qq.com> Date: Thu, 31 Dec 2020 17:17:09 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81add=20new=20interface=202=E3=80=81fix?= =?UTF-8?q?=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- body_map.go | 1 + go.mod | 2 +- go.sum | 8 +- wechat/v3/cert.go | 2 +- wechat/v3/client_test.go | 94 +++++++++++-- wechat/v3/model.go | 142 +++++++++++++++---- wechat/v3/pay_public_api.go | 274 +++++++++++++++++++++++++++++++++--- wechat/v3/sign.go | 77 +++++++++- 8 files changed, 529 insertions(+), 71 deletions(-) diff --git a/body_map.go b/body_map.go index 4d8facde..c4db66b8 100644 --- a/body_map.go +++ b/body_map.go @@ -218,6 +218,7 @@ func (bm BodyMap) EncodeAliPaySignParams() string { return buf.String()[:buf.Len()-1] } +// ("bar=baz&foo=quux") func (bm BodyMap) EncodeGetParams() string { var ( buf strings.Builder diff --git a/go.mod b/go.mod index fcc3448f..01609599 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/iGoogle-ink/gopay go 1.13 require ( - github.com/iGoogle-ink/gotil v1.0.14-0.20201227112208-5bb46f75fce5 + github.com/iGoogle-ink/gotil v1.0.14-0.20201231083614-08a507cfc2b3 github.com/pkg/errors v0.9.1 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a ) diff --git a/go.sum b/go.sum index 5485e769..09a4feab 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iGoogle-ink/gotil v1.0.14-0.20201227112208-5bb46f75fce5 h1:QK3Bo95W5pz7lcrik09HUcu20UsLtyLRnrxTSUc0jB4= -github.com/iGoogle-ink/gotil v1.0.14-0.20201227112208-5bb46f75fce5/go.mod h1:mMNhhc9QSCZVtnfs/DXJBcUvE11ngkcTPNA0K9KREpI= +github.com/iGoogle-ink/gotil v1.0.14-0.20201231083614-08a507cfc2b3 h1:oiCYpwDeDM9fI7MhHz7cXexq6LrpOd6dI/Mp10uZKJs= +github.com/iGoogle-ink/gotil v1.0.14-0.20201231083614-08a507cfc2b3/go.mod h1:I7xZY7ewakSnpIZyREMGfjrG2m6YPJuBCZCpc9Ouffs= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -252,6 +252,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -700,6 +701,9 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI= +gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.9/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/wechat/v3/cert.go b/wechat/v3/cert.go index 6906294c..ece2964f 100644 --- a/wechat/v3/cert.go +++ b/wechat/v3/cert.go @@ -32,7 +32,7 @@ func (c *ClientV3) GetPlatformCerts() (certs *PlatformCertRsp, err error) { if err != nil { return nil, err } - certs = &PlatformCertRsp{StatusCode: res.StatusCode, SignInfo: si} + certs = &PlatformCertRsp{Code: res.StatusCode, SignInfo: si} if res.StatusCode != http.StatusOK { certs.Error = string(bs) return certs, nil diff --git a/wechat/v3/client_test.go b/wechat/v3/client_test.go index 87906ce3..73fab892 100644 --- a/wechat/v3/client_test.go +++ b/wechat/v3/client_test.go @@ -49,12 +49,13 @@ func TestGetPlatformCerts(t *testing.T) { xlog.Error(err) return } - xlog.Debug("certs.StatusCode", certs.StatusCode) - xlog.Debug("certs.SignInfo", certs.SignInfo) - - for _, v := range certs.Certs { - xlog.Debug("cert:", v) + if certs.Code == Success { + for _, v := range certs.Certs { + xlog.Debug("cert:", v) + } + return } + xlog.Errorf("certs:%s", certs.Error) } func TestV3VerifySign(t *testing.T) { @@ -94,7 +95,11 @@ func TestV3Jsapi(t *testing.T) { xlog.Error(err) return } - xlog.Debug("wxRsp:", wxRsp) + if wxRsp.Code == Success { + xlog.Debugf("wxRsp:%#v", wxRsp.Response) + return + } + xlog.Errorf("wxRsp:%s", wxRsp.Error) } func TestV3Native(t *testing.T) { @@ -118,9 +123,11 @@ func TestV3Native(t *testing.T) { xlog.Error(err) return } - xlog.Debug("wxRsp.StatusCode:", wxRsp.StatusCode) - xlog.Debugf("wxRsp.SignInfo:%#v", wxRsp.SignInfo) - xlog.Debugf("wxRsp.Response:%#v", wxRsp.Response) + if wxRsp.Code == Success { + xlog.Debugf("wxRsp:%#v", wxRsp.Response) + return + } + xlog.Errorf("wxRsp:%s", wxRsp.Error) } func TestV3QueryOrder(t *testing.T) { @@ -130,8 +137,73 @@ func TestV3QueryOrder(t *testing.T) { xlog.Error(err) return } - if wxRsp.StatusCode == 200 { + if wxRsp.Code == Success { xlog.Debugf("wxRsp:%#v", wxRsp.Response) + return + } + xlog.Errorf("wxRsp:%s", wxRsp.Error) +} + +func TestV3CloseOrder(t *testing.T) { + wxRsp, err := client.V3TransactionCloseOrder("FY160932049419637602") + if err != nil { + xlog.Error(err) + return + } + if wxRsp.Code == Success { + xlog.Error("success") + return + } + xlog.Errorf("wxRsp:%s", wxRsp.Error) +} + +func TestV3BillTradeBill(t *testing.T) { + bm := make(gopay.BodyMap) + bm.Set("bill_date", "2020-12-30"). + Set("tar_type", "GZIP") + + wxRsp, err := client.V3BillTradeBill(bm) + if err != nil { + xlog.Error(err) + return + } + if wxRsp.Code == Success { + xlog.Debugf("wxRsp:%#v", wxRsp.Response) + return + } + xlog.Errorf("wxRsp:%s", wxRsp.Error) +} + +func TestV3BillFundFlowBill(t *testing.T) { + bm := make(gopay.BodyMap) + bm.Set("bill_date", "2020-12-30"). + Set("tar_type", "GZIP") + + wxRsp, err := client.V3BillFundFlowBill(bm) + if err != nil { + xlog.Error(err) + return + } + if wxRsp.Code == Success { + xlog.Debugf("wxRsp:%#v", wxRsp.Response) + return + } + xlog.Errorf("wxRsp:%s", wxRsp.Error) +} + +func TestV3BillDownLoadBill(t *testing.T) { + url := "https://api.mch.weixin.qq.com/v3/billdownload/file?token=4MWpG4bWfL3smAe2AeB8scfp1MN0LYORxW691-jI-wL9J9fA6F0qG0q66y44xrur&tartype=gzip" + fileBytes, err := client.V3BillDownLoadBill(url) + if err != nil { + xlog.Error(err) + return } - xlog.Debugf("wxRsp:%s", wxRsp.Error) + xlog.Debugf("fileBytes:%v", fileBytes) + + // 申请账单时采用 GZIP 压缩,返回 bytes 为压缩文件 + //err = ioutil.WriteFile("bill.zip", fileBytes, 0666) + //if err != nil { + // xlog.Error("ioutil.WriteFile:", err) + // return + //} } diff --git a/wechat/v3/model.go b/wechat/v3/model.go index d8730d0a..bbfd676c 100644 --- a/wechat/v3/model.go +++ b/wechat/v3/model.go @@ -3,6 +3,9 @@ package wechat type OrderNoType uint8 const ( + Success = 0 + SignTypeRSA = "RSA" + MethodPost = "POST" MethodGet = "GET" HeaderAuthorization = "Authorization" @@ -16,14 +19,26 @@ const ( v3BaseUrlCh = "https://api.mch.weixin.qq.com" // 中国国内 + // 基础H支付 v3GetCerts = "/v3/certificates" - v3ApiPayApp = "/v3/pay/transactions/app" - v3ApiJsapi = "/v3/pay/transactions/jsapi" - v3ApiNative = "/v3/pay/transactions/native" - v3ApiH5 = "/v3/pay/transactions/h5" - v3ApiQueryOrderTransactionId = "/v3/pay/transactions/id/%s" // transaction_id - v3ApiQueryOrderOutTradeNo = "/v3/pay/transactions/out-trade-no/%s" // out_trade_no - v3ApiCloseOrder = "/v3/pay/transactions/out-trade-no/%s/close" // out_trade_no + v3ApiPayApp = "/v3/pay/transactions/app" // APP 下单 + v3ApiJsapi = "/v3/pay/transactions/jsapi" // JSAPI 下单 + v3ApiNative = "/v3/pay/transactions/native" // Native 下单 + v3ApiH5 = "/v3/pay/transactions/h5" // H5 下单 + v3ApiQueryOrderTransactionId = "/v3/pay/transactions/id/%s" // transaction_id 查询订单 + v3ApiQueryOrderOutTradeNo = "/v3/pay/transactions/out-trade-no/%s" // out_trade_no 查询订单 + v3ApiCloseOrder = "/v3/pay/transactions/out-trade-no/%s/close" // out_trade_no 关闭订单 + v3ApiTradeBill = "/v3/bill/tradebill" // 申请交易账单 + v3ApiFundFlowBill = "/v3/bill/fundflowbill" // 申请资金账单 + v3ApiLevel2FundFlowBill = "/v3/ecommerce/bill/fundflowbill" // 申请二级商户资金账单 + + // 合单支付 + v3CombinePayApp = "/v3/combine-transactions/app" + v3CombinePayH5 = "/v3/combine-transactions/h5" + v3CombinePayJsapi = "/v3/combine-transactions/jsapi" + v3CombineNative = "/v3/combine-transactions/native" + v3CombineQuery = "/v3/combine-transactions/out-trade-no/%s" + v3CombineClose = "/v3/combine-transactions/out-trade-no/%s/close" // 订单号类型,1-微信订单号,2-商户订单号 TransactionId OrderNoType = 1 @@ -40,42 +55,95 @@ const ( ) type PlatformCertRsp struct { - StatusCode int `json:"-"` - SignInfo *SignInfo `json:"-"` - Certs []*PlatformCertItem `json:"certs"` - Error string `json:"-"` + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Certs []*PlatformCertItem `json:"certs"` + Error string `json:"-"` } // Prepay 支付Rsp type PrepayRsp struct { - StatusCode int `json:"-"` - SignInfo *SignInfo `json:"-"` - Response *Prepay `json:"response,omitempty"` - Error string `json:"-"` + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *Prepay `json:"response,omitempty"` + Error string `json:"-"` } // H5 支付Rsp type H5Rsp struct { - StatusCode int `json:"-"` - SignInfo *SignInfo `json:"-"` - Response *H5Url `json:"response,omitempty"` - Error string `json:"-"` + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *H5Url `json:"response,omitempty"` + Error string `json:"-"` } // Native 支付Rsp type NativeRsp struct { - StatusCode int `json:"-"` - SignInfo *SignInfo `json:"-"` - Response *Native `json:"response,omitempty"` - Error string `json:"-"` + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *Native `json:"response,omitempty"` + Error string `json:"-"` } // 查询订单 Rsp type QueryOrderRsp struct { - StatusCode int `json:"-"` - SignInfo *SignInfo `json:"-"` - Response *QueryOrder `json:"response,omitempty"` - Error string `json:"-"` + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *QueryOrder `json:"response,omitempty"` + Error string `json:"-"` +} + +// 关闭订单 Rsp +type CloseOrderRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Error string `json:"-"` +} + +// 交易、资金账单 Rsp +type BillRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *TradeBill `json:"response,omitempty"` + Error string `json:"-"` +} + +// 二级商户资金账单 Rsp +type Level2FundFlowBillRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *DownloadBill `json:"response,omitempty"` + Error string `json:"-"` +} + +// ==================================分割================================== + +type JSAPIPayParams struct { + AppId string `json:"appId"` + TimeStamp string `json:"timeStamp"` + NonceStr string `json:"nonceStr"` + Package string `json:"package"` + SignType string `json:"signType"` + PaySign string `json:"paySign"` +} + +type AppPayParams struct { + Appid string `json:"appid"` + Partnerid string `json:"partnerid"` + Prepayid string `json:"prepayid"` + Package string `json:"package"` + Noncestr string `json:"noncestr"` + Timestamp string `json:"timestamp"` + PaySign string `json:"paySign"` +} + +type AppletParams struct { + AppId string `json:"appId"` + TimeStamp string `json:"timeStamp"` + NonceStr string `json:"nonceStr"` + Package string `json:"package"` + SignType string `json:"signType"` + PaySign string `json:"paySign"` } // ==================================分割================================== @@ -178,3 +246,23 @@ type QueryOrder struct { SceneInfo *SceneInfo `json:"scene_info,omitempty"` // 支付场景描述 PromotionDetail []*PromotionDetail `json:"promotion_detail,omitempty"` // 优惠功能,享受优惠时返回该字段 } + +type TradeBill struct { + HashType string `json:"hash_type"` + HashValue string `json:"hash_value"` + DownloadUrl string `json:"download_url"` +} + +type BillDetail struct { + BillSequence int `json:"bill_sequence"` // 商户将多个文件按账单文件序号的顺序合并为完整的资金账单文件,起始值为1 + HashType string `json:"hash_type"` + HashValue string `json:"hash_value"` + DownloadUrl string `json:"download_url"` + EncryptKey string `json:"encrypt_key"` // 加密账单文件使用的加密密钥。密钥用商户证书的公钥进行加密,然后进行Base64编码 + Nonce string `json:"nonce"` // 加密账单文件使用的随机字符串 +} + +type DownloadBill struct { + DownloadBillCount int `json:"download_bill_count"` + DownloadBillList []*BillDetail `json:"download_bill_list"` +} diff --git a/wechat/v3/pay_public_api.go b/wechat/v3/pay_public_api.go index 797ba5b1..3982477f 100644 --- a/wechat/v3/pay_public_api.go +++ b/wechat/v3/pay_public_api.go @@ -2,20 +2,30 @@ package wechat import ( "encoding/json" - "errors" "fmt" "net/http" + "strings" "time" "github.com/iGoogle-ink/gopay" "github.com/iGoogle-ink/gotil" + "github.com/pkg/errors" ) // APP下单API +// Code = 0 is success // 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_1.shtml func (c *ClientV3) V3TransactionApp(bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) { ts := time.Now().Unix() nonceStr := gotil.GetRandomString(32) + if bm != nil { + if bm.Get("appid") == gotil.NULL { + bm.Set("appid", c.Appid) + } + if bm.Get("mchid") == gotil.NULL { + bm.Set("mchid", c.Mchid) + } + } authorization, err := c.authorization(MethodPost, v3ApiPayApp, nonceStr, ts, bm) if err != nil { return nil, err @@ -24,23 +34,33 @@ func (c *ClientV3) V3TransactionApp(bm gopay.BodyMap) (wxRsp *PrepayRsp, err err if err != nil { return nil, err } - wxRsp = &PrepayRsp{StatusCode: res.StatusCode, SignInfo: si} - if res.StatusCode != http.StatusOK { - wxRsp.Error = string(bs) - return wxRsp, nil - } + wxRsp = &PrepayRsp{Code: Success, SignInfo: si} wxRsp.Response = new(Prepay) if err = json.Unmarshal(bs, wxRsp.Response); err != nil { return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } return wxRsp, c.verifySyncSign(si) } // JSAPI/小程序下单API +// Code = 0 is success // 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_2.shtml func (c *ClientV3) V3TransactionJsapi(bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) { ts := time.Now().Unix() nonceStr := gotil.GetRandomString(32) + if bm != nil { + if bm.Get("appid") == gotil.NULL { + bm.Set("appid", c.Appid) + } + if bm.Get("mchid") == gotil.NULL { + bm.Set("mchid", c.Mchid) + } + } authorization, err := c.authorization(MethodPost, v3ApiJsapi, nonceStr, ts, bm) if err != nil { return nil, err @@ -49,23 +69,33 @@ func (c *ClientV3) V3TransactionJsapi(bm gopay.BodyMap) (wxRsp *PrepayRsp, err e if err != nil { return nil, err } - wxRsp = &PrepayRsp{StatusCode: res.StatusCode, SignInfo: si} - if res.StatusCode != http.StatusOK { - wxRsp.Error = string(bs) - return wxRsp, nil - } + wxRsp = &PrepayRsp{Code: Success, SignInfo: si} wxRsp.Response = new(Prepay) if err = json.Unmarshal(bs, wxRsp.Response); err != nil { return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } return wxRsp, c.verifySyncSign(si) } // Native下单API +// Code = 0 is success // 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_3.shtml func (c *ClientV3) V3TransactionNative(bm gopay.BodyMap) (wxRsp *NativeRsp, err error) { ts := time.Now().Unix() nonceStr := gotil.GetRandomString(32) + if bm != nil { + if bm.Get("appid") == gotil.NULL { + bm.Set("appid", c.Appid) + } + if bm.Get("mchid") == gotil.NULL { + bm.Set("mchid", c.Mchid) + } + } authorization, err := c.authorization(MethodPost, v3ApiNative, nonceStr, ts, bm) if err != nil { return nil, err @@ -74,23 +104,33 @@ func (c *ClientV3) V3TransactionNative(bm gopay.BodyMap) (wxRsp *NativeRsp, err if err != nil { return nil, err } - wxRsp = &NativeRsp{StatusCode: res.StatusCode, SignInfo: si} - if res.StatusCode != http.StatusOK { - wxRsp.Error = string(bs) - return wxRsp, nil - } + wxRsp = &NativeRsp{Code: Success, SignInfo: si} wxRsp.Response = new(Native) if err = json.Unmarshal(bs, wxRsp.Response); err != nil { return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } return wxRsp, c.verifySyncSign(si) } // H5下单API +// Code = 0 is success // 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_4.shtml func (c *ClientV3) V3TransactionH5(bm gopay.BodyMap) (wxRsp *H5Rsp, err error) { ts := time.Now().Unix() nonceStr := gotil.GetRandomString(32) + if bm != nil { + if bm.Get("appid") == gotil.NULL { + bm.Set("appid", c.Appid) + } + if bm.Get("mchid") == gotil.NULL { + bm.Set("mchid", c.Mchid) + } + } authorization, err := c.authorization(MethodPost, v3ApiH5, nonceStr, ts, bm) if err != nil { return nil, err @@ -99,19 +139,21 @@ func (c *ClientV3) V3TransactionH5(bm gopay.BodyMap) (wxRsp *H5Rsp, err error) { if err != nil { return nil, err } - wxRsp = &H5Rsp{StatusCode: res.StatusCode, SignInfo: si} - if res.StatusCode != http.StatusOK { - wxRsp.Error = string(bs) - return wxRsp, nil - } + wxRsp = &H5Rsp{Code: Success, SignInfo: si} wxRsp.Response = new(H5Url) if err = json.Unmarshal(bs, wxRsp.Response); err != nil { return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } return wxRsp, c.verifySyncSign(si) } // 查询订单API +// Code = 0 is success // 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml func (c *ClientV3) V3TransactionQueryOrder(orderNoType OrderNoType, orderNo string) (wxRsp *QueryOrderRsp, err error) { var ( @@ -136,14 +178,200 @@ func (c *ClientV3) V3TransactionQueryOrder(orderNoType OrderNoType, orderNo stri return nil, err } - wxRsp = &QueryOrderRsp{StatusCode: res.StatusCode, SignInfo: si} + wxRsp = &QueryOrderRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(QueryOrder) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode wxRsp.Error = string(bs) return wxRsp, nil } - wxRsp.Response = new(QueryOrder) + return wxRsp, c.verifySyncSign(si) +} + +// 关闭订单API +// Code = 0 is success +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_6.shtml +func (c *ClientV3) V3TransactionCloseOrder(tradeNo string) (wxRsp *CloseOrderRsp, err error) { + var ( + ts = time.Now().Unix() + nonceStr = gotil.GetRandomString(32) + url = fmt.Sprintf(v3ApiCloseOrder, tradeNo) + ) + bm := make(gopay.BodyMap) + bm.Set("mchid", c.Mchid) + authorization, err := c.authorization(MethodPost, url, nonceStr, ts, bm) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdPost(bm, url, authorization) + if err != nil { + return nil, err + } + + wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si} + if res.StatusCode != http.StatusNoContent { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } + return wxRsp, c.verifySyncSign(si) +} + +// 申请交易账单API +// Code = 0 is success +// 注意:账单日期不可写当天日期 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/bill/chapter3_1.shtml +func (c *ClientV3) V3BillTradeBill(bm gopay.BodyMap) (wxRsp *BillRsp, err error) { + var ( + ts = time.Now().Unix() + nonceStr = gotil.GetRandomString(32) + uri string + ) + if bm != nil { + if bm.Get("bill_date") == gotil.NULL { + now := time.Now() + yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(gotil.DateLayout) + bm.Set("bill_date", yesterday) + } + } + uri = v3ApiTradeBill + "?" + bm.EncodeGetParams() + authorization, err := c.authorization(MethodGet, uri, nonceStr, ts, nil) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdGet(uri, authorization) + if err != nil { + return nil, err + } + + wxRsp = &BillRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(TradeBill) if err = json.Unmarshal(bs, wxRsp.Response); err != nil { return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } return wxRsp, c.verifySyncSign(si) } + +// 申请资金账单API +// Code = 0 is success +// 注意:账单日期不可写当天日期 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/bill/chapter3_2.shtml +func (c *ClientV3) V3BillFundFlowBill(bm gopay.BodyMap) (wxRsp *BillRsp, err error) { + var ( + ts = time.Now().Unix() + nonceStr = gotil.GetRandomString(32) + uri string + ) + if bm != nil { + if bm.Get("bill_date") == gotil.NULL { + now := time.Now() + yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(gotil.DateLayout) + bm.Set("bill_date", yesterday) + } + } + uri = v3ApiFundFlowBill + "?" + bm.EncodeGetParams() + authorization, err := c.authorization(MethodGet, uri, nonceStr, ts, nil) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdGet(uri, authorization) + if err != nil { + return nil, err + } + + wxRsp = &BillRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(TradeBill) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } + return wxRsp, c.verifySyncSign(si) +} + +// 申请二级商户资金账单API +// Code = 0 is success +// 注意:账单日期不可写当天日期 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/bill/chapter3_2.shtml +func (c *ClientV3) V3BillLevel2FundFlowBill(bm gopay.BodyMap) (wxRsp *Level2FundFlowBillRsp, err error) { + var ( + ts = time.Now().Unix() + nonceStr = gotil.GetRandomString(32) + uri string + ) + if bm != nil { + if bm.Get("bill_date") == gotil.NULL { + now := time.Now() + yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(gotil.DateLayout) + bm.Set("bill_date", yesterday) + } + if bm.Get("account_type") == gotil.NULL { + bm.Set("account_type", "ALL") + } + if bm.Get("algorithm") == gotil.NULL { + bm.Set("algorithm", "AEAD_AES_256_GCM") + } + + } + uri = v3ApiLevel2FundFlowBill + "?" + bm.EncodeGetParams() + authorization, err := c.authorization(MethodGet, uri, nonceStr, ts, nil) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdGet(uri, authorization) + if err != nil { + return nil, err + } + + wxRsp = &Level2FundFlowBillRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(DownloadBill) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } + return wxRsp, c.verifySyncSign(si) +} + +// 下载账单API +// Code = 0 is success +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/bill/chapter3_3.shtml +func (c *ClientV3) V3BillDownLoadBill(downloadUrl string) (fileBytes []byte, err error) { + if downloadUrl == gopay.NULL { + return nil, errors.New("invalid download url") + } + var ( + ts = time.Now().Unix() + nonceStr = gotil.GetRandomString(32) + ) + split := strings.Split(downloadUrl, ".com") + if len(split) != 2 { + return nil, errors.New("invalid download url") + } + authorization, err := c.authorization(MethodGet, split[1], nonceStr, ts, nil) + if err != nil { + return nil, err + } + res, _, bs, err := c.doProdGet(split[1], authorization) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + return nil, errors.New(string(bs)) + } + return bs, nil +} diff --git a/wechat/v3/sign.go b/wechat/v3/sign.go index 119c36e1..da9d1d92 100644 --- a/wechat/v3/sign.go +++ b/wechat/v3/sign.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" + "time" "github.com/iGoogle-ink/gopay" "github.com/iGoogle-ink/gotil" @@ -42,18 +43,82 @@ func V3VerifySign(timestamp, nonce, signBody, sign, wxPkContent string) (err err return nil } +// PaySignOfJSAPI 获取 JSAPI paySign +func (c *ClientV3) PaySignOfJSAPI(prepayid string) (jsapi *JSAPIPayParams, err error) { + ts := gotil.Int642String(time.Now().Unix()) + nonceStr := gotil.GetRandomString(32) + prepayId := "prepay_id=" + prepayid + + _str := c.Appid + "\n" + ts + "\n" + nonceStr + "\n" + prepayId + "\n" + sign, err := c.rsaSign(_str) + if err != nil { + return nil, err + } + + jsapi = &JSAPIPayParams{ + AppId: c.Appid, + TimeStamp: ts, + NonceStr: nonceStr, + Package: prepayId, + SignType: SignTypeRSA, + PaySign: sign, + } + return jsapi, nil +} + +// PaySignOfApp 获取 App paySign +func (c *ClientV3) PaySignOfApp(prepayid string) (app *AppPayParams, err error) { + ts := gotil.Int642String(time.Now().Unix()) + nonceStr := gotil.GetRandomString(32) + prepayId := "prepay_id=" + prepayid + + _str := c.Appid + "\n" + ts + "\n" + nonceStr + "\n" + prepayId + "\n" + sign, err := c.rsaSign(_str) + if err != nil { + return nil, err + } + + app = &AppPayParams{ + Appid: c.Appid, + Partnerid: c.Mchid, + Prepayid: prepayid, + Package: "Sign=WXPay", + Noncestr: nonceStr, + Timestamp: ts, + PaySign: sign, + } + return app, nil +} + +// PaySignOfApp 获取 App paySign +func (c *ClientV3) PaySignOfApplet(prepayid string) (applet *AppletParams, err error) { + ts := gotil.Int642String(time.Now().Unix()) + nonceStr := gotil.GetRandomString(32) + prepayId := "prepay_id=" + prepayid + + _str := c.Appid + "\n" + ts + "\n" + nonceStr + "\n" + prepayId + "\n" + sign, err := c.rsaSign(_str) + if err != nil { + return nil, err + } + + applet = &AppletParams{ + AppId: c.Appid, + TimeStamp: ts, + NonceStr: nonceStr, + Package: prepayId, + SignType: SignTypeRSA, + PaySign: sign, + } + return applet, nil +} + // v3 鉴权请求Header func (c *ClientV3) authorization(method, path, nonceStr string, timestamp int64, bm gopay.BodyMap) (string, error) { var ( jb = "" ) if bm != nil { - if bm.Get("appid") == gotil.NULL { - bm.Set("appid", c.Appid) - } - if bm.Get("mchid") == gotil.NULL { - bm.Set("mchid", c.Mchid) - } jb = bm.JsonBody() } ts := gotil.Int642String(timestamp)