Skip to content

Commit

Permalink
MCH API V3 + 交互中间件
Browse files Browse the repository at this point in the history
  • Loading branch information
blusewang committed Aug 17, 2021
1 parent f672e7e commit 3647140
Show file tree
Hide file tree
Showing 14 changed files with 434 additions and 183 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
*.out
.idea/

*.swp
*.swp

mch_v3_test.go
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ wechat weixin sdk,支持微信应用和商户。
log.Println(data)
```

# 商户账号API(V3版)
功能初步实现。

# 简易中间件
`SetClientMiddleware(middleware func(req *http.Request,res *http.Response,err error))` 将每一次网络交互过程都开放出来,以便做自定义日志。

# 为微信业务数据提供的额外工具方法
- `NewRandStr` 生成符合微信要求随机字符
- `LimitString` 限制长度,并将微信不支持的字符替换成'x',能满足公众号App的字符要求
Expand Down
37 changes: 37 additions & 0 deletions http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2021 YBCZ, Inc. All rights reserved.
//
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file in the root of the source
// tree.

package wx

import (
"net/http"
)

var _cli *http.Client
var _middleware func(req *http.Request, res *http.Response, err error)

type mt struct {
t http.Transport
}

func (m *mt) RoundTrip(req *http.Request) (res *http.Response, err error) {
res, err = m.t.RoundTrip(req)
if _middleware != nil {
_middleware(req, res, err)
}
return
}

func client() *http.Client {
if _cli == nil {
_cli = &http.Client{Transport: &mt{http.Transport{}}}
}
return _cli
}

func SetClientMiddleware(middleware func(req *http.Request, res *http.Response, err error)) {
_middleware = middleware
}
38 changes: 24 additions & 14 deletions mch.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

var cache = make(map[string]*http.Client)

// 商户账号
// MchAccount 商户账号
type MchAccount struct {
MchId string
MchKey string
Expand All @@ -36,17 +36,17 @@ type MchAccount struct {
MchRSAPublicKey []byte // 加密银行卡信息时用的公钥
}

// 创建请求
// NewMchReqWithApp 创建请求
func (ma MchAccount) NewMchReqWithApp(api mch_api.MchApi, appId string) (req *mchReq) {
return &mchReq{account: ma, privateClient: cache[ma.MchId], api: api, appId: appId}
}

// 创建请求
// NewMchReq 创建请求
func (ma MchAccount) NewMchReq(api mch_api.MchApi) (req *mchReq) {
return &mchReq{account: ma, privateClient: cache[ma.MchId], api: api}
}

// 订单签名给App
// OrderSign4App 订单签名给App
func (ma MchAccount) OrderSign4App(or mch_api.PayUnifiedOrderRes) map[string]interface{} {
data := make(map[string]interface{})
data["appid"] = or.AppId
Expand All @@ -60,7 +60,7 @@ func (ma MchAccount) OrderSign4App(or mch_api.PayUnifiedOrderRes) map[string]int
return data
}

// 订单签名,适用于H5、小程序
// OrderSign 订单签名,适用于H5、小程序
func (ma MchAccount) OrderSign(or mch_api.PayUnifiedOrderRes) map[string]interface{} {
data := make(map[string]interface{})
data["appId"] = or.AppId
Expand All @@ -75,7 +75,7 @@ func (ma MchAccount) OrderSign(or mch_api.PayUnifiedOrderRes) map[string]interfa
return data
}

// 验证支付成功通知
// PayNotify 验证支付成功通知
func (ma MchAccount) PayNotify(pn mch_api.PayNotify) bool {
if !pn.IsSuccess() || pn.Sign == "" {
return false
Expand All @@ -93,7 +93,7 @@ func (ma MchAccount) PayNotify(pn mch_api.PayNotify) bool {
return false
}

// 银行卡机要信息加密
// RsaEncrypt 银行卡机要信息加密
func (ma MchAccount) RsaEncrypt(plain string) (out string) {
block, _ := pem.Decode(ma.MchRSAPublicKey)
publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
Expand Down Expand Up @@ -146,13 +146,23 @@ func (ma MchAccount) newPrivateClient() (cli http.Client, err error) {
return
}
cert.PrivateKey = key
cli = http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
DisableCompression: true,
},
cli = *client()
cli.Transport.(*mt).t.TLSClientConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
cli.Transport.(*mt).t.DisableCompression = true
//cli = http.Client{
// Transport: &http.Transport{
// TLSClientConfig: &tls.Config{
// Certificates: []tls.Certificate{cert},
// },
// DisableCompression: true,
// },
//}
return
}

// NewMchReqV3 创建请求
func (ma MchAccount) NewMchReqV3(api mch_api.MchApi) (req *mchReqV3) {
return &mchReqV3{account: ma, api: api, hashHandler: sha256.New()}
}
14 changes: 7 additions & 7 deletions mch_api/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ type MchBaseResponse struct {
ErrCodeDes string `xml:"err_code_des,omitempty"`
}

// 是否成功处理
// IsSuccess 是否成功处理
func (m MchBaseResponse) IsSuccess() bool {
return m.ReturnCode == "SUCCESS" && m.ResultCode == "SUCCESS" && (m.ErrCode == "SUCCESS" || m.ErrCode == "")
}

// 如果出错,是否是微信程序错误
// IsUnCertain 如果出错,是否是微信程序错误
func (m MchBaseResponse) IsUnCertain() bool {
return m.ErrCode == "SYSTEMERROR"
}

// 转为Golang错误
// ToError 转为Golang错误
func (m MchBaseResponse) ToError() error {
if m.ErrCodeDes != "" {
return errors.New(fmt.Sprintf("%v %v", m.ErrCode, m.ErrCodeDes))
Expand All @@ -52,7 +52,7 @@ func (m MchBaseResponse) ToError() error {
}
}

// `ProfitSharing`设置为"Y"为分账定单标记。
// PayUnifiedOrderData `ProfitSharing`设置为"Y"为分账定单标记。
// 不设置,或设置为"N",为普通定单
type PayUnifiedOrderData struct {
MchBase
Expand All @@ -74,7 +74,7 @@ type PayUnifiedOrderRes struct {
PrepayId string `xml:"prepay_id"`
}

// 支付成功通知
// PayNotify 支付成功通知
type PayNotify struct {
MchBaseResponse
MchBase
Expand Down Expand Up @@ -108,7 +108,7 @@ type PayNotify struct {
TimeEnd string `xml:"time_end"`
}

// 回复支付成功通知
// PayNotifyRes 回复支付成功通知
type PayNotifyRes MchBaseResponse

type PayOrderQueryData struct {
Expand Down Expand Up @@ -141,7 +141,7 @@ type PayRefundRes struct {
CashFee int64 `xml:"cash_fee"`
}

// 分账结果中的接收者
// PayProfitSharingReceiver 分账结果中的接收者
type PayProfitSharingReceiver struct {
Type string `json:"type"`
Account string `json:"account"`
Expand Down
12 changes: 6 additions & 6 deletions mch_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,34 +32,34 @@ type mchReq struct {
err error
}

// 填充POST里的Body数据
// Send 填充POST里的Body数据
func (mr *mchReq) Send(data interface{}) *mchReq {
mr.sendData = data
return mr
}

// 使用 HMAC-SHA256 签名
// UseHMacSign 使用 HMAC-SHA256 签名
// 默认采用 MD5 签名
func (mr *mchReq) UseHMacSign() *mchReq {
mr.isHmacSign = true
return mr
}

// 使用私有证书通信
// UsePrivateCert 使用私有证书通信
func (mr *mchReq) UsePrivateCert() *mchReq {
mr.isPrivateClient = true
return mr
}

// 绑定请求结果的解码数据体
// Bind 绑定请求结果的解码数据体
func (mr *mchReq) Bind(data interface{}) *mchReq {
mr.res = data
return mr
}

// 执行
// Do 执行
func (mr *mchReq) Do() (err error) {
var cli = *http.DefaultClient
var cli http.Client
if mr.isPrivateClient {
if privateClientCache[mr.account.MchId] != nil {
cli = *privateClientCache[mr.account.MchId]
Expand Down
92 changes: 92 additions & 0 deletions mch_req_v3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2020 YBCZ, Inc. All rights reserved.
//
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file in the root of the source
// tree.

package wx

import (
"crypto"
rand2 "crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/blusewang/wx/mch_api"
"hash"
"net/http"
)

// 商户请求
type mchReqV3 struct {
account MchAccount
cert *x509.Certificate
privateKey *rsa.PrivateKey
hashHandler hash.Hash
api mch_api.MchApi
ts int64
nonceStr string
sendData interface{}
res interface{}
err error
}

// Send 填充POST里的Body数据
func (mr *mchReqV3) Send(data interface{}) *mchReqV3 {
mr.sendData = data
return mr
}

// Bind 绑定请求结果的解码数据体
func (mr *mchReqV3) Bind(data interface{}) *mchReqV3 {
mr.res = data
return mr
}

// Do 执行
func (mr *mchReqV3) Do() (err error) {

return
}

func (mr *mchReqV3) prepareCert() (err error) {
cb, _ := pem.Decode(mr.account.MchSSLCert)
mr.cert, err = x509.ParseCertificate(cb.Bytes)
if err != nil {
return
}
cb, _ = pem.Decode(mr.account.MchSSLKey)
key, err := x509.ParsePKCS8PrivateKey(cb.Bytes)
if err != nil {
return
}
mr.privateKey = key.(*rsa.PrivateKey)
mr.hashHandler = sha256.New()
return
}

func (mr *mchReqV3) sign(request *http.Request, body interface{}) (err error) {
raw, err := json.Marshal(body)
if err != nil {
return
}
mr.nonceStr = NewRandStr(32)
str := fmt.Sprintf("%v\n%v\n%v\n%v\n%v\n", request.Method, request.URL.Path, mr.ts, mr.nonceStr, string(raw))
mr.hashHandler.Reset()
mr.hashHandler.Write([]byte(str))
signRaw, err := rsa.SignPKCS1v15(rand2.Reader, mr.privateKey, crypto.SHA256, mr.hashHandler.Sum(nil))
if err != nil {
return
}

request.Header.Set("Authorization", fmt.Sprintf(`WECHATPAY2-SHA256-RSA2048 mchid="%v",nonce_str="%v",signature="%v",timestamp="%v",serial_no="%X"`,
mr.account.MchId, mr.nonceStr, base64.StdEncoding.EncodeToString(signRaw), mr.ts, mr.cert.SerialNumber))
request.Header.Set("User-Agent", "Gdb/1.0")
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
return
}
11 changes: 10 additions & 1 deletion mch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@
package wx

import (
"bytes"
"github.com/blusewang/wx/mch_api"
"io/ioutil"
"log"
"net/http"
"reflect"
"testing"
)

func TestMchAccount_NewMchReq(t *testing.T) {
log.SetFlags(log.Ltime | log.Lshortfile)
SetClientMiddleware(func(req *http.Request, res *http.Response, err error) {
log.Println(req, res, err)
raw, _ := ioutil.ReadAll(res.Body)
log.Println(string(raw))
res.Body = ioutil.NopCloser(bytes.NewReader(raw))
})
mch := MchAccount{
MchId: "",
MchKey: "",
Expand All @@ -35,7 +44,7 @@ func TestMchAccount_NewMchReq(t *testing.T) {
Description: "",
},
})
err := mch.NewMchReqWithApp(mch_api.PayProfitSharing, "").
err := mch.NewMchReqWithApp(mch_api.PayProfitSharing, "wxbb4d55eb95f282f4").
Send(&body).
UseHMacSign().
UsePrivateCert().
Expand Down
Loading

0 comments on commit 3647140

Please sign in to comment.