Files
jx-callback/business/partner/purchase/mtwm/order_afs.go
邹宗楠 17bcf8cfa4 1
2025-05-29 12:49:43 +08:00

421 lines
19 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 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,
AfsFinishedAt: time.Now(),
//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)
afsOrder.AfsFinishedAt = time.Now()
}
}
if afsOrder != nil {
//直接就来一个新的售后单,并且还是售后完成的
if orderStatus.Status == model.AfsOrderStatusFinished {
afsOrder.Status = model.AfsOrderStatusFinished
}
// 补丁,这种类型的退款申请,不会在推送退款成功消息,直接将售后单标记为完成
if strings.Contains(afsOrder.ReasonDesc, "商家开通极速退款服务,用户申请系统自动通过") {
afsOrder.Status = model.AfsOrderStatusFinished
}
if refundData.NotifyType == mtwmapi.MsgTypeOrderAgree && msg.Cmd == mtwmapi.MsgTypeOrderRefund {
order, _ := partner.CurOrderManager.LoadOrder(orderStatus.RefVendorOrderID, model.VendorIDMTWM)
order.Status = model.OrderStatusCanceled
dao.UpdateEntity(db, order, "Status")
}
}
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 - utils.Float64TwoInt64((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 = "用户申请退货且退款"
}
// 0-等待处理中1-商家驳回退款请求2-商家同意退款3-客服驳回退款请求4-客服帮商家同意退款5-超时未处理系统自动同意6-系统自动确认7-用户取消退款申请8-用户取消退款申诉
if refundData.ResType == 2 || refundData.ResType == 4 || refundData.ResType == 5 || refundData.ResType == 6 {
orderStatus.Status = model.AfsOrderStatusFinished
} else if refundData.ResType == 1 || refundData.ResType == 3 || refundData.ResType == 7 || refundData.ResType == 8 {
orderStatus.Status = model.AfsOrderStatusFailed
}
// 1-已申请 10-初审已同意 11-初审已驳回 16-初审已申诉 17-初审申诉已同意 18-初审申诉已驳回 20-终审已发起(用户已发货) 21-终审已同意 22-终审已驳回 26-终审已申诉 27-终审申诉已同意 28-终审申诉已驳回 30-已取消
switch refundData.Status {
case "21", "27", "10", "17":
orderStatus.Status = model.AfsOrderStatusFinished
case "22", "28", "11", "18":
orderStatus.Status = model.AfsOrderStatusFailed
}
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
//}