添加wxpay回调消息处理
This commit is contained in:
@@ -1,12 +1,17 @@
|
|||||||
package wxpay
|
package wxpay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
"git.rosy.net.cn/baseapi/utils"
|
||||||
|
"github.com/clbanning/mxj"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CData string
|
type CData string
|
||||||
@@ -17,11 +22,85 @@ func (c CData) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
}{string(c)}, start)
|
}{string(c)}, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MsgTypeUnkown = 0 // 未知
|
||||||
|
MsgTypePay = 1 // 支付结果回调
|
||||||
|
MsgTypeRefund = 2 // 退款结果回调
|
||||||
|
)
|
||||||
|
|
||||||
type CallbackResponse struct {
|
type CallbackResponse struct {
|
||||||
XMLName xml.Name `json:"-" xml:"xml"`
|
XMLName xml.Name `json:"-" xml:"xml"`
|
||||||
|
|
||||||
ReturnCode string `json:"return_code" xml:"return_code"`
|
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 (
|
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)
|
data, err := ioutil.ReadAll(request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, Err2CallbackResponse(err, "")
|
return nil, Err2CallbackResponse(err, "")
|
||||||
}
|
}
|
||||||
msg, _, err = a.checkResultAsMap(string(data))
|
mapData, _, err := a.checkResultAsMap(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, Err2CallbackResponse(err, "")
|
return nil, Err2CallbackResponse(err, "")
|
||||||
}
|
}
|
||||||
sign := utils.Interface2String(msg[sigKey])
|
sign := utils.Interface2String(mapData[sigKey])
|
||||||
desiredSign := a.signParam(msg)
|
desiredSign := a.signParam(mapData)
|
||||||
if desiredSign != sign {
|
if desiredSign != sign {
|
||||||
return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi/platformapi"
|
"git.rosy.net.cn/baseapi/platformapi"
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
"git.rosy.net.cn/baseapi/utils"
|
||||||
@@ -86,25 +87,6 @@ func (r *RequestBase) SetSignType(signType string) {
|
|||||||
r.SignType = signType
|
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 {
|
type OrderQueryParam struct {
|
||||||
RequestBase
|
RequestBase
|
||||||
TransactionID string `json:"transaction_id" xml:"transaction_id"`
|
TransactionID string `json:"transaction_id" xml:"transaction_id"`
|
||||||
@@ -225,11 +207,6 @@ func (a *API) signParam(params map[string]interface{}) (sig string) {
|
|||||||
return sig
|
return sig
|
||||||
}
|
}
|
||||||
|
|
||||||
// func unmarshalXML(data []byte, result interface{}) error {
|
|
||||||
// d := xml.NewDecoder(bytes.NewReader(data))
|
|
||||||
// return d.Decode(result)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func mustMarshalXML(obj interface{}) []byte {
|
func mustMarshalXML(obj interface{}) []byte {
|
||||||
byteArr, err := xml.Marshal(obj)
|
byteArr, err := xml.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -263,57 +240,6 @@ func (a *API) AccessAPI(action string, requestParam IRequestBase) (retVal map[st
|
|||||||
return retVal, err
|
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) {
|
func (a *API) checkResultAsMap(xmlStr string) (result map[string]interface{}, errLevel string, err error) {
|
||||||
mv, err := mxj.NewMapXml([]byte(xmlStr))
|
mv, err := mxj.NewMapXml([]byte(xmlStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -341,6 +267,15 @@ func SceneInfoList2String(sceneList []*CreateOrderSceneInfo) (str string) {
|
|||||||
return string(utils.MustMarshal(sceneList))
|
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) {
|
func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo, err error) {
|
||||||
param := &OrderQueryParam{
|
param := &OrderQueryParam{
|
||||||
TransactionID: transactionID,
|
TransactionID: transactionID,
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package wxpay
|
package wxpay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi"
|
"git.rosy.net.cn/baseapi"
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
"git.rosy.net.cn/baseapi/utils"
|
||||||
|
"github.com/clbanning/mxj"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -44,3 +46,29 @@ func TestCreateUnifiedOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Log(utils.Format4Output(result, false))
|
t.Log(utils.Format4Output(result, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestXml2Json(t *testing.T) {
|
||||||
|
xmlStr := strings.Replace(`
|
||||||
|
<root>
|
||||||
|
<out_refund_no><![CDATA[131811191610442717309]]></out_refund_no>
|
||||||
|
<out_trade_no><![CDATA[71106718111915575302817]]></out_trade_no>
|
||||||
|
<refund_account><![CDATA[REFUND_SOURCE_RECHARGE_FUNDS]]></refund_account>
|
||||||
|
<refund_fee><![CDATA[3960]]></refund_fee>
|
||||||
|
<refund_id><![CDATA[50000408942018111907145868882]]></refund_id>
|
||||||
|
<refund_recv_accout><![CDATA[支付用户零钱]]></refund_recv_accout>
|
||||||
|
<refund_request_source><![CDATA[API]]></refund_request_source>
|
||||||
|
<refund_status><![CDATA[SUCCESS]]></refund_status>
|
||||||
|
<settlement_refund_fee><![CDATA[3960]]></settlement_refund_fee>
|
||||||
|
<settlement_total_fee><![CDATA[3960]]></settlement_total_fee>
|
||||||
|
<success_time><![CDATA[2018-11-19 16:24:13]]></success_time>
|
||||||
|
<total_fee><![CDATA[3960]]></total_fee>
|
||||||
|
<transaction_id><![CDATA[4200000215201811190261405420]]></transaction_id>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
`, "\\n", "\n", -1)
|
||||||
|
mv, err := mxj.NewMapXml([]byte(xmlStr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(utils.Format4Output(mv, false))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user