From 39c5845b2f99fc6de0b51ce17ed5bc6b954b028b Mon Sep 17 00:00:00 2001 From: gazebo Date: Thu, 21 Nov 2019 09:36:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=80=80=E6=AC=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformapi/wxpay/callback.go | 55 ++++++++++--------- platformapi/wxpay/callback_test.go | 35 ++++++++++++ platformapi/wxpay/wxpay.go | 86 ++++++++++++++++++++++++++---- platformapi/wxpay/wxpay_test.go | 26 +++++++-- 4 files changed, 164 insertions(+), 38 deletions(-) create mode 100644 platformapi/wxpay/callback_test.go diff --git a/platformapi/wxpay/callback.go b/platformapi/wxpay/callback.go index d80f2328..46571ad6 100644 --- a/platformapi/wxpay/callback.go +++ b/platformapi/wxpay/callback.go @@ -1,17 +1,16 @@ package wxpay import ( - "bytes" - "crypto/md5" "encoding/base64" - "encoding/binary" "encoding/xml" "fmt" "io/ioutil" "net/http" + "strings" "git.rosy.net.cn/baseapi/utils" "github.com/clbanning/mxj" + "github.com/nanjishidu/gomini/gocrypto" ) type CData string @@ -69,7 +68,7 @@ type RefundReqInfo struct { OutRefundNo string `json:"out_refund_no"` OutTradeNo string `json:"out_trade_no"` RefundAccount string `json:"refund_account"` - RefundFee string `json:"refund_fee"` + RefundFee string `json:"refund_fee" xml:"refund_fee"` RefundID string `json:"refund_id"` RefundRecvAccout string `json:"refund_recv_accout"` RefundRequestSource string `json:"refund_request_source"` @@ -94,7 +93,8 @@ type RefundResultMsg struct { ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` - ReqInfo *RefundReqInfo `json:"req_info,omitempty" xml:"req_info,omitempty"` + ReqInfoStr string `json:"req_info,omitempty"` + ReqInfoObj *RefundReqInfo `json:"req_info_obj,omitempty"` } type CallbackMsg struct { @@ -127,13 +127,12 @@ func Err2CallbackResponse(err error, data string) *CallbackResponse { func (a *API) decodeReqInfo(msg string) (decryptedMsg string, err error) { binMsg, err := base64.StdEncoding.DecodeString(msg) if err == nil { - aesKey := []byte(fmt.Sprintf("%x", md5.Sum([]byte(a.appKey)))) - binResult, err2 := utils.AESCBCDecpryt(binMsg, aesKey, aesKey[:16]) + // aesKey := []byte(fmt.Sprintf("%x", md5.Sum([]byte(a.appKey)))) + // binResult, err2 := utils.AESCBCDecpryt(binMsg, aesKey, aesKey[:16]) + gocrypto.SetAesKey(strings.ToLower(gocrypto.Md5(a.appKey))) + binResult, err2 := gocrypto.AesECBDecrypt(binMsg) if err = err2; err == nil { - var msgLen int32 - if err = binary.Read(bytes.NewBuffer(binResult[16:]), binary.BigEndian, &msgLen); err == nil { - decryptedMsg = string(binResult[16+4 : 16+4+msgLen]) - } + decryptedMsg = string(binResult) } } return decryptedMsg, err @@ -144,15 +143,15 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR if err != nil { return nil, Err2CallbackResponse(err, "") } - mapData, _, err := a.checkResultAsMap(string(data)) + return a.getCallbackMsg(string(data)) +} + +func (a *API) getCallbackMsg(msgBody string) (msg *CallbackMsg, callbackResponse *CallbackResponse) { + mapData, _, err := a.checkResultAsMap(msgBody) if err != nil { return nil, Err2CallbackResponse(err, "") } - sign := utils.Interface2String(mapData[sigKey]) - desiredSign := a.signParam(mapData) - if desiredSign != sign { - return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", desiredSign, sign), "") - } + msg = &CallbackMsg{ MapData: mapData, } @@ -163,13 +162,15 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR ReturnMsg: utils.Interface2String(mapData["return_msg"]), } } else { - if transactionID := utils.Interface2String(mapData["transaction_id"]); transactionID != "" { - msg.MsgType = MsgTypePay - var payResult *PayResultMsg - if err = utils.Map2StructByJson(mapData, &payResult, false); err == nil { - msg.Data = payResult + reqInfo := utils.Interface2String(mapData["req_info"]) + if reqInfo == "" { + sign := utils.Interface2String(mapData[sigKey]) + desiredSign := a.signParam(mapData) + if desiredSign != sign { + return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", desiredSign, sign), "") } - } else if reqInfo := utils.Interface2String(mapData["req_info"]); reqInfo != "" { + } + if reqInfo != "" { msg.MsgType = MsgTypeRefund var refundResult *RefundResultMsg if err = utils.Map2StructByJson(mapData, &refundResult, false); err == nil { @@ -177,12 +178,18 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR mv, err2 := mxj.NewMapXml([]byte(reqInfo)) if err = err2; err == nil { reqInfoMap := mv["root"].(map[string]interface{}) - if err = utils.Map2StructByJson(reqInfoMap, &refundResult.ReqInfo, false); err == nil { + if err = utils.Map2StructByJson(reqInfoMap, &refundResult.ReqInfoObj, false); err == nil { msg.Data = refundResult } } } } + } else if transactionID := utils.Interface2String(mapData["transaction_id"]); transactionID != "" { + msg.MsgType = MsgTypePay + var payResult *PayResultMsg + if err = utils.Map2StructByJson(mapData, &payResult, false); err == nil { + msg.Data = payResult + } } } if err != nil { diff --git a/platformapi/wxpay/callback_test.go b/platformapi/wxpay/callback_test.go new file mode 100644 index 00000000..ccdb5f65 --- /dev/null +++ b/platformapi/wxpay/callback_test.go @@ -0,0 +1,35 @@ +package wxpay + +import ( + "strings" + "testing" + + "git.rosy.net.cn/baseapi/utils" +) + +func TestPayCallback(t *testing.T) { + bodyMsg := strings.Replace(` + \n\n\n\n\n\n\n\n\n\n\n\n\n1\n\n\n + + + + `, "\\n", "\n", -1) + result, err := api.getCallbackMsg(bodyMsg) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestPayRefundCallback(t *testing.T) { + bodyMsg := strings.Replace(` + SUCCESS + + + `, "\\n", "\n", -1) + result, err := api.getCallbackMsg(bodyMsg) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/wxpay/wxpay.go b/platformapi/wxpay/wxpay.go index a79d1f9c..8dcb64ee 100644 --- a/platformapi/wxpay/wxpay.go +++ b/platformapi/wxpay/wxpay.go @@ -3,6 +3,7 @@ package wxpay import ( "bytes" "crypto/md5" + "crypto/tls" "encoding/xml" "fmt" "net/http" @@ -16,8 +17,8 @@ import ( ) const ( - prodURL = "https://api.mch.weixin.qq.com/pay" - sandboxURL = "https://api.mch.weixin.qq.com/sandboxnew/pay" + prodURL = "https://api.mch.weixin.qq.com" + sandboxURL = "https://api.mch.weixin.qq.com/sandboxnew" ) const ( @@ -169,6 +170,46 @@ type CreateOrderResult struct { CodeURL string `json:"code_url"` } +type PayRefundParam struct { + RequestBase + + TransactionID string `json:"transaction_id,omitempty" xml:"transaction_id,omitempty"` + OutTradeNo string `json:"out_trade_no,omitempty" xml:"out_trade_no,omitempty"` + OutRefundNo string `json:"out_refund_no" xml:"out_refund_no"` + TotalFee int `json:"total_fee" xml:"total_fee"` + RefundFee int `json:"refund_fee" xml:"refund_fee"` + + RefundFeeType string `json:"refund_fee_type,omitempty" xml:"refund_fee_type,omitempty"` + RefundDesc CData `json:"refund_desc,omitempty" xml:"refund_desc,omitempty"` + NotifyURL string `json:"notify_url,omitempty" xml:"notify_url,omitempty"` +} + +type PayRefundResult struct { + ReturnCode string `json:"return_code" xml:"return_code"` + ReturnMsg string `json:"return_msg" xml:"return_msg"` + + AppID string `json:"appid" xml:"appid"` + DeviceInfo string `json:"device_info,omitempty" xml:"device_info,omitempty"` + MchID string `json:"mch_id" xml:"mch_id"` + NonceStr string `json:"nonce_str" xml:"nonce_str"` + Sign string `json:"sign" xml:"sign"` + ResultCode string `json:"result_code" xml:"result_code"` + ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` + ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` + + CashFee string `json:"cash_fee"` + CashRefundFee string `json:"cash_refund_fee"` + CouponRefundCount string `json:"coupon_refund_count"` + CouponRefundFee string `json:"coupon_refund_fee"` + OutRefundNo string `json:"out_refund_no"` + OutTradeNo string `json:"out_trade_no"` + RefundChannel string `json:"refund_channel"` + RefundFee string `json:"refund_fee"` + RefundID string `json:"refund_id"` + TotalFee string `json:"total_fee"` + TransactionID string `json:"transaction_id"` +} + func New(appID, appKey, mchID string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { @@ -183,6 +224,19 @@ func New(appID, appKey, mchID string, config ...*platformapi.APIConfig) *API { } } +func NewWithCertificate(appID, appKey, mchID string, certPEMBlock, keyPEMBlock []byte, config ...*platformapi.APIConfig) (a *API) { + certs, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) + if err == nil { + a = New(appID, appKey, mchID, config...) + a.client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{certs}, + }, + } + } + return a +} + func (a *API) GetAppID() string { return a.appID } @@ -237,6 +291,12 @@ func (a *API) AccessAPI(action string, requestParam IRequestBase) (retVal map[st retVal, errLevel, err = a.checkResultAsMap(jsonResult1[platformapi.KeyData].(string)) return errLevel, err }) + if err == nil { + if utils.Interface2String(retVal["result_code"]) != ResponseCodeSuccess { + err = utils.NewErrorCode(utils.Interface2String(retVal["err_code_des"]), utils.Interface2String(retVal["err_code"])) + retVal = nil + } + } return retVal, err } @@ -251,13 +311,6 @@ func (a *API) checkResultAsMap(xmlStr string) (result map[string]interface{}, er errLevel = platformapi.ErrLevelGeneralFail err = utils.NewErrorCode(utils.Interface2String(result["return_msg"]), returnCode) result = nil - } else { - // if utils.Interface2String(result["result_code"]) != ResponseCodeSuccess { - // errLevel = platformapi.ErrLevelGeneralFail - // err = utils.NewErrorCode(utils.Interface2String(result["err_code_desc"]), utils.Interface2String(result["err_code"])) - // result = nil - // } else { - // } } } return result, errLevel, err @@ -281,7 +334,7 @@ func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo TransactionID: transactionID, OutTradeNo: outTradeNo, } - retVal, err := a.AccessAPI("orderquery", param) + retVal, err := a.AccessAPI("pay/orderquery", param) if err == nil { err = utils.Map2StructByJson(retVal, &orderInfo, false) } @@ -289,9 +342,20 @@ func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo } func (a *API) CreateUnifiedOrder(param *CreateOrderParam) (createOrderResult *CreateOrderResult, err error) { - retVal, err := a.AccessAPI("unifiedorder", param) + retVal, err := a.AccessAPI("pay/unifiedorder", param) if err == nil { err = utils.Map2StructByJson(retVal, &createOrderResult, false) } return createOrderResult, err } + +func (a *API) PayRefund(param *PayRefundParam) (refundResult *PayRefundResult, err error) { + if a.client.Transport == nil { + return nil, fmt.Errorf("没有配置证书,不能退款") + } + retVal, err := a.AccessAPI("secapi/pay/refund", param) + if err == nil { + err = utils.Map2StructByJson(retVal, &refundResult, false) + } + return refundResult, err +} diff --git a/platformapi/wxpay/wxpay_test.go b/platformapi/wxpay/wxpay_test.go index d3a96222..834675f1 100644 --- a/platformapi/wxpay/wxpay_test.go +++ b/platformapi/wxpay/wxpay_test.go @@ -1,6 +1,7 @@ package wxpay import ( + "io/ioutil" "strings" "testing" @@ -20,8 +21,10 @@ func init() { logger, _ := zap.NewDevelopment() sugarLogger = logger.Sugar() baseapi.Init(sugarLogger) - - api = New("wx4b5930c13f8b1170", "XKJPOIHJ233adf01KJIXlIeQDSDKFJAD", "1390686702") + certPEMBlock, _ := ioutil.ReadFile("1390686702_20190115_cert/apiclient_cert.pem") + keyPEMBlock, _ := ioutil.ReadFile("1390686702_20190115_cert/apiclient_key.pem") + api = NewWithCertificate("wx4b5930c13f8b1170", "XKJPOIHJ233adf01KJIXlIeQDSDKFJAD", "1390686702", certPEMBlock, keyPEMBlock) + // api = New("wx4b5930c13f8b1170", "XKJPOIHJ233adf01KJIXlIeQDSDKFJAD", "1390686702") } func TestOrderQuery(t *testing.T) { @@ -33,10 +36,12 @@ func TestOrderQuery(t *testing.T) { } func TestCreateUnifiedOrder(t *testing.T) { + orderNo := "367609100BA711EAAA20186590E02977" // utils.GetUUID() + // t.Log(orderNo) result, err := api.CreateUnifiedOrder(&CreateOrderParam{ Body: "这里一个测试商品", NotifyURL: "http://callback.test.jxc4.com/wxpay/msg/", - OutTradeNo: utils.GetUUID(), + OutTradeNo: orderNo, SpbillCreateIP: "114.114.114.114", TradeType: TradeTypeNative, TotalFee: 1, @@ -47,6 +52,21 @@ func TestCreateUnifiedOrder(t *testing.T) { t.Log(utils.Format4Output(result, false)) } +func TestPayRefund(t *testing.T) { + result, err := api.PayRefund(&PayRefundParam{ + TransactionID: "", + OutTradeNo: "8E0DD6260B7511EA908C186590E02977", + NotifyURL: "http://callback.test.jxc4.com/wxpay/msg/", + OutRefundNo: utils.GetUUID(), + TotalFee: 1, + RefundFee: 1, + }) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + func TestXml2Json(t *testing.T) { xmlStr := strings.Replace(`