diff --git a/platformapi/platformapi.go b/platformapi/platformapi.go index d6dbbf0e..37f0c684 100644 --- a/platformapi/platformapi.go +++ b/platformapi/platformapi.go @@ -116,9 +116,9 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. request.Header.Del(KeyTrackInfo) } trackInfo += ", " + utils.GetUUID() - baseapi.SugarLogger.Debugf("begin AccessPlatformAPIWithRetry:%s do:%s url:%v", trackInfo, request.Method, request.URL) + baseapi.SugarLogger.Debugf("begin AccessPlatformAPIWithRetry:%s do:%s url:%v, request:%s", trackInfo, request.Method, request.URL, getClonedData(request.URL, savedBuf)) response, err := client.Do(request) - baseapi.SugarLogger.Debugf("end AccessPlatformAPIWithRetry:%s do url:%v, request:%s", trackInfo, request.URL, getClonedData(request.URL, savedBuf)) + baseapi.SugarLogger.Debugf("end AccessPlatformAPIWithRetry:%s do:%s url:%v", trackInfo, request.Method, request.URL) if err != nil { baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s client.Get return err:%v", trackInfo, err) err, ok := err.(net.Error) @@ -126,13 +126,13 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. if ok /*&& err.Timeout()*/ && recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { // 只要是网络错误都重试 continue } else { - baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s access api url:%v, request:%v, error:%v", trackInfo, request.URL, getClonedData(request.URL, savedBuf), err) + baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s access api url:%v, error:%v", trackInfo, request.URL, err) return ErrAPIAccessFailed } } usedMilliSecond := time.Now().Sub(beginTime) / time.Millisecond if usedMilliSecond > 5000 { - baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s access api too slow, url:%v, request:%v, usedMilliSecond:%d", trackInfo, request.URL, getClonedData(request.URL, savedBuf), usedMilliSecond) + baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s access api too slow, url:%v, usedMilliSecond:%d", trackInfo, request.URL, usedMilliSecond) } defer response.Body.Close() if response.StatusCode != http.StatusOK { @@ -144,9 +144,9 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. } } if bodyData, err := ioutil.ReadAll(response.Body); err == nil { - baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s HTTP code is:%d, url:%v, request:%v, response:%s", trackInfo, response.StatusCode, request.URL, getClonedData(request.URL, savedBuf), string(bodyData)) + baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s HTTP code is:%d, url:%v, response:%s", trackInfo, response.StatusCode, request.URL, string(bodyData)) } else { - baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s ioutil.ReadAll failed, HTTP code is:%d, url:%v, request:%v, error:%v", trackInfo, response.StatusCode, request.URL, getClonedData(request.URL, savedBuf), err) + baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s ioutil.ReadAll failed, HTTP code is:%d, url:%v, error:%v", trackInfo, response.StatusCode, request.URL, err) } return ErrHTTPCodeIsNot200 } @@ -158,14 +158,13 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. ) bodyData, err := ioutil.ReadAll(response.Body) if err != nil { - baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s ioutil.ReadAll failed, url:%v, request:%v, error:%v", trackInfo, request.URL, getClonedData(request.URL, savedBuf), err) + baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s ioutil.ReadAll failed, url:%v, error:%v", trackInfo, request.URL, err) errLevel = ErrLevelRecoverableErr // 读取数据错误,或数据格式错误认为是偶发情况,重试 } else { + baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s url:%v, response:%s", trackInfo, request.URL, string(bodyData)) if err = utils.TryUnmarshalUseNumber(bodyData, &bodyGeneral); err != nil { parseJSONErr = err err = nil // 尝试忽略解析成json错 - } else { - baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s url:%v, response:%s", trackInfo, request.URL, utils.Format4Output(bodyGeneral, true)) } // 临时处理返回值居然不是map的情况 if bodyMap2, ok := bodyGeneral.(map[string]interface{}); ok { @@ -179,14 +178,14 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. } } errLevel, err = handleResponse(response, string(bodyData), bodyMap) - if err != nil && parseJSONErr != nil { + if err != nil && errLevel == ErrLevelRecoverableErr && parseJSONErr != nil { const maxOutputLen = 2000 bodyDataLen := len(bodyData) bodyData2 := bodyData if bodyDataLen > maxOutputLen { bodyData2 = bodyData2[:maxOutputLen] } - baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s TryUnmarshalUseNumber failed, url:%v, request:%v, error:%v, bodyData:%s", trackInfo, request.URL, getClonedData(request.URL, savedBuf), parseJSONErr, string(bodyData2)) + baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s TryUnmarshalUseNumber failed, url:%v, error:%v", trackInfo, request.URL, parseJSONErr) } } @@ -208,7 +207,7 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. continue } } - baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s failed, url:%v, response:%s, error:%v", trackInfo, request.URL, utils.Format4Output(bodyMap, true), err) + baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s failed, url:%v, error:%v", trackInfo, request.URL, err) return err } } diff --git a/platformapi/wxpayapi/callback.go b/platformapi/wxpayapi/callback.go index 365274d3..acb4c27c 100644 --- a/platformapi/wxpayapi/callback.go +++ b/platformapi/wxpayapi/callback.go @@ -37,6 +37,16 @@ type CallbackResponse struct { type BaseResultMsg 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"` + ResultMsg string `json:"result_msg" xml:"result_msg"` + ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` + ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` } type PayResultMsg struct { @@ -49,6 +59,7 @@ type PayResultMsg struct { NonceStr string `json:"nonce_str" xml:"nonce_str"` Sign string `json:"sign" xml:"sign"` ResultCode string `json:"result_code" xml:"result_code"` + ResultMsg string `json:"result_msg" xml:"result_msg"` ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` @@ -90,6 +101,7 @@ type RefundResultMsg struct { NonceStr string `json:"nonce_str" xml:"nonce_str"` Sign string `json:"sign" xml:"sign"` ResultCode string `json:"result_code" xml:"result_code"` + ResultMsg string `json:"result_msg" xml:"result_msg"` ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` @@ -147,49 +159,47 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR } func (a *API) getCallbackMsg(msgBody string) (msg *CallbackMsg, callbackResponse *CallbackResponse) { - mapData, _, err := a.checkResultAsMap(msgBody) + mapData, _, err := a.parseXmlStrAsMap(msgBody) if err != nil { return nil, Err2CallbackResponse(err, "") } + returnCode := utils.Interface2String(mapData["return_code"]) + if returnCode != ResponseCodeSuccess { // 如果return_code出错,直接返回 + return nil, SuccessResponse + } + + reqInfo := utils.Interface2String(mapData["req_info"]) + transactionID := utils.Interface2String(mapData["transaction_id"]) + if reqInfo == "" && transactionID != "" { // 对于支付结果通知进行签名验证(退款结果通知不支持验证) + 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, } - returnCode := utils.Interface2String(mapData["return_code"]) - if returnCode != ResponseCodeSuccess { - msg.Data = &BaseResultMsg{ - ReturnCode: returnCode, - ReturnMsg: utils.Interface2String(mapData["return_msg"]), - } - } else { - 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), "") - } - } - if 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.ReqInfoObj, false); err == nil { - msg.Data = refundResult - } + if 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.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 - } + } + } else if 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/wxpayapi/wxpayapi.go b/platformapi/wxpayapi/wxpayapi.go index 8f2b33fd..47f5468d 100644 --- a/platformapi/wxpayapi/wxpayapi.go +++ b/platformapi/wxpayapi/wxpayapi.go @@ -153,6 +153,11 @@ type CreateOrderParam struct { SceneInfo string `json:"scene_info,omitempty" xml:"scene_info,omitempty"` } +type CloseOrderParam struct { + RequestBase + OutTradeNo string `json:"out_trade_no" xml:"out_trade_no"` +} + type CreateOrderResult struct { ReturnCode string `json:"return_code" xml:"return_code"` ReturnMsg string `json:"return_msg" xml:"return_msg"` @@ -163,6 +168,7 @@ type CreateOrderResult struct { NonceStr string `json:"nonce_str" xml:"nonce_str"` Sign string `json:"sign" xml:"sign"` ResultCode string `json:"result_code" xml:"result_code"` + ResultMsg string `json:"result_msg" xml:"result_msg"` ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` @@ -195,6 +201,7 @@ type PayRefundResult struct { NonceStr string `json:"nonce_str" xml:"nonce_str"` Sign string `json:"sign" xml:"sign"` ResultCode string `json:"result_code" xml:"result_code"` + ResultMsg string `json:"result_msg" xml:"result_msg"` ErrCode string `json:"err_code,omitempty" xml:"err_code,omitempty"` ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` @@ -315,12 +322,19 @@ func (a *API) AccessAPI(action string, requestParam IRequestBase) (retVal map[st return retVal, err } -func (a *API) checkResultAsMap(xmlStr string) (result map[string]interface{}, errLevel string, err error) { +func (a *API) parseXmlStrAsMap(xmlStr string) (result map[string]interface{}, errLevel string, err error) { mv, err := mxj.NewMapXml([]byte(xmlStr)) if err != nil { errLevel = platformapi.ErrLevelGeneralFail } else { result = mv["xml"].(map[string]interface{}) + } + return result, errLevel, err +} + +func (a *API) checkResultAsMap(xmlStr string) (result map[string]interface{}, errLevel string, err error) { + result, errLevel, err = a.parseXmlStrAsMap(xmlStr) + if err == nil { returnCode := utils.Interface2String(result["return_code"]) if returnCode != ResponseCodeSuccess { errLevel = platformapi.ErrLevelGeneralFail @@ -344,6 +358,20 @@ func PayTime2Time(str string) (t time.Time) { return t } +func (a *API) mapData2Err(mapData map[string]interface{}) (err error) { + if resultCode := utils.Interface2String(mapData["result_code"]); resultCode != ResponseCodeSuccess { + err = utils.NewErrorCode(utils.Interface2String(mapData["err_code_des"]), utils.Interface2String(mapData["err_code"])) + } + return err +} + +func (a *API) translateResult(mapData map[string]interface{}, dataPtr interface{}) (err error) { + if err = a.mapData2Err(mapData); err == nil && dataPtr != nil { + err = utils.Map2StructByJson(mapData, dataPtr, false) + } + return err +} + func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo, err error) { param := &OrderQueryParam{ TransactionID: transactionID, @@ -351,7 +379,7 @@ func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo } retVal, err := a.AccessAPI("pay/orderquery", param) if err == nil { - err = utils.Map2StructByJson(retVal, &orderInfo, false) + err = a.translateResult(retVal, &orderInfo) } return orderInfo, err } @@ -359,18 +387,32 @@ func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo func (a *API) CreateUnifiedOrder(param *CreateOrderParam) (createOrderResult *CreateOrderResult, err error) { retVal, err := a.AccessAPI("pay/unifiedorder", param) if err == nil { - err = utils.Map2StructByJson(retVal, &createOrderResult, false) + err = a.translateResult(retVal, &createOrderResult) } return createOrderResult, err } +// 关闭订单 +// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3 +// 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。 +// 此函数好像可以重复操作,且关闭一个不存在的订单也不会报错的样 +func (a *API) CloseOrder(outTradeNo string) (err error) { + retVal, err := a.AccessAPI("pay/closeorder", &CloseOrderParam{ + OutTradeNo: outTradeNo, + }) + if err == nil { + err = a.translateResult(retVal, nil) + } + return 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) + err = a.translateResult(retVal, &refundResult) } return refundResult, err } diff --git a/platformapi/wxpayapi/wxpayapi_test.go b/platformapi/wxpayapi/wxpayapi_test.go index 98ae4cd4..964c2a63 100644 --- a/platformapi/wxpayapi/wxpayapi_test.go +++ b/platformapi/wxpayapi/wxpayapi_test.go @@ -36,8 +36,8 @@ func TestOrderQuery(t *testing.T) { } func TestCreateUnifiedOrder(t *testing.T) { - orderNo := "367609100BA711EAAA20186590E02977" // utils.GetUUID() - // t.Log(orderNo) + orderNo := utils.GetUUID() + t.Log(orderNo) result, err := api.CreateUnifiedOrder(&CreateOrderParam{ Body: "这里一个测试商品", NotifyURL: "http://callback.test.jxc4.com/wxpay/msg/", @@ -52,6 +52,15 @@ func TestCreateUnifiedOrder(t *testing.T) { t.Log(utils.Format4Output(result, false)) } +func TestCloseOrder(t *testing.T) { + orderNo := "5CA4BC5E22C611EA9AB9186590E02977" // utils.GetUUID() + // t.Log(orderNo) + err := api.CloseOrder(orderNo) + if err != nil { + t.Fatal(err) + } +} + func TestPayRefund(t *testing.T) { result, err := api.PayRefund(&PayRefundParam{ TransactionID: "",