package mtwm import ( "errors" "fmt" "git.rosy.net.cn/baseapi/platformapi/dingdingapi" "git.rosy.net.cn/jx-callback/business/jxutils/ddmsg" "net/url" "strconv" "strings" "time" "git.rosy.net.cn/baseapi/platformapi/mtwmapi" "git.rosy.net.cn/baseapi/utils" "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/globals" ) var ( // AfsVendorStatus2StatusMap = map[int]int{ // mtwmapi.ResTypePending: model.AfsOrderStatusWait4Approve, // mtwmapi.ResTypeMerchantRefused: model.AfsOrderStatusFailed, // mtwmapi.ResTypeMerchantAgreed: model.AfsOrderStatusFinished, // mtwmapi.ResTypeCSRefused: model.AfsOrderStatusFailed, // mtwmapi.ResTypeCSAgreed: model.AfsOrderStatusFinished, // mtwmapi.ResTypeTimeoutAutoAgreed: model.AfsOrderStatusFinished, // mtwmapi.ResTypeAutoAgreed: model.AfsOrderStatusFinished, // mtwmapi.ResTypeUserCancelApply: model.AfsOrderStatusFailed, // mtwmapi.ResTypeUserCancelComplain: model.AfsOrderStatusFailed, // } AfsVendorStatus2StatusMap = map[string]int{ mtwmapi.NotifyTypeApply: model.AfsOrderStatusWait4Approve, mtwmapi.NotifyTypePartyApply: model.AfsOrderStatusWait4Approve, mtwmapi.NotifyTypeSuccess: model.AfsOrderStatusFinished, mtwmapi.NotifyTypeReject: model.AfsOrderStatusFailed, mtwmapi.NotifyTypeCancelRefund: model.AfsOrderStatusFailed, mtwmapi.NotifyTypeCancelRefundComplaint: model.AfsOrderStatusFailed, } ) func (c *PurchaseHandler) isAfsMsg(msg *mtwmapi.CallbackMsg) bool { if msg.Cmd == mtwmapi.MsgTypeOrderRefund || msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { // refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) orderID := utils.Str2Int64(GetOrderIDFromMsg(msg)) order, _ := partner.CurOrderManager.LoadOrder(utils.Int64ToStr(orderID), model.VendorIDMTWM) if order != nil { return true //TODO 有的美团订单售前退款,也当做售后处理试试 //} } } return false } func (c *PurchaseHandler) OnAfsOrderMsg(msg *mtwmapi.CallbackMsg) (retVal *mtwmapi.CallbackResponse) { jxutils.CallMsgHandlerAsync(func() { retVal = c.onAfsOrderMsg(msg) }, jxutils.ComposeUniversalOrderID(GetOrderIDFromMsg(msg), model.VendorIDMTWM)) return retVal } // todo 对于退款与部分退款,order.go与这个文件中对于状态的处理不一致 func (c *PurchaseHandler) onAfsOrderMsg(msg *mtwmapi.CallbackMsg) (retVal *mtwmapi.CallbackResponse) { var ( err error db = dao.GetDB() ) orderStatus := c.callbackAfsMsg2Status(msg) needCallNew := orderStatus.Status == model.AfsOrderStatusWait4Approve || orderStatus.Status == model.AfsOrderStatusNew if !needCallNew { _, err = partner.CurOrderManager.LoadAfsOrder(orderStatus.VendorOrderID, orderStatus.VendorID) if err != nil { if dao.IsNoRowsError(err) { needCallNew = true } else { return mtwmapi.Err2CallbackResponse(err, "") } } } if needCallNew { var afsOrder *model.AfsOrder refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) if msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { afsOrder = &model.AfsOrder{ VendorID: model.VendorIDMTWM, AfsOrderID: orderStatus.VendorOrderID, VendorOrderID: orderStatus.RefVendorOrderID, VendorStoreID: "", StoreID: 0, AfsCreatedAt: utils.Timestamp2Time(refundData.Timestamp), VendorAppealType: "", AppealType: model.AfsAppealTypeRefund, VendorReasonType: "", ReasonType: model.AfsReasonNotOthers, ReasonDesc: utils.LimitUTF8StringLen(refundData.Reason, 1024), ReasonImgList: utils.LimitUTF8StringLen(strings.Join(refundData.PictureList, ","), 1024), RefundType: model.AfsTypePartRefund, VendorOrgCode: msg.AppID, //RefundMoney: , // FreightUserMoney: afsInfo.OrderFreightMoney, // AfsFreightMoney: afsInfo.AfsFreight, // BoxMoney: afsInfo.PackagingMoney, // TongchengFreightMoney: afsInfo.TongchengFreightMoney, // SkuBoxMoney: afsInfo.MealBoxMoney, } var refundMoney float64 = 0 for _, sku := range refundData.FoodList { skuCount := sku.Count if skuCount == 0 { skuCount = 1 } orderSku := &model.OrderSkuFinancial{ // VendorID: model.VendorIDMTWM, AfsOrderID: afsOrder.AfsOrderID, // VendorOrderID: afsOrder.VendorOrderID, // VendorStoreID: afsOrder.VendorStoreID, // StoreID: afsOrder.StoreID, IsAfsOrder: 1, Count: sku.Count, // ConfirmTime: afsOrder.AfsCreateAt, VendorSkuID: sku.SkuID, SkuID: int(utils.Str2Int64WithDefault(sku.SkuID, 0)), Name: sku.FoodName, UserMoney: jxutils.StandardPrice2Int(sku.RefundPrice)*int64(skuCount) + jxutils.StandardPrice2Int(sku.BoxPrice)*int64(sku.BoxNum), } if orderSku.VendorSkuID == "" || orderSku.VendorSkuID == "0" { orderSku.VendorSkuID = sku.AppFoodCode } afsOrder.SkuUserMoney += orderSku.UserMoney afsOrder.Skus = append(afsOrder.Skus, orderSku) refundMoney += sku.RefundPrice } afsOrder.RefundMoney = utils.Float64TwoInt64(refundMoney * 100) //afsOrder.PmSubsidyMoney += afsOrder.RefundMoney - afsOrder.SkuUserMoney } else { if afsOrder = c.createAfsOrder(msg.FormData); afsOrder != nil { // if orderFinancial, err2 := partner.CurOrderManager.LoadOrderFinancial(orderStatus.RefVendorOrderID, model.VendorIDMTWM); err2 == nil { // afsOrder = c.OrderFinancialDetail2Refund(orderFinancial, msg.FormData) afsOrder.AfsOrderID = orderStatus.VendorOrderID afsOrder.RefundType = model.AfsTypeFullRefund afsOrder.AppealType = model.AfsAppealTypeRefund afsOrder.VendorReasonType = "" afsOrder.ReasonType = model.AfsReasonNotOthers afsOrder.ReasonDesc = utils.LimitUTF8StringLen(refundData.Reason, 1024) afsOrder.ReasonImgList = utils.LimitUTF8StringLen(strings.Join(refundData.PictureList, ","), 1024) } } if afsOrder != nil { //直接就来一个新的售后单,并且还是售后完成的 if orderStatus.Status == model.AfsOrderStatusFinished { afsOrder.AfsFinishedAt = afsOrder.AfsCreatedAt } // 补丁,这种类型的退款申请,不会在推送退款成功消息,直接将售后单标记为完成 if strings.Contains(afsOrder.ReasonDesc, "商家开通极速退款服务,用户申请系统自动通过") { afsOrder.Status = model.AfsOrderStatusFinished afsOrder.AfsFinishedAt = time.Now() } } err = partner.CurOrderManager.OnAfsOrderNew(afsOrder, orderStatus) } else { // msg-status 1-已申请 10-初审已同意 11-初审已驳回 16-初审已申诉 17-初审申诉已同意 18-初审申诉已驳回 20-终审已发起(用户已发货) 21-终审已同意 22-终审已驳回 26-终审已申诉 27-终审申诉已同意 28-终审申诉已驳回 30-已取消 if err := partner.CurOrderManager.OnAfsOrderStatusChanged(orderStatus); err == nil { order, _ := partner.CurOrderManager.LoadOrder(orderStatus.RefVendorOrderID, model.VendorIDMTWM) mtmApi := getAPI(order.VendorOrgCode, order.StoreID, order.VendorStoreID) // 订单回调全额退款接口时,将订单状态修改为取消 refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) if refundData.NotifyType == mtwmapi.MsgTypeOrderAgree && msg.Cmd == mtwmapi.MsgTypeOrderRefund { order.Status = model.OrderStatusCanceled dao.UpdateEntity(db, order, "Status") } else if refundData.Status == "21" { skuList, _ := dao.GetSimpleOrderSkus(db, orderStatus.RefVendorOrderID, nil) totalSkuCount := 0 for _, v := range skuList { totalSkuCount += v.Count } financialSku, _ := dao.GetOrderRefundSkuList(db, []string{orderStatus.RefVendorOrderID}) refundSkuCount := 0 for _, v := range financialSku { refundSkuCount += v.Count } if totalSkuCount == refundSkuCount { order.Status = model.OrderStatusCanceled dao.UpdateEntity(db, order, "Status") } } // 已经审核过的售后单子 if orderStatus.Status == model.AfsOrderStatusNew || orderStatus.Status == model.AfsOrderStatusFinished { // 售后金额 refundDetail, err := mtmApi.GetOrderRefundDetail(utils.Str2Int64(order.VendorOrderID), 0) if err == nil && len(refundDetail) > 0 { for _, rd := range refundDetail { if rd.RefundPartialEstimateCharge.SettleAmount != "" { order.TotalShopMoney = order.TotalShopMoney + jxutils.StandardPrice2Int(utils.Str2Float64(rd.RefundPartialEstimateCharge.SettleAmount)) } } switch order.EarningType { case model.EarningTypePoints: // 扣点 //更新订单new_earning_price waybill, _ := partner.CurOrderManager.LoadWaybill(order.VendorWaybillID, order.WaybillVendorID) if waybill == nil { if (order.NewEarningPrice == 0 || order.NewEarningPrice != order.TotalShopMoney*int64(100-order.OrderPayPercentage/2)/int64(100)) && order.OrderPayPercentage <= 50 { order.NewEarningPrice = order.TotalShopMoney * int64(100-order.OrderPayPercentage/2) / int64(100) } } else { if (order.NewEarningPrice == 0 || order.NewEarningPrice != (order.TotalShopMoney-waybill.DesiredFee)*int64(100-order.OrderPayPercentage/2)/int64(100)) && order.OrderPayPercentage <= 50 { order.NewEarningPrice = order.TotalShopMoney*int64(100-order.OrderPayPercentage/2)/int64(100) - waybill.DesiredFee } } dao.UpdateEntity(db, order, "TotalShopMoney", "NewEarningPrice") case model.EarningTypeQuote: // 报价 orderSkuShopPriceList := make(map[int]int64, 0) orderSkuEarningPriceList := make(map[int]int64, 0) for _, os := range order.Skus { orderSkuShopPriceList[os.SkuID] = os.ShopPrice orderSkuEarningPriceList[os.SkuID] = os.EarningPrice } for _, rd := range refundDetail { for _, rdw := range rd.WmAppRetailForOrderPartRefundList { switch rd.RefundType { // 退款类型:1-全额退款;2-部分退款;3-退差价;5-按金额灵活退。 case 3: // 缺重退部分 if _, ok := orderSkuShopPriceList[utils.Str2Int(rdw.SkuID)]; ok { order.ShopPrice = order.ShopPrice - jxutils.StandardPrice2Int((rdw.RefundPrice/rdw.FoodPrice)*float64(orderSkuShopPriceList[utils.Str2Int(rdw.SkuID)])) order.EarningPrice = order.EarningPrice - jxutils.StandardPrice2Int((rdw.RefundPrice/rdw.FoodPrice)*float64(orderSkuEarningPriceList[utils.Str2Int(rdw.SkuID)])) } default: // 全退 if _, ok := orderSkuShopPriceList[utils.Str2Int(rdw.SkuID)]; ok { order.ShopPrice = order.ShopPrice - (orderSkuShopPriceList[utils.Str2Int(rdw.SkuID)] * int64(rdw.Count)) order.EarningPrice = order.EarningPrice - (orderSkuEarningPriceList[utils.Str2Int(rdw.SkuID)] * int64(rdw.Count)) } } } order.NewEarningPrice = order.EarningPrice } } dao.UpdateEntity(db, order, "TotalShopMoney", "ShopPrice", "EarningPrice") } else { globals.SugarLogger.Debugf("美团平台售后获取退款金额信息错误: = %v", err) ddmsg.SendUserMessage(dingdingapi.MsgTyeText, "2452A93EEB9111EC9B06525400E86DC0", "美团平台售后获取退款金额信息错误", fmt.Sprintf("orderid := %d,%s,%v", model.VendorIDMTWM, order.VendorOrderID, err)) } } // 新订单平台自动同意退单,某一些情况下产生的运单没退!这个处理一下! if refundData.NotifyType == mtwmapi.MsgTypeOrderAgree && (msg.Cmd == mtwmapi.MsgTypeOrderRefund || msg.Cmd == mtwmapi.MsgTypeOrderAgree) { wayBill, _ := dao.GetWaybills(db, orderStatus.RefVendorOrderID, nil) for _, v := range wayBill { handlerInfo := partner.GetDeliveryPlatformFromVendorID(v.WaybillVendorID) if handlerInfo != nil { handlerInfo.Handler.CancelWaybill(v, 0, "订单转移被取消10") } } } if order.Status == model.OrderStatusCanceled { num, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", utils.Int64ToFloat64(order.TotalShopMoney)/float64(100)), 64) applyErr := mtmApi.ApplyCompensation(&mtwmapi.ApplyCompensationRes{ OrderId: utils.Str2Int64(order.VendorOrderID), Reason: "商户申请", ApplyStatus: mtwmapi.ApplyCompensationStatusOne, Amount: num, }) if applyErr != nil { partner.CurOrderManager.OnOrderMsg(order, utils.Int2Str(order.Status), fmt.Sprintf("售后取消订单申请赔付失败:%s", applyErr.Error())) } else { partner.CurOrderManager.OnOrderMsg(order, utils.Int2Str(order.Status), fmt.Sprintf("售后取消订单申请赔付:%s", "成功")) } } } } return mtwmapi.Err2CallbackResponse(err, "") } func (p *PurchaseHandler) createAfsOrder(orderData url.Values) (afsOrder *model.AfsOrder) { afsOrder, err := partner.CurOrderManager.CreateAfsOrderFromOrder(orderData.Get("order_id"), model.VendorIDMTWM) if err == nil { afsOrder.AfsOrderID = orderData.Get("refund_id") afsOrder.AfsCreatedAt = utils.Timestamp2Time(utils.Str2Int64(orderData.Get("timestamp"))) if afsOrder.AfsOrderID == "" { afsOrder.AfsOrderID = afsOrder.VendorOrderID } } else { afsOrder = nil } return afsOrder } func (c *PurchaseHandler) callbackAfsMsg2Status(msg *mtwmapi.CallbackMsg) (orderStatus *model.OrderStatus) { refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) orderStatus = &model.OrderStatus{ VendorID: model.VendorIDMTWM, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundData.OrderID), RefVendorID: model.VendorIDMTWM, VendorStatus: refundData.Status, // fmt.Sprintf("%s:%d", refundData.NotifyType, refundData.ResType), Status: c.GetAfsStatusFromVendorStatus(refundData.ResType, refundData.NotifyType), StatusTime: utils.Timestamp2Time(refundData.Timestamp), Remark: refundData.Reason, } if refundData.RefundID > 0 { orderStatus.VendorOrderID = utils.Int64ToStr(refundData.RefundID) } else { orderStatus.VendorOrderID = orderStatus.RefVendorOrderID } if refundData.NotifyType == "" && refundData.ResType == model.NO { orderStatus.VendorStatus = "用户申请退货且退款" } // 1-已申请 10-初审已同意 11-初审已驳回 16-初审已申诉 17-初审申诉已同意 18-初审申诉已驳回 20-终审已发起(用户已发货) 21-终审已同意 22-终审已驳回 26-终审已申诉 27-终审申诉已同意 28-终审申诉已驳回 30-已取消 if refundData.Status == "21" { orderStatus.Status = model.AfsOrderStatusFinished orderStatus.VendorStatus += "," + refundData.Status } return orderStatus } func (c *PurchaseHandler) GetAfsStatusFromVendorStatus(resType int, notifyType string) int { // 当resType为0且notifyType为空的时候,是退货退款,之前是未处理这个,现在退货退款默认成退款未处理 if notifyType == "" { notifyType = mtwmapi.NotifyTypeApply } status := AfsVendorStatus2StatusMap[notifyType] if status == model.AfsOrderStatusWait4Approve && resType != mtwmapi.ResTypePending { status = model.AfsOrderStatusNew } return status } // 审核售后单申请 func (c *PurchaseHandler) AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) { if globals.EnableMtwmStoreWrite { if approveType == partner.AfsApproveTypeRefused { err = getAPI(order.VendorOrgCode, jxutils.GetSaleStoreIDFromAfsOrder(order), order.VendorStoreID).OrderRefundReject(utils.Str2Int64(order.VendorOrderID), reason) } else if approveType == partner.AfsApproveTypeRefusedToRefundMoney { return errors.New("此平台暂时不支持") } else { err = getAPI(order.VendorOrgCode, jxutils.GetSaleStoreIDFromAfsOrder(order), order.VendorStoreID).OrderRefundAgree(utils.Str2Int64(order.VendorOrderID), reason) } } return err } // 确认收到退货 func (c *PurchaseHandler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) { err = fmt.Errorf("内部错误,美团外卖平台不支持确认收到退货操作") return err } // 发起全款退款 func (c *PurchaseHandler) RefundOrder(ctx *jxcontext.Context, order *model.GoodsOrder, reason string) (err error) { return fmt.Errorf("%s不支持售后全额退款,请让买家发起退款", model.VendorChineseNames[model.VendorIDMTWM]) } // 发起部分退款 func (c *PurchaseHandler) PartRefundOrder(ctx *jxcontext.Context, order *model.GoodsOrder, refundSkuList []*model.OrderSku, reason string) (err error) { return c.AdjustOrder(ctx, order, refundSkuList, reason) } func (c *PurchaseHandler) GetOrderAfsInfo(ctx *jxcontext.Context, vendorOrderID, afsOrderID string) (orderAfsInfo *partner.OrderAfsInfo, err error) { orderAfsInfo = &partner.OrderAfsInfo{} var afsTotalShopMoney int64 order, err := partner.CurOrderManager.LoadOrder(vendorOrderID, model.VendorIDMTWM) if list, err := getAPI(order.VendorOrgCode, jxutils.GetShowStoreIDFromOrder(order), order.VendorStoreID).GetOrderRefundDetail(utils.Str2Int64(vendorOrderID), 0); err == nil { for _, v := range list { if v.RefundPartialEstimateCharge.SettleAmount != "" { afsTotalShopMoney += jxutils.StandardPrice2Int(utils.Str2Float64(v.RefundPartialEstimateCharge.SettleAmount)) } } } orderAfsInfo.AfsTotalShopMoney = order.TotalShopMoney + afsTotalShopMoney return orderAfsInfo, err } // //func (c *PurchaseHandler) GetOrderAfsInfo(ctx *jxcontext.Context, vendorOrderID, afsOrderID string) (orderAfsInfo *partner.OrderAfsInfo, err error) { // orderAfsInfo = &partner.OrderAfsInfo{} // var afsTotalShopMoney int64 // order, err := partner.CurOrderManager.LoadOrder(vendorOrderID, model.VendorIDMTWM) // // if list, err := getAPI(order.VendorOrgCode, jxutils.GetShowStoreIDFromOrder(order), order.VendorStoreID).GetOrderRefundDetail(utils.Str2Int64(vendorOrderID), 0); err == nil { // for _, v := range list { // if v.RefundPartialEstimateCharge.SettleAmount != "" { // afsTotalShopMoney += jxutils.StandardPrice2Int(utils.Str2Float64(v.RefundPartialEstimateCharge.SettleAmount)) // } // } // } // if err == nil { // //orderAfsInfo.AfsTotalShopMoney = order.TotalShopMoney + afsTotalShopMoney // orderAfsInfo.AfsTotalShopMoney = afsTotalShopMoney // } // return orderAfsInfo, err //}