From c470a8e77fb90ca8f7955ef1e5c3442eef32b705 Mon Sep 17 00:00:00 2001 From: lwl0608 Date: Mon, 29 Apr 2024 15:47:45 +0800 Subject: [PATCH] add zone refund --- app/mix/dao/mysql_zone.go | 56 ++- app/mix/service/logic/vas.go | 619 +++++++++++++++++++++++++----- app/mix/service/logic/vas_zone.go | 41 ++ app/mix/service/vasservice.go | 6 + dbstruct/vas_mysql.go | 27 +- 5 files changed, 640 insertions(+), 109 deletions(-) diff --git a/app/mix/dao/mysql_zone.go b/app/mix/dao/mysql_zone.go index f0499d42..fe35c592 100644 --- a/app/mix/dao/mysql_zone.go +++ b/app/mix/dao/mysql_zone.go @@ -108,7 +108,7 @@ func (m *Mysql) CreateZoneUnlock(ctx *gin.Context, tx *sqlx.Tx, mid, zid int64) return nil } -// 添加到空间成员 +// 解锁空间动态 func (m *Mysql) UnlockZoneMoment(ctx *gin.Context, tx *sqlx.Tx, mid, zid, momentId int64, orderId string) error { var err error // 先获取,没有再添加 @@ -144,6 +144,23 @@ func (m *Mysql) UnlockZoneMoment(ctx *gin.Context, tx *sqlx.Tx, mid, zid, moment return err } +// 退款空间动态 +func (m *Mysql) RefundZoneMoment(ctx *gin.Context, tx *sqlx.Tx, mid, zid, momentId int64) error { + var err error + sqlStr := fmt.Sprintf("delete from %s where mid=? and zid=? and moment_id=?", TableVasZoneMomentUnlock) + args := []any{mid, zid, momentId} + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, args...) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, args...) + } + if err != nil { + return err + } + return err +} + // 获取动态解锁信息 by mid, moment_ids func (m *Mysql) GetZoneMomentUnlockListByMidMomentIds(ctx *gin.Context, tx *sqlx.Tx, mid int64, momentIds []int64) (list []*dbstruct.ZoneMomentUnlock, err error) { list = make([]*dbstruct.ZoneMomentUnlock, 0) @@ -249,6 +266,23 @@ func (m *Mysql) UnlockZoneSuperfanship(ctx *gin.Context, tx *sqlx.Tx, mid, zid, return err } +// 超粉退款 +func (m *Mysql) RefundZoneSuperfanship(ctx *gin.Context, tx *sqlx.Tx, mid, zid int64) error { + var err error + sqlStr := "update " + TableVasZoneUnlock + " set superfanship_ct=?, superfanship_until=?, superfanship_unlock_type=? where mid=? and zid=?" + args := []any{0, 0, dbstruct.ZoneUnlockTypeRefund, mid, zid} + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, args...) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, args...) + } + if err != nil { + return err + } + return err +} + // 增加空间消费详情 func (m *Mysql) CreateZoneConsumeHis(ctx *gin.Context, tx *sqlx.Tx, zch *dbstruct.ZoneConsumeHis) error { var err error @@ -268,6 +302,26 @@ func (m *Mysql) CreateZoneConsumeHis(ctx *gin.Context, tx *sqlx.Tx, zch *dbstruc return nil } +// 获取空间消费详情 +func (m *Mysql) GetZoneConsumeHisByOrderId(ctx *gin.Context, tx *sqlx.Tx, orderId string) (list []*dbstruct.ZoneConsumeHis, err error) { + list = make([]*dbstruct.ZoneConsumeHis, 0) + sqlStr := fmt.Sprintf("select * from %s where order_id=?", TableVasZoneMomentUnlock) + if tx != nil { + err = tx.SelectContext(ctx, &list, sqlStr, orderId) + } else { + db := m.getDBVas() + err = db.SelectContext(ctx, &list, sqlStr, orderId) + } + if err == sql.ErrNoRows { + err = nil + return + } + if err != nil { + return + } + return +} + // 增加空间消费 func (m *Mysql) IncZoneConsume(ctx *gin.Context, tx *sqlx.Tx, mid, zid, inc int64) error { var err error diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index 4c754426..74117ec1 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -9,7 +9,6 @@ import ( "encoding/pem" "errors" "fmt" - "github.com/go-pay/gopay/wechat/v3" "service/api/base" "service/api/errs" accountproto "service/api/proto/account/proto" @@ -2042,14 +2041,19 @@ func (v *Vas) RefundOrder(ctx *gin.Context, req *vasproto.RefundOrderReq, opt *v switch product.Id { case dbstruct.ProductIdH5ZoneAdmission: if opt == nil { - opt = vasproto.NewRefundOrderOpt().SetZoneRefundReq(&vasproto.ZoneRefundReq{ - Zid: order.GetZid(), - ContactName: "op", - ContactPhone: "op", - Note: "op", - }) + opt = vasproto.NewRefundOrderOpt().SetZoneRefundReq(&vasproto.ZoneRefundReq{Zid: order.GetZid(), ContactName: "op", ContactPhone: "op", Note: "op"}) } err = v.refundZoneAdmission(ctx, order, req, opt) + case dbstruct.ProductIdH5ZoneMoment: + if opt == nil { + opt = vasproto.NewRefundOrderOpt().SetZoneRefundReq(&vasproto.ZoneRefundReq{Zid: order.GetZid(), ContactName: "op", ContactPhone: "op", Note: "op"}) + } + err = v.refundZoneMoment(ctx, order, req, opt) + case dbstruct.ProductIdH5ZoneSuperfanship: + if opt == nil { + opt = vasproto.NewRefundOrderOpt().SetZoneRefundReq(&vasproto.ZoneRefundReq{Zid: order.GetZid(), ContactName: "op", ContactPhone: "op", Note: "op"}) + } + err = v.refundZoneSuperfanship(ctx, order, req, opt) default: err = fmt.Errorf("不支持该商品退款: %s", product.Id) } @@ -2177,6 +2181,13 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order, req *vas return err } + // 扣除空间消费 + err = v.RollbackZoneConsume(ctx, tx, orderId) + if err != nil { + logger.Error("RollbackZoneConsume fail, orderId: %v", orderId) + return err + } + // 删除会员解锁记录 err = v.store.DeleteUserVasMembershipUnlock(ctx, tx, order.GetMid()) if err != nil { @@ -2284,29 +2295,9 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order, req *vas } // 退款 - switch order.GetPayType() { - case vasproto.PayTypeAlipay, vasproto.PayTypeAlipayH5: - alipayCli := alipaycli.GetAlipayClientByAppId(order.GetOid3()) - resp, err := alipayCli.RefundOne(ctx, &alipaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) - if err != nil { - logger.Error("alipayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) - return err - } - case vasproto.PayTypeWxpayNative, vasproto.PayTypeWxpayJsapi, vasproto.PayTypeWxpayH5: - wxpayCli := wxpaycli.GetDefaultWxpayClient() - resp, err := wxpayCli.RefundOne(ctx, &wxpaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) - if err != nil { - logger.Error("wxpayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) - return err - } + err = v.payRefund(ctx, order) + if err != nil { + return err } return nil @@ -2402,29 +2393,9 @@ func (v *Vas) refundCoins(ctx *gin.Context, order *dbstruct.Order, req *vasproto } // 退款 - switch order.GetPayType() { - case vasproto.PayTypeAlipay, vasproto.PayTypeAlipayH5: - alipayCli := alipaycli.GetAlipayClientByAppId(order.GetOid3()) - resp, err := alipayCli.RefundOne(ctx, &alipaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) - if err != nil { - logger.Error("alipayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) - return err - } - case vasproto.PayTypeWxpayNative, vasproto.PayTypeWxpayJsapi, vasproto.PayTypeWxpayH5: - wxpayCli := wxpaycli.GetDefaultWxpayClient() - resp, err := wxpayCli.RefundOne(ctx, &wxpaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) - if err != nil { - logger.Error("wxpayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) - return err - } + err = v.payRefund(ctx, order) + if err != nil { + return err } return nil @@ -2520,35 +2491,15 @@ func (v *Vas) refundMoneyContactWechat(ctx *gin.Context, order *dbstruct.Order, } // 退款 - switch order.GetPayType() { - case vasproto.PayTypeAlipay, vasproto.PayTypeAlipayH5: - alipayCli := alipaycli.GetAlipayClientByAppId(order.GetOid3()) - resp, err := alipayCli.RefundOne(ctx, &alipaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) - if err != nil { - logger.Error("alipayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) - return err - } - case vasproto.PayTypeWxpayNative, vasproto.PayTypeWxpayJsapi, vasproto.PayTypeWxpayH5: - wxpayCli := wxpaycli.GetDefaultWxpayClient() - resp, err := wxpayCli.RefundOne(ctx, &wxpaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) - if err != nil { - logger.Error("wxpayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) - return err - } + err = v.payRefund(ctx, order) + if err != nil { + return err } return nil } -// todo: 现金:普通空间退款 +// 现金:普通空间退款 func (v *Vas) refundZoneAdmission(ctx *gin.Context, order *dbstruct.Order, req *vasproto.RefundOrderReq, opt *vasproto.RefundOrderOpt) error { switch order.GetOrderStatus() { case dbstruct.VasOrderStatusNone, dbstruct.VasOrderStatusInit: @@ -2766,31 +2717,469 @@ func (v *Vas) refundZoneAdmission(ctx *gin.Context, order *dbstruct.Order, req * } // 退款 - switch order.GetPayType() { - case vasproto.PayTypeAlipay, vasproto.PayTypeAlipayH5: - alipayCli := alipaycli.GetAlipayClientByAppId(order.GetOid3()) - var resp *alipay.TradeRefundResponse - resp, err = alipayCli.RefundOne(ctx, &alipaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) + err = v.payRefund(ctx, order) + if err != nil { + return err + } + + return nil +} + +// 现金:空间动态退款 +func (v *Vas) refundZoneMoment(ctx *gin.Context, order *dbstruct.Order, req *vasproto.RefundOrderReq, opt *vasproto.RefundOrderOpt) error { + switch order.GetOrderStatus() { + case dbstruct.VasOrderStatusNone, dbstruct.VasOrderStatusInit: + return errors.New("订单还未支付,无法退款") + case dbstruct.VasOrderStatusRefund: + return errors.New("已退款,请勿重复操作") + } + + var ( + mid = order.GetMid() + zid = int64(0) + momentId = order.GetMomentId() + orderId = order.GetID() + isFinish = order.GetOrderStatus() == dbstruct.VasOrderStatusFinish + zoneRefundReq *vasproto.ZoneRefundReq + ) + if opt.GetZoneRefundReq() != nil { + zoneRefundReq = opt.GetZoneRefundReq() + } + if zoneRefundReq == nil { + return fmt.Errorf("zoneRefundReq is nil, order_id: %v", orderId) + } + zid = zoneRefundReq.Zid + // 和订单的zid不匹配 + if order.GetZid() != zid { + err := fmt.Errorf("与订单的zid不匹配,orderZid: %v, reqZid: %v", order.GetZid(), zid) + return err + } + + // 开启事务 + 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.VasOrderStatusRefund, + }, + err, + ) + } + if err != nil { - logger.Error("alipayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) + 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 + } + }() + + // 用户的充值退款记录 + chargeChList, err := v.store.GetChargeCHList(ctx, tx, orderId) + if err != nil { + logger.Error("GetChargeCHList fail, orderId: %v, err: %v", orderId, err) + return err + } + if len(chargeChList) <= 0 || len(chargeChList) > 1 { + err = errors.New("invalid charge ch list") + logger.Error("invalid charge ch list, orderId: %v, err: %v", orderId, err) + return err + } + chargeCh := chargeChList[0] + chargeChNew := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(chargeCh.GetMid()), + Uid: goproto.Int64(chargeCh.GetUid()), + Did: goproto.String(chargeCh.GetDid()), + Type: goproto.Int32(chargeCh.GetType()), + SType: goproto.Int32(dbstruct.CHSTypeChargeZoneRefundMoment), + TypeId: goproto.String(chargeCh.GetTypeId()), + OrderId: goproto.String(chargeCh.GetOrderId()), + Change: goproto.Int64(-chargeCh.GetChange()), + Ct: goproto.Int64(time.Now().Unix()), + } + err = v.store.CreateConsumeHistory(ctx, tx, chargeChNew) + if err != nil { + logger.Error("CreateConsumeHistory fail, chargeChNew: %v, err: %v", util.ToJson(chargeChNew), err) + return err + } + + // 修改订单状态 xx -> 已退款 + err = v.store.UpdateOrderStatus(ctx, tx, orderId, order.GetOrderStatus(), dbstruct.VasOrderStatusRefund) + if err != nil { + logger.Error("UpdateOrderStatus fail, err: %v", err) + return err + } + + // 扣除空间消费 + err = v.RollbackZoneConsume(ctx, tx, orderId) + if err != nil { + logger.Error("RollbackZoneConsume fail, orderId: %v", orderId) + return err + } + + // 删除空间动态 + err = v.store.RefundZoneMoment(ctx, tx, mid, zid, momentId) + if err != nil { + logger.Error("RefundZoneMoment fail, mid: %v, zid: %v, momentId: %v, err: %v", mid, zid, momentId, err) + return err + } + + // 添加退款记录 + zrh := &dbstruct.ZoneRefundHis{ + Mid: goproto.Int64(mid), + Zid: goproto.Int64(zid), + ContactName: goproto.String(zoneRefundReq.ContactName), + ContactPhone: goproto.String(zoneRefundReq.ContactPhone), + Note: goproto.String(fmt.Sprintf("{\"moment_id\":%d}", momentId)), + OrderId: goproto.String(orderId), + ProductId: goproto.String(order.GetProductId()), + } + err = v.store.CreateZoneRefundHis(ctx, tx, zrh) + if err != nil { + logger.Error("CreateZoneRefundHis fail, zrh: %v, err: %v", util.ToJson(zrh), 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) + } + + for _, ch := range incomeChList { + streamerMid := ch.GetMid() + if streamerMid <= 0 { + err = errors.New("收入streamerMid错误") + logger.Error("invalid streamerMid: %v", streamerMid) return err } - case vasproto.PayTypeWxpayNative, vasproto.PayTypeWxpayJsapi, vasproto.PayTypeWxpayH5: - wxpayCli := wxpaycli.GetDefaultWxpayClient() - var resp *wechat.RefundRsp - resp, err = wxpayCli.RefundOne(ctx, &wxpaycli.RefundOneParam{ - OutTradeNo: orderId, - RefundAmount: order.GetPayAmount(), - RefundReason: "用户退款", - }) + + // 获取钱包 + wallet, err := v.store.GetWalletForUpdate(ctx, tx, streamerMid) if err != nil { - logger.Error("wxpayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) 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.CHSTypeIncomeRefundZoneAdmission), + 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()), + } + switch ch.GetSType() { + case dbstruct.CHSTypeIncomeThirdPartner: + chNew.SType = goproto.Int32(dbstruct.CHSTypeIncomeRefundThirdPartner) + case dbstruct.CHSTypeIncomeCollaborator: + chNew.SType = goproto.Int32(dbstruct.CHSTypeIncomeRefundCollaborator) + case dbstruct.CHSTypeIncomeZoneStreamer: + chNew.SType = goproto.Int32(dbstruct.CHSTypeIncomeRefundZoneStreamer) + } + 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 + } + } + } + + // 退款 + err = v.payRefund(ctx, order) + if err != nil { + return err + } + + return nil +} + +// 现金:超粉退款 +func (v *Vas) refundZoneSuperfanship(ctx *gin.Context, order *dbstruct.Order, req *vasproto.RefundOrderReq, opt *vasproto.RefundOrderOpt) error { + switch order.GetOrderStatus() { + case dbstruct.VasOrderStatusNone, dbstruct.VasOrderStatusInit: + return errors.New("订单还未支付,无法退款") + case dbstruct.VasOrderStatusRefund: + return errors.New("已退款,请勿重复操作") + } + + var ( + mid = order.GetMid() + zid = int64(0) + orderId = order.GetID() + isFinish = order.GetOrderStatus() == dbstruct.VasOrderStatusFinish + zoneRefundReq *vasproto.ZoneRefundReq + ) + if opt.GetZoneRefundReq() != nil { + zoneRefundReq = opt.GetZoneRefundReq() + } + if zoneRefundReq == nil { + return fmt.Errorf("zoneRefundReq is nil, order_id: %v", orderId) + } + zid = zoneRefundReq.Zid + // 和订单的zid不匹配 + if order.GetZid() != zid { + err := fmt.Errorf("与订单的zid不匹配,orderZid: %v, reqZid: %v", order.GetZid(), zid) + return err + } + + // 开启事务 + 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.VasOrderStatusRefund, + }, + 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 + } + }() + + // 用户的充值退款记录 + chargeChList, err := v.store.GetChargeCHList(ctx, tx, orderId) + if err != nil { + logger.Error("GetChargeCHList fail, orderId: %v, err: %v", orderId, err) + return err + } + if len(chargeChList) <= 0 || len(chargeChList) > 1 { + err = errors.New("invalid charge ch list") + logger.Error("invalid charge ch list, orderId: %v, err: %v", orderId, err) + return err + } + chargeCh := chargeChList[0] + chargeChNew := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(chargeCh.GetMid()), + Uid: goproto.Int64(chargeCh.GetUid()), + Did: goproto.String(chargeCh.GetDid()), + Type: goproto.Int32(chargeCh.GetType()), + SType: goproto.Int32(dbstruct.CHSTypeChargeZoneRefundSuperfanship), + TypeId: goproto.String(chargeCh.GetTypeId()), + OrderId: goproto.String(chargeCh.GetOrderId()), + Change: goproto.Int64(-chargeCh.GetChange()), + Ct: goproto.Int64(time.Now().Unix()), + } + err = v.store.CreateConsumeHistory(ctx, tx, chargeChNew) + if err != nil { + logger.Error("CreateConsumeHistory fail, chargeChNew: %v, err: %v", util.ToJson(chargeChNew), err) + return err + } + + // 修改订单状态 xx -> 已退款 + err = v.store.UpdateOrderStatus(ctx, tx, orderId, order.GetOrderStatus(), dbstruct.VasOrderStatusRefund) + if err != nil { + logger.Error("UpdateOrderStatus fail, err: %v", err) + return err + } + + // 扣除空间消费 + err = v.RollbackZoneConsume(ctx, tx, orderId) + if err != nil { + logger.Error("RollbackZoneConsume fail, orderId: %v", orderId) + return err + } + + // 删除空间超粉 + err = v.store.RefundZoneSuperfanship(ctx, tx, mid, zid) + if err != nil { + logger.Error("RefundZoneAdmission fail, mid: %v, zid: %v, err: %v", mid, zid, err) + return err + } + + // 删除空间超粉成员 + err = v.store.DeleteZoneMember(ctx, tx, mid, zid, dbstruct.ZoneMemberTypeSuperfan) + if err != nil { + logger.Error("DeleteZoneMember fail, mid: %v, zid: %v, err: %v", mid, zid, err) + return err + } + + // 添加退款记录 + zrh := &dbstruct.ZoneRefundHis{ + Mid: goproto.Int64(mid), + Zid: goproto.Int64(zid), + ContactName: goproto.String(zoneRefundReq.ContactName), + ContactPhone: goproto.String(zoneRefundReq.ContactPhone), + Note: goproto.String(zoneRefundReq.Note), + OrderId: goproto.String(orderId), + ProductId: goproto.String(order.GetProductId()), + } + err = v.store.CreateZoneRefundHis(ctx, tx, zrh) + if err != nil { + logger.Error("CreateZoneRefundHis fail, zrh: %v, err: %v", util.ToJson(zrh), 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) + } + + for _, ch := range incomeChList { + 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.CHSTypeIncomeRefundZoneAdmission), + 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()), + } + switch ch.GetSType() { + case dbstruct.CHSTypeIncomeThirdPartner: + chNew.SType = goproto.Int32(dbstruct.CHSTypeIncomeRefundThirdPartner) + case dbstruct.CHSTypeIncomeCollaborator: + chNew.SType = goproto.Int32(dbstruct.CHSTypeIncomeRefundCollaborator) + case dbstruct.CHSTypeIncomeZoneStreamer: + chNew.SType = goproto.Int32(dbstruct.CHSTypeIncomeRefundZoneStreamer) + } + 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 + } + } + } + + // 退款 + err = v.payRefund(ctx, order) + if err != nil { + return err } return nil @@ -2907,6 +3296,13 @@ func (v *Vas) refundCoinContactWechat(ctx *gin.Context, order *dbstruct.CoinOrde return err } + // 回滚空间消费 + err = v.RollbackZoneConsume(ctx, tx, orderId) + if err != nil { + logger.Error("RollbackZoneConsume fail, orderId: %v", orderId) + return err + } + // 删除微信解锁记录 err = v.store.DeleteUserVasUnlock(ctx, tx, mid, orderId, dbstruct.ProductIdContactWechat) if err != nil { @@ -3004,3 +3400,34 @@ func (v *Vas) refundCoinContactWechat(ctx *gin.Context, order *dbstruct.CoinOrde return nil } + +func (v *Vas) payRefund(ctx *gin.Context, order *dbstruct.Order) error { + var ( + orderId = order.GetID() + ) + switch order.GetPayType() { + case vasproto.PayTypeAlipay, vasproto.PayTypeAlipayH5: + alipayCli := alipaycli.GetAlipayClientByAppId(order.GetOid3()) + resp, err := alipayCli.RefundOne(ctx, &alipaycli.RefundOneParam{ + OutTradeNo: orderId, + RefundAmount: order.GetPayAmount(), + RefundReason: "用户退款", + }) + if err != nil { + logger.Error("alipayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) + return err + } + case vasproto.PayTypeWxpayNative, vasproto.PayTypeWxpayJsapi, vasproto.PayTypeWxpayH5: + wxpayCli := wxpaycli.GetDefaultWxpayClient() + resp, err := wxpayCli.RefundOne(ctx, &wxpaycli.RefundOneParam{ + OutTradeNo: orderId, + RefundAmount: order.GetPayAmount(), + RefundReason: "用户退款", + }) + if err != nil { + logger.Error("wxpayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err) + return err + } + } + return nil +} diff --git a/app/mix/service/logic/vas_zone.go b/app/mix/service/logic/vas_zone.go index 79fd82b8..3295783f 100644 --- a/app/mix/service/logic/vas_zone.go +++ b/app/mix/service/logic/vas_zone.go @@ -93,6 +93,47 @@ func (v *Vas) IncZoneConsume(ctx *gin.Context, tx *sqlx.Tx, zid, mid, streamerMi return nil } +// 减少空间消费 +func (v *Vas) RollbackZoneConsume(ctx *gin.Context, tx *sqlx.Tx, orderId string) error { + var err error + // 获取空间消费记录,如果不是1个,则非法 + zchList, _ := v.store.GetZoneConsumeHisByOrderId(ctx, tx, orderId) + if len(zchList) == 0 { + logger.Info("nil zone consume, orderId: %v", orderId) + return nil + } + if len(zchList) != 1 { + logger.Error("invalid zchList: %v, orderId: %v", util.ToJson(zchList), orderId) + err = fmt.Errorf("非法空间消费历史,请找开发") + return err + } + zchOld := zchList[0] + zid := zchOld.GetZid() + mid := zchOld.GetMid() + + // 扣除空间消费 + err = v.store.DecZoneConsume(ctx, tx, mid, zid, zchOld.GetConsume()) + if err != nil { + logger.Error("DecZoneConsume fail, mid: %v, zid: %v, pay: %v, err: %v", mid, zid, zchOld.GetConsume(), err) + return err + } + + // 增加空间消费记录 + zch := &dbstruct.ZoneConsumeHis{ + Mid: goproto.Int64(mid), + Zid: goproto.Int64(zid), + Consume: goproto.Int64(-zchOld.GetConsume()), + OrderId: goproto.String(zchOld.GetOrderId()), + ProductId: goproto.String(zchOld.GetProductId()), + } + err = v.store.CreateZoneConsumeHis(ctx, tx, zch) + if err != nil { + logger.Error("CreateZoneConsumeHis fail, zch, err: %v", util.ToJson(zch), err) + return err + } + return nil +} + // 设置动态价格 func (v *Vas) UpdateZoneMomentPrice(ctx *gin.Context, req *vasproto.UpdateZoneMomentPriceReq) error { err := v.store.UpsertZoneMomentPrice(ctx, req.ZoneMomentPrice) diff --git a/app/mix/service/vasservice.go b/app/mix/service/vasservice.go index 53c56d9a..cada48c0 100644 --- a/app/mix/service/vasservice.go +++ b/app/mix/service/vasservice.go @@ -305,6 +305,12 @@ func (s *Service) chListCharge(ctx *gin.Context, chList []*dbstruct.ConsumeHisto case dbstruct.CHSTypeChargeZoneRefundAdmission: item.Desc = "加入空间退款" item.Change = fmt.Sprintf("+%.1f元", float64(util.AbsInt64(chDB.GetChange()))/100.0) + case dbstruct.CHSTypeChargeZoneRefundMoment: + item.Desc = "解锁空间动态退款" + item.Change = fmt.Sprintf("+%.1f元", float64(util.AbsInt64(chDB.GetChange()))/100.0) + case dbstruct.CHSTypeChargeZoneRefundSuperfanship: + item.Desc = "解锁空间超粉退款" + item.Change = fmt.Sprintf("+%.1f元", float64(util.AbsInt64(chDB.GetChange()))/100.0) } list = append(list, item) diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 73905815..16070b43 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -524,16 +524,18 @@ const ( CHSTypeCostMembership = 10003 // 消费明细,会员资格解锁(伪金币记录,会员资格解锁中间无转金币过程) CHSTypeCostRefundMembership = 10004 // 消费明细,会员资格解锁退款(伪金币记录,会员资格解锁中间无转金币过程) - CHSTypeChargeUser = 20001 // 充值明细,用户自己冲 - CHSTypeChargeOp = 20002 // 充值明细,OP充值 - CHSTypeChargeRefundCoins = 20003 // 充值明细,金币退款 - CHSTypeChargeRefundMembership = 20004 // 充值明细,会员退款 - CHSTypeChargeMembership = 20005 // 充值明细,会员充值 - CHSTypeChargeRefundContactWechat = 20006 // 充值明细,微信金币退款 - CHSTypeChargeZoneMoment = 20007 // 充值明细,动态解锁 - CHSTypeChargeZoneAdmission = 20008 // 充值明细,空间会员 - CHSTypeChargeZoneSuperfanship = 20009 // 充值明细,空间超粉 - CHSTypeChargeZoneRefundAdmission = 20010 // 充值明细,空间普通会员退款 + CHSTypeChargeUser = 20001 // 充值明细,用户自己冲 + CHSTypeChargeOp = 20002 // 充值明细,OP充值 + CHSTypeChargeRefundCoins = 20003 // 充值明细,金币退款 + CHSTypeChargeRefundMembership = 20004 // 充值明细,会员退款 + CHSTypeChargeMembership = 20005 // 充值明细,会员充值 + CHSTypeChargeRefundContactWechat = 20006 // 充值明细,微信金币退款 + CHSTypeChargeZoneMoment = 20007 // 充值明细,动态解锁 + CHSTypeChargeZoneAdmission = 20008 // 充值明细,空间会员 + CHSTypeChargeZoneSuperfanship = 20009 // 充值明细,空间超粉 + CHSTypeChargeZoneRefundAdmission = 20010 // 充值明细,空间普通会员退款 + CHSTypeChargeZoneRefundMoment = 20011 // 充值明细,空间动态退款 + CHSTypeChargeZoneRefundSuperfanship = 20012 // 充值明细,空间动态退款 CHSTypeIncomeContact = 30001 // 收入明细,联系方式 CHSTypeIncomeInvite = 30002 // 收入明细,邀请分成 @@ -1224,8 +1226,9 @@ func (p *ZoneMember) GetCt() int64 { // 空间动态解锁 const ( - ZoneMomentUnlockStatusLock = 0 // 未解锁 - ZoneMomentUnlockStatusUnlock = 1 // 已解锁 + ZoneMomentUnlockStatusRefund = -1 // 已退款 + ZoneMomentUnlockStatusLock = 0 // 未解锁 + ZoneMomentUnlockStatusUnlock = 1 // 已解锁 ) type ZoneMomentUnlock struct {