211 lines
6.9 KiB
Go
211 lines
6.9 KiB
Go
package wxpayapi
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"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
|
||
|
||
func (c CData) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||
return e.EncodeElement(struct {
|
||
string `xml:",cdata"`
|
||
}{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"`
|
||
}
|
||
|
||
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 {
|
||
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"`
|
||
|
||
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" xml:"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"`
|
||
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"`
|
||
|
||
ReqInfoStr string `json:"req_info,omitempty"`
|
||
ReqInfoObj *RefundReqInfo `json:"req_info_obj,omitempty"`
|
||
}
|
||
|
||
type CallbackMsg struct {
|
||
MsgType int
|
||
MapData map[string]interface{}
|
||
Data interface{}
|
||
}
|
||
|
||
var (
|
||
SuccessResponse = &CallbackResponse{
|
||
ReturnCode: ResponseCodeSuccess,
|
||
ReturnMsg: "OK",
|
||
}
|
||
)
|
||
|
||
func Err2CallbackResponse(err error, data string) *CallbackResponse {
|
||
if err == nil {
|
||
return SuccessResponse
|
||
}
|
||
returnMsg := err.Error()
|
||
if data != "" {
|
||
returnMsg = fmt.Sprintf("%s,%s", returnMsg, data)
|
||
}
|
||
return &CallbackResponse{
|
||
ReturnCode: ResponseCodeFail,
|
||
ReturnMsg: CData(returnMsg),
|
||
}
|
||
}
|
||
|
||
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])
|
||
gocrypto.SetAesKey(strings.ToLower(gocrypto.Md5(a.appKey)))
|
||
binResult, err2 := gocrypto.AesECBDecrypt(binMsg)
|
||
if err = err2; err == nil {
|
||
decryptedMsg = string(binResult)
|
||
}
|
||
}
|
||
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, "")
|
||
}
|
||
return a.getCallbackMsg(string(data))
|
||
}
|
||
|
||
func (a *API) getCallbackMsg(msgBody string) (msg *CallbackMsg, callbackResponse *CallbackResponse) {
|
||
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 != "" { // 对于支付结果通知进行签名验证(退款结果通知不支持验证)
|
||
sigType := utils.Interface2String(mapData[sigTypeKey])
|
||
sign := utils.Interface2String(mapData[sigKey])
|
||
desiredSign := a.signParam(sigType, mapData)
|
||
if desiredSign != sign {
|
||
return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", desiredSign, sign), "")
|
||
}
|
||
}
|
||
|
||
msg = &CallbackMsg{
|
||
MapData: mapData,
|
||
}
|
||
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 != "" {
|
||
msg.MsgType = MsgTypePay
|
||
var payResult *PayResultMsg
|
||
if err = utils.Map2StructByJson(mapData, &payResult, false); err == nil {
|
||
msg.Data = payResult
|
||
}
|
||
}
|
||
if err != nil {
|
||
callbackResponse = Err2CallbackResponse(err, "")
|
||
}
|
||
return msg, callbackResponse
|
||
}
|