package mtps import ( "crypto/sha1" "errors" "fmt" "net/http" "net/url" "sort" "strings" "git.rosy.net.cn/baseapi/platformapi/mtpsapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxcallback/orderman" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/business/partner/delivery" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" "github.com/astaxie/beego/client/orm" beego "github.com/astaxie/beego/server/web" ) const ( // maxOrderPrice = 6399 // 单位为分,达达最大价格,超过这个价格配送费会增加 maxOrderWeight = 5000 // 5公斤 ) var ( ErrCanNotFindMTPSStore = errors.New("不能找到美团配送站点配置") ErrStoreNoPriceInfo = errors.New("找不到门店的美团配送价格信息") ) var ( curDeliveryHandler *DeliveryHandler ) type DeliveryHandler struct { } func init() { if api.MtpsAPI != nil { curDeliveryHandler = new(DeliveryHandler) partner.RegisterDeliveryPlatform(curDeliveryHandler, true) } } func (c *DeliveryHandler) GetVendorID() int { return model.VendorIDMTPS } func OnWaybillMsg(msg *mtpsapi.CallbackOrderMsg) (retVal *mtpsapi.CallbackResponse) { return curDeliveryHandler.OnWaybillMsg(msg) } func OnWaybillExcept(msg *mtpsapi.CallbackOrderExceptionMsg) (retVal *mtpsapi.CallbackResponse) { return curDeliveryHandler.OnWaybillExcept(msg) } func (c *DeliveryHandler) OnWaybillMsg(msg *mtpsapi.CallbackOrderMsg) (retVal *mtpsapi.CallbackResponse) { jxutils.CallMsgHandler(func() { retVal = c.onWaybillMsg(msg) }, jxutils.ComposeUniversalOrderID(msg.OrderID, model.VendorIDMTPS)) return retVal } func (c *DeliveryHandler) OnWaybillExcept(msg *mtpsapi.CallbackOrderExceptionMsg) (retVal *mtpsapi.CallbackResponse) { jxutils.CallMsgHandler(func() { order := &model.Waybill{ VendorWaybillID: msg.MtPeisongID, VendorWaybillID2: utils.Int64ToStr(msg.DeliveryID), WaybillVendorID: model.VendorIDMTPS, CourierName: msg.CourierName, CourierMobile: msg.CourierPhone, Status: model.WaybillStatusUnknown, // todo 这里要再确定一下是否只要收到订单异常消息就只简单当成一个消息 VendorStatus: utils.Int2Str(msg.ExceptionCode), StatusTime: utils.Timestamp2Time(msg.Timestamp), } order.VendorOrderID, order.OrderVendorID = jxutils.SplitUniversalOrderID(msg.OrderID) retVal = mtpsapi.Err2CallbackResponse(partner.CurOrderManager.OnWaybillStatusChanged(order), "mtps OnWaybillExcept") }, jxutils.ComposeUniversalOrderID(msg.OrderID, model.VendorIDDada)) return retVal } func (c *DeliveryHandler) onWaybillMsg(msg *mtpsapi.CallbackOrderMsg) (retVal *mtpsapi.CallbackResponse) { order, goodsOrder := c.callbackMsg2Waybill(msg) // 多次取消,只处理第一次 if msg.Status == mtpsapi.OrderStatusCanceled { orderStatus, _ := orderman.FixedOrderManager.GetWayBillStatusList(msg.OrderID, msg.MtPeisongID, model.VendorIDMTPS) for _, v := range orderStatus { if v.VendorStatus == "99" { return mtpsapi.SuccessResponse } } } store, _ := dao.GetStoreDetail(dao.GetDB(), goodsOrder.JxStoreID, goodsOrder.VendorID, goodsOrder.VendorOrgCode) switch msg.Status { case mtpsapi.OrderStatusWaitingForSchedule: data, err := api.MtpsAPI.QueryOrderStatus(msg.DeliveryID, msg.MtPeisongID) if err != nil { globals.SugarLogger.Debugf("获取运单信息错误,可能是果园运单:%s,%v", utils.Format4Output(msg, false), err) break } order.DesiredFee = utils.Float64TwoInt64(utils.MustInterface2Float64(data["delivery_fee"]) * 100) order.ActualFee = utils.Float64TwoInt64(utils.MustInterface2Float64(data["pay_amount"]) * 100) order.DesiredFee += int64(store.FreightMarkup) order.Status = model.WaybillStatusNew case mtpsapi.OrderStatusAccepted: // 已接单 data, err := api.MtpsAPI.QueryOrderStatus(msg.DeliveryID, msg.MtPeisongID) if err != nil { globals.SugarLogger.Debugf("获取运单信息错误,可能是果园运单:%s,%v", utils.Format4Output(msg, false), err) break } order.DesiredFee = utils.Float64TwoInt64(utils.MustInterface2Float64(data["delivery_fee"]) * 100) order.ActualFee = utils.Float64TwoInt64(utils.MustInterface2Float64(data["pay_amount"]) * 100) order.DesiredFee += int64(store.FreightMarkup) order.Status = model.WaybillStatusCourierAssigned order.Remark = order.CourierName + "," + order.CourierMobile case mtpsapi.OrderStatusPickedUp: // 已取货 order.Status = model.WaybillStatusDelivering case mtpsapi.OrderStatusDeliverred: order.Status = model.WaybillStatusDelivered case mtpsapi.OrderStatusCanceled: order.Status = model.WaybillStatusCanceled default: return mtpsapi.SuccessResponse } order2, _ := partner.CurOrderManager.LoadOrder(order.VendorOrderID, order.OrderVendorID) //查不到订单可能就是果园的订单 if order2 == nil && beego.BConfig.RunMode != "jxgy" { c.pushToGy(msg) return mtpsapi.SuccessResponse } // 加入调度器 err := mtpsapi.Err2CallbackResponse(partner.CurOrderManager.OnWaybillStatusChanged(order), order.VendorStatus) switch order.OrderVendorID { case model.VendorIDDD: pushMTPSToTiktok(msg.Status, order) case model.VendorIDMTWM, model.VendorIDTaoVegetable: delivery.GetOrderRiderInfoToPlatform(order.VendorOrderID, order.Status) // 骑手位置更新 } return err } func pushMTPSToTiktok(msgStatus int, order *model.Waybill) { result := &mtpsapi.RiderInfo{ OrderId: order.VendorOrderID, ThirdCarrierOrderId: order.VendorOrderID, CourierName: order.CourierName, CourierPhone: order.CourierMobile, LogisticsProviderCode: "10032", LogisticsStatus: order.Status, OpCode: "", } switch msgStatus { case mtpsapi.OrderStatusWaitingForSchedule: // 待接单,召唤骑手 result.LogisticsStatus = model.WaybillStatusNew result.LogisticsContext = model.RiderWaitRider case mtpsapi.OrderStatusAccepted: // 已接单 result.LogisticsStatus = model.WaybillStatusCourierAssigned // 分配骑手 result.LogisticsContext = model.RiderWaitGetGoods case mtpsapi.OrderStatusDeliverred: // 完成 result.LogisticsStatus = model.WaybillStatusDelivered result.LogisticsContext = model.RiderGetOrderDelivered case mtpsapi.OrderStatusCanceled: // 取消 result.LogisticsStatus = model.WaybillStatusCanceled result.LogisticsContext = model.RiderGetOrderCanceled case mtpsapi.OrderStatusPickedUp: // 骑手到店 result.LogisticsStatus = model.WaybillStatusCourierArrived result.LogisticsContext = model.RiderToStore default: result.LogisticsStatus = 0 result.LogisticsContext = model.RiderGetOrderDeliverOther } delivery.PullTiktokRiderInfo(result) if result.LogisticsStatus == model.WaybillStatusCourierArrived { result.LogisticsStatus = model.WaybillStatusDelivering result.LogisticsContext = model.RiderGetOrderDelivering delivery.PullTiktokRiderInfo(result) } } func (c *DeliveryHandler) pushToGy(msg *mtpsapi.CallbackOrderMsg) { cl := http.Client{} params := make(map[string]interface{}) params["mt_peisong_id"] = msg.MtPeisongID params["courier_name"] = msg.CourierName params["delivery_id"] = msg.DeliveryID params["appkey"] = msg.AppKey params["order_id"] = msg.OrderID params["courier_phone"] = msg.CourierPhone params["status"] = msg.Status params["timestamp"] = msg.Timestamp params["cancel_reason_id"] = msg.CancelReasonId params["cancel_reason"] = msg.CancelReason urls := utils.Map2URLValues(params) sign := signParams(urls) params["sign"] = sign request, err := http.NewRequest(http.MethodPost, "http://callback-jxgy.jxc4.com/mtps/status", strings.NewReader(utils.Map2URLValues(params).Encode())) if err != nil { return } request.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") cl.Do(request) } func signParams(params url.Values) string { keys := make([]string, 0) for k := range params { if k != "sign" { keys = append(keys, k) } } sort.Strings(keys) finalStr := "b1M}9?:sTbsB[OF2gNORnN(|(iy9rB8(`7]|[wGLnbmt`evfM>E:A90DjHAW:UPE" 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 (c *DeliveryHandler) callbackMsg2Waybill(msg *mtpsapi.CallbackOrderMsg) (retVal *model.Waybill, good2 *model.GoodsOrder) { retVal = &model.Waybill{ VendorWaybillID: msg.MtPeisongID, VendorWaybillID2: utils.Int64ToStr(msg.DeliveryID), WaybillVendorID: model.VendorIDMTPS, CourierName: msg.CourierName, CourierMobile: msg.CourierPhone, VendorStatus: utils.Int2Str(msg.Status), StatusTime: utils.Timestamp2Time(msg.Timestamp), Remark: msg.CancelReason, } var good *model.GoodsOrder sql := `SELECT * FROM goods_order WHERE vendor_order_id = ? ORDER BY order_created_at DESC LIMIT 1 OFFSET 0` sqlParams := []interface{}{msg.OrderID} err := dao.GetRow(dao.GetDB(), &good, sql, sqlParams) if err != nil || good == nil || good.VendorOrderID == "" { retVal.OrderVendorID = 0 } else { retVal.OrderVendorID = good.VendorID retVal.VendorOrderID = good.VendorOrderID } return retVal, good } // 老方法是自己计算 //func (c *DeliveryHandler) GetWaybillFee(order *model.GoodsOrder) (deliveryFeeInfo *partner.WaybillFeeInfo, err error) { // db := dao.GetDB() // deliveryFeeInfo = &partner.WaybillFeeInfo{} // deliveryFeeInfo.RefDeliveryFee, deliveryFeeInfo.RefAddFee, err = delivery.CalculateOrderDeliveryFee(order, time.Now(), db) // if err == nil { // if _, err = c.getMTPSShopID(order, db); err == nil { // deliveryFeeInfo.DeliveryFee = deliveryFeeInfo.RefDeliveryFee // } // } // return deliveryFeeInfo, err //} // GetWaybillFee 新方法平台返回(预下单) func (c *DeliveryHandler) GetWaybillFee(order *model.GoodsOrder) (deliveryFeeInfo *partner.WaybillFeeInfo, err error) { shopWeight := float64(order.Weight) / float64(order.Weight) if shopWeight > 50 { return nil, fmt.Errorf("订单重量超过五十千克,无法出货") } deliveryFeeInfo = &partner.WaybillFeeInfo{} // 获取送货单id deliveryID := c.getDeliveryID(order) shopId, err := c.getMTPSShopID(order, dao.GetDB()) if err != nil { return nil, err } param := &mtpsapi.PreCreateByShopParam{ DeliveryID: deliveryID, OrderID: order.VendorOrderID, ShopID: shopId, DeliveryServiceCode: mtpsapi.DeliveryServiceCodeRapid, ReceiverName: order.ConsigneeName, ReceiverAddress: order.ConsigneeAddress, ReceiverPhone: order.ConsigneeMobile, ReceiverLng: order.ConsigneeLng, ReceiverLat: order.ConsigneeLat, GoodsValue: utils.Int64ToFloat64(order.ActualPayPrice) / 100, GoodsWeight: float64(int(shopWeight*100)) / 100, // 系统重量转换为千克 PayTypeCode: 0, ExpectedDeliveryTime: mtpsapi.DeliveryServiceCodeRapid, // 4002飞速达,4011快速达,4012及时达,4013集中送 OuterOrderSourceDesc: "101", } if param.GoodsWidth <= model.NO { param.GoodsWidth = model.YES } deliveryFeeInfo.RefDeliveryFee, deliveryFeeInfo.RefAddFee, err = api.MtpsAPI.PreCreateByShop(param) deliveryFeeInfo.DeliveryFee = deliveryFeeInfo.RefDeliveryFee return deliveryFeeInfo, err } // CreateWaybill(美团配送) func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, maxDeliveryFee int64) (bill *model.Waybill, err error) { db := dao.GetDB() // 检查配送平台是否被禁用 vendorOrgCode, err := dao.GetVendorOrgCode(db, model.VendorIDFengNiao, "", model.VendorOrgTypeDelivery) if err != nil { return nil, err } if len(vendorOrgCode) > 0 && vendorOrgCode[0].IsOpen == model.YES { return nil, fmt.Errorf("此平台配送已被系统关闭,暂不发配送 [%v]", vendorOrgCode[0].Comment) } // 忽略坐标转换错误,即使是转换出错,也只能当成转换成功来处理,底层会有错误日志输出 lngFloat, latFloat, _ := jxutils.IntCoordinate2MarsStandard(order.ConsigneeLng, order.ConsigneeLat, order.CoordinateType) billParams := &mtpsapi.CreateOrderByShopParam{ OrderID: jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID), DeliveryServiceCode: mtpsapi.DeliveryServiceCodeRapid, ReceiverName: utils.FilterMb4(order.ConsigneeName), ReceiverAddress: utils.FilterMb4(order.ConsigneeAddress), ReceiverPhone: order.ConsigneeMobile, CoordinateType: model.CoordinateTypeMars, ReceiverLng: jxutils.StandardCoordinate2Int(lngFloat), ReceiverLat: jxutils.StandardCoordinate2Int(latFloat), GoodsValue: jxutils.IntPrice2Standard(order.ActualPayPrice), // todo 超价处理 GoodsWeight: float64(jxutils.IntWeight2Float(limitOrderWeight(order.Weight))), // ExpectedDeliveryTime: order.ExpectedDeliveredTime.Unix(), OrderType: mtpsapi.OrderTypeASAP, } if billParams.GoodsWidth <= model.NO { billParams.GoodsWidth = model.YES } // 获取送货单id billParams.DeliveryID = c.getDeliveryID(order) // 获取商铺id if billParams.ShopID, err = c.getMTPSShopID(order, db); err != nil { return nil, err } // 获取美团入参结构体 goods := &mtpsapi.GoodsDetail{ Goods: []*mtpsapi.GoodsItem{}, } goodItemMap := map[string]*mtpsapi.GoodsItem{} if len(order.Skus) > model.NO { for _, sku := range order.Skus { goodItem := &mtpsapi.GoodsItem{ GoodCount: sku.Count, GoodPrice: jxutils.IntPrice2Standard(sku.SalePrice), } goodItem.GoodName, goodItem.GoodUnit = jxutils.GetNameAndUnitFromSkuName(sku.SkuName) // 好像SKU名不能重复,否则会报错,尝试处理一下 if item, ok := goodItemMap[goodItem.GoodName]; !ok { goods.Goods = append(goods.Goods, goodItem) goodItemMap[goodItem.GoodName] = goodItem } else { item.GoodCount += goodItem.GoodCount } } } else { goodItem := &mtpsapi.GoodsItem{ GoodCount: model.YES, GoodPrice: jxutils.IntPrice2Standard(model.YES), } goodItem.GoodName, goodItem.GoodUnit = jxutils.GetNameAndUnitFromSkuName("本地暂无商品信息 500g/份") // 好像SKU名不能重复,否则会报错,尝试处理一下 if item, ok := goodItemMap[goodItem.GoodName]; !ok { goods.Goods = append(goods.Goods, goodItem) goodItemMap[goodItem.GoodName] = goodItem } else { item.GoodCount += goodItem.GoodCount } } billParams.Note = utils.FilterMb4("客户电话:" + order.ConsigneeMobile + "," + order.BuyerComment + ",配送遇到问题,可联系18048531223取消配送单,禁止未配送直接完成定单!") billParams.GoodsDetail = string(utils.MustMarshal(goods)) billParams.GoodsPickupInfo = fmt.Sprintf("%s第%d号单", model.VendorChineseNames[order.VendorID], order.OrderSeq) billParams.PoiSeq = fmt.Sprintf("#%d", order.OrderSeq) if !globals.EnableStoreWrite { return nil, fmt.Errorf("测试环境不能真正创建运单") } // 通知美团订单,获取返回订单配送费 result, err := api.MtpsAPI.CreateOrderByShop2(billParams) if err != nil { return nil, err } bill = &model.Waybill{ VendorOrderID: order.VendorOrderID, OrderVendorID: order.VendorID, VendorWaybillID: result.MtPeisongID, VendorWaybillID2: utils.Int64ToStr(result.DeliveryID), WaybillVendorID: model.VendorIDMTPS, DesiredFee: utils.Float64TwoInt64(result.DeliveryFee), } // 当前运单总费大于配送阈值15,日志打印提示 delivery.OnWaybillCreated(bill) return bill, err } func (c *DeliveryHandler) CancelWaybill(bill *model.Waybill, cancelReasonID int, cancelReason string) (err error) { cancelReasonID = mtpsapi.CancelReasonMerchantOther if cancelReason == "" { cancelReason = "顾客主动取消" } _, err = api.MtpsAPI.CancelOrder(utils.Str2Int64(bill.VendorWaybillID2), bill.VendorWaybillID, cancelReasonID, cancelReason) return err } func (c *DeliveryHandler) getDeliveryID(order *model.GoodsOrder) (retVal int64) { // jxorder表当前已经有50多万条记录了,加100万避免冲突 // 508505 return order.ID + 1000000 } func (c *DeliveryHandler) getMTPSShopID(order *model.GoodsOrder, db *dao.DaoDB) (retVal string, err error) { saleStoreID := jxutils.GetSaleStoreIDFromOrder(order) storeCourierList, err2 := dao.GetOpenedStoreCouriersByStoreID(db, saleStoreID, model.VendorIDMTPS) if err = err2; err != nil && err != orm.ErrNoRows { return "", err } if len(storeCourierList) == 0 { return "", partner.ErrStoreHaveNoCourier } retVal = storeCourierList[0].VendorStoreID if beego.BConfig.RunMode == "dev" { retVal = "test_0001" } return retVal, nil } func limitOrderWeight(weight int) int { if weight > maxOrderWeight { return maxOrderWeight } return weight } func (c *DeliveryHandler) ComplaintRider(bill *model.Waybill, resonID int, resonContent string) (err error) { if globals.EnableStoreWrite { err = api.MtpsAPI.EvaluateRider(utils.Str2Int64(bill.VendorWaybillID2), bill.VendorWaybillID, 1, resonContent) } return err } func (c *DeliveryHandler) GetRidderPosition(ctx *jxcontext.Context, vendorOrgCode, vendorOrderID, vendorWaybillID, vendorWaybillID2 string) (lng, lat float64, err error) { intLng, intLat, err := api.MtpsAPI.RiderLocation(utils.Str2Int64(vendorWaybillID2), vendorWaybillID) if err == nil { lng = jxutils.IntCoordinate2Standard(intLng) lat = jxutils.IntCoordinate2Standard(intLat) } return lng, lat, err } // 获取骑手信息 美团配送 deliveryId,mtPeisongId这两参数美团专属 func (c *DeliveryHandler) GetRiderInfo(orderId string, deliveryId int64, mtPeisongId string) (rider *mtpsapi.RiderInfo, err error) { // 获取订单状态 order, err := api.MtpsAPI.QueryOrderStatus(deliveryId, mtPeisongId) if err != nil { return nil, err } // 获取骑手位置 lng, lat, err := api.MtpsAPI.RiderLocation(deliveryId, mtPeisongId) if err != nil { return nil, err } result := &mtpsapi.RiderInfo{ OrderId: orderId, ThirdCarrierOrderId: utils.Interface2String(order["mt_peisong_id"]), CourierName: utils.Interface2String(order["courier_name"]), CourierPhone: utils.Interface2String(order["courier_phone"]), LogisticsProviderCode: mtpsapi.MTPsCode, LogisticsStatus: int(utils.MustInterface2Int64(order["status"])), // 默认正在配送中 Latitude: utils.Float64ToStr(jxutils.IntCoordinate2Standard(lat)), Longitude: utils.Float64ToStr(jxutils.IntCoordinate2Standard(lng)), } switch int(utils.MustInterface2Int64(order["status"])) { case 0: result.LogisticsStatus = model.WaybillStatusNew result.LogisticsContext = model.RiderWaitRider case 20: //已接单 result.LogisticsStatus = model.WaybillStatusCourierAssigned result.LogisticsContext = model.RiderGetOrder case 30: //已取货 result.LogisticsStatus = model.WaybillStatusDelivering result.LogisticsContext = model.RiderGetOrderDelivering case 50: //已送达 result.LogisticsStatus = model.WaybillStatusDelivered result.LogisticsContext = model.RiderGetOrderDelivered case 90: //已取消 result.LogisticsStatus = model.WaybillStatusCanceled result.LogisticsContext = model.RiderGetOrderCanceled default: result.LogisticsStatus = model.WaybillStatusDeliverFailed result.LogisticsContext = model.RiderGetOrderDeliverOther } return result, nil } func (c *DeliveryHandler) GetDeliverLiquidatedDamages(orderId string, deliverId string) (money int64, err error) { statusList, err := orderman.FixedOrderManager.GetWayBillStatusList(orderId, deliverId, model.VendorIDMTPS) if err != nil { return 0, err } isMerchantCancel := false // 获取发起取消的人员 for _, v := range statusList { if v.VendorStatus == utils.Int64ToStr(model.WaybillStatusCancel) { isMerchantCancel = true // 商户取消 break } } bill, err := partner.CurOrderManager.LoadWaybill(deliverId, model.VendorIDMTPS) if err != nil { return 0, err } // 已经分配骑手,且超过十五分钟,不扣款 for i := len(statusList) - 1; i >= 0; i-- { // 取消不管 if statusList[i].VendorStatus == utils.Int2Str(mtpsapi.OrderStatusCanceled) { continue } // 送达 if statusList[i].VendorStatus == utils.Int2Str(mtpsapi.OrderStatusDeliverred) { return bill.DesiredFee, nil } // 到店 if statusList[i].VendorStatus == utils.Int2Str(mtpsapi.OrderStatusPickedUp) { if isMerchantCancel { return bill.DesiredFee, nil } return 0, nil } // 接单 if statusList[i].VendorStatus == utils.Int2Str(mtpsapi.OrderStatusAccepted) { if isMerchantCancel { return 200, nil } return 0, nil } // 待调度 if statusList[i].VendorStatus == utils.Int2Str(mtpsapi.OrderStatusWaitingForSchedule) { return 0, nil } } return 0, err } func (c *DeliveryHandler) UpdateWaybillTip(ctx *jxcontext.Context, vendorOrgCode, vendorStoreID, vendorOrderID, vendorWaybillID, vendorWaybillID2, cityCode string, tipFee int64) (err error) { // 无法获取已经添加的小费金额 return fmt.Errorf("美团暂不支持添加小费") //return api.MtpsAPI.AddTip(vendorOrderID, vendorWaybillID, tipFee) } func (c *DeliveryHandler) GetWaybillTip(ctx *jxcontext.Context, vendorOrgCode, vendorStoreID, vendorOrderID, vendorWaybillID, vendorWaybillID2 string) (tipFee int64, err error) { return tipFee, err }