Files
baseapi/platformapi/wxpay/wxpay.go
gazebo ed010619f1 微信支付CreateUnifiedOrder
微信API,入统一为struct,出统一用mxj再转struct
2019-11-19 18:28:04 +08:00

363 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"
)
const (
FeeTypeCNY = "CNY"
)
const (
TradeTypeJSAPI = "JSAPI" // JSAPI支付或小程序支付
TradeTypeNative = "NATIVE"
TradeTypeAPP = "APP"
TradeTypeMicroPay = "MICROPAY"
)
type API struct {
appID string
appKey string
mchID string
client *http.Client
config *platformapi.APIConfig
}
type RequestBase struct {
XMLName xml.Name `json:"-" xml:"xml"`
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"`
SignType string `json:"sign_type,omitempty" xml:"sign_type,omitempty"`
}
type IRequestBase interface {
SetAppID(appID string)
SetMchID(mchID string)
SetNonceStr(nonceStr string)
SetSign(sign string)
SetSignType(signType string)
}
func (r *RequestBase) SetAppID(appID string) {
r.AppID = appID
}
func (r *RequestBase) SetMchID(mchID string) {
r.MchID = mchID
}
func (r *RequestBase) SetNonceStr(nonceStr string) {
r.NonceStr = nonceStr
}
func (r *RequestBase) SetSign(sign string) {
r.Sign = sign
}
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"`
OutTradeNo string `json:"out_trade_no" xml:"out_trade_no"`
}
type OrderInfo 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"`
Attach string `json:"attach"`
BankType string `json:"bank_type"`
CashFee string `json:"cash_fee"`
CashFeeType string `json:"cash_fee_type"`
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"`
TradeState string `json:"trade_state"`
TradeStateDesc string `json:"trade_state_desc"`
TradeType string `json:"trade_type"`
TransactionID string `json:"transaction_id"`
}
type CreateOrderSceneInfo struct {
ID string `json:"id"`
Name string `json:"name"`
AreaCode string `json:"area_code"`
Address string `json:"address"`
}
type CreateOrderParam struct {
RequestBase
Body string `json:"body" xml:"body"`
NotifyURL string `json:"notify_url" xml:"notify_url"`
OutTradeNo string `json:"out_trade_no" xml:"out_trade_no"`
SpbillCreateIP string `json:"spbill_create_ip" xml:"spbill_create_ip"`
TradeType string `json:"trade_type" xml:"trade_type"`
TotalFee int `json:"total_fee" xml:"total_fee"`
Detail CData `json:"detail.omitempty" xml:"detail,omitempty"`
Attach string `json:"attach,omitempty" xml:"attach,omitempty"`
FeeType string `json:"fee_type,omitempty" xml:"fee_type,omitempty"`
TimeStart string `json:"time_start,omitempty" xml:"time_start,omitempty"`
TimeExpire string `json:"time_expire,omitempty" xml:"time_expire,omitempty"`
GoodsTag string `json:"goods_tag,omitempty" xml:"goods_tag,omitempty"`
ProductID string `json:"product_id,omitempty" xml:"product_id,omitempty"`
LimitPay string `json:"limit_pay,omitempty" xml:"limit_pay,omitempty"`
OpenID string `json:"openid,omitempty" xml:"openid,omitempty"`
Receipt string `json:"receipt,omitempty" xml:"receipt,omitempty"`
SceneInfo string `json:"scene_info,omitempty" xml:"scene_info,omitempty"`
}
type CreateOrderResult 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"`
TradeType string `json:"trade_type"`
PrepayID string `json:"prepay_id"`
CodeURL string `json:"code_url"`
}
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, requestParam IRequestBase) (retVal map[string]interface{}, err error) {
requestParam.SetAppID(a.appID)
requestParam.SetMchID(a.mchID)
requestParam.SetNonceStr(utils.GetUUID())
requestParam.SetSignType(sigType)
requestParam.SetSign(a.signParam(utils.Struct2FlatMap(requestParam)))
fullURL := utils.GenerateGetURL(prodURL, action, nil)
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(mustMarshalXML(requestParam)))
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) 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 SceneInfoList2String(sceneList []*CreateOrderSceneInfo) (str string) {
return string(utils.MustMarshal(sceneList))
}
func (a *API) OrderQuery(transactionID, outTradeNo string) (orderInfo *OrderInfo, err error) {
param := &OrderQueryParam{
TransactionID: transactionID,
OutTradeNo: outTradeNo,
}
retVal, err := a.AccessAPI("orderquery", param)
if err == nil {
err = utils.Map2StructByJson(retVal, &orderInfo, false)
}
return orderInfo, err
}
func (a *API) CreateUnifiedOrder(param *CreateOrderParam) (createOrderResult *CreateOrderResult, err error) {
retVal, err := a.AccessAPI("unifiedorder", param)
if err == nil {
err = utils.Map2StructByJson(retVal, &createOrderResult, false)
}
return createOrderResult, err
}