package tiktok_store import ( "fmt" tiktokShop "git.rosy.net.cn/baseapi/platformapi/tiktok_shop/tiktok_api" "git.rosy.net.cn/jx-callback/globals/api" "net/url" "strings" "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[string]int{ tiktokShop.CallbackRefundOrderMsgTagId: model.AfsOrderStatusWait4Approve, // 发起售后申请待审核 tiktokShop.CallbackUpdateRefundOrderMsgTagId: model.AfsOrderStatusWait4Approve, // 修改售后待审核 tiktokShop.CallbackRefundOrderSuccessMsgTagId: model.AfsOrderStatusFinished, // 退款成功 tiktokShop.CallbackRefundOrderRefuseMsgTagId: model.AfsOrderStatusFailed, // 拒绝退款 tiktokShop.CallbackRefundShopMsgTagId: model.AfsOrderStatusFailed, // 拒绝退货 tiktokShop.CallbackReturnApplyAgreedMsgTagId: model.AfsOrderStatusFinished, // 同意退货 tiktokShop.CallbackReturnRefundAgreedMsgTagId: model.AfsOrderStatusFinished, // 同意退款 } AfsTagIDMap = map[string]string{ tiktokShop.CallbackRefundOrderMsgTagId: "1", tiktokShop.CallbackUpdateRefundOrderMsgTagId: "1", tiktokShop.CallbackRefundOrderSuccessMsgTagId: "1", tiktokShop.CallbackRefundOrderRefuseMsgTagId: "1", tiktokShop.CallbackRefundShopMsgTagId: "1", tiktokShop.CallbackReturnApplyAgreedMsgTagId: "1", tiktokShop.CallbackReturnRefundAgreedMsgTagId: "1", tiktokShop.CallbackExchangeComfirmedMsgTagId: "1", tiktokShop.CallbackArbitrateDiscussUploadMsgTagId: "1", tiktokShop.CallbackArbitrateServiceInterveneMsgTagId: "1", tiktokShop.CallbackArbitrateCancelledMsgTagId: "1", tiktokShop.CallbackArbitrateAuditedMsgTagId: "1", tiktokShop.CallbackArbitrateSubmitedMsgTagId: "1", tiktokShop.CallbackArbitrateSubmitingMsgTagId: "1", tiktokShop.CallbackArbitrateAppliedMsgTagId: "1", tiktokShop.CallbackExpirationChangeMsgTagId: "1", tiktokShop.CallbackRefundClosedMsgTagId: "1", tiktokShop.CallbackBuyerReturnGoodsMsgTagId: "1", tiktokShop.CallbackSpecialRefundMsgTagId: "1", tiktokShop.CallbackSpecialRefundSuccessMsgTagId: "1", tiktokShop.CallbackAuditAgreeResendMsgTagId: "1", tiktokShop.CallbackResendFillLogisticsMsgTagId: "1", } // ReasonCodeMap 申请退货理由,可能会更新 ReasonCodeMap = map[int]string{ 1: "商品已发出,如买家不再需要请拒收后申请仅退款或收到后申请退货退款", 2: "商品已经签收,如买家不再需要可以申请退货退款", 3: "买家误操作/取消申请", 4: "问题已解决,待用户收货", 5: "商品已发出,如买家不再需要请拒收后申请仅退款或收到后申请退货退款", 6: "买家误操作/取消申请", 7: "协商一致,用户取消退款", 8: "已与买家协商补偿,包括差价、赠品、额外补偿", 9: "已与买家协商补发商品", 10: "已与买家协商换货", 11: "买家上传的单号有误,商家尚未收到货,请核实正确物流单号后重新上传", 12: "退货与原订单不符(商品不符、退货地址不符)", 13: "退回商品影响二次销售", 14: "买家误操作/取消申请", 15: "协商一致,用户取消退款", 16: "买家误操作/取消申请", 17: "协商一致,用户取消退款", 18: "商品影响二次销售", 19: "定制商品不支持七天无理由退货,定制商品不接受质量问题以外的退货", 20: "定制商品不支持七天无理由退货,定制商品不接受质量问题以外的退货", 21: "买家申请的金额有误", 22: "运费未协商一致", 23: "商品没问题,买家未举证或凭证无效", 24: "已在约定时间发货", 25: "运费未协商一致", 26: "商品已经签收,如买家不再需要可以申请退货退款", 27: "商品没问题,买家未举证或举证无效", 28: "已在约定时间发货", 29: "买家申请的金额有误", 30: "发票没问题,买家未举证", 31: "发票已补寄", 32: "买家发票信息不完整", 33: "运费未协商一致", 34: "申请时间已超7天无理由退换货时间", 35: "不支持买家主观原因退换货", 36: "买家填错号码", 37: "已完成服务,买家未提供凭证或凭证无效", 38: "买家填错号码", 39: "已完成服务,买家未提供凭证或凭证无效", 40: "和达人达成一致,取消终止", 41: "其他", 42: "其他", 43: "未收到货/退货单号有误", 44: "退货与原订单不符(商品不符、退货地址不符)", 45: "商家已发货", 46: "商品已经签收,如买家不再需要可以申请退货退款", 47: "已与买家协商一致仅退款", 48: "问题已解决,待用户确认收货", 49: "已与买家协商一致延迟发货", 50: "未少发漏发", 51: "已与买家协商补偿,包括差价、赠品、额外补偿", 52: "已与买家协商一致延迟发货", 53: "其他", 54: "本单已购买【养死包赔】保险,请从保险理赔入口申请理赔", 55: "本单已购买【开箱无忧】保险,请从保险理赔入口申请理赔", 56: "商品没问题,买家未举证或举证无效", 57: "已完成服务,买家未举证或举证无效", 58: "本单已购买【食安保】保险,请从保险理赔入口申请理赔", } ) // 是否为售后消息 func (c *PurchaseHandler) isAfsMsg(msg *tiktokShop.OrderCallback) bool { _, ok := AfsTagIDMap[msg.MsgId] return ok } func (c *PurchaseHandler) OnAfsOrderMsg(msg *mtwmapi.CallbackMsg) (retVal *mtwmapi.CallbackResponse) { jxutils.CallMsgHandlerAsync(func() { retVal = c.onAfsOrderMsg(msg) }, jxutils.ComposeUniversalOrderID(GetOrderIDFromMsg(msg), model.VendorIDEBAI)) return retVal } // todo 对于退款与部分退款,order.go与这个文件中对于状态的处理不一致 func (c *PurchaseHandler) onAfsOrderMsg(msg *tiktokShop.OrderCallback) (retVal *tiktokShop.CallbackResponse) { var err error orderStatus := c.callbackAfsMsg2Status(msg) for _, v := range orderStatus { needCallNew := v.Status == model.AfsOrderStatusWait4Approve || v.Status == model.AfsOrderStatusNew if !needCallNew { if _, err := partner.CurOrderManager.LoadAfsOrder(v.VendorOrderID, v.VendorID); err != nil { if dao.IsNoRowsError(err) { needCallNew = true } else { return tiktokShop.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, } for _, sku := range refundData.FoodList { orderSku := &model.OrderSkuFinancial{ Count: sku.Count, VendorSkuID: sku.SkuID, SkuID: int(utils.Str2Int64WithDefault(sku.SkuID, 0)), Name: sku.FoodName, UserMoney: jxutils.StandardPrice2Int(sku.RefundPrice)*int64(sku.Count) + jxutils.StandardPrice2Int(sku.BoxPrice)*int64(sku.BoxNum), } afsOrder.SkuUserMoney += orderSku.UserMoney afsOrder.Skus = append(afsOrder.Skus, orderSku) } } else { if afsOrder = c.createAfsOrder(msg.FormData); afsOrder != nil { 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 } err = partner.CurOrderManager.OnAfsOrderNew(afsOrder, orderStatus) } } else { err = partner.CurOrderManager.OnAfsOrderStatusChanged(orderStatus) } } 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 *tiktokShop.OrderCallback) (orderStatus []*model.OrderStatus) { switch msg.MsgId { case tiktokShop.CallbackRefundOrderMsgTagId: // 买家发起售后申请消息 for _, v := range msg.Body[tiktokShop.CallbackRefundOrderMsgTagId] { refundOrder := v.(*tiktokShop.BuyerRefundCreatedData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "create"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackRefundOrderMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.ApplyTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } case tiktokShop.CallbackUpdateRefundOrderMsgTagId: // 买家修改售后申请消息 for _, v := range msg.Body[tiktokShop.CallbackUpdateRefundOrderMsgTagId] { refundOrder := v.(*tiktokShop.BuyerRefundModifiedData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "update"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackUpdateRefundOrderMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.ModifyTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } case tiktokShop.CallbackRefundOrderSuccessMsgTagId: // 退款成功消息 for _, v := range msg.Body[tiktokShop.CallbackRefundOrderSuccessMsgTagId] { refundOrder := v.(*tiktokShop.BusinessRefundSuccessData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "refund_success"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackUpdateRefundOrderMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.SuccessTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } case tiktokShop.CallbackRefundOrderRefuseMsgTagId: // 拒绝退款消息 for _, v := range msg.Body[tiktokShop.CallbackRefundOrderSuccessMsgTagId] { refundOrder := v.(*tiktokShop.BusinessRefundSuccessData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "refund_money_fail"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackUpdateRefundOrderMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.SuccessTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } case tiktokShop.CallbackRefundShopMsgTagId: // 拒绝退货申请消息 for _, v := range msg.Body[tiktokShop.CallbackRefundShopMsgTagId] { refundOrder := v.(*tiktokShop.BusinessNotReturnApplyRefusedData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "refuse_shop_fail"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackRefundShopMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.RefuseTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } case tiktokShop.CallbackReturnApplyAgreedMsgTagId: // 同意退货申请消息 for _, v := range msg.Body[tiktokShop.CallbackReturnApplyAgreedMsgTagId] { refundOrder := v.(*tiktokShop.BusinessRefundSuccessData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "refund_shop_success"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackReturnApplyAgreedMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.SuccessTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } case tiktokShop.CallbackReturnRefundAgreedMsgTagId: // 同意退款消息 for _, v := range msg.Body[tiktokShop.CallbackReturnRefundAgreedMsgTagId] { refundOrder := v.(*tiktokShop.BusinessRefundSuccessData) orderMsg := &model.OrderStatus{ VendorID: model.VendorIDDD, OrderType: model.OrderTypeAfsOrder, RefVendorOrderID: utils.Int64ToStr(refundOrder.PId), RefVendorID: model.VendorIDDD, VendorStatus: fmt.Sprintf("%s:%s", "order", "refund_money_success"), Status: c.GetAfsStatusFromVendorStatus(refundOrder.AftersaleType, tiktokShop.CallbackReturnRefundAgreedMsgTagId), StatusTime: utils.Timestamp2Time(int64(refundOrder.SuccessTime)), } if k, ok := ReasonCodeMap[refundOrder.ReasonCode]; ok { orderMsg.Remark = k } else { orderMsg.Remark = "抖音reason_code对应reason不足,需要更新.code:" + string(refundOrder.ReasonCode) } if refundOrder.AftersaleId > 0 { orderMsg.VendorOrderID = utils.Int64ToStr(refundOrder.AftersaleId) } else { orderMsg.VendorOrderID = orderMsg.RefVendorOrderID } orderStatus = append(orderStatus, orderMsg) } default: return nil } return orderStatus } func (c *PurchaseHandler) GetAfsStatusFromVendorStatus(resType int, notifyType string) int { 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 { 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 if list, err := api.MtwmAPI.GetOrderRefundDetail(utils.Str2Int64(vendorOrderID), 0); err == nil { for _, v := range list { if v.RefundPartialEstimateCharge.SettleAmount != "" { afsTotalShopMoney += jxutils.StandardPrice2Int(utils.Str2Float64(v.RefundPartialEstimateCharge.SettleAmount)) } } } if order, err := partner.CurOrderManager.LoadOrder(vendorOrderID, model.VendorIDMTWM); err == nil { orderAfsInfo.AfsTotalShopMoney = order.TotalShopMoney + afsTotalShopMoney } return orderAfsInfo, err }