diff --git a/platformapi/wxpay/callback.go b/platformapi/wxpay/callback.go index d279b0df..d80f2328 100644 --- a/platformapi/wxpay/callback.go +++ b/platformapi/wxpay/callback.go @@ -1,12 +1,17 @@ package wxpay import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/binary" "encoding/xml" "fmt" "io/ioutil" "net/http" "git.rosy.net.cn/baseapi/utils" + "github.com/clbanning/mxj" ) type CData string @@ -17,11 +22,85 @@ func (c CData) MarshalXML(e *xml.Encoder, start xml.StartElement) error { }{string(c)}, start) } +const ( + MsgTypeUnkown = 0 // 未知 + MsgTypePay = 1 // 支付结果回调 + MsgTypeRefund = 2 // 退款结果回调 +) + type CallbackResponse struct { XMLName xml.Name `json:"-" xml:"xml"` ReturnCode string `json:"return_code" xml:"return_code"` - ReturnMsg CData `json:"return_msg" xml:"return_msg"` + ReturnMsg CData `json:"return_msg" xml:"return_msg"` +} + +type BaseResultMsg struct { + ReturnCode string `json:"return_code" xml:"return_code"` + ReturnMsg string `json:"return_msg" xml:"return_msg"` +} + +type PayResultMsg 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"` + + BankType string `json:"bank_type"` + CashFee string `json:"cash_fee"` + FeeType string `json:"fee_type"` + IsSubscribe string `json:"is_subscribe"` + OpenID string `json:"openid"` + OutTradeNo string `json:"out_trade_no"` + TimeEnd string `json:"time_end"` + TotalFee string `json:"total_fee"` + TradeType string `json:"trade_type"` + TransactionID string `json:"transaction_id"` +} + +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"` + RefundID string `json:"refund_id"` + RefundRecvAccout string `json:"refund_recv_accout"` + RefundRequestSource string `json:"refund_request_source"` + RefundStatus string `json:"refund_status"` + SettlementRefundFee string `json:"settlement_refund_fee"` + SettlementTotalFee string `json:"settlement_total_fee"` + SuccessTime string `json:"success_time"` + TotalFee string `json:"total_fee"` + TransactionID string `json:"transaction_id"` +} + +type RefundResultMsg 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"` + + ReqInfo *RefundReqInfo `json:"req_info,omitempty" xml:"req_info,omitempty"` +} + +type CallbackMsg struct { + MsgType int + MapData map[string]interface{} + Data interface{} } var ( @@ -45,19 +124,69 @@ func Err2CallbackResponse(err error, data string) *CallbackResponse { } } -func (a *API) GetCallbackMsg(request *http.Request) (msg map[string]interface{}, callbackResponse *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]) + 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]) + } + } + } + return decryptedMsg, err +} + +func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackResponse *CallbackResponse) { data, err := ioutil.ReadAll(request.Body) if err != nil { return nil, Err2CallbackResponse(err, "") } - msg, _, err = a.checkResultAsMap(string(data)) + mapData, _, err := a.checkResultAsMap(string(data)) if err != nil { return nil, Err2CallbackResponse(err, "") } - sign := utils.Interface2String(msg[sigKey]) - desiredSign := a.signParam(msg) + sign := utils.Interface2String(mapData[sigKey]) + desiredSign := a.signParam(mapData) if desiredSign != sign { return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", desiredSign, sign), "") } - return msg, nil + msg = &CallbackMsg{ + MapData: mapData, + } + returnCode := utils.Interface2String(mapData["return_code"]) + if returnCode != ResponseCodeSuccess { + msg.Data = &BaseResultMsg{ + ReturnCode: returnCode, + 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 + } + } else if reqInfo := utils.Interface2String(mapData["req_info"]); reqInfo != "" { + msg.MsgType = MsgTypeRefund + var refundResult *RefundResultMsg + if err = utils.Map2StructByJson(mapData, &refundResult, false); err == nil { + if reqInfo, err = a.decodeReqInfo(reqInfo); err == nil { + 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 { + msg.Data = refundResult + } + } + } + } + } + } + if err != nil { + callbackResponse = Err2CallbackResponse(err, "") + } + return msg, callbackResponse } diff --git a/platformapi/wxpay/wxpay.go b/platformapi/wxpay/wxpay.go index 17df8908..a79d1f9c 100644 --- a/platformapi/wxpay/wxpay.go +++ b/platformapi/wxpay/wxpay.go @@ -8,6 +8,7 @@ import ( "net/http" "sort" "strings" + "time" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" @@ -86,25 +87,6 @@ func (r *RequestBase) SetSignType(signType string) { r.SignType = signType } -type ResponseResult struct { - // XMLName xml.Name `json:"-" xml:"xml"` - - 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"` - - SignType string `json:"sign_type,omitempty" xml:"sign_type,omitempty"` - OpenID string `json:"openid,omitempty" xml:"openid,omitempty"` -} - type OrderQueryParam struct { RequestBase TransactionID string `json:"transaction_id" xml:"transaction_id"` @@ -225,11 +207,6 @@ func (a *API) signParam(params map[string]interface{}) (sig string) { return sig } -// func unmarshalXML(data []byte, result interface{}) error { -// d := xml.NewDecoder(bytes.NewReader(data)) -// return d.Decode(result) -// } - func mustMarshalXML(obj interface{}) []byte { byteArr, err := xml.Marshal(obj) if err != nil { @@ -263,57 +240,6 @@ func (a *API) AccessAPI(action string, requestParam IRequestBase) (retVal map[st return retVal, err } -// func (a *API) checkResult(xmlStr string) (result *ResponseResult, errLevel string, err error) { -// err = unmarshalXML([]byte(xmlStr), &result) -// if err != nil { -// errLevel = platformapi.ErrLevelGeneralFail -// } else { -// if result.ReturnCode != ResponseCodeSuccess { -// errLevel = platformapi.ErrLevelGeneralFail -// err = utils.NewErrorCode(result.ReturnMsg, result.ReturnCode) -// result = nil -// } else { -// // if result.ResultCode != ResponseCodeSuccess { -// // errLevel = platformapi.ErrLevelGeneralFail -// // err = utils.NewErrorCode(result.ErrCodeDes, result.ErrCode) -// // result = nil -// // } else { -// // } -// } -// } -// return result, errLevel, err -// } - -// func (a *API) AccessAPIByMap(action string, params map[string]interface{}) (retVal map[string]interface{}, err error) { -// params2 := utils.MergeMaps(params, map[string]interface{}{ -// "appid": a.appID, -// "mch_id": a.mchID, -// "nonce_str": utils.GetUUID(), -// "sign_type": sigType, -// }) -// params2[sigKey] = a.signParam(params2) - -// fullURL := utils.GenerateGetURL(prodURL, action, nil) -// xmlBytes, err := mxj.Map(params2).Xml("xml") -// if err != nil { -// return nil, err -// } -// err = platformapi.AccessPlatformAPIWithRetry(a.client, -// func() *http.Request { -// request, _ := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(xmlBytes)) -// return request -// }, -// a.config, -// func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { -// if jsonResult1 == nil { -// return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") -// } -// retVal, errLevel, err = a.checkResultAsMap(jsonResult1[platformapi.KeyData].(string)) -// return errLevel, err -// }) -// return retVal, err -// } - func (a *API) checkResultAsMap(xmlStr string) (result map[string]interface{}, errLevel string, err error) { mv, err := mxj.NewMapXml([]byte(xmlStr)) if err != nil { @@ -341,6 +267,15 @@ func SceneInfoList2String(sceneList []*CreateOrderSceneInfo) (str string) { return string(utils.MustMarshal(sceneList)) } +func Time2PayTime(t time.Time) (str string) { + return t.Format("20060102150405") +} + +func PayTime2Time(str string) (t time.Time) { + t, _ = time.ParseInLocation("20060102150405", str, time.Local) + return t +} + func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo, err error) { param := &OrderQueryParam{ TransactionID: transactionID, diff --git a/platformapi/wxpay/wxpay_test.go b/platformapi/wxpay/wxpay_test.go index 3b4c3779..d3a96222 100644 --- a/platformapi/wxpay/wxpay_test.go +++ b/platformapi/wxpay/wxpay_test.go @@ -1,10 +1,12 @@ package wxpay import ( + "strings" "testing" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/utils" + "github.com/clbanning/mxj" "go.uber.org/zap" ) @@ -44,3 +46,29 @@ func TestCreateUnifiedOrder(t *testing.T) { } t.Log(utils.Format4Output(result, false)) } + +func TestXml2Json(t *testing.T) { + xmlStr := strings.Replace(` + + + + + + + + + + + + + + + + + `, "\\n", "\n", -1) + mv, err := mxj.NewMapXml([]byte(xmlStr)) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(mv, false)) +}