refund coin order

This commit is contained in:
lwl0608 2024-03-19 16:04:36 +08:00
parent 05fbd7e04e
commit fca64cbb67
7 changed files with 293 additions and 13 deletions

View File

@ -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 {
}

View File

@ -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())

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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 // 自动提现明细
)