From fca64cbb67ea76982584e539f8bc8e00890053aa Mon Sep 17 00:00:00 2001 From: lwl0608 Date: Tue, 19 Mar 2024 16:04:36 +0800 Subject: [PATCH] refund coin order --- api/proto/vas/proto/op.go | 8 ++ app/mix/controller/init.go | 1 + app/mix/controller/vas.go | 16 +++ app/mix/dao/mysql.go | 15 +++ app/mix/service/logic/vas.go | 231 +++++++++++++++++++++++++++++++++- app/mix/service/vasservice.go | 20 ++- dbstruct/vas_mysql.go | 15 +-- 7 files changed, 293 insertions(+), 13 deletions(-) diff --git a/api/proto/vas/proto/op.go b/api/proto/vas/proto/op.go index 85a49df4..8d8e298d 100644 --- a/api/proto/vas/proto/op.go +++ b/api/proto/vas/proto/op.go @@ -54,3 +54,11 @@ type RefundOrderReq struct { type RefundOrderData struct { } + +type RefundCoinOrderReq struct { + OrderId string `json:"order_id"` + Operator string `json:"operator"` +} + +type RefundCoinOrderData struct { +} diff --git a/app/mix/controller/init.go b/app/mix/controller/init.go index 840ccef3..0a7a7286 100644 --- a/app/mix/controller/init.go +++ b/app/mix/controller/init.go @@ -241,6 +241,7 @@ func Init(r *gin.Engine) { opVasPayGroup.POST("create_order", middleware.JSONParamValidator(vasproto.OpCreateOrderReq{}), OpCreateOrder) opVasPayGroup.POST("coin_order_list", middleware.JSONParamValidator(vasproto.OpCoinOrderListReq{}), OpOrderList) opVasPayGroup.POST("refund_order", middleware.JSONParamValidator(vasproto.RefundOrderReq{}), RefundOrder) + opVasPayGroup.POST("refund_coin_order", middleware.JSONParamValidator(vasproto.RefundCoinOrderReq{}), RefundCoinOrder) // 验证码 opVeriCodeGroup := r.Group("/op/veri_code", PrepareOp()) diff --git a/app/mix/controller/vas.go b/app/mix/controller/vas.go index 5ddc3fe4..cf8088fc 100644 --- a/app/mix/controller/vas.go +++ b/app/mix/controller/vas.go @@ -324,3 +324,19 @@ func RefundOrder(ctx *gin.Context) { } ReplyOk(ctx, nil) } + +// 金币订单退款 +func RefundCoinOrder(ctx *gin.Context) { + req := ctx.MustGet("client_req").(*vasproto.RefundCoinOrderReq) + ec, err := service.DefaultService.RefundCoinOrder(ctx, req) + if ec != errcode.ErrCodeVasSrvOk { + logger.Error("RefundCoinOrder fail, req: %v, ec: %v", util.ToJson(req), ec) + if err != nil { + ReplyErrorMsg(ctx, err.Error()) + return + } + ReplyErrCodeMsg(ctx, ec) + return + } + ReplyOk(ctx, nil) +} diff --git a/app/mix/dao/mysql.go b/app/mix/dao/mysql.go index e6629528..3f3ee5a5 100644 --- a/app/mix/dao/mysql.go +++ b/app/mix/dao/mysql.go @@ -732,6 +732,21 @@ func (m *Mysql) GetUnlockWechatList(ctx *gin.Context, tx *sqlx.Tx, mid int64, of return } +// 删除会员解锁记录 +func (m *Mysql) DeleteUserVasUnlock(ctx *gin.Context, tx *sqlx.Tx, mid int64, orderId, productId string) (err error) { + sqlStr := fmt.Sprintf("delete from %s where mid=? and order_id=? and product_id=?", TableVasUserUnlock) + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, mid, orderId, productId) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, mid, orderId, productId) + } + if err != nil { + return + } + return +} + // 获取消费历史 func (m *Mysql) GetUCHList(ctx *gin.Context, tx *sqlx.Tx, mid int64, typ int32, offset, limit int) (list []*dbstruct.ConsumeHistory, err error) { list = make([]*dbstruct.ConsumeHistory, 0) diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index 9f29bea6..44439381 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -2431,6 +2431,7 @@ func (v *Vas) UnlockMembership(ctx *gin.Context, mid int64, product *dbstruct.Pr // 订单退款,只退充值 func (v *Vas) RefundOrder(ctx *gin.Context, req *vasproto.RefundOrderReq) error { + // 获取订单 order, err := v.store.GetOrderById(ctx, nil, req.OrderId) if err != nil { return err @@ -2439,8 +2440,17 @@ func (v *Vas) RefundOrder(ctx *gin.Context, req *vasproto.RefundOrderReq) error return errs.ErrVasOrderNotExists } - switch order.GetProductId() { - case dbstruct.ProductIdMembership: + // 获取商品 + product, err := v.store.GetProductById(ctx, order.GetProductId()) + if err != nil { + return err + } + if product == nil { + return errs.ErrVasProductNotExists + } + + switch product.Type { + case dbstruct.ProductTypeMoneyMembership: return v.refundMembership(ctx, order, req) default: return errors.New("invalid product") @@ -2610,7 +2620,7 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order, req *vas Uid: goproto.Int64(ch.GetMid()), Did: goproto.String(ch.GetDid()), Type: goproto.Int32(dbstruct.CHTypeIncome), - SType: goproto.Int32(dbstruct.CHSTypeIncomeRefund), + SType: goproto.Int32(dbstruct.CHSTypeIncomeRefundMembership), TypeId: goproto.String(ch.GetTypeId()), OrderId: goproto.String(ch.GetOrderId()), Change: goproto.Int64(-change), @@ -2691,3 +2701,218 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order, req *vas return nil } + +// 金币订单退款 +func (v *Vas) RefundCoinOrder(ctx *gin.Context, req *vasproto.RefundCoinOrderReq) error { + // 获取订单 + order, err := v.store.GetCoinOrderById(ctx, nil, req.OrderId) + if err != nil { + return err + } + if order == nil { + return errs.ErrVasOrderNotExists + } + + // 获取商品 + product, err := v.store.GetProductById(ctx, order.GetProductId()) + if err != nil { + return err + } + if product == nil { + return errs.ErrVasProductNotExists + } + + switch order.GetProductId() { + case dbstruct.ProductIdContactWechat: + return v.refundContactWechat(ctx, order, req) + default: + return errors.New("invalid product") + } +} + +func (v *Vas) refundContactWechat(ctx *gin.Context, order *dbstruct.CoinOrder, req *vasproto.RefundCoinOrderReq) error { + switch order.GetOrderStatus() { + case dbstruct.VasCoinOrderStatusNone, dbstruct.VasOrderStatusInit: + return errors.New("订单还未成功支付,无法退款") + case dbstruct.VasCoinOrderStatusRefund: + return errors.New("已退款,请勿重复操作") + } + + var ( + mid = order.GetMid() + orderId = order.GetID() + isFinish = order.GetOrderStatus() == dbstruct.VasCoinOrderStatusFinish + ) + + // 开启事务 + tx, err := v.store.VasBegin(ctx) + if err != nil { + logger.Error("vas begin fail, err: %v", err) + return err + } + defer func() { + if order != nil { + _ = v.AddOplogOrder( + ctx, + &dbstruct.OplogOrder{ + OrderId: orderId, + Action: dbstruct.OrderOpLogActionUpdate, + Operator: req.Operator, + Detail: fmt.Sprintf("微信联系方式"), + BeforeStatus: order.GetOrderStatus(), + AfterStatus: dbstruct.VasCoinOrderStatusRefund, + }, + err, + ) + } + + if err != nil { + logger.Error("global err, req: %v, err: %v", util.ToJson(req), err) + } + errTx := v.store.DealTxCR(tx, err) + if errTx != nil { + logger.Error("DealTxCR fail, err: %v", errTx) + return + } + }() + + // 用户消费记录 + costChList, err := v.store.GetCostCHList(ctx, tx, orderId) + if err != nil { + logger.Error("GetCostCHList fail, err: %v", err) + return err + } + if len(costChList) <= 0 || len(costChList) > 1 { + err = errors.New("invalid cost ch list") + logger.Error("invalid cost ch list, orderId: %v, err: %v", orderId, err) + return err + } + costCh := costChList[0] + costChNew := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(costCh.GetMid()), + Uid: goproto.Int64(costCh.GetUid()), + Did: goproto.String(costCh.GetDid()), + Type: goproto.Int32(costCh.GetType()), + SType: goproto.Int32(dbstruct.CHSTypeCostRefundContactWechat), + TypeId: goproto.String(costCh.GetTypeId()), + OrderId: goproto.String(costCh.GetOrderId()), + Change: goproto.Int64(-costCh.GetChange()), + Ct: goproto.Int64(time.Now().Unix()), + } + err = v.store.CreateConsumeHistory(ctx, tx, costChNew) + if err != nil { + logger.Error("CreateConsumeHistory fail, costChNew: %v, err: %v", util.ToJson(costChNew), err) + return err + } + + // 给用户加回金币 + err = v.store.IncCoins(ctx, tx, mid, costCh.GetChange()) + if err != nil { + logger.Error("IncCoins fail, mid: %v, change: %v, err: %v", mid, costCh.GetChange(), err) + return err + } + + // 修改金币订单状态 xx -> 已退款 + err = v.store.UpdateCoinOrderStatus(ctx, tx, orderId, dbstruct.VasCoinOrderStatusRefund) + if err != nil { + logger.Error("UpdateCoinOrderStatus fail, err: %v", err) + return err + } + + // 删除微信解锁记录 + err = v.store.DeleteUserVasUnlock(ctx, tx, mid, orderId, dbstruct.ProductIdContactWechat) + if err != nil { + logger.Error("UpdateCoinOrderStatus fail, mid: %v, orderId: %v, err: %v", mid, orderId, err) + return err + } + + // 获取收入记录 + chListTmp, err := v.store.GetIncomeCHList(ctx, tx, orderId) + if err != nil { + return err + } + incomeChList := make([]*dbstruct.ConsumeHistory, 0) + for _, ch := range chListTmp { + if ch.GetMid() == common.OfficialMid { + continue + } + incomeChList = append(incomeChList, ch) + } + if len(incomeChList) > 1 { + err = errors.New("收入记录错误,请找开发同学排查") + return err + } + + // 有分成的情况 + if len(incomeChList) > 0 { + ch := incomeChList[0] + streamerMid := ch.GetMid() + if streamerMid <= 0 { + err = errors.New("收入streamerMid错误") + logger.Error("invalid streamerMid: %v", streamerMid) + return err + } + + // 主播钱包 + wallet, err := v.store.GetWalletForUpdate(ctx, tx, streamerMid) + if err != nil { + return err + } + + // 扣主播金币 + change := ch.GetChange() + err = v.store.DecDiamonds(ctx, tx, streamerMid, change) + if err != nil { + return err + } + + // 扣金币的收入记录 + chNew := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(streamerMid), + Uid: goproto.Int64(ch.GetMid()), + Did: goproto.String(ch.GetDid()), + Type: goproto.Int32(dbstruct.CHTypeIncome), + SType: goproto.Int32(dbstruct.CHSTypeIncomeRefundContactWechat), + TypeId: goproto.String(ch.GetTypeId()), + OrderId: goproto.String(ch.GetOrderId()), + Change: goproto.Int64(-change), + Before: goproto.Int64(wallet.GetDiamonds()), + After: goproto.Int64(wallet.GetDiamonds() - change), + Ct: goproto.Int64(time.Now().Unix()), + } + err = v.store.CreateConsumeHistory(ctx, tx, chNew) + if err != nil { + logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chNew), err) + return err + } + + // 如果已结算,扣提现钻石 + if isFinish { + // 扣提现钻石 + err = v.store.DecWithdrawDiamonds(ctx, tx, streamerMid, change) + if err != nil { + logger.Error("DecWithdrawDiamonds fail, streamerMid: %v, change: %v, err: %v", streamerMid, change, err) + return err + } + + // 提现钻石记录 + wh := &dbstruct.WithdrawDiamondsHis{ + Mid: goproto.Int64(streamerMid), + IncomeChId: goproto.Int64(ch.GetId()), + OrderId: goproto.String(ch.GetOrderId()), + Ct: goproto.Int64(time.Now().Unix()), + BeforeWithdrawDiamonds: goproto.Int64(wallet.GetWithdrawDiamonds()), + AfterWithdrawDiamonds: goproto.Int64(wallet.GetWithdrawDiamonds() - change), + Change: goproto.Int64(-change), + ProductId: goproto.String(order.GetProductId()), + } + err = v.store.CreateWithdrawDiamondsHis(ctx, tx, wh) + if err != nil { + logger.Error("CreateWithdrawDiamondsHis fail, wh: %v, err: %v", util.ToJson(wh), err) + return err + } + } + } + + return nil +} diff --git a/app/mix/service/vasservice.go b/app/mix/service/vasservice.go index 1cf79b0d..7293dabd 100644 --- a/app/mix/service/vasservice.go +++ b/app/mix/service/vasservice.go @@ -246,7 +246,7 @@ func (s *Service) chListCost(ctx *gin.Context, chList []*dbstruct.ConsumeHistory if chDB.GetChange() < 0 { item.Change = fmt.Sprintf("%d金币", chDB.GetChange()) } - case dbstruct.CHSTypeCostRefund: + case dbstruct.CHSTypeCostRefundContactWechat: item.Desc = fmt.Sprintf("购买\"%s\"微信退款", util.DerefString(acnt.Name)) item.Change = changeMark + fmt.Sprintf("%d金币", chDB.GetChange()) case dbstruct.CHSTypeCostMembership: @@ -315,8 +315,11 @@ func (s *Service) chListIncome(ctx *gin.Context, chList []*dbstruct.ConsumeHisto case dbstruct.CHSTypeIncomeInvite: item.Desc = "邀请收益" item.Change = changeMark + fmt.Sprintf("%d钻石", chDB.GetChange()) - case dbstruct.CHSTypeIncomeRefund: - item.Desc = "会员退款" + case dbstruct.CHSTypeIncomeRefundMembership: + item.Desc = "用户会员退款" + item.Change = changeMark + fmt.Sprintf("%d钻石", chDB.GetChange()) + case dbstruct.CHSTypeIncomeRefundContactWechat: + item.Desc = "用户微信退款" item.Change = changeMark + fmt.Sprintf("%d钻石", chDB.GetChange()) } list = append(list, item) @@ -546,3 +549,14 @@ func (s *Service) RefundOrder(ctx *gin.Context, req *vasproto.RefundOrderReq) (e } return } + +func (s *Service) RefundCoinOrder(ctx *gin.Context, req *vasproto.RefundCoinOrderReq) (ec errcode.ErrCode, err error) { + ec = errcode.ErrCodeVasSrvOk + err = _DefaultVas.RefundCoinOrder(ctx, req) + if err != nil { + logger.Error("RefundCoinOrder fail, req: %v, err: %v", util.ToJson(req), err) + ec = errcode.ErrCodeVasSrvFail + return + } + return +} diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 9502602f..899533fd 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -490,10 +490,10 @@ const ( CHTypeIncome = 3 // 收入明细(钻石) CHTypeWithdraw = 4 // 提现明细(钻石) - CHSTypeCostContact = 10001 // 消费明细,联系方式 - CHSTypeCostRefund = 10002 // 消费明细,金币退款 - CHSTypeCostMembership = 10003 // 消费明细,会员资格解锁(伪金币记录,会员资格解锁中间无转金币过程) - CHSTypeCostRefundMembership = 10004 // 消费明细,会员资格解锁退款(伪金币记录,会员资格解锁中间无转金币过程) + CHSTypeCostContact = 10001 // 消费明细,联系方式 + CHSTypeCostRefundContactWechat = 10002 // 消费明细,微信联系方式退款 + CHSTypeCostMembership = 10003 // 消费明细,会员资格解锁(伪金币记录,会员资格解锁中间无转金币过程) + CHSTypeCostRefundMembership = 10004 // 消费明细,会员资格解锁退款(伪金币记录,会员资格解锁中间无转金币过程) CHSTypeChargeUser = 20001 // 充值明细,用户自己冲 CHSTypeChargeOp = 20002 // 充值明细,OP充值 @@ -501,9 +501,10 @@ const ( CHSTypeChargeRefundMembership = 20004 // 充值明细,会员退款 CHSTypeChargeMembership = 20005 // 充值明细,会员充值 - CHSTypeIncomeContact = 30001 // 收入明细,联系方式 - CHSTypeIncomeInvite = 30002 // 收入明细,邀请分成 - CHSTypeIncomeRefund = 30003 // 收入明细,退款 + CHSTypeIncomeContact = 30001 // 收入明细,联系方式 + CHSTypeIncomeInvite = 30002 // 收入明细,邀请分成 + CHSTypeIncomeRefundMembership = 30003 // 收入明细,会员退款 + CHSTypeIncomeRefundContactWechat = 30004 // 收入明细,微信退款 CHSTypeWithdrawDiamondAuto = 40001 // 自动提现明细 )