add zone refund
This commit is contained in:
parent
a167a0ddff
commit
c470a8e77f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue