diff --git a/platformapi/wxpay/callback.go b/platformapi/wxpay/callback.go index caa3fdcb..d279b0df 100644 --- a/platformapi/wxpay/callback.go +++ b/platformapi/wxpay/callback.go @@ -9,9 +9,9 @@ import ( "git.rosy.net.cn/baseapi/utils" ) -type CDData string +type CData string -func (c CDData) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (c CData) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return e.EncodeElement(struct { string `xml:",cdata"` }{string(c)}, start) @@ -21,7 +21,7 @@ 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"` + ReturnMsg CData `json:"return_msg" xml:"return_msg"` } var ( @@ -41,7 +41,7 @@ func Err2CallbackResponse(err error, data string) *CallbackResponse { } return &CallbackResponse{ ReturnCode: ResponseCodeFail, - ReturnMsg: CDData(returnMsg), + ReturnMsg: CData(returnMsg), } } diff --git a/platformapi/wxpay/wxpay.go b/platformapi/wxpay/wxpay.go index bfd98e53..17df8908 100644 --- a/platformapi/wxpay/wxpay.go +++ b/platformapi/wxpay/wxpay.go @@ -28,33 +28,81 @@ const ( 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"` - 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"` + 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"` + // 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"` - 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"` + SignType string `json:"sign_type,omitempty" xml:"sign_type,omitempty"` + OpenID string `json:"openid,omitempty" xml:"openid,omitempty"` } type OrderQueryParam struct { @@ -63,12 +111,80 @@ type OrderQueryParam struct { 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 +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 { @@ -109,10 +225,10 @@ func (a *API) signParam(params map[string]interface{}) (sig string) { return sig } -func unmarshalXML(data []byte, result interface{}) error { - d := xml.NewDecoder(bytes.NewReader(data)) - return d.Decode(result) -} +// 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) @@ -122,69 +238,18 @@ func mustMarshalXML(obj interface{}) []byte { 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)) +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(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)) + request, _ := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(mustMarshalXML(requestParam))) return request }, a.config, @@ -198,6 +263,57 @@ func (a *API) AccessAPIByMap(action string, params map[string]interface{}) (retV 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 { @@ -221,18 +337,26 @@ func (a *API) checkResultAsMap(xmlStr string) (result map[string]interface{}, er 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, ¶m.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 +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 } diff --git a/platformapi/wxpay/wxpay_test.go b/platformapi/wxpay/wxpay_test.go index 3a81c1c0..3b4c3779 100644 --- a/platformapi/wxpay/wxpay_test.go +++ b/platformapi/wxpay/wxpay_test.go @@ -29,3 +29,18 @@ func TestOrderQuery(t *testing.T) { } t.Log(utils.Format4Output(result, false)) } + +func TestCreateUnifiedOrder(t *testing.T) { + result, err := api.CreateUnifiedOrder(&CreateOrderParam{ + Body: "这里一个测试商品", + NotifyURL: "http://callback.test.jxc4.com/wxpay/msg/", + OutTradeNo: utils.GetUUID(), + SpbillCreateIP: "114.114.114.114", + TradeType: TradeTypeNative, + TotalFee: 1, + }) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +}