Files
baseapi/platformapi/mtpsapi/mtpsapi.go

405 lines
12 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 mtpsapi
import (
"crypto/sha1"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
mtpsAPIURL = "https://peisongopen.meituan.com/api"
signKey = "sign"
)
const (
OrderStatusWaitingForSchedule = 0
OrderStatusAccepted = 20
OrderStatusPickedUp = 30
OrderStatusDeliverred = 50
OrderStatusCanceled = 99
)
const (
OrderExceptCodePhonePoweroff = 10001
OrderExceptCodePhoneSuspended = 10002
OrderExceptCodePhoneNobody = 10003
OrderExceptCodePhoneEmpty = 10004
OrderExceptCodePhoneWrong = 10005
OrderExceptCodePhoneOther = 10006
OrderExceptCodeAddressWrong = 10101
OrderExceptCodeAddressOutOfService = 10201
OrderExceptCodeUserReject = 10202
OrderExceptCodeUserRequestDelay = 10203
OrderExceptCodeStoreClosed = 10401
)
var (
OrderStatusName = map[int]string{
OrderExceptCodePhonePoweroff: "电话关机",
OrderExceptCodePhoneSuspended: "电话停机",
OrderExceptCodePhoneNobody: "无人接听",
OrderExceptCodePhoneEmpty: "电话空号",
OrderExceptCodePhoneWrong: "电话错误",
OrderExceptCodePhoneOther: "电话其它原因",
OrderExceptCodeAddressWrong: "地址错误",
OrderExceptCodeAddressOutOfService: "地址超区",
OrderExceptCodeUserReject: "用户拒收",
OrderExceptCodeUserRequestDelay: "要求延迟配送",
OrderExceptCodeStoreClosed: "门店关店",
}
)
const (
DeliveryServiceCodeSuperRapid = 4002 // 飞速达
DeliveryServiceCodeRapid = 4011 // 快速达
DeliveryServiceCodeIntime = 4012 // 及时达
DeliveryServiceCodeTogether = 4013 // 集中送
)
const (
PickupTypeClientSendToStation = 1
PickupTypeMtPick = 2
)
const (
OrderTypeASAP = 0
OrderTypeBook = 1
)
const (
CoordinateTypeMars = 0 // 火星坐标(高德,腾讯地图均采用火星坐标),此为默认项
CoordinateTypeBaidu = 1 // 百度坐标
)
// 错误码
const (
ResponseCodeSuccess = 0
ResponseCodeInvalidParam = 4 // 参数格式错误
ResponseCodeFlowControl = 11 // 接口流控
ResponseCodeShopNotExist = 112 // 门店不存在
)
// 取消原因
const (
CancelReasonClientActive = 101
CancelReasonClientChangeTimeOrAddress = 102
CancelReasonGoodsRelated = 103
CancelReasonMerchantOther = 199
CancelReasonMtpsAttitude = 201
CancelReasonRidderSendNotIntime = 202
CancelReasonRideerGetGoodNotIntime = 203
CancelReasonRideerMtpsOther = 299
CancelReasonRideerOther = 399
)
type OrderInfoCommon struct {
DeliveryID int64
MtPeisongID string
OrderID string
CourierName string
CourierPhone string
}
type OrderInfo struct {
OrderInfoCommon
Status int
OperateTime int
CancelReasonID int
CancelReason string
}
type OrderResponse struct {
MtPeisongID string `json:"mt_peisong_id"`
DeliveryID int64 `json:"delivery_id"`
OrderID string `json:"order_id"`
}
type ResponseResult struct {
Code int `json:"code"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
type CreateOrderByShopParam struct {
// 以下为必要参数
DeliveryID int64 `json:"delivery_id"`
OrderID string `json:"order_id"`
ShopID string `json:"shop_id"`
DeliveryServiceCode int `json:"delivery_service_code"`
ReceiverName string `json:"receiver_name"`
ReceiverAddress string `json:"receiver_address"`
ReceiverPhone string `json:"receiver_phone"`
ReceiverLng int `json:"receiver_lng"`
ReceiverLat int `json:"receiver_lat"`
GoodsValue float64 `json:"goods_value"`
GoodsWeight float64 `json:"goods_weight"`
// 以下为可选参数
CoordinateType int `json:"coordinate_type"`
GoodsHeight float64 `json:"goods_height,omitempty"`
GoodsWidth float64 `json:"goods_width,omitempty"`
GoodsLength float64 `json:"goods_length,omitempty"`
GoodsDetail string `json:"goods_detail,omitempty"`
GoodsPickupInfo string `json:"goods_pickup_info,omitempty"`
GoodsDeliveryInfo string `json:"goods_delivery_info,omitempty"`
ExpectedPickupTime int64 `json:"expected_pickup_time,omitempty"` // 期望取货时间时区为GMT+8当前距离Epoch1970年1月1日) 以秒计算的时间即unix-timestamp。
// 期望送达时间时区为GMT+8当前距离Epoch1970年1月1日) 以秒计算的时间即unix-timestamp
// 即时单:以发单时间 + 服务包时效作为期望送达时间(当天送服务包需客户指定期望送达时间)
// 预约单:以客户传参数据为准(预约时间必须大于当前下单时间+服务包时效+3分钟
ExpectedDeliveryTime int64 `json:"expected_delivery_time,omitempty"`
OrderType int `json:"order_type,omitempty"`
PoiSeq string `json:"poi_seq,omitempty"`
Note string `json:"note,omitempty"`
CashOnDelivery float64 `json:"cash_on_delivery,omitempty"`
CashOnPickup float64 `json:"cash_on_pickup,omitempty"`
InvoiceTitle string `json:"invoice_title,omitempty"`
}
type GoodsItem struct {
GoodCount int `json:"goodCount"`
GoodName string `json:"goodName"`
GoodPrice float64 `json:"goodPrice"`
GoodUnit string `json:"goodUnit"`
}
type GoodsDetail struct {
Goods []*GoodsItem `json:"goods"`
}
type API struct {
platformapi.APICookie
appKey string
secret string
client *http.Client
config *platformapi.APIConfig
}
var (
exceedLimitCodes = map[int]int{
ResponseCodeFlowControl: 1,
}
)
func New(appKey, secret string, config ...*platformapi.APIConfig) *API {
curConfig := platformapi.DefAPIConfig
if len(config) > 0 {
curConfig = *config[0]
}
return &API{
appKey: appKey,
secret: secret,
client: &http.Client{Timeout: curConfig.ClientTimeout},
config: &curConfig,
}
}
func (a *API) signParams(params url.Values) string {
keys := make([]string, 0)
for k := range params {
if k != signKey {
keys = append(keys, k)
}
}
sort.Strings(keys)
finalStr := a.secret
for _, key := range keys {
valStr := strings.Join(params[key], "")
if valStr != "" {
finalStr += key + valStr
}
}
// baseapi.SugarLogger.Debug(finalStr)
return fmt.Sprintf("%x", sha1.Sum([]byte(finalStr)))
}
func (a *API) AccessAPI2(baseURL, action string, params map[string]interface{}) (retVal *ResponseResult, err error) {
if params == nil {
panic("params is nil!")
}
params2 := utils.Map2URLValues(params)
if baseURL == mtpsAPIURL {
params2.Set("appkey", a.appKey)
params2.Set("timestamp", utils.Int64ToStr(utils.GetCurTimestamp()))
params2.Set("version", "1.0")
params2.Set(signKey, a.signParams(params2))
} else if a.GetCookieCount() == 0 {
return nil, fmt.Errorf("需要设置Store Cookie才能使用此方法")
}
// baseapi.SugarLogger.Debug(params2.Encode())
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodPost, baseURL+"/"+action, strings.NewReader(params2.Encode()))
request.Header.Set("charset", "UTF-8")
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if baseURL != mtpsAPIURL {
a.FillRequestCookies(request)
}
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")
}
code := int(utils.MustInterface2Int64(jsonResult1["code"]))
retVal = &ResponseResult{
Code: code,
}
if code == ResponseCodeSuccess {
if innerData, ok := jsonResult1["data"]; ok {
retVal.Data, _ = innerData.(map[string]interface{})
}
return platformapi.ErrLevelSuccess, nil
}
msgKey := "message"
if baseURL != mtpsAPIURL {
msgKey = "msg"
}
retVal.Message, _ = jsonResult1[msgKey].(string)
newErr := utils.NewErrorIntCode(retVal.Message, code)
if _, ok := exceedLimitCodes[code]; ok {
return platformapi.ErrLevelExceedLimit, newErr
}
return platformapi.ErrLevelCodeIsNotOK, newErr
})
return retVal, err
}
func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *ResponseResult, err error) {
return a.AccessAPI2(mtpsAPIURL, action, params)
}
func (a *API) result2OrderResponse(result *ResponseResult) (order *OrderResponse) {
order = new(OrderResponse)
order.MtPeisongID = result.Data["mt_peisong_id"].(string)
order.DeliveryID = utils.MustInterface2Int64(result.Data["delivery_id"])
order.OrderID = result.Data["order_id"].(string)
return order
}
func (a *API) CreateOrderByShop(basicParams *CreateOrderByShopParam, addParams map[string]interface{}) (order *OrderResponse, err error) {
params := utils.Struct2MapByJson(basicParams)
params["goods_value"] = strconv.FormatFloat(basicParams.GoodsValue, 'f', 2, 64)
params["goods_weight"] = strconv.FormatFloat(basicParams.GoodsWeight, 'f', 2, 64)
allParams := utils.MergeMaps(params, addParams)
if params["order_type"] != utils.Int2Str(OrderTypeBook) {
delete(allParams, "expected_delivery_time")
}
if result, err := a.AccessAPI("order/createByShop", allParams); err != nil {
return nil, err
} else {
return a.result2OrderResponse(result), nil
}
}
func (a *API) CreateOrderByShop2(basicParams *CreateOrderByShopParam) (order *OrderResponse, err error) {
params := utils.Struct2MapByJson(basicParams)
if basicParams.OrderType != OrderTypeBook {
delete(params, "expected_delivery_time")
}
if result, err := a.AccessAPI("order/createByShop", params); err != nil {
return nil, err
} else {
return a.result2OrderResponse(result), nil
}
}
func (a *API) QueryOrderStatus(deliveryId int64, mtPeiSongId string) (retVal map[string]interface{}, err error) {
params := map[string]interface{}{
"delivery_id": deliveryId,
"mt_peisong_id": mtPeiSongId,
}
if result, err := a.AccessAPI("order/status/query", params); err != nil {
return nil, err
} else {
return result.Data, nil
}
}
func (a *API) CancelOrder(deliveryId int64, mtPeiSongId string, cancelReasonId int, cancelReason string) (result *OrderResponse, err error) {
params := map[string]interface{}{
"delivery_id": deliveryId,
"mt_peisong_id": mtPeiSongId,
"cancel_reason_id": cancelReasonId,
"cancel_reason": cancelReason,
}
if result, err := a.AccessAPI("order/delete", params); err != nil {
return nil, err
} else {
return a.result2OrderResponse(result), nil
}
}
func (a *API) simulateOrderBehavior(action string, deliveryId int64, mtPeiSongId string) (err error) {
params := map[string]interface{}{
"delivery_id": deliveryId,
"mt_peisong_id": mtPeiSongId,
}
_, err = a.AccessAPI("test/order/"+action, params)
return err
}
func (a *API) SimulateArrange(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("arrange", deliveryId, mtPeiSongId)
}
func (a *API) SimulatePickup(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("pickup", deliveryId, mtPeiSongId)
}
func (a *API) SimulateDeliver(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("deliver", deliveryId, mtPeiSongId)
}
func (a *API) SimulateRearrange(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("rearrange", deliveryId, mtPeiSongId)
}
func (a *API) SimulateReportException(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("reportException", deliveryId, mtPeiSongId)
}
//评价骑手
func (a *API) EvaluateRider(deliveryId int64, mtPeiSongId string, score int, commentContent string) (err error) {
params := map[string]interface{}{
"delivery_id": deliveryId,
"mt_peisong_id": mtPeiSongId,
"score": score,
"comment_content": commentContent,
}
_, err = a.AccessAPI("order/evaluate", params)
return err
}
// 获取骑手当前位置
// https://peisong.meituan.com/open/doc#section2-9
func (a *API) RiderLocation(deliveryId int64, mtPeiSongId string) (lng, lat int, err error) {
params := map[string]interface{}{
"delivery_id": deliveryId,
"mt_peisong_id": mtPeiSongId,
}
result, err := a.AccessAPI("order/rider/location", params)
if err == nil {
lng = int(utils.ForceInterface2Int64(result.Data["lng"]))
lat = int(utils.ForceInterface2Int64(result.Data["lat"]))
}
return lng, lat, err
}