添加腾讯支持

This commit is contained in:
gazebo
2019-10-29 11:38:03 +08:00
parent a3cbf24a92
commit 45c280d8d6
4 changed files with 336 additions and 1 deletions

View File

@@ -166,7 +166,10 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.
// 临时处理返回值居然不是map的情况
if bodyMap2, ok := bodyGeneral.(map[string]interface{}); ok {
bodyMap = bodyMap2
} else if bodyGeneral != nil {
} else {
if bodyGeneral == nil {
bodyGeneral = string(bodyData)
}
bodyMap = map[string]interface{}{
KeyData: bodyGeneral,
}

View File

@@ -0,0 +1,63 @@
package wxpay
import (
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
"git.rosy.net.cn/baseapi/utils"
)
type CDData string
func (c CDData) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(struct {
string `xml:",cdata"`
}{string(c)}, start)
}
type CallbackResponse struct {
XMLName xml.Name `json:"-" xml:"xml"`
ReturnCode string `json:"return_code" xml:"return_code"`
ReturnMsg CDData `json:"return_msg" xml:"return_msg"`
}
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: CDData(returnMsg),
}
}
func (a *API) GetCallbackMsg(request *http.Request) (msg map[string]interface{}, callbackResponse *CallbackResponse) {
data, err := ioutil.ReadAll(request.Body)
if err != nil {
return nil, Err2CallbackResponse(err, "")
}
msg, _, err = a.checkResultAsMap(string(data))
if err != nil {
return nil, Err2CallbackResponse(err, "")
}
sign := utils.Interface2String(msg[sigKey])
desiredSign := a.signParam(msg)
if desiredSign != sign {
return nil, Err2CallbackResponse(fmt.Errorf("desiredSign:%s <> sign:%s", desiredSign, sign), "")
}
return msg, nil
}

238
platformapi/wxpay/wxpay.go Normal file
View File

@@ -0,0 +1,238 @@
package wxpay
import (
"bytes"
"crypto/md5"
"encoding/xml"
"fmt"
"net/http"
"sort"
"strings"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
"github.com/clbanning/mxj"
)
const (
prodURL = "https://api.mch.weixin.qq.com/pay"
sandboxURL = "https://api.mch.weixin.qq.com/sandboxnew/pay"
)
const (
ResponseCodeSuccess = "SUCCESS"
ResponseCodeFail = "FAIL"
sigKey = "sign"
sigTypeKey = "sign_type"
sigType = "MD5"
)
type RequestBase struct {
XMLName xml.Name `json:"-" xml:"xml"`
AppID string `json:"appid" xml:"appid"`
MchID string `json:"mch_id" xml:"mch_id"`
NonceStr string `json:"nonce_str" xml:"nonce_str"`
Sign string `json:"sign" xml:"sign"`
SignType string `json:"sign_type,omitempty" xml:"sign_type,omitempty"`
}
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"`
MchID string `json:"mch_id" xml:"mch_id"`
NonceStr string `json:"nonce_str" xml:"nonce_str"`
Sign string `json:"sign" xml:"sign"`
SignType string `json:"sign_type,omitempty" xml:"sign_type,omitempty"`
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"`
DeviceInfo string `json:"device_info,omitempty" xml:"device_info,omitempty"`
OpenID string `json:"openid,omitempty" xml:"openid,omitempty"`
}
type OrderQueryParam struct {
RequestBase
TransactionID string `json:"transaction_id" xml:"transaction_id"`
OutTradeNo string `json:"out_trade_no" xml:"out_trade_no"`
}
type API struct {
appID string
appKey string
mchID string
client *http.Client
config *platformapi.APIConfig
}
func New(appID, appKey, mchID string, config ...*platformapi.APIConfig) *API {
curConfig := platformapi.DefAPIConfig
if len(config) > 0 {
curConfig = *config[0]
}
return &API{
appID: appID,
appKey: appKey,
mchID: mchID,
client: &http.Client{Timeout: curConfig.ClientTimeout},
config: &curConfig,
}
}
func (a *API) GetAppID() string {
return a.appID
}
func (a *API) GetMchID() string {
return a.mchID
}
func (a *API) signParam(params map[string]interface{}) (sig string) {
var valueList []string
for k, v := range params {
if k != sigKey {
if str := fmt.Sprint(v); str != "" {
valueList = append(valueList, fmt.Sprintf("%s=%s", k, str))
}
}
}
sort.Sort(sort.StringSlice(valueList))
valueList = append(valueList, fmt.Sprintf("key=%s", a.appKey))
sig = strings.Join(valueList, "&")
sig = fmt.Sprintf("%X", md5.Sum([]byte(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 {
byteArr, err := xml.Marshal(obj)
if err != nil {
panic(fmt.Sprintf("err when Marshal obj:%v with error:%v", obj, err))
}
return byteArr
}
func (a *API) AccessAPI(action string, params interface{}, baseParam *RequestBase) (retVal *ResponseResult, err error) {
baseParam.AppID = a.appID
baseParam.MchID = a.mchID
baseParam.NonceStr = utils.GetUUID()
baseParam.SignType = sigType
baseParam.Sign = a.signParam(utils.Struct2FlatMap(params))
fullURL := utils.GenerateGetURL(prodURL, action, nil)
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(mustMarshalXML(params)))
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.checkResult(jsonResult1[platformapi.KeyData].(string))
return errLevel, 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) {
mv, err := mxj.NewMapXml([]byte(xmlStr))
if err != nil {
errLevel = platformapi.ErrLevelGeneralFail
} else {
result = mv["xml"].(map[string]interface{})
returnCode := utils.Interface2String(result["return_code"])
if returnCode != ResponseCodeSuccess {
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
}
func (a *API) OrderQuery(transactionID, outTradeNo string) (retVal map[string]interface{}, err error) {
// param := &OrderQueryParam{
// TransactionID: transactionID,
// OutTradeNo: outTradeNo,
// }
// retVal, err = a.AccessAPI("orderquery", param, &param.RequestBase)
param := map[string]interface{}{}
if transactionID != "" {
param["transaction_id"] = transactionID
} else {
param["out_trade_no"] = outTradeNo
}
retVal, err = a.AccessAPIByMap("orderquery", param)
return retVal, err
}

View File

@@ -0,0 +1,31 @@
package wxpay
import (
"testing"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
var (
api *API
sugarLogger *zap.SugaredLogger
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
baseapi.Init(sugarLogger)
api = New("wx4b5930c13f8b1170", "XKJPOIHJ233adf01KJIXlIeQDSDKFJAD", "1390686702")
}
func TestOrderQuery(t *testing.T) {
result, err := api.OrderQuery("4200000411201910186159598703", "")
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(result, false))
}