Files
baseapi/platformapi/mtpsapi/mtpsapi.go
2023-06-05 17:25:07 +08:00

526 lines
18 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"
"errors"
"fmt"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
const (
mtpsAPIURL = "https://peisongopen.meituan.com/api"
signKey = "sign"
)
const (
OrderStatusWaitingForSchedule = 0 // 0待调度
OrderStatusAccepted = 20 // 20已接单
OrderStatusPickedUp = 30 // 30已取货
OrderStatusDeliverred = 50 // 50已送达
OrderStatusCanceled = 99 // 99已取消
OrderStatusFakeToStore = 200 // 自定义假的到店
)
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 = 100029 // 快速达,时效型
DeliveryServiceLocalDa = 100009 // 同城达
DeliveryServiceCodeIntime = 100006 // 及时达(这个暂时没用)
DeliveryServiceCodeTogether = 4013 // 集中送
//DeliveryServiceCodeSuperRapid = 4002 // 飞速达
//DeliveryServiceCodeRapid = 4011 // 快速达
//DeliveryServiceCodeIntime = 100006 // 及时达
//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"`
DeliveryFee float64 `json:"delivery_fee"`
}
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
token 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) {
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才能使用此方法")
}
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodPost, baseURL+"/"+action, strings.NewReader(params2.Encode()))
request.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
request.Header.Set("Referer", "https://page.peisong.meituan.com/open/admin/poilist")
if baseURL != mtpsAPIURL {
request.Header.Set("csrfToken", a.token)
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)
}
// 获取美团配送内部订单id外部系统订单id美团配送费用(创建订单)
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)
order.DeliveryFee = utils.MustInterface2Float64(result.Data["delivery_fee"]) // 原价
return order
}
// 美团取消订单
func (a *API) result2OrderCancelOrderRes(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,
}
result, err := a.AccessAPI("order/status/query", params)
if err != nil {
return nil, err
}
result.Data["delivery_fee"] = utils.MustInterface2Float64(result.Data["delivery_fee"]) + (float64(utils.WayBillDeliveryMarkUp) / 100)
result.Data["pay_amount"] = utils.MustInterface2Float64(result.Data["pay_amount"])
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.result2OrderCancelOrderRes(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
}
type PreCreateByShopParam struct {
DeliveryID int64 `json:"delivery_id"`
OrderID string `json:"order_id"`
OuterOrderSourceDesc string `json:"outer_order_source_desc"` //101.美团(外卖&闪购) 102.饿了么 103.京东到家
OuterOrderSourceNo string `json:"outer_order_source_no"`
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"`
PayTypeCode int `json:"pay_type_code"`
GoodsValue float64 `json:"goods_value"`
GoodsWeight float64 `json:"goods_weight"`
ExpectedDeliveryTime int64 `json:"expected_delivery_time"`
// 以下为可选参数
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分钟
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"`
}
// 美团预发单
func (a *API) PreCreateByShop(basicParams *PreCreateByShopParam) (deliveryFee, baseDeliveryFee int64, err error) {
params := utils.Struct2MapByJson(basicParams)
result, err := a.AccessAPI("order/preCreateByShop", params)
if err != nil {
return 0, 0, err
}
deliveryFee = utils.Float64TwoInt64(utils.MustInterface2Float64(result.Data["delivery_fee"]) * 100) // 订单配送价格
baseDeliveryFee = utils.Float64TwoInt64(utils.MustInterface2Float64(result.Data["delivery_fee"]) * 100) // 订单优惠价格
//baseDeliveryFee = utils.Float64TwoInt64(utils.MustInterface2Float64(result.Data["base_delivery_fee"]) * 100) // 订单优惠价格
return deliveryFee, baseDeliveryFee, nil
}
// 预发单
func (a *API) CheckOrder(shopID, receiver_address string, receiver_lng, receiver_lat, mock_order_time int64) (result *ResponseResult, err error) {
result, err = a.AccessAPI("order/check", map[string]interface{}{
"shop_id": shopID,
"delivery_service_code": DeliveryServiceCodeRapid,
"receiver_address": receiver_address,
"receiver_lng": receiver_lng,
"receiver_lat": receiver_lat,
"mock_order_time": mock_order_time,
"check_type": 1,
})
return result, err
}
// 添加小费
func (a *API) AddTip(orderId, deliveryId string, tip int64) error {
result, err := a.AccessAPI("order/addTip", map[string]interface{}{
"delivery_id": orderId,
"mt_peisong_id": deliveryId,
"tip_amount": tip,
"serial_number": utils.Int64ToStr(time.Now().Unix()),
})
if err != nil {
return err
}
if result.Code != 0 {
return errors.New(result.Message)
}
return nil
}
func IsOpen(err error) bool {
if err == nil {
return true
} else if strings.Contains(err.Error(), "门店非营业状态") {
return false
}
return false
}
func IsExist(err error) bool {
if err == nil {
return true
} else if strings.Contains(err.Error(), "门店不存在") {
return false
}
return false
}