Merge branch 'feature-refund_v2-wxy-20241204' into test

# Conflicts:
#	app/mix/dao/mongo.go
#	app/mix/dao/mongo_vas.go
#	dbstruct/vas_mongo.go
This commit is contained in:
wangxinyu 2024-12-05 12:58:06 +08:00
commit 921c97f895
8 changed files with 474 additions and 0 deletions

View File

@ -221,6 +221,62 @@ type ZoneRefundResp struct {
Data *ZoneRefundData `json:"data"`
}
// 空间退款列表
type ZoneRefundListReq struct {
base.BaseRequest
Zid int64 `json:"zid"` // 空间id
AuditType int64 `json:"audit_type"` // 审核状态 1待处理2已处理
Offset int `json:"offset"`
Limit int `json:"limit"`
}
type ZoneRefundListData struct {
List []*ZoneRefundListVo `json:"list"`
Limit int `json:"limit"`
Offset int `json:"offset"`
More int32 `json:"more"`
}
type ZoneRefundListResp struct {
base.BaseResponse
Data *ZoneRefundListData `json:"data"`
}
type ZoneRefundListVo struct {
AuditId string
RefundT int64
RefundsStatus int64
//*dbstruct.RefundInfo
Account *dbstruct.Account `json:"account"`
}
// 空间退款详情
type ZoneRefundInfoReq struct {
base.BaseRequest
Zid int64 `json:"zid"` // 空间 id
AuditId string `json:"audit_id"` // 审核 id列表 id
}
type ZoneRefundInfoData struct {
ZoneRefundInfoVo *ZoneRefundInfoVo
}
type ZoneRefundInfoResp struct {
base.BaseResponse
Data *ZoneRefundInfoData `json:"data"`
}
type ZoneRefundInfoVo struct {
RefundInfo *dbstruct.RefundInfo
}
type ZoneRefundAuditReq struct {
base.BaseRequest
Zid int64 `json:"zid"` // 空间 id
AuditId string `json:"audit_id"` // 审核 id列表 id
RefundsStatus string `json:"refunds_status"` // 审核 id列表 id
}
// 更新空间价格信息
type UpdateZoneVasReq struct {
*dbstruct.ZoneVas

View File

@ -256,6 +256,10 @@ func Init(r *gin.Engine) {
apiZoneGroup.POST("create_order", middleware.JSONParamValidator(vasproto.ZoneCreateOrderReq{}), middleware.JwtAuthenticator(), ZoneCreateOrder)
apiZoneGroup.POST("refund_page", middleware.JSONParamValidator(vasproto.ZoneRefundPageReq{}), middleware.JwtAuthenticator(), ZoneRefundPage)
apiZoneGroup.POST("refund", middleware.JSONParamValidator(vasproto.ZoneRefundReq{}), middleware.JwtAuthenticator(), ZoneRefund)
apiZoneGroup.POST("refund_v2", middleware.JSONParamValidator(vasproto.ZoneRefundReq{}), middleware.JwtAuthenticator(), ZoneRefundV2)
apiZoneGroup.POST("refund_list", middleware.JSONParamValidator(vasproto.ZoneRefundReq{}), middleware.JwtAuthenticator(), ZoneRefundList)
apiZoneGroup.POST("refund_info", middleware.JSONParamValidator(vasproto.ZoneRefundReq{}), middleware.JwtAuthenticator(), ZoneRefundInfo)
apiZoneGroup.POST("refund_audit", middleware.JSONParamValidator(vasproto.ZoneRefundReq{}), middleware.JwtAuthenticator(), ZoneRefundAudit)
apiZoneGroup.POST("member_list", middleware.JSONParamValidator(vasproto.GetZoneMemberListReq{}), middleware.JwtAuthenticator(), ZoneMemberList)
apiZoneGroup.POST("member_list_v2", middleware.JSONParamValidator(vasproto.GetZoneMemberListV2Req{}), middleware.JwtAuthenticator(), ZoneMemberListV2)
apiZoneGroup.POST("search_zone_member", middleware.JSONParamValidator(vasproto.SearchMemberReq{}), middleware.JwtAuthenticator(), SearchZoneMember)

View File

@ -2,6 +2,7 @@ package controller
import (
"github.com/gin-gonic/gin"
"service/api/consts"
"service/api/errcode"
vasproto "service/api/proto/vas/proto"
"service/app/mix/service"
@ -108,6 +109,111 @@ func ZoneRefund(ctx *gin.Context) {
ReplyOk(ctx, nil)
}
func ZoneRefundV2(ctx *gin.Context) {
req := ctx.MustGet("client_req").(*vasproto.ZoneRefundReq)
if req.Mid <= 0 || req.Zid <= 0 {
logger.Error("ZoneRefundV2, invalid param, req: %v", util.ToJson(req))
ReplyErrCodeMsg(ctx, errcode.ErrCodeBadParam)
return
}
ec, err := service.DefaultService.ZoneRefundV2(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("ZoneRefundV2 fail, req: %v, ec: %v", util.ToJson(req), ec)
if ec == errcode.ErrCodeVasSrvFail && err != nil {
ReplyErrorMsg(ctx, err.Error())
return
}
ReplyErrCodeMsg(ctx, ec)
return
}
ReplyOk(ctx, nil)
}
func ZoneRefundList(ctx *gin.Context) {
req := ctx.MustGet("client_req").(*vasproto.ZoneRefundListReq)
if req.Mid <= 0 || req.Zid <= 0 {
logger.Error("ZoneRefundList, invalid param, req: %v", util.ToJson(req))
ReplyErrCodeMsg(ctx, errcode.ErrCodeBadParam)
return
}
//设置默认页长
if req.Limit == 0 {
req.Limit = consts.DefaultPageSize
}
list, ec, err := service.DefaultService.ZoneRefundList(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("ZoneRefundList fail, req: %v, ec: %v", util.ToJson(req), ec)
if ec == errcode.ErrCodeVasSrvFail && err != nil {
ReplyErrorMsg(ctx, err.Error())
return
}
ReplyErrCodeMsg(ctx, ec)
return
}
data := &vasproto.ZoneRefundListData{
List: list,
Limit: req.Limit,
Offset: req.Offset,
}
if len(list) >= req.Limit {
data.More = 1
}
ReplyOk(ctx, data)
}
func ZoneRefundInfo(ctx *gin.Context) {
req := ctx.MustGet("client_req").(*vasproto.ZoneRefundInfoReq)
if req.Mid <= 0 || req.AuditId != "" || len(req.AuditId) != 0 {
logger.Error("ZoneRefundInfo, invalid param, req: %v", util.ToJson(req))
ReplyErrCodeMsg(ctx, errcode.ErrCodeBadParam)
return
}
info, ec, err := service.DefaultService.ZoneRefundInfo(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("ZoneRefundInfo fail, req: %v, ec: %v", util.ToJson(req), ec)
if ec == errcode.ErrCodeVasSrvFail && err != nil {
ReplyErrorMsg(ctx, err.Error())
return
}
ReplyErrCodeMsg(ctx, ec)
return
}
data := &vasproto.ZoneRefundInfoData{
ZoneRefundInfoVo: info,
}
ReplyOk(ctx, data)
}
func ZoneRefundAudit(ctx *gin.Context) {
req := ctx.MustGet("client_req").(*vasproto.ZoneRefundAuditReq)
if req.Mid <= 0 || req.AuditId != "" || len(req.AuditId) != 0 {
logger.Error("ZoneRefundAudit, invalid param, req: %v", util.ToJson(req))
ReplyErrCodeMsg(ctx, errcode.ErrCodeBadParam)
return
}
ec, err := service.DefaultService.ZoneRefundAudit(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("ZoneRefundAudit fail, req: %v, ec: %v", util.ToJson(req), ec)
if ec == errcode.ErrCodeVasSrvFail && err != nil {
ReplyErrorMsg(ctx, err.Error())
return
}
ReplyErrCodeMsg(ctx, ec)
return
}
ReplyOk(ctx, nil)
}
func ZoneMemberList(ctx *gin.Context) {
req := ctx.MustGet("client_req").(*vasproto.GetZoneMemberListReq)
if req.Mid <= 0 || req.Zid <= 0 {

View File

@ -267,6 +267,9 @@ const (
COLRavenIQTest = "Raven_IQ_test"
COLRavenIQTestVisit = "Raven_IQ_test_visit"
DBZoneRefund = "zone_refund"
COLZoneRefund = "zone_refund_info"
DBHistory = "history"
COLHistory = "history"
@ -718,6 +721,10 @@ func (m *Mongo) getColRavenIQTestVisit() *qmgo.Collection {
return m.clientMix.Database(DBRavenIQTest).Collection(COLRavenIQTestVisit)
}
func (m *Mongo) GetZoneRefund() *qmgo.Collection {
return m.clientMix.Database(DBZoneRefund).Collection(COLZoneRefund)
}
// 前端页面路由表表
func (m *Mongo) getColFrontendRoute() *qmgo.Collection {
return m.clientMix.Database(DBFrontendRoute).Collection(COLFrontendRoute)

View File

@ -1,6 +1,7 @@
package dao
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/qiniu/qmgo"
"github.com/qiniu/qmgo/operator"
@ -351,3 +352,79 @@ func (m *Mongo) GetWithdrawFreezeByMid(ctx *gin.Context, mid int64) (*dbstruct.W
}
return doc, nil
}
// 用户退款表:查询主播审核列表
func (m *Mongo) GetZoneRefundList(ctx *gin.Context, zid int64, auditType int64) ([]*dbstruct.RefundInfo, error) {
list := make([]*dbstruct.RefundInfo, 0)
doc := m.GetZoneRefund()
query := qmgo.M{
"zid": zid,
"audit_type": auditType,
}
err := doc.Find(ctx, query).All(&list)
if errors.Is(err, qmgo.ErrNoSuchDocuments) {
err = nil
return nil, nil
}
if err != nil {
return nil, err
}
return list, nil
}
// 用户退款表:查询主播审核详情页
func (m *Mongo) GetZoneRefundInfo(ctx *gin.Context, auditId string) (*dbstruct.RefundInfo, error) {
doc := new(dbstruct.RefundInfo)
col := m.GetZoneRefund()
query := qmgo.M{
"_id": auditId,
}
err := col.Find(ctx, query).One(&doc)
if errors.Is(err, qmgo.ErrNoSuchDocuments) {
err = nil
return nil, nil
}
if err != nil {
return nil, err
}
return doc, nil
}
// 用户退款表:主播审核
func (m *Mongo) SetZoneRefundAuditInfo(ctx *gin.Context, auditId string, refundsStatus string) error {
col := m.GetZoneRefund()
_, err := col.Bulk().UpsertOne(
qmgo.M{
"_id": auditId,
},
qmgo.M{
operator.Set: qmgo.M{
"refunds_status": refundsStatus,
"ut": time.Now().Unix(),
},
},
).Run(ctx)
if errors.Is(err, qmgo.ErrNoSuchDocuments) {
err = nil
return err
}
if err != nil {
return err
}
return nil
}
// 用户退款表2小时内自动通过
func (m *Mongo) AddZoneRefundAutomatic(ctx *gin.Context, doc *dbstruct.RefundInfo) error {
col := m.GetZoneRefund()
_, err := col.InsertOne(ctx, doc)
if err != nil {
return err
}
return nil
}

View File

@ -1106,6 +1106,132 @@ func (v *Vas) ZoneRefund(ctx *gin.Context, req *vasproto.ZoneRefundReq) error {
return nil
}
// 退款接口 v2
func (v *Vas) ZoneRefundV2(ctx *gin.Context, req *vasproto.ZoneRefundReq) error {
redisKey := getCheckZoneRefundFreqKey(req.Mid)
redisCli := redis.GetRedisClient()
// 获取上次退款时间
t, _ := redisCli.GetInt64(redisKey)
logger.Info("ZoneRefundV2 check freq, key: %v, t: %v", redisKey, t)
if time.Now().Unix()-t < 3600*12 {
return fmt.Errorf("12小时内您最多只能进行1次退款")
}
// redis记录退款时间
_ = redisCli.Set(redisKey, time.Now().Unix(), 86400)
// 获取解锁信息
zv, err := v.store.GetZoneUnlock(ctx, nil, req.Mid, req.Zid)
if err == sql.ErrNoRows || zv == nil {
logger.Error("ZoneRefundV2 no zone unlock info, mid: %v, zid: %v", req.Mid, req.Zid)
return fmt.Errorf("没有解锁信息")
}
if err != nil {
logger.Error("ZoneRefundV2 get zone unlock info fail, mid: %v, zid: %v, err: %v", req.Mid, req.Zid, err)
return err
}
// 判断
if zv.GetAdmissionUntil() == 0 || len(zv.GetAdmissionOrderId()) <= 0 {
err = fmt.Errorf("无空间解锁记录")
logger.Error("ZoneRefundV2 no zone unlock info, mid: %v, zid: %v", req.Mid, req.Zid)
return err
}
// 获取订单
order, err := v.store.GetOrderById(ctx, nil, zv.GetAdmissionOrderId())
if err == sql.ErrNoRows || order == nil {
logger.Error("ZoneRefundV2 no zone admission order info, mid: %v, zid: %v, orderId: %v", req.Mid, req.Zid, zv.GetAdmissionOrderId())
return fmt.Errorf("没有相关订单")
}
if err != nil {
logger.Error("ZoneRefundV2 get zone admission order info fail, mid: %v, zid: %v, orderId: %v, err: %v", req.Mid, req.Zid, zv.GetAdmissionOrderId(), err)
return err
}
//if order.GetOid3() == alipaycli.AppIdMiYuanTianShi {
// return fmt.Errorf("订单正在处理请24小时后重试")
//}
// [0,2]: 直接退款
// (2,24]: 主播审核,发送消息通知
// (24, ♾️): 不支持页面退款,需要客服处理
refundsStatus := dbstruct.Refunds_Awaiting
timeInterval := time.Now().Unix() - order.GetCt()
if timeInterval >= 0 && timeInterval <= 2*3600 {
// 退款
err = v.RefundOrder(ctx, &vasproto.RefundOrderReq{
OrderId: order.GetID(),
Operator: fmt.Sprintf("%d", req.Mid),
}, vasproto.NewRefundOrderOpt().SetZoneRefundReq(req))
if err != nil {
logger.Error("ZoneRefundV2 RefundOrder fail, mid: %v, zid: %v, orderId: %v, err: %v", req.Mid, req.Zid, order.GetID(), err)
return err
}
refundsStatus = dbstruct.Refunds_Automatic
} else if timeInterval > 2*3600 && timeInterval <= 24*3600 {
// refundsStatus = dbstruct.Refunds_Awaiting
// 发送主播审核消息
} else {
refundsStatus = dbstruct.Refunds_Prohibit
}
refund := &dbstruct.RefundInfo{
Zid: req.Zid,
//Price: order.GetPayAmount(),
//CoinPrice: order.GetPayAmount(),
Mid: req.Mid,
ByTime: order.GetCt(),
RefundT: time.Now().Unix(),
RefundsStatus: int64(refundsStatus),
ContactName: req.ContactName,
ContactPhone: req.ContactPhone,
Note: req.Note,
Ct: time.Now().Unix(),
Ut: time.Now().Unix(),
}
if order.GetPayType() == vasproto.PayTypeCoin {
refund.CoinPrice = order.GetPayAmount()
} else {
refund.Price = order.GetPayAmount()
}
err = v.store.AddZoneRefundAutomatic(ctx, refund)
if err != nil {
return err
}
return nil
}
// 退款审核列表页
func (v *Vas) ZoneRefundList(ctx *gin.Context, req *vasproto.ZoneRefundListReq) (list []*dbstruct.RefundInfo, err error) {
list, err = v.store.GetZoneRefundList(ctx, req.Zid, req.AuditType)
if err != nil {
return
}
return
}
// 退款审核详情页
func (v *Vas) ZoneRefundInfo(ctx *gin.Context, req *vasproto.ZoneRefundInfoReq) (info *dbstruct.RefundInfo, err error) {
info, err = v.store.GetZoneRefundInfo(ctx, req.AuditId)
if err != nil {
return
}
return
}
// 退款主播审核
func (v *Vas) ZoneRefundAudit(ctx *gin.Context, req *vasproto.ZoneRefundAuditReq) error {
err := v.store.SetZoneRefundAuditInfo(ctx, req.AuditId, req.RefundsStatus)
if err != nil {
return err
}
return nil
}
// 空间成员列表
func (v *Vas) GetZoneMemberList(ctx *gin.Context, zid int64, memType int32) (list []*dbstruct.ZoneMember, err error) {
list, err = v.store.GetZoneMemberList(ctx, nil, zid, memType)

View File

@ -1003,6 +1003,78 @@ func (s *Service) ZoneRefund(ctx *gin.Context, req *vasproto.ZoneRefundReq) (ec
return
}
func (s *Service) ZoneRefundV2(ctx *gin.Context, req *vasproto.ZoneRefundReq) (ec errcode.ErrCode, err error) {
err = _DefaultVas.ZoneRefundV2(ctx, req)
ec, err = errs.DealVasErr(err)
if err != nil {
logger.Error("ZoneRefundV2 fail, err: %v", err)
return
}
return
}
func (s *Service) ZoneRefundList(ctx *gin.Context, req *vasproto.ZoneRefundListReq) (list []*vasproto.ZoneRefundListVo, ec errcode.ErrCode, err error) {
// 用户退款列表
refundList, err := _DefaultVas.ZoneRefundList(ctx, req)
ec, err = errs.DealVasErr(err)
if err != nil {
logger.Error("ZoneRefundList fail, err: %v", err)
return
}
mids := make([]int64, 0)
for _, rf := range refundList {
mids = append(mids, rf.Mid)
}
// 获取用户信息
acntMap, _ := _DefaultAccount.GetAccountMapByMids(ctx, mids)
// 组装成主播审批界面信息
for _, rf := range refundList {
mid := rf.Mid
tmp := &vasproto.ZoneRefundListVo{
Account: acntMap[mid],
AuditId: rf.AuditId,
RefundT: rf.RefundT,
RefundsStatus: rf.RefundsStatus,
}
list = append(list, tmp)
}
return
}
func (s *Service) ZoneRefundInfo(ctx *gin.Context, req *vasproto.ZoneRefundInfoReq) (info *vasproto.ZoneRefundInfoVo, ec errcode.ErrCode, err error) {
// 用户退款详情页
refundInfo, err := _DefaultVas.ZoneRefundInfo(ctx, req)
ec, err = errs.DealVasErr(err)
if err != nil {
logger.Error("ZoneRefundInfo fail, err: %v", err)
return
}
if refundInfo != nil {
info = &vasproto.ZoneRefundInfoVo{
RefundInfo: refundInfo,
}
}
return
}
func (s *Service) ZoneRefundAudit(ctx *gin.Context, req *vasproto.ZoneRefundAuditReq) (ec errcode.ErrCode, err error) {
// 主播退款审核
err = _DefaultVas.ZoneRefundAudit(ctx, req)
ec, err = errs.DealVasErr(err)
if err != nil {
logger.Error("ZoneRefundAudit fail, err: %v", err)
return
}
return
}
func (s *Service) ZoneMemberList(ctx *gin.Context, req *vasproto.GetZoneMemberListReq) (list []*vasproto.ZoneMemberVo, ec errcode.ErrCode, err error) {
list = make([]*vasproto.ZoneMemberVo, 0)

View File

@ -204,3 +204,29 @@ type WithdrawFreeze struct {
Ct int64 `json:"ct" bson:"ct"`
OperatorMid int64 `json:"operator_mid" bson:"operator_mid"`
}
// 用户退款状态
const (
Refunds_Awaiting = 1 // 待退款
Refunds_Approved = 2 // 主播审核通过
Refunds_Rejected = -1 // 主播审核拒绝
Refunds_Automatic = 3 // 2小时内,自动通过
Refunds_Overtime = 4 // 主播审核超时,自动通过
Refunds_Prohibit = 5 // 超过 24 小时,禁止退款(人工审核退款)
)
type RefundInfo struct {
AuditId string `json:"audit_id" bson:"_id"`
Zid int64 `json:"zid" bson:"zid"`
Price int64 `json:"price" bson:"price"`
CoinPrice int64 `json:"coin_price" bson:"coin_price"`
Mid int64 `json:"mid" bson:"mid"`
ByTime int64 `json:"by_time" bson:"by_time"`
RefundT int64 `json:"refund_t" bson:"refund_t"`
RefundsStatus int64 `json:"refunds_status" bson:"refunds_status"`
ContactName string `json:"contact_name" bson:"contact_name"`
ContactPhone string `json:"contact_phone" bson:"contact_phone"`
Note string `json:"note" bson:"note"`
Ct int64 `json:"ct" bson:"ct"`
Ut int64 `json:"ut" bson:"ut"`
}