package productser import ( "91porn-server/common/constant/redisconst" "91porn-server/common/game" "91porn-server/middleware/ua" "91porn-server/models/v/advanceordermod" "91porn-server/models/v/dailytaskmod" "91porn-server/models/v/discount_area_mod" "91porn-server/models/v/imgroupmembermod" "91porn-server/models/v/imgroupmod" "91porn-server/models/v/media_buy_record_mod" "91porn-server/models/v/mediamod" "91porn-server/models/v/nakedchatmod" "91porn-server/models/v/nakedchatordermod" "encoding/json" "errors" "fmt" "math" "strconv" "time" "go.mongodb.org/mongo-driver/bson" "91porn-server/app/appg" "91porn-server/app/service/taskser" "91porn-server/common" "91porn-server/common/constant" "91porn-server/common/db" "91porn-server/common/log" sli "91porn-server/common/slice" "91porn-server/common/stderr" "91porn-server/common/timeutil" "91porn-server/models/commod" "91porn-server/models/l/payvidlgmod" "91porn-server/models/v/audiobookmod" "91porn-server/models/v/backpackmod" "91porn-server/models/v/newactivity" "91porn-server/models/v/oncetaskmod" "91porn-server/models/v/payaudiobookmod" "91porn-server/models/v/prdcthsomod" "91porn-server/models/v/prizemod" "91porn-server/models/v/productmod" "91porn-server/models/v/txnmod" "91porn-server/models/v/usermod" "91porn-server/models/v/videocoupon" "91porn-server/models/v/videodiscountmod" "91porn-server/models/v/vidmod" "91porn-server/models/v/vipconfigmod" "91porn-server/models/v/walletmod" "github.com/shopspring/decimal" "go.mongodb.org/mongo-driver/bson/primitive" ) // Buy 购买商品 func Buy(uid uint64, productType commod.ProductType, productID, couponID, serviceID primitive.ObjectID, num uint64, userContact, sys string, ChapterID string, goldVideoCouponNum int, isH5 bool, ua ua.UA, ip string, experimentAttribution VIPExperimentAttribution) stderr.Code { normalizedAttribution, err := validateVIPExperimentAttribution(uid, productID, experimentAttribution) if err != nil { log.Warn("invalid VIP experiment coin purchase attribution", log.Any("uid", uid), log.Any("productID", productID), log.E(err)) return stderr.ErrParamError } switch productType { case prdcthsomod.VIP, commod.NEWUSERCard: return BuyVIP(uid, productID, couponID, sys, false, ua, ip, normalizedAttribution) case prdcthsomod.AdvanceCard: return BuyAdvanceCard(uid, productID, couponID, sys, false, ua, ip, normalizedAttribution) case prdcthsomod.GameAdvanceCard: return BuyGameAdvanceCard(uid, productID, couponID, sys, false, ua, ip, normalizedAttribution) case prdcthsomod.VIDEO: return BuyVid(uid, productID, goldVideoCouponNum, ua, ip) case prdcthsomod.Media: return BuyMedia(uid, productID, ua, ip) case prdcthsomod.MeetingCard: return BuyMeetingCard(uid, productID, sys, ua, ip) case prdcthsomod.OTHER, commod.PhysicalGoods: return BuyOtherCard(uid, productID, sys, ua, ip) case commod.AudioBook: return BuyAudioBook(uid, productID, ChapterID, ua, ip) case commod.VideoDiscount: return BuyVideoDiscountCard(uid, productID, sys, ua, ip) case commod.VideoFreeCard: return BuyVideoFreeCard(uid, productID, sys, ua, ip) case commod.ImGroup: return BuyImGroup(uid, productID, sys) case commod.NakedChat: return BuyNakedChat(uid, productID, num, userContact, sys) default: return stderr.ErrParamError } } func BuyNakedChat(uid uint64, pid primitive.ObjectID, num uint64, userContact, sys string) stderr.Code { // 获取该裸聊 info, err := nakedchatmod.GetInfo(pid) if err != nil { return stderr.BuyFailed } if info.Price <= 0 { return stderr.BuyFailed } var buyNum uint64 for _, v := range info.Options { if num == v { buyNum = v break } } // 并没有配置该项 if buyNum == 0 { return stderr.BuyFailed } // 判断钱包余额是否足够, 做出扣款计划 wallet, err := walletmod.GetWallet(uid) if err != nil { return stderr.ErrNetWorkBusy } if wallet == nil { return stderr.InsufficientBalance } amount := int64(info.Price * buyNum) // 扣除金币,加入群聊 plan := debitPlan(wallet, amount) if plan == nil { // 余额不足无法做出扣款计划 return stderr.InsufficientBalance } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err = walletmod.Debit(t, plan, uid) if err != nil && err.Error() != "wallet not found" { return err } orderId, err := nakedchatordermod.Insert(t, nakedchatordermod.NakedChatOrder{ Nid: info.ID, Uid: uid, UserContact: userContact, Num: buyNum, Price: info.Price, Amount: amount, Remark: fmt.Sprintf("购买裸聊%v分钟", buyNum), Snapshot: info, Status: 0, CreatedAt: time.Now(), UpdatedAt: time.Now(), }) if err != nil { return stderr.BuyFailed } var realAmount decimal.Decimal if wallet != nil { realAmount = walletmod.GetRealAmount(wallet) } tl := txnmod.TransactionLog{TransNo: orderId, UID: uid, Amount: -amount, ActualAmount: float64(-amount), TranType: txnmod.BuyNakedChat.Key(), TranTypeInt: int64(txnmod.BuyNakedChat), Desc: fmt.Sprintf("购买裸聊-%v-%v分钟", info.Title, buyNum), DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: realAmount, } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } if err = nakedchatmod.IncSaleNum(t, info.ID, 1); err != nil { return err } return nil }); err != nil { log.Warn(fmt.Sprintf("productser BuyNakedChat Transaction err [%s]", err.Error())) return stderr.BuyFailed } return stderr.Success } func BuyImGroup(uid uint64, pid primitive.ObjectID, sys string) stderr.Code { // 获取该群组 group, err := imgroupmod.GetInfo(pid) if err != nil { return stderr.BuyFailed } // 判断是否已经加入 res, err := imgroupmembermod.GetInfoByCond(bson.M{"uid": uid, "groupId": group.GroupId}) if err != nil { return stderr.BuyFailed } // 已经购买过了 if !res.ID.IsZero() && res.Status != 2 { return stderr.BuyFailed } if group.Price == 0 { // 直接写入记录 _, err = imgroupmembermod.Insert(nil, imgroupmembermod.ImGroupMember{ GroupId: group.GroupId, Uid: uid, Price: group.Price, Status: 1, CreatedAt: time.Now(), }) if err != nil { return stderr.BuyFailed } return stderr.Success } // 判断钱包余额是否足够, 做出扣款计划 wallet, err := walletmod.GetWallet(uid) if err != nil { return stderr.ErrNetWorkBusy } if wallet == nil { return stderr.InsufficientBalance } // 扣除金币,加入群聊 plan := debitPlan(wallet, group.Price) if plan == nil { // 余额不足无法做出扣款计划 return stderr.InsufficientBalance } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err = walletmod.Debit(t, plan, uid) if err != nil && err.Error() != "wallet not found" { return err } memberId, err := imgroupmembermod.Insert(t, imgroupmembermod.ImGroupMember{ GroupId: group.GroupId, Uid: uid, Price: group.Price, Status: 1, CreatedAt: time.Now(), }) if err != nil { return stderr.BuyFailed } var realAmount decimal.Decimal if wallet != nil { realAmount = walletmod.GetRealAmount(wallet) } tl := txnmod.TransactionLog{TransNo: memberId, UID: uid, Amount: -group.Price, ActualAmount: float64(-group.Price), TranType: txnmod.JoinGroup.Key(), TranTypeInt: int64(txnmod.JoinGroup), Desc: "加入群组付费-" + group.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: realAmount, } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } return nil }); err != nil { log.Warn(fmt.Sprintf("productser BuyImGroup Transaction err [%s]", err.Error())) return stderr.BuyFailed } common.Go(func() { // 群组成员加1 imgroupmod.IncMemberNum(nil, group.ID, 1) }) return stderr.Success } func BuyMedia(uid uint64, mediaId primitive.ObjectID, ua ua.UA, ip string) stderr.Code { hasBought, err := media_buy_record_mod.IsWholeBuy(uid, mediaId) if err != nil { return stderr.ErrNetWorkBusy } if hasBought { return stderr.RepeatPurchase } v, err := mediamod.GetInfo(mediaId) if err != nil { return stderr.ErrNetWorkBusy } if v.ID.IsZero() { return stderr.CodeEmptyData } if v.MediaType == mediamod.MediaTypeDrama { // 短剧只能通过带 contentID 的单集购买入口解锁。 return stderr.ErrParamError } if v.Permission == 0 { return stderr.Failure } if (v.Permission == 1 && v.Price == 0) || v.Permission == 2 { return stderr.Success } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } code := buyMedia(*u, v, ua, ip) // 购买动漫类 if code == stderr.Success { // 完成日常任务 taskser.CompleteDailyTask(nil, uid, dailytaskmod.DailyUnlockByGold) } return code } // buyMedia 整本购买acg func buyMedia(u usermod.User, media mediamod.Media, ua ua.UA, ip string) stderr.Code { /* 购买ACG时: - VIP折扣和视频折扣卡不可用 - 视频抵用券不可用 - 金币视频抵用券不可用 */ // 判断钱包余额是否足够, 做出扣款计划 wallet, err := walletmod.GetWallet(u.UID) if err != nil { return stderr.ErrNetWorkBusy } if wallet == nil { return stderr.InsufficientBalance } plan := debitPlan(wallet, media.Price) if plan == nil { // 余额不足无法做出扣款计划 return stderr.InsufficientBalance } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err = walletmod.Debit(t, plan, u.UID) if err != nil && err.Error() != "wallet not found" { return err } pv := &media_buy_record_mod.MediaBuyRecord{ ID: orderId, MediaId: media.ID, MediaType: media.MediaType, ContentId: primitive.NilObjectID, Uid: u.UID, Type: 1, Coins: media.Price, PayMoney: media.Price, CreatedAt: orderCreatedAt, UpdateTime: time.Now(), } if err = media_buy_record_mod.Create(t, pv); err != nil { return err } var realAmount decimal.Decimal if wallet != nil { realAmount = walletmod.GetRealAmount(wallet) } tl := txnmod.TransactionLog{TransNo: pv.ID, UID: u.UID, Amount: -media.Price, ActualAmount: float64(-media.Price), TranType: txnmod.BuyAcg.Key(), TranTypeInt: int64(txnmod.BuyAcg), Desc: "ACG-整本购买-" + media.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: realAmount, } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } return nil }); err != nil { log.Warn(fmt.Sprintf("productser buyMedia Transaction err [%s]", err.Error())) return stderr.BuyFailed } common.Go(func() { if err = mediamod.IncPurchasesCount(media.ID, 1); err != nil { log.Warn("media IncPurchaseCount err", log.Any("mediaId", media.ID), log.E(err)) } }) return stderr.Success } func checkVipRenew(u *usermod.User, p *productmod.Product) (time.Time, int, int) { now := time.Now() var end time.Time level := p.VipLevel payVidDiscount := p.PayVidDiscount d := time.Hour * 24 * time.Duration(p.Duration) if u.VipExpireDate.After(now) { //renew end = u.VipExpireDate.Add(d) if u.VipLevel > p.VipLevel { //当前用户的vip等级比这次购买的大,使用用户的 level = u.VipLevel } //当前用户的折扣比这次购买的大,使用用户的 if u.PayVidDiscount < payVidDiscount && u.PayVidDiscount > 0 { payVidDiscount = u.PayVidDiscount } } else { end = now.Add(time.Duration(d)) } return end, level, payVidDiscount } // 扣除计划, 返回如何扣除本次支出 func debitPlan(w *walletmod.Wallet, amt int64) *walletmod.DebitPlan { l1 := w.Amount - amt // l1: 计算amout扣除以后的剩余值(负数的话则表示不足的值) if l1 >= 0 { // amount够了 只需要在amount内扣除即可 return &walletmod.DebitPlan{ Amount: amt, } } // amount不足时, 先扣除amout, 不足部分在income内扣除 if w.Income+l1 >= 0 { // income足够支付差额 return &walletmod.DebitPlan{ Amount: w.Amount, Income: -l1, } } return nil //余额不足 } // BuyVIP 购买产品产生的行为 func BuyVIP(uid uint64, productID, couponID primitive.ObjectID, sys string, isRecharge bool, ua ua.UA, ip string, experimentAttribution VIPExperimentAttribution) stderr.Code { p, err := productmod.FindProduct(productID, sys) if err != nil || p == nil { return stderr.ErrParamError } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } // 检查用户升级 CheckUserUpgrade(uid, p) w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } var price int64 = p.DiscountedPrice // 使用优惠卷(仅限金币购买) if !couponID.IsZero() && p.IsAmountPay { goodsDetail, err := backpackmod.GetGoodsDetail(couponID) if err != nil { return stderr.ErrNetWorkBusy } if goodsDetail == nil { return stderr.ErrNetWorkBusy } price = decimal.NewFromInt(price).Mul(decimal.NewFromInt(goodsDetail.GoodsValue)).Div(decimal.NewFromInt(10)).IntPart() } plan := debitPlan(w, price) if plan == nil { return stderr.InsufficientBalance } if isRecharge { plan.Amount -= p.GiveCoin } if p.DownloadCount > 0 { plan.DownloadCount = -p.DownloadCount } vipExpire, vipLevel, payVidDiscount := checkVipRenew(u, p) sel := usermod.UserSelector{VipExpireDate: &vipExpire, VipLevel: &vipLevel, PayVidDiscount: &payVidDiscount, VipName: &p.Name} if p.GoldVideoFreeDay > 0 { expire := time.Time{} if u.GoldVideoFreeExpire.IsZero() || u.GoldVideoFreeExpire.Before(time.Now()) { expire = time.Now().AddDate(0, 0, p.GoldVideoFreeDay) } else { expire = u.GoldVideoFreeExpire.AddDate(0, 0, p.GoldVideoFreeDay) } sel.GoldVideoFreeExpire = &expire if p.GoldVideoFreeLimit > u.GoldVideoFreeLimit { sel.GoldVideoFreeLimit = &p.GoldVideoFreeLimit } } if p.AiUndressCount > 0 { plan.AiUndressFreeTimes = int64(-p.AiUndressCount) } if p.LuckyDrawCount > 0 { plan.LotteryTimes = -p.LuckyDrawCount // 负数经 Debit 的 $inc 即为增加抽奖次数 } if p.BroadcastDays > 0 { expire := time.Time{} if u.BroadcastExpire.IsZero() || u.BroadcastExpire.Before(time.Now()) { expire = time.Now().AddDate(0, 0, p.BroadcastDays) } else { expire = u.BroadcastExpire.AddDate(0, 0, p.BroadcastDays) } sel.BroadcastExpire = &expire } if p.DramaDays > 0 { expire := usermod.RenewDramaExpire(u.DramaExpire, time.Now(), p.DramaDays) sel.DramaExpire = &expire } plan.Consumption = 1 sel.ChatPrice = &p.ChatPrice var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { var err error discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: productID, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.VIP, SysType: u.SysType, ProductSnapShot: p, IsUpgrade: p.IsUpgrade, CurrentVipName: p.CurrentVipName, CurrentVipPrice: p.CurrentVipPrice, PurchasePrice: p.PurchasePrice, DiscDoc: discDoc, CreatedAt: orderCreatedAt, ExperimentID: experimentAttribution.ExperimentID, ExperimentVariant: experimentAttribution.ExperimentVariant, SessionID: experimentAttribution.SessionID, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } if err = usermod.UpdateVIP(t, uid, u.VipExpireDate, sel); err != nil { return err } txnLogs := []txnmod.TransactionLog{ { UID: uid, Amount: -price, ActualAmount: float64(-price), TranType: txnmod.PayVIP.Key(), TranTypeInt: int64(txnmod.PayVIP), TransNo: history.ID, Desc: "VIP购买-" + p.Name, DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), }, } // 判断会员卡是否赠送AI脱衣免费次数 if p.AiUndressCount > 0 { aiLog := txnmod.TransactionLog{ UID: uid, Amount: int64(p.AiUndressCount), ActualAmount: float64(p.AiUndressCount), TranType: txnmod.VipCardGiveAiUndressFreeCount.Key(), TranTypeInt: int64(txnmod.VipCardGiveAiUndressFreeCount), TransNo: history.ID, Desc: fmt.Sprintf("购买%s-赠送AI脱衣免费次数[%v次]", p.Name, p.AiUndressCount), DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, aiLog) } //插入购买会员卡赠送金币流水 if p.GiveCoin > 0 && isRecharge { giveLog := txnmod.TransactionLog{ UID: uid, Amount: p.GiveCoin, ActualAmount: float64(p.GiveCoin), TranType: txnmod.VipCardGive.Key(), TranTypeInt: int64(txnmod.VipCardGive), TransNo: history.ID, Desc: "VIP购买-" + p.Name + "-赠送金币", DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, giveLog) if err = txnmod.InsertTransactionLog(t, &giveLog); err != nil { return err } } if p.DownloadCount > 0 { txnLogs = append(txnLogs, txnmod.TransactionLog{ UID: uid, DownloadCount: p.DownloadCount, TranType: txnmod.GiveDownload.Key(), TranTypeInt: int64(txnmod.GiveDownload), TransNo: history.ID, Desc: fmt.Sprintf("VIP购买-%v", p.Name) + fmt.Sprintf("-赠送[%d]次数", p.DownloadCount), DiscDoc: u.DiscDoc, SysType: u.SysType, }) } if p.LuckyDrawCount > 0 { txnLogs = append(txnLogs, txnmod.TransactionLog{ UID: uid, LotteryTimes: p.LuckyDrawCount, TranType: txnmod.GiveLotteryTimesCount.Key(), TranTypeInt: int64(txnmod.GiveLotteryTimesCount), TransNo: history.ID, Desc: fmt.Sprintf("VIP购买-%v", p.Name) + fmt.Sprintf("-赠送[%d]次抽奖", p.LuckyDrawCount), DiscDoc: u.DiscDoc, SysType: u.SysType, }) } if err = txnmod.InsertManyTransactionLog(t, txnLogs); err != nil { log.Warn(fmt.Sprintf("productser BuyVIP Transaction err %s", err.Error())) return err } if !couponID.IsZero() { if err = backpackmod.UseGoods(t, couponID); err != nil { return err } } // 金币视频观影券 if coupons := HandleGoldVideoCoupon(p, uid, videocoupon.GoldVideoCouponSourceVIP); len(coupons) > 0 { if err = videocoupon.InsertManyTrans(t, coupons); err != nil { return err } } common.Go(func() { _ = taskser.CompleteOnceTask(nil, uid, oncetaskmod.OnceTaskTypeUserBuyVip) }) return nil }); err != nil { log.Warn(fmt.Sprintf("productser BuyVIP Transaction err %s", err.Error())) return stderr.BuyFailed //通知消息 } return stderr.Success } // BuyAdvanceCard 购买产品产生的行为 func BuyAdvanceCard(uid uint64, productID, couponID primitive.ObjectID, sys string, isRecharge bool, ua ua.UA, ip string, experimentAttribution VIPExperimentAttribution) stderr.Code { var ( sel usermod.UserSelector payVidDiscount int changeStatus int price int64 dsc = "金币购买" ) p, err := productmod.FindProduct(productID, sys) if err != nil || p == nil { return stderr.ErrParamError } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } // 检查用户VIP升级 CheckUserUpgrade(uid, p) data, err := advanceordermod.IsExist(bson.M{"uid": uid, "productID": productID}) if err != nil { log.Error(fmt.Sprintf("金币购买预售卡-productID[%s] 查询预售订单信息异常 [%v]", productID.Hex(), err)) return stderr.ErrNetWorkBusy } if data == nil || data.ID.IsZero() { var advanceOrder advanceordermod.AdvanceOrder prepaidPrivilege := advanceordermod.AdvanceCardPrepaidPrivilege{} if p.PrepaidPrivilege != nil { prepaidPrivilege = advanceordermod.AdvanceCardPrepaidPrivilege{ CoinVideoLimitPerDay: p.PrepaidPrivilege.CoinVideoLimitPerDay, LuckyDrawLimitPerDay: p.PrepaidPrivilege.LuckyDrawLimitPerDay, AiUndressLimitPerDay: p.PrepaidPrivilege.AiUndressLimitPerDay, DownloadLimitPerDay: p.PrepaidPrivilege.DownloadLimitPerDay, } } // 创建预售订单 now := time.Now() advanceOrder.UID = u.UID advanceOrder.CreatedAt = now advanceOrder.Status = advanceordermod.AdvanceProcessing advanceOrder.AdvanceAmount = p.AdvanceAmount * 10 advanceOrder.BalanceAmount = p.BalanceAmount * 10 advanceOrder.StartTime = p.StartTime advanceOrder.EndTime = p.EndTime advanceOrder.PrepaidPrivilege = prepaidPrivilege advanceOrder.TodayUse = advanceordermod.DayUse{} advanceOrder.ProductID = p.ID advanceOrder.TotalAmount = (p.AdvanceAmount + p.BalanceAmount) * 10 data, err = advanceordermod.CreditOrder(nil, &advanceOrder) if err != nil { return stderr.ErrDbInsertError } changeStatus = advanceordermod.AdvanceSUCCESS } else if data.Status == advanceordermod.AdvanceProcessing { // 有预售订单,且是首付状态 changeStatus = advanceordermod.AdvanceSUCCESS } else if (data.Status == advanceordermod.AdvanceSUCCESS && time.Now().After(p.StartTime)) || data.Status == advanceordermod.BalanceProcessing { // 尾款预付状态 changeStatus = advanceordermod.BalanceSUCCESS } else if data.Status == advanceordermod.AdvanceSUCCESS && !time.Now().After(p.StartTime) { return stderr.AdvanceOrderPayTimeIsErr } else if data.Status == advanceordermod.BalanceSUCCESS { return stderr.AdvanceOrderStatusIsErr } w, err := walletmod.GetWallet(uid) if err != nil { return stderr.ErrNetWorkBusy } if w == nil || w.ID.IsZero() { return stderr.InsufficientBalance } var fullPrivilege bool var duration = p.Duration var vipLevel = p.VipLevel var tranType = txnmod.BuyAdvanceVIP if changeStatus == advanceordermod.AdvanceSUCCESS { duration = p.AdvanceDuration // 会员持续时间不能超过预付过期时间 if time.Now().AddDate(0, 0, duration).After(p.AdvanceExpires) { td := p.AdvanceExpires.Sub(time.Now()) duration = int(math.Round(td.Hours() / 24)) if duration < 1 { duration = 1 } } vipLevel = p.AdvanceVipLevel price = p.AdvanceAmount dsc = "预售预付金币购买" } if changeStatus == advanceordermod.BalanceSUCCESS { price = p.BalanceAmount dsc = "预售尾款金币购买" fullPrivilege = true tranType = txnmod.BuyBalanceVIP } // 使用优惠卷(仅限金币购买) if !couponID.IsZero() && p.IsAmountPay { goodsDetail, err := backpackmod.GetGoodsDetail(couponID) if err != nil { return stderr.ErrNetWorkBusy } if goodsDetail == nil { return stderr.ErrNetWorkBusy } price = decimal.NewFromInt(price).Mul(decimal.NewFromInt(goodsDetail.GoodsValue)).Div(decimal.NewFromInt(10)).IntPart() } plan := debitPlan(w, price) if plan == nil { return stderr.InsufficientBalance } if isRecharge { plan.Amount -= p.GiveCoin } // VIP变更 if duration > 0 { var expire time.Time payVidDiscount = p.PayVidDiscount // VIP未过期 if u.VipExpireDate.After(time.Now()) { if changeStatus == advanceordermod.BalanceSUCCESS { expire = u.VipExpireDate.AddDate(0, 0, duration) } if changeStatus == advanceordermod.AdvanceSUCCESS { expire = time.Now().AddDate(0, 0, duration) } if u.VipLevel > p.VipLevel { vipLevel = u.VipLevel } if u.PayVidDiscount > 0 && u.PayVidDiscount < p.PayVidDiscount { payVidDiscount = u.PayVidDiscount } } else { expire = time.Now().AddDate(0, 0, duration) } log.Info(fmt.Sprintf("到期时间:%v", expire)) sel.VipExpireDate = &expire sel.VipLevel = &vipLevel sel.PayVidDiscount = &payVidDiscount } // 增加金币视频免费天数 if p.GoldVideoFreeDay > 0 && fullPrivilege { var expire time.Time if u.GoldVideoFreeExpire.IsZero() || u.GoldVideoFreeExpire.Before(time.Now()) { if changeStatus == advanceordermod.BalanceSUCCESS { expire = time.Now().AddDate(0, 0, p.GoldVideoFreeDay) } if changeStatus == advanceordermod.AdvanceSUCCESS { expire = p.EndTime } } else { if changeStatus == advanceordermod.BalanceSUCCESS { expire = u.GoldVideoFreeExpire.AddDate(0, 0, p.GoldVideoFreeDay) } if changeStatus == advanceordermod.AdvanceSUCCESS { expire = p.EndTime } } sel.GoldVideoFreeExpire = &expire sel.GoldVideoFreeLimit = &p.GoldVideoFreeLimit } if p.BroadcastDays > 0 { expire := time.Time{} if u.BroadcastExpire.IsZero() || u.BroadcastExpire.Before(time.Now()) { expire = time.Now().AddDate(0, 0, p.BroadcastDays) } else { expire = u.BroadcastExpire.AddDate(0, 0, p.BroadcastDays) } sel.BroadcastExpire = &expire } if p.DramaDays > 0 && fullPrivilege { expire := usermod.RenewDramaExpire(u.DramaExpire, time.Now(), p.DramaDays) sel.DramaExpire = &expire } if p.AllGoldVideoFree && fullPrivilege { free := true sel.AllGoldVideoFree = &free } if p.Name != "" { sel.VipName = &p.Name } if changeStatus == advanceordermod.BalanceSUCCESS && p.AiUndressCount > 0 { plan.AiUndressFreeTimes = int64(-p.AiUndressCount) } if changeStatus == advanceordermod.BalanceSUCCESS && p.DownloadCount > 0 { plan.DownloadCount = -p.DownloadCount } if changeStatus == advanceordermod.BalanceSUCCESS && p.LuckyDrawCount > 0 { plan.LotteryTimes = -p.LuckyDrawCount } plan.Consumption = 1 sel.ChatPrice = &p.ChatPrice var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { var err error discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: productID, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.AdvanceCard, AdvanceOrderStatus: changeStatus, // 新增的预售订单状态 SysType: u.SysType, ProductSnapShot: p, IsUpgrade: p.IsUpgrade, CurrentVipName: p.CurrentVipName, CurrentVipPrice: p.CurrentVipPrice, PurchasePrice: p.PurchasePrice, DiscDoc: discDoc, CreatedAt: orderCreatedAt, ExperimentID: experimentAttribution.ExperimentID, ExperimentVariant: experimentAttribution.ExperimentVariant, SessionID: experimentAttribution.SessionID, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } if err = usermod.UpdateVIP(t, uid, u.VipExpireDate, sel); err != nil { return err } txnLogs := []txnmod.TransactionLog{ { UID: uid, Amount: -price, ActualAmount: float64(-price), TranType: tranType.Key(), TranTypeInt: int64(tranType), TransNo: history.ID, Desc: fmt.Sprintf("%s-%s", dsc, p.Name), DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), }, } if changeStatus == advanceordermod.BalanceSUCCESS { // 判断会员卡是否赠送AI脱衣免费次数 if p.AiUndressCount > 0 { aiLog := txnmod.TransactionLog{ UID: uid, Amount: int64(p.AiUndressCount), ActualAmount: float64(p.AiUndressCount), TranType: txnmod.VipCardGiveAiUndressFreeCount.Key(), TranTypeInt: int64(txnmod.VipCardGiveAiUndressFreeCount), TransNo: history.ID, Desc: fmt.Sprintf("购买%s-赠送AI脱衣免费次数[%v次]", p.Name, p.AiUndressCount), DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, aiLog) } //插入购买会员卡赠送金币流水 if p.GiveCoin > 0 && isRecharge { giveLog := txnmod.TransactionLog{ UID: uid, Amount: p.GiveCoin, ActualAmount: float64(p.GiveCoin), TranType: txnmod.VipCardGive.Key(), TranTypeInt: int64(txnmod.VipCardGive), TransNo: history.ID, Desc: "预售卡购买-" + p.Name + "-赠送金币", DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, giveLog) if err = txnmod.InsertTransactionLog(t, &giveLog); err != nil { return err } } if p.DownloadCount > 0 { txnLogs = append(txnLogs, txnmod.TransactionLog{ UID: uid, DownloadCount: p.DownloadCount, TranType: txnmod.GiveDownload.Key(), TranTypeInt: int64(txnmod.GiveDownload), TransNo: history.ID, Desc: fmt.Sprintf("预售卡购买-%v", p.Name) + fmt.Sprintf("-赠送[%d]次数", p.DownloadCount), DiscDoc: u.DiscDoc, SysType: u.SysType, }) } if p.LuckyDrawCount > 0 { txnLogs = append(txnLogs, txnmod.TransactionLog{ UID: uid, LotteryTimes: p.LuckyDrawCount, TranType: txnmod.GiveLotteryTimesCount.Key(), TranTypeInt: int64(txnmod.GiveLotteryTimesCount), TransNo: history.ID, Desc: fmt.Sprintf("预售卡购买-%v", p.Name) + fmt.Sprintf("-赠送[%d]次数", p.LuckyDrawCount), DiscDoc: u.DiscDoc, SysType: u.SysType, }) } } // 修改预付订单状态 err = advanceordermod.Update(t, data.ID, bson.M{"status": changeStatus}) if err != nil { log.Warn(fmt.Sprintf("productser Buy AdvanceCard advanceordermod Update Transaction err %s", err.Error())) return err } if err = txnmod.InsertManyTransactionLog(t, txnLogs); err != nil { log.Warn(fmt.Sprintf("productser Buy AdvanceCard Transaction err %s", err.Error())) return err } if !couponID.IsZero() { if err = backpackmod.UseGoods(t, couponID); err != nil { return err } } // 金币视频观影券 if coupons := HandleGoldVideoCoupon(p, uid, videocoupon.GoldVideoCouponSourceVIP); len(coupons) > 0 { if err = videocoupon.InsertManyTrans(t, coupons); err != nil { return err } } return nil }); err != nil { log.Warn(fmt.Sprintf("productser Buy AdvanceCard Transaction err %s", err.Error())) return stderr.BuyFailed //通知消息 } return stderr.Success } func BuyGameAdvanceCard(uid uint64, productID, couponID primitive.ObjectID, sys string, isRecharge bool, ua ua.UA, ip string, experimentAttribution VIPExperimentAttribution) stderr.Code { var ( sel usermod.UserSelector vipLevel int payVidDiscount int price int64 dsc = "金币购买" ) p, err := productmod.FindProduct(productID, sys) if err != nil || p == nil { return stderr.ErrParamError } price = p.DiscountedPrice u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil { return stderr.ErrNetWorkBusy } if w == nil || w.ID.IsZero() { return stderr.InsufficientBalance } plan := debitPlan(w, price) if plan == nil { return stderr.InsufficientBalance } if isRecharge { plan.Amount -= p.GiveCoin } // 获取三方游戏码 gameCode, err := game.GainTripartiteGameCode(uid) if err != nil { log.Error(fmt.Sprintf("金币购买游戏预售-uid[%v] 获取游戏码信息异常 [%v]", uid, err)) return stderr.ErrReqForbidden } // VIP变更 if p.SendGame && p.Duration > 0 { var expire time.Time vipLevel = p.VipLevel payVidDiscount = p.PayVidDiscount // VIP未过期 if u.VipExpireDate.After(time.Now()) { expire = u.VipExpireDate.AddDate(0, 0, p.Duration) if u.VipLevel > p.VipLevel { vipLevel = u.VipLevel } if u.PayVidDiscount > 0 && u.PayVidDiscount < p.PayVidDiscount { payVidDiscount = u.PayVidDiscount } } else { expire = time.Now().AddDate(0, 0, p.Duration) } log.Info(fmt.Sprintf("到期时间:%v", expire)) sel.VipExpireDate = &expire sel.VipLevel = &vipLevel sel.PayVidDiscount = &payVidDiscount } // 增加金币视频免费天数 if p.SendGame && p.GoldVideoFreeDay > 0 { var expire time.Time if u.GoldVideoFreeExpire.IsZero() || u.GoldVideoFreeExpire.Before(time.Now()) { expire = time.Now().AddDate(0, 0, p.GoldVideoFreeDay) } else { expire = u.GoldVideoFreeExpire.AddDate(0, 0, p.GoldVideoFreeDay) } sel.GoldVideoFreeExpire = &expire } if p.AllGoldVideoFree { free := true sel.AllGoldVideoFree = &free } if p.SendGame && p.Name != "" { sel.VipName = &p.Name } if p.AiUndressCount > 0 { plan.AiUndressFreeTimes = int64(-p.AiUndressCount) } if p.DownloadCount > 0 { plan.DownloadCount = -p.DownloadCount } if p.LuckyDrawCount > 0 { plan.LotteryTimes = -p.LuckyDrawCount } plan.Consumption = 1 if p.ChatPrice > 0 { sel.ChatPrice = &p.ChatPrice } goodsList := make([]backpackmod.Backpack, 1) now := time.Now() goodsList[0] = backpackmod.Backpack{ UID: uid, GoodsName: p.Name, GoodsType: backpackmod.GameCode, GoodsValue: p.DiscountedPrice, GoodsOrigin: fmt.Sprintf("金币购买"), GoodsDesc: gameCode, Status: backpackmod.Unused, ExpiredTime: now.AddDate(0, 0, int(p.Duration)), CreateTime: now, } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { var err error discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: productID, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.GameAdvanceCard, DiscDoc: discDoc, SysType: u.SysType, GameCode: fmt.Sprintf("AAAA-B-%v", time.Now().Nanosecond()), ProductSnapShot: p, CreatedAt: orderCreatedAt, ExperimentID: experimentAttribution.ExperimentID, ExperimentVariant: experimentAttribution.ExperimentVariant, SessionID: experimentAttribution.SessionID, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } if err = usermod.UpdateVIP(t, uid, u.VipExpireDate, sel); err != nil { return err } txnLogs := []txnmod.TransactionLog{ { UID: uid, Amount: -price, ActualAmount: float64(-price), TranType: txnmod.BuyGameAdvanceVIP.Key(), TranTypeInt: int64(txnmod.BuyGameAdvanceVIP), TransNo: history.ID, Desc: fmt.Sprintf("%s-%s", dsc, p.Name), DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), }, } // 判断会员卡是否赠送AI脱衣免费次数 if p.AiUndressCount > 0 { aiLog := txnmod.TransactionLog{ UID: uid, Amount: int64(p.AiUndressCount), ActualAmount: float64(p.AiUndressCount), TranType: txnmod.VipCardGiveAiUndressFreeCount.Key(), TranTypeInt: int64(txnmod.VipCardGiveAiUndressFreeCount), TransNo: history.ID, Desc: fmt.Sprintf("购买%s-赠送AI脱衣免费次数[%v次]", p.Name, p.AiUndressCount), DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, aiLog) } //插入购买会员卡赠送金币流水 if p.GiveCoin > 0 && isRecharge { giveLog := txnmod.TransactionLog{ UID: uid, Amount: p.GiveCoin, ActualAmount: float64(p.GiveCoin), TranType: txnmod.VipCardGive.Key(), TranTypeInt: int64(txnmod.VipCardGive), TransNo: history.ID, Desc: "游戏预售卡购买-" + p.Name + "-赠送金币", DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, giveLog) if err = txnmod.InsertTransactionLog(t, &giveLog); err != nil { return err } } if p.DownloadCount > 0 { txnLogs = append(txnLogs, txnmod.TransactionLog{ UID: uid, DownloadCount: p.DownloadCount, TranType: txnmod.GiveDownload.Key(), TranTypeInt: int64(txnmod.GiveDownload), TransNo: history.ID, Desc: fmt.Sprintf("预售卡购买-%v", p.Name) + fmt.Sprintf("-赠送[%d]次数", p.DownloadCount), DiscDoc: u.DiscDoc, SysType: u.SysType, }) } // 保存用户游戏码 err = backpackmod.AddGoodsMany(t, uid, goodsList) if err != nil { return err } if err = txnmod.InsertManyTransactionLog(t, txnLogs); err != nil { log.Warn(fmt.Sprintf("productser Buy AdvanceCard Transaction err %s", err.Error())) return err } common.Go(func() { _ = taskser.CompleteOnceTask(nil, uid, oncetaskmod.OnceTaskTypeUserBuyVip) }) return nil }); err != nil { log.Warn(fmt.Sprintf("productser Buy AdvanceCard Transaction err %s", err.Error())) return stderr.BuyFailed //通知消息 } return stderr.Success } func debitPlanAmount(w *walletmod.Wallet, amt int64) *walletmod.DebitPlan { p := walletmod.DebitPlan{} l1 := w.Amount - amt if l1 >= 0 { //amount够了 p.Amount = amt return &p } // l1 < 0 return nil } // VIPUp 购买产品产生的行为 func VIPUp(uid uint64) stderr.Code { cfg, err := vipconfigmod.FindOne() if err != nil { return stderr.ErrNetWorkBusy } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } vipExpire := time.Time{} var vipUpPrice int64 vipLevel := 2 now := time.Now() if !(u.VipExpireDate.After(now) && u.VipExpireDate.Before(now.AddDate(20, 0, 0))) { return stderr.ErrInvalidRequest } vipExpire = u.VipExpireDate.AddDate(0, 0, 9999) switch u.VipLevel { case 1: vipUpPrice = cfg.VipUpPrice case 2: vipUpPrice = cfg.SVipUpPrice default: } plan := debitPlanAmount(w, vipUpPrice) if plan == nil { return stderr.InsufficientBalance } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { var err error discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } if err = usermod.UpdateVIP(t, uid, u.VipExpireDate, usermod.UserSelector{VipExpireDate: &vipExpire, VipLevel: &vipLevel}); err != nil { return err } txnLog := txnmod.TransactionLog{UID: uid, Amount: -vipUpPrice, ActualAmount: float64(-vipUpPrice), TranType: txnmod.PayVIP.Key(), TranTypeInt: int64(txnmod.PayVIP), Desc: "升级永久VIP", DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } return txnmod.InsertTransactionLog(t, &txnLog) }); err != nil { log.Warn(fmt.Sprintf("productser BuyVIP Transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } func BuyVIP_d(t *db.MongoTool, uid uint64, p productmod.Product, giveGold int64) error { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return errors.New("user is null") } //新用户8折 /*if u.CreatedAt.Add(time.Hour*24).After(time.Now()) && p.ProductType == prdcthsomod.VIP { p.DiscountedPrice = int64(float64(p.DiscountedPrice) * 0.8) }*/ vipExpire, vipLevel, payVidDiscount := checkVipRenew(u, &p) if err != nil { return stderr.ErrNetWorkBusy } wallet, err := walletmod.GetWallet(uid) if err != nil { return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: primitive.NewObjectID(), UID: uid, ProductID: p.ID, Name: p.Name, Amount: p.DiscountedPrice, ProductType: prdcthsomod.VIP, DiscDoc: u.DiscDoc, SysType: u.SysType, ProductSnapShot: &p, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } sel := usermod.UserSelector{VipExpireDate: &vipExpire, VipLevel: &vipLevel, PayVidDiscount: &payVidDiscount} if p.GoldVideoFreeDay > 0 { expire := time.Time{} if u.GoldVideoFreeExpire.IsZero() || u.GoldVideoFreeExpire.Before(time.Now()) { expire = time.Now().AddDate(0, 0, p.GoldVideoFreeDay) } else { expire = u.GoldVideoFreeExpire.AddDate(0, 0, p.GoldVideoFreeDay) } sel.GoldVideoFreeExpire = &expire } if p.DramaDays > 0 { expire := usermod.RenewDramaExpire(u.DramaExpire, time.Now(), p.DramaDays) sel.DramaExpire = &expire } if err = usermod.UpdateVIP(t, uid, u.VipExpireDate, sel); err != nil { return err } txnLogs := []txnmod.TransactionLog{txnmod.TransactionLog{UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.PayVIP.Key(), TranTypeInt: int64(txnmod.PayVIP), TransNo: history.ID, Desc: "VIP购买-" + p.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), CurrencyType: txnmod.CurrencyTypeCash, }} // 插入日志 if giveGold > 0 { giveLog := txnmod.TransactionLog{UID: uid, Amount: giveGold, ActualAmount: float64(giveGold), TranType: txnmod.VipCardGive.Key(), TranTypeInt: int64(txnmod.VipCardGive), TransNo: history.ID, Desc: "VIP购买-" + p.Name + "-赠送金币", DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } txnLogs = append(txnLogs, giveLog) } if err = txnmod.InsertManyTransactionLog(t, txnLogs); err != nil { log.Warn(fmt.Sprintf("productser BuyVIP Transaction err %s", err.Error())) return err } // 赠送观影券 coupons := HandleGoldVideoCoupon(&p, uid, videocoupon.GoldVideoCouponSourceVIP) if len(coupons) > 0 { if err = videocoupon.InsertMany(coupons); err != nil { return err } } return nil } func publiserTaxLevel(publisherID uint64) (int64, float64, int, int) { u, err := usermod.FindUserByUID(publisherID) if err != nil || u == nil { return 0, 0, 0, 0 } return u.TaxLevel, u.VideoDeduction, u.VideoDeductionCount, u.VideoDeductionPayCount } func BuyVid(uid uint64, vid primitive.ObjectID, goldVideoCouponNum int, ua ua.UA, ip string) stderr.Code { var code stderr.Code hasBought, err := payvidlgmod.IsPay4Video(uid, vid) if err != nil { return stderr.ErrNetWorkBusy } if hasBought { return stderr.RepeatPurchase } v, err := vidmod.GetVideoInfo(vid.Hex()) if err != nil { return stderr.ErrNetWorkBusy } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } switch v.NewsType { case vidmod.COVER, vidmod.PIC: code = buyCoverVid(*u, v, ua, ip) case vidmod.SEED_LINK: code = buySeedUrlVid(*u, v, ua, ip) default: // 判断视频是否在折扣区 discountArea, err := discount_area_mod.GetDiscountAreaById(v.DiscountAreaId) if err != nil { return stderr.ErrNetWorkBusy } // 用户是vip且视频在折扣区内打折,使用折扣区的购买逻辑 if u.IsVIP(time.Now()) && !discountArea.ID.IsZero() { // 购买折扣区视频 code = buyDiscountAreaVideo(*u, v, discountArea, ua, ip) } else { // 购买普通视频帖子 code = buyNormalVid(*u, v, goldVideoCouponNum, ua, ip) } } if code != stderr.Success { return code } // 完成日常任务 taskser.CompleteDailyTask(nil, uid, dailytaskmod.DailyUnlockByGold) return stderr.Success } // 购买种子链接帖子 func buySeedUrlVid(u usermod.User, v vidmod.VideoModel, ua ua.UA, ip string) stderr.Code { // 购买图片帖子 // 判断钱包余额是否足够, 做出扣款计划 wallet, err := walletmod.GetWallet(u.UID) if err != nil || wallet == nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() plan := debitPlan(wallet, v.Coins) if plan == nil { // 余额不足无法做出扣款计划 return stderr.InsufficientBalance } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err = walletmod.Debit(t, plan, u.UID) if err != nil { return err } deducated := false pIncome := decimal.NewFromInt(0) publisherIncome, _ := pIncome.Float64() taxAmount := float64(v.Coins) pv := payvidlgmod.Pay4VidLog{ ID: orderId, UID: u.UID, VideoID: v.ID, NewsType: v.NewsType, PlayTime: v.PlayTime, Coins: v.Coins, PayMoney: v.Coins, Tax: 0, TaxAmount: taxAmount, Title: v.Title, PublisherIncome: publisherIncome, PublisherID: v.PublisherID, Uniq: payvidlgmod.Unique(u.UID, v.ID), DiscDoc: u.DiscDoc, SysType: u.SysType, IsVideoDeduction: deducated, CreatedAt: orderCreatedAt, } if err = payvidlgmod.InsertVideoPayRecord(t, pv); err != nil { return err } tl := txnmod.TransactionLog{TransNo: pv.ID, UID: u.UID, Amount: -v.Coins, ActualAmount: float64(-v.Coins), TranType: txnmod.PayVID.Key(), TranTypeInt: int64(txnmod.PayVID), Desc: "付费种子链接-" + pv.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } return nil }); err != nil { log.Warn(fmt.Sprintf("productser BuyVid Transaction err [%s]", err.Error())) return stderr.BuyFailed } if err = vidmod.IncPurchaseCount(v.ID); err != nil { log.Warn("productser IncPurchaseCount err", log.Any("vid", v.ID), log.E(err)) } return stderr.Success } // 购买图片帖子 func buyCoverVid(u usermod.User, v vidmod.VideoModel, ua ua.UA, ip string) stderr.Code { // 购买图片帖子 /* 购买图片时: - VIP折扣和视频折扣卡不可用 - 视频抵用券不可用 - 金币视频抵用券不可用 */ // 判断钱包余额是否足够, 做出扣款计划 wallet, err := walletmod.GetWallet(u.UID) if err != nil || wallet == nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() plan := debitPlan(wallet, v.Coins) if plan == nil { // 余额不足无法做出扣款计划 return stderr.InsufficientBalance } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err = walletmod.Debit(t, plan, u.UID) if err != nil { return err } deducated := false pIncome := decimal.NewFromInt(0) publisherIncome, _ := pIncome.Float64() taxAmount := float64(v.Coins) pv := payvidlgmod.Pay4VidLog{ ID: orderId, UID: u.UID, VideoID: v.ID, PlayTime: v.PlayTime, Coins: v.Coins, PayMoney: v.Coins, NewsType: v.NewsType, Tax: 0, TaxAmount: taxAmount, Title: v.Title, PublisherIncome: publisherIncome, PublisherID: v.PublisherID, Uniq: payvidlgmod.Unique(u.UID, v.ID), DiscDoc: u.DiscDoc, SysType: u.SysType, IsVideoDeduction: deducated, CreatedAt: orderCreatedAt, } if err = payvidlgmod.InsertVideoPayRecord(t, pv); err != nil { return err } tl := txnmod.TransactionLog{TransNo: pv.ID, UID: u.UID, Amount: -v.Coins, ActualAmount: float64(-v.Coins), TranType: txnmod.PayVID.Key(), TranTypeInt: int64(txnmod.PayVID), Desc: "付费图片-" + pv.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } // 图片不计入博主收益, 只计入博主图片出售数量 _ = usermod.IncCoverCount(v.PublisherID) return nil }); err != nil { log.Warn(fmt.Sprintf("productser BuyVid Transaction err [%s]", err.Error())) return stderr.BuyFailed } if err = vidmod.IncPurchaseCount(v.ID); err != nil { log.Warn("productser IncPurchaseCount err", log.Any("vid", v.ID), log.E(err)) } return stderr.Success } // 购买普通视频帖子 func buyNormalVid(u usermod.User, v vidmod.VideoModel, goldVideoCouponNum int, ua ua.UA, ip string) stderr.Code { now := time.Now() // 是否启用视频抵用券 videoCoupons, err := videocoupon.GetByUIDAndCouponNum(u.UID, goldVideoCouponNum) if err != nil { return stderr.ErrNetWorkBusy } chooseCoupon, err := CheckGoldVideoCoupon(int64(goldVideoCouponNum), v.Coins, &u, videoCoupons) if err != stderr.Success { return err.(stderr.Code) } if u.IsVIP(now) && u.PayVidDiscount > 0 && goldVideoCouponNum <= 0 { v.Coins = decimal.NewFromInt(int64(u.PayVidDiscount)).Shift(-1). Mul(decimal.NewFromInt(v.Coins)).Round(0).IntPart() } goldVideoFreeLimit := usermod.GetGoldVideoFreeLimit(&u) if !chooseCoupon { // 计算用户是否拥有视频折扣卡 // 如果用户拥有视频折扣卡, 则购买视频时计算折后价 vDiscLog, err := videodiscountmod.GetByUID(u.UID) if err != nil { return stderr.ErrNetWorkBusy } // 用户购买了视频免费卡(30金币以下免费) if v.Coins < goldVideoFreeLimit && (u.GoldVideoFreeExpire.After(now) || (u.VideoFreeExpiration != nil && u.VideoFreeExpiration.After(now))) { v.Coins = 0 // 直接返回成功,这个是个金币免费视频,不需要写入购买记录 return stderr.Success } else { // 用户购买了视频折扣卡 if vDiscLog.Expiration.After(now) && vDiscLog.VideoDiscount > 0 { discountedCoins := decimal.NewFromInt(int64(vDiscLog.VideoDiscount)).Shift(-1). Mul(decimal.NewFromInt(v.Coins)).Round(0).IntPart() if discountedCoins < v.Coins { v.Coins = discountedCoins // 若折扣后价格高于原价, 则按原价计算 } } } if u.AllGoldVideoFree && u.GoldVideoFreeExpire.After(now) { v.Coins = 0 } } // 判断钱包余额是否足够, 做出扣款计划 var wallet *walletmod.Wallet wallet, err = walletmod.GetWallet(u.UID) if err != nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() if (wallet == nil || wallet.ID.IsZero()) && v.Coins == 0 { var p walletmod.CreditPlan var amount int64 = 0 p.Amount = &amount wallet, err = walletmod.Credit(nil, p, u.UID) if err != nil { return stderr.ErrNetWorkBusy } if wallet == nil || wallet.ID.IsZero() { return stderr.ErrNetWorkBusy } } plan := debitPlan(wallet, v.Coins) if goldVideoCouponNum <= 0 && plan == nil { // 当不使用抵用券且无法做出扣款计划时, 说明余额不足 return stderr.InsufficientBalance } taxLevel := constant.DefaultBloggerVideoIncomeTaxLevel /** TODO: 默认视频分成为6/4分成 */ tl, videoDeduction, videoDeductionCount, videoDeductionPayCount := publiserTaxLevel(v.PublisherID) if tl > 0 && tl <= 10 { taxLevel = tl } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { if !chooseCoupon { // 不使用金币视频抵用券, 根据扣款计划做出扣款 wallet, err = walletmod.Debit(t, plan, u.UID) if err != nil { return err } } else { // 使用金币折扣券, 则不扣款, 只进行抵用券扣除操作 var sel usermod.UserSelector if u.GoldVideoCoupon != nil && len(u.GoldVideoCoupon) > 0 { // 兼容老数据 goldVideoCoupon := make([]usermod.UserGoldVideoCoupon, 0) for _, gvc := range u.GoldVideoCoupon { if gvc.Gold == goldVideoCouponNum { gvc.Count -= 1 } if gvc.Count > 0 { goldVideoCoupon = append(goldVideoCoupon, gvc) } } sel.GoldVideoCoupon = &goldVideoCoupon //更新用户金币视频抵用券 if _, err = usermod.UpdateTrans(t, u.UID, sel); err != nil { return err } usedCoupon := videocoupon.UserGoldVideoCoupon{ UID: u.UID, Num: goldVideoCouponNum, Source: videocoupon.GoldVideoCouponSourceVIP, Used: true, CreatedAt: now, UpdatedAt: now, } // 记录用户使用老版观影券 if err = videocoupon.InsertOne(usedCoupon); err != nil { return err } } else { // 使用新版观影券 if err = videocoupon.UseOneCoupon(videoCoupons); err != nil { return err } } } // 是否扣量以及税率计算 deducated := checkVideoDeduction(videoDeduction, videoDeductionCount, videoDeductionPayCount, v.PublisherID, goldVideoCouponNum) ta := decimal.NewFromInt(v.Coins).Mul(decimal.NewFromInt(taxLevel).Div(decimal.NewFromInt(10))) pIncome := decimal.NewFromInt(v.Coins).Sub(ta) publisherIncome, _ := pIncome.Float64() taxAmount, _ := ta.Float64() pv := payvidlgmod.Pay4VidLog{ ID: orderId, UID: u.UID, VideoID: v.ID, PlayTime: v.PlayTime, NewsType: v.NewsType, Coins: v.Coins, PayMoney: v.Coins, Tax: taxLevel, TaxAmount: taxAmount, Title: v.Title, PublisherIncome: publisherIncome, PublisherID: v.PublisherID, Uniq: payvidlgmod.Unique(u.UID, v.ID), DiscDoc: u.DiscDoc, SysType: u.SysType, IsVideoDeduction: deducated, CreatedAt: orderCreatedAt, } if err = payvidlgmod.InsertVideoPayRecord(t, pv); err != nil { return err } tl := txnmod.TransactionLog{TransNo: pv.ID, UID: u.UID, Amount: -v.Coins, ActualAmount: float64(-v.Coins), TranType: txnmod.PayVID.Key(), TranTypeInt: int64(txnmod.PayVID), Desc: "付费视频-" + pv.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } if goldVideoCouponNum > 0 { tl.Desc = "金币抵用券" + strconv.Itoa(goldVideoCouponNum) + "-购买视频-" + pv.Title tl.TranType = txnmod.GoldCouplePayVID.Key() tl.TranTypeInt = int64(txnmod.GoldCouplePayVID) tl.ActualAmount = 0 } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } // 扣量判断 if deducated { return nil } //此时计算收益 收益分为两个部分 整数部分 10 小数部分 1 //计算小数部分 //与当前收益相加 pw, err := walletmod.GetWallet(v.PublisherID) if err != nil { return stderr.ErrNetWorkBusy } pwPot, vidIncome := float64(0), float64(0) if pw != nil { pwPot = pw.IncomePot vidIncome = pw.VidIncome } income := pIncome.Add(decimal.NewFromFloat(pwPot)) incomef, _ := pIncome.Add(decimal.NewFromFloat(vidIncome)).Float64() incomeInt := income.IntPart() incomePot, _ := income.Sub(decimal.NewFromInt(incomeInt)).Float64() //增加钱包虚拟货币 wallet, err = walletmod.CreditIncomeBasePot(t, incomeInt, incomef, incomePot, v.PublisherID) if err != nil { return err } tl = txnmod.TransactionLog{TransNo: pv.ID, UID: v.PublisherID, RechargeId: u.UID, Amount: v.Coins, ActualAmount: publisherIncome, Tax: taxLevel, TaxAmount: taxAmount, TranType: txnmod.WorksIncome.Key(), TranTypeInt: int64(txnmod.WorksIncome), Desc: "视频收益-" + v.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } //插入一收益流水 return txnmod.InsertTransactionLog(t, &tl) }); err != nil { log.Warn(fmt.Sprintf("productser BuyVid Transaction err [%s]", err.Error())) return stderr.BuyFailed } if err = vidmod.IncPurchaseCount(v.ID); err != nil { log.Warn("productser IncPurchaseCount err", log.Any("vid", v.ID), log.E(err)) } return stderr.Success } // 购买折扣专区的视频 func buyDiscountAreaVideo(u usermod.User, v vidmod.VideoModel, discountArea discount_area_mod.DiscountArea, ua ua.UA, ip string) stderr.Code { // 视频原价 originCoins := v.Coins // 如果是在折扣专区里,按照折扣专区价格来 if !discountArea.ID.IsZero() { newPrice := decimal.NewFromInt(int64(discountArea.Discount)).Shift(-2). Mul(decimal.NewFromInt(originCoins)).Round(0).IntPart() v.Coins = newPrice } if v.Coins == 0 { // 根本不需要购买 return stderr.Success } // 判断钱包余额是否足够, 做出扣款计划 var wallet *walletmod.Wallet wallet, err := walletmod.GetWallet(u.UID) if err != nil { return stderr.ErrNetWorkBusy } if (wallet == nil || wallet.ID.IsZero()) && v.Coins > 0 { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() if (wallet == nil || wallet.ID.IsZero()) && v.Coins == 0 { var p walletmod.CreditPlan var amount int64 = 0 p.Amount = &amount wallet, err = walletmod.Credit(nil, p, u.UID) if err != nil { return stderr.ErrNetWorkBusy } if wallet == nil || wallet.ID.IsZero() { return stderr.ErrNetWorkBusy } } plan := debitPlan(wallet, v.Coins) if plan == nil { // 当无法做出扣款计划时, 说明余额不足 return stderr.InsufficientBalance } taxLevel := constant.DefaultBloggerVideoIncomeTaxLevel /** TODO: 默认视频分成为6/4分成 */ tl, videoDeduction, videoDeductionCount, videoDeductionPayCount := publiserTaxLevel(v.PublisherID) if tl > 0 && tl <= 10 { taxLevel = tl } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { //根据扣款计划做出扣款 wallet, err = walletmod.Debit(t, plan, u.UID) if err != nil { return err } // 是否扣量以及税率计算 deducated := checkVideoDeduction(videoDeduction, videoDeductionCount, videoDeductionPayCount, v.PublisherID, 0) ta := decimal.NewFromInt(v.Coins).Mul(decimal.NewFromInt(taxLevel).Div(decimal.NewFromInt(10))) pIncome := decimal.NewFromInt(v.Coins).Sub(ta) publisherIncome, _ := pIncome.Float64() taxAmount, _ := ta.Float64() pv := payvidlgmod.Pay4VidLog{ ID: orderId, UID: u.UID, VideoID: v.ID, PlayTime: v.PlayTime, NewsType: v.NewsType, Coins: v.Coins, PayMoney: v.Coins, Tax: taxLevel, TaxAmount: taxAmount, Title: v.Title, PublisherIncome: publisherIncome, PublisherID: v.PublisherID, Uniq: payvidlgmod.Unique(u.UID, v.ID), DiscDoc: u.DiscDoc, SysType: u.SysType, IsVideoDeduction: deducated, CreatedAt: orderCreatedAt, } if err = payvidlgmod.InsertVideoPayRecord(t, pv); err != nil { return err } tl := txnmod.TransactionLog{TransNo: pv.ID, UID: u.UID, Amount: -v.Coins, ActualAmount: float64(-v.Coins), TranType: txnmod.PayVID.Key(), TranTypeInt: int64(txnmod.PayVID), Desc: "付费VIP折扣区视频-" + pv.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } if err = txnmod.InsertTransactionLog(t, &tl); err != nil { return err } // 扣量判断 if deducated { return nil } //此时计算收益 收益分为两个部分 整数部分 10 小数部分 1 //计算小数部分 //与当前收益相加 pw, err := walletmod.GetWallet(v.PublisherID) if err != nil { return stderr.ErrNetWorkBusy } pwPot, vidIncome := float64(0), float64(0) if pw != nil { pwPot = pw.IncomePot vidIncome = pw.VidIncome } income := pIncome.Add(decimal.NewFromFloat(pwPot)) incomef, _ := pIncome.Add(decimal.NewFromFloat(vidIncome)).Float64() incomeInt := income.IntPart() incomePot, _ := income.Sub(decimal.NewFromInt(incomeInt)).Float64() //增加钱包虚拟货币 wallet, err = walletmod.CreditIncomeBasePot(t, incomeInt, incomef, incomePot, v.PublisherID) if err != nil { return err } tl = txnmod.TransactionLog{TransNo: pv.ID, UID: v.PublisherID, RechargeId: u.UID, Amount: v.Coins, ActualAmount: publisherIncome, Tax: taxLevel, TaxAmount: taxAmount, TranType: txnmod.WorksIncome.Key(), TranTypeInt: int64(txnmod.WorksIncome), Desc: "视频收益-" + v.Title, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } //插入一收益流水 return txnmod.InsertTransactionLog(t, &tl) }); err != nil { log.Warn(fmt.Sprintf("productser BuyVid Transaction err [%s]", err.Error())) return stderr.BuyFailed } if err = vidmod.IncPurchaseCount(v.ID); err != nil { log.Warn("productser IncPurchaseCount err", log.Any("vid", v.ID), log.E(err)) } return stderr.Success } func makeSerials(giftLeft uint32, quantity int32) (list []uint32) { list = make([]uint32, quantity+1) for i := int32(1); i <= quantity; i++ { list[i] = 300 - giftLeft + uint32(i) } return } // 购买嫩模币 func BuyModel(uid uint64, req newactivity.BuyReq) stderr.Code { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } //一份嫩模币=10元=100金币 price := decimal.NewFromInt32(req.Quantity).Mul(decimal.NewFromInt32(100)) plan := debitPlan(w, price.IntPart()) if plan == nil { return stderr.InsufficientBalance } var desc string if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } date := timeutil.BeginningOfDay(time.Now()) if time.Now().Hour() >= 20 { date = date.Add(24 * time.Hour) } giftLeft, location, err := newactivity.UpdateStock(t, req, date) if err != nil { return err } record := newactivity.SoldRecord{ Date: date, ModelId: req.ModelId, UserId: uint32(uid), UserName: u.Name, UserLogo: u.Portrait, Quantity: req.Quantity, SoldOut: req.BuyOut, } if !req.BuyOut { record.Serials = makeSerials(giftLeft, req.Quantity) } id, err := newactivity.InsertSoldLog(t, &record) if err != nil { return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: primitive.NewObjectID(), UID: uid, ProductID: primitive.NilObjectID, Name: "嫩模币", Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.MODEL, SysType: u.SysType, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } desc = "参与活动:嫩模之夜," + makeDesc(date, location, req) txnLog := txnmod.TransactionLog{UID: uid, Amount: -price.IntPart(), ActualAmount: float64(-price.IntPart()), TranType: txnmod.NengModel.Key(), TranTypeInt: int64(txnmod.NengModel), TransNo: id, Desc: desc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } return txnmod.InsertTransactionLog(t, &txnLog) }); err != nil { log.Warn(fmt.Sprintf("productser BuyModel Transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } func makeDesc(date time.Time, location string, req newactivity.BuyReq) (desc string) { desc = "赠送" + date.Format("0102") + "期," desc += location + strconv.Itoa(int(req.ModelId)) desc += "号嫩模" + strconv.Itoa(int(req.Quantity)) + "份礼物" return } // 使用机器人购买嫩模 func BuyModelFakeUser(uid uint64, req newactivity.BuyReq) stderr.Code { wallet, err := walletmod.GetWallet(uid) if err != nil { return stderr.ErrNetWorkBusy } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } //一份嫩模币=10元=100金币 price := decimal.NewFromInt32(req.Quantity).Mul(decimal.NewFromInt32(100)) var desc string if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { date := timeutil.BeginningOfDay(time.Now()) if time.Now().Hour() >= 20 { date = date.Add(24 * time.Hour) } giftLeft, location, err := newactivity.UpdateStock(t, req, date) if err != nil { return err } record := newactivity.SoldRecord{ Date: date, ModelId: req.ModelId, UserId: uint32(uid), UserName: u.Name, UserLogo: u.Portrait, Quantity: req.Quantity, SoldOut: req.BuyOut, } if !req.BuyOut { record.Serials = makeSerials(giftLeft, req.Quantity) } id, err := newactivity.InsertSoldLog(t, &record) if err != nil { return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: primitive.NewObjectID(), UID: uid, ProductID: primitive.NilObjectID, Name: "嫩模币", Amount: price.IntPart(), Income: 0, ProductType: prdcthsomod.MODEL, SysType: u.SysType, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } desc = "参与活动:嫩模之夜," + makeDesc(date, location, req) txnLog := txnmod.TransactionLog{UID: uid, Amount: -price.IntPart(), ActualAmount: float64(-price.IntPart()), TranType: txnmod.NengModel.Key(), TranTypeInt: int64(txnmod.NengModel), TransNo: id, Desc: desc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } return txnmod.InsertTransactionLog(t, &txnLog) }); err != nil { log.Warn(fmt.Sprintf("productser BuyModel Transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } // 扣除指定的游戏币 func DeductGameCoins(uid uint64, deductCoin int64) stderr.Code { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } plan := debitPlan(w, deductCoin) if plan == nil { return stderr.InsufficientBalance } var desc string if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: primitive.NewObjectID(), UID: uid, ProductID: primitive.NilObjectID, Name: "游戏币", Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.GAME, SysType: u.SysType, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{UID: uid, Amount: -deductCoin, ActualAmount: float64(-deductCoin), TranType: txnmod.GameCoin.Key(), TranTypeInt: int64(txnmod.GameCoin), Desc: desc, TransNo: history.ID, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } return txnmod.InsertTransactionLog(t, &txnLog) }); err != nil { log.Warn(fmt.Sprintf("productser DeductGameCoins Transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } func BuyMeetingCard(uid uint64, productID primitive.ObjectID, sys string, ua ua.UA, ip string) stderr.Code { p, err := productmod.FindProduct(productID, sys) if err != nil || p == nil { return stderr.ErrParamError } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() plan := debitPlan(w, p.DiscountedPrice) if plan == nil { return stderr.InsufficientBalance } if p.GiveCoin > 0 { plan.Amount = plan.Amount + p.GiveCoin } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: productID, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.MeetingCard, DiscDoc: discDoc, SysType: u.SysType, ProductSnapShot: p, CreatedAt: orderCreatedAt, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.MeetingCard.Key(), TranTypeInt: int64(txnmod.MeetingCard), TransNo: history.ID, Desc: "购买-" + p.Name, DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } return txnmod.InsertTransactionLog(t, &txnLog) }); err != nil { log.Warn(fmt.Sprintf("productser buy meetingcard transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } func BuyMeetingCard_d(t *db.MongoTool, uid uint64, p productmod.Product) error { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return errors.New("user is null") } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: primitive.NewObjectID(), UID: uid, ProductID: p.ID, Name: p.Name, Amount: p.DiscountedPrice, ProductType: prdcthsomod.MeetingCard, DiscDoc: u.DiscDoc, SysType: u.SysType, ProductSnapShot: &p, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.MeetingCard.Key(), TranTypeInt: int64(txnmod.MeetingCard), TransNo: history.ID, Desc: "购买-" + p.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: w.RealAmount(), CurrencyType: txnmod.CurrencyTypeCash, } if err = txnmod.InsertTransactionLog(t, &txnLog); err != nil { return err } return nil } func BuyOtherCard(uid uint64, productID primitive.ObjectID, sys string, ua ua.UA, ip string) stderr.Code { p, err := productmod.FindProduct(productID, sys) if err != nil || p == nil { return stderr.ErrParamError } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() plan := debitPlan(w, p.DiscountedPrice) if plan == nil { return stderr.InsufficientBalance } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: productID, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.OTHER, DiscDoc: discDoc, SysType: u.SysType, ProductSnapShot: p, CreatedAt: orderCreatedAt, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.Other.Key(), TranTypeInt: int64(txnmod.Other), TransNo: history.ID, Desc: "购买-" + p.Name, DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } return txnmod.InsertTransactionLog(t, &txnLog) }); err != nil { log.Warn(fmt.Sprintf("productser buy otherCard transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } func BuyOtherCard_d(t *db.MongoTool, uid uint64, p productmod.Product) error { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return errors.New("user is null") } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: primitive.NewObjectID(), UID: uid, ProductID: p.ID, Name: p.Name, Amount: p.DiscountedPrice, ProductType: prdcthsomod.OTHER, DiscDoc: u.DiscDoc, SysType: u.SysType, ProductSnapShot: &p, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.Other.Key(), TranTypeInt: int64(txnmod.Other), TransNo: history.ID, Desc: "购买-" + p.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: w.RealAmount(), CurrencyType: txnmod.CurrencyTypeCash, } if err = txnmod.InsertTransactionLog(t, &txnLog); err != nil { return err } return nil } func CountdownTiroCard(uid uint64) (countdownSec int64, err error) { u, err := usermod.FindUserByUID(uid) if err != nil { return } if u == nil { err = errors.New("invalid user") return } var newUserHour int64 = 24 //查询数据库里面的字段值直接赋予给newUserHour //data, err := vipconfigmod.FindOne() //if err != nil { // return //} //if data.TimeConfig > 0 { // newUserHour = data.TimeConfig //} /*data, _ := productmod.FindByProductType(commod.NEWUSERCard) if len(data) > 0 { newUserHour = data[0].ShowCountdownTime }*/ now := time.Now() countdownSec = u.CreatedAt.Add(time.Duration(newUserHour)*time.Hour).Unix() - now.Unix() if countdownSec < 0 { countdownSec = 0 } return } func BuyAudioBook(uid uint64, lid primitive.ObjectID, chapterID string, ua ua.UA, ip string) stderr.Code { l, err := audiobookmod.GetByID(lid) if err != nil { return stderr.ErrNetWorkBusy } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } pab, err := payaudiobookmod.FindByUIDAndObjID(uid, lid) if err != nil { return stderr.ErrNetWorkBusy } desc := "购买有声小说:" + l.Title + "." var price int64 = 0 episodeNumberSet := make([]int, 0) if chapterID == "" { desc += "(全购:" for i := range l.ContentSet { if l.ContentSet[i].ListenPermission == audiobookmod.ListenPermissionBuy && (!sli.ContainsInt(pab.EpisodeNumberSet, l.ContentSet[i].EpisodeNumber)) { price += l.ContentSet[i].Price desc += l.ContentSet[i].Name + "." episodeNumberSet = append(episodeNumberSet, l.ContentSet[i].EpisodeNumber) } } desc += ")." } else { episodeNumber, err := strconv.Atoi(chapterID) if err != nil { return stderr.ErrParamError } for i := range l.ContentSet { if l.ContentSet[i].EpisodeNumber == episodeNumber { if l.ContentSet[i].ListenPermission == audiobookmod.ListenPermissionBuy && !sli.ContainsInt(pab.EpisodeNumberSet, episodeNumber) { price += l.ContentSet[i].Price desc += "(单购:" + l.ContentSet[i].Name + ".)" episodeNumberSet = append(episodeNumberSet, l.ContentSet[i].EpisodeNumber) } break } } } if price == 0 { return stderr.RepeatPurchase } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } plan := debitPlan(w, price) if plan == nil { return stderr.InsufficientBalance } pid := l.ID.Hex() if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } txnLog := txnmod.TransactionLog{ UID: uid, ProductID: &pid, Amount: -price, ActualAmount: float64(-price), TranType: txnmod.AudioBook.Key(), TranTypeInt: int64(txnmod.AudioBook), Desc: desc, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } if err = txnmod.InsertTransactionLog(t, &txnLog); err != nil { return err } return payaudiobookmod.Insert(payaudiobookmod.PayAudioBookHistory{ UID: uid, EpisodeNumberSet: episodeNumberSet, ObjID: lid, }, t) }); err != nil { log.Warn(fmt.Sprintf("productser BuyVIP Transaction err %s", err.Error())) return stderr.BuyFailed } return stderr.Success } func checkVideoDisciountRenew(videoDiscLog videodiscountmod.VideoDiscountLog, p productmod.Product) time.Time { duration := time.Hour * 24 * time.Duration(p.Duration) // Duration以天为单位 now := time.Now() if videoDiscLog.Expiration.After(now) { return videoDiscLog.Expiration.Add(duration) } return now.Add(duration) } func checkVideoFreeCardRenew(user usermod.User, product productmod.Product, t time.Time) time.Time { duration := time.Hour * 24 * time.Duration(product.Duration) if user.VideoFreeExpiration != nil && user.VideoFreeExpiration.After(t) { return user.VideoFreeExpiration.Add(duration) } return t.Add(duration) } func BuyVideoFreeCard(uid uint64, pid primitive.ObjectID, sys string, ua ua.UA, ip string) stderr.Code { p, err := productmod.FindProduct(pid, sys) if err != nil || p == nil { return stderr.ErrParamError } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() expiration := checkVideoFreeCardRenew(*u, *p, time.Now()) if err != nil { return stderr.ErrParamError } plan := debitPlan(w, p.DiscountedPrice) if plan == nil { return stderr.InsufficientBalance } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { discDoc := u.DiscDoc wallet, err := walletmod.Debit(t, plan, uid) if err != nil { //扣钱 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: pid, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.VideoFreeCard, DiscDoc: u.DiscDoc, SysType: u.SysType, ProductSnapShot: p, CreatedAt: orderCreatedAt, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.VideoFreeCard.Key(), TranTypeInt: int64(txnmod.VideoFreeCard), Desc: "购买-" + p.Name, DiscDoc: discDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), TransNo: history.ID, } if err = txnmod.InsertTransactionLog(t, &txnLog); err != nil { return err } userSet := usermod.UserSelector{ VideoFreeExpiration: &expiration, } _, _ = usermod.UpdateTrans(t, uid, userSet) return nil }); err != nil { log.Error("BuyVideoFreeCard", log.Any("uid", uid), log.Any("productID", pid), log.Any("sys", sys)) return stderr.BuyFailed } return stderr.Success } func BuyVideoDiscountCard(uid uint64, pid primitive.ObjectID, sys string, ua ua.UA, ip string) stderr.Code { p, err := productmod.FindProduct(pid, sys) if err != nil || p == nil { return stderr.ErrParamError } u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } var orderId = primitive.NewObjectID() var orderCreatedAt = time.Now() videoDiscountLog, err := videodiscountmod.GetByUID(uid) if err != nil { return stderr.ErrNetWorkBusy } expiration := checkVideoDisciountRenew(videoDiscountLog, *p) plan := debitPlan(w, p.DiscountedPrice) if plan == nil { return stderr.InsufficientBalance } if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { wallet, err := walletmod.Debit(t, plan, uid) // 尝试直接从钱包扣钱 if err != nil { // 钱包余额不足 return err } history := prdcthsomod.ProductHistory{ //插入商品购买记录 ID: orderId, UID: uid, ProductID: pid, Name: p.Name, Amount: plan.Amount, Income: plan.Income, ProductType: prdcthsomod.VideoDiscount, DiscDoc: u.DiscDoc, SysType: u.SysType, ProductSnapShot: p, CreatedAt: orderCreatedAt, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.VideoDiscount.Key(), TranTypeInt: int64(txnmod.VideoDiscount), TransNo: history.ID, Desc: "购买-" + p.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), } if err = txnmod.InsertTransactionLog(t, &txnLog); err != nil { return err } set := videodiscountmod.EditSelector{ UID: &uid, Expiration: &expiration, VideoDiscount: &p.VideoDiscount, } return videodiscountmod.Upsert(t, &set) }); err != nil { log.Error("BuyVideoDiscountCard", log.Any("uid", uid), log.Any("productID", pid), log.Any("sys", sys)) return stderr.BuyFailed } return stderr.Success } func BuyVideoDiscountCard_d(t *db.MongoTool, uid uint64, p productmod.Product) error { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return stderr.ErrNetWorkBusy } videoDiscountLog, err := videodiscountmod.GetByUID(uid) if err != nil { return stderr.ErrNetWorkBusy } expiration := checkVideoDisciountRenew(videoDiscountLog, p) discountSet := videodiscountmod.EditSelector{ UID: &uid, Expiration: &expiration, VideoDiscount: &p.VideoDiscount, } if err = videodiscountmod.Upsert(t, &discountSet); err != nil { return err } history := prdcthsomod.ProductHistory{ ID: primitive.NewObjectID(), UID: uid, ProductID: p.ID, Name: p.Name, Amount: p.DiscountedPrice, ProductType: productmod.VideoDiscount, SysType: u.SysType, DiscDoc: u.DiscDoc, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.VideoDiscount.Key(), TranTypeInt: int64(txnmod.VideoDiscount), Desc: "购买-" + p.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: w.RealAmount(), CurrencyType: txnmod.CurrencyTypeCash, TransNo: history.ID, } return txnmod.InsertTransactionLog(t, &txnLog) } // BuyVideoFreeCard_d 购买视频免费卡,兼容旧版客户端请求 func BuyVideoFreeCard_d(t *db.MongoTool, uid uint64, p productmod.Product) error { u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return stderr.ErrNetWorkBusy } now := time.Now() expiration := checkVideoFreeCardRenew(*u, p, now) userSet := usermod.UserSelector{ VideoFreeExpiration: &expiration, } if _, err = usermod.UpdateTrans(t, uid, userSet); err != nil { return err } history := prdcthsomod.ProductHistory{ ID: primitive.NewObjectID(), UID: uid, ProductID: p.ID, Name: p.Name, Amount: p.DiscountedPrice, ProductType: prdcthsomod.VideoFreeCard, SysType: u.SysType, DiscDoc: u.DiscDoc, } if err = prdcthsomod.InsertProductHistory(t, &history); err != nil { return err } wallet, err := walletmod.GetWallet(uid) if err != nil { return err } txnLog := txnmod.TransactionLog{ UID: uid, Amount: -p.DiscountedPrice, ActualAmount: float64(-p.DiscountedPrice), TranType: txnmod.VideoFreeCard.Key(), TranTypeInt: int64(txnmod.VideoFreeCard), Desc: "购买-" + p.Name, DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: walletmod.GetRealAmount(wallet), CurrencyType: txnmod.CurrencyTypeCash, TransNo: history.ID, } return txnmod.InsertTransactionLog(t, &txnLog) } func DelBroughtHistory(videoID primitive.ObjectID, uid uint64) error { return payvidlgmod.DelVideoPayRecord(videoID, uid) } func GetCouponDetail(uid uint64, productID string, productType commod.ProductType, sys string) (ActivityCouponDetailResponse, stderr.Code) { var ( couponType int goodsType int isAmountPay bool ) data := ActivityCouponDetailResponse{} id, err := primitive.ObjectIDFromHex(productID) if err != nil { return data, stderr.ErrParamError } switch productType { case commod.VIP, commod.NEWUSERCard: // 获取商品信息 productDetail, err := productmod.FindProduct(id, sys) if err != nil { return data, stderr.ErrDbQueryError } if productDetail.IsAmountPay { isAmountPay = true } data.OriginalPrice = productDetail.OriginalPrice data.DiscountedPrice = productDetail.DiscountedPrice couponType = int(prizemod.VIPDiscount) goodsType = int(backpackmod.VIPDiscount) default: return data, stderr.ErrParamError } // 获取所有优惠卷 couponList, err := prizemod.GetCouponList(couponType) if err != nil { return data, stderr.ErrDbQueryError } // 获取个人拥有的优惠卷 goodsList, err := backpackmod.GetCouponListByUID(uid, goodsType) if err != nil { return data, stderr.ErrDbQueryError } // 会员卡(金币购买) if isAmountPay { for _, v := range couponList { couponDetail := ActivityCoupon{} if productType == commod.VIP { couponDetail.DiscountedPrice = decimal.NewFromInt(v.Value).Mul(decimal.NewFromInt(data.DiscountedPrice)).Div(decimal.NewFromInt(10)).IntPart() } else { couponDetail.DiscountedPrice = decimal.NewFromInt(v.Value).Mul(decimal.NewFromInt(data.OriginalPrice)).Div(decimal.NewFromInt(10)).IntPart() } couponDetail.Name = v.Name for _, gv := range goodsList { if v.Value == gv.GoodsValue { if couponDetail.ID.IsZero() { couponDetail.ID = gv.ID } couponDetail.Count++ } } data.CouponList = append(data.CouponList, couponDetail) } } if data.DiscountedPrice < data.OriginalPrice { data.IsDiscounted = true } return data, stderr.Success } // 视频扣量校验 func checkVideoDeduction(videoDeduction float64, videoDeductionCount, videoDeductionPayCount int, uid uint64, goldVideoCouponNum int) bool { //使用金币视频抵用券, 则直接返回扣量, 且不计入博主视频的购买数量和扣量次数 if goldVideoCouponNum > 0 { log.Info("checkVideoDeduction, goldVideoCouponNum---100%扣量", log.Any("uid", uid)) return true } //根据用户扣量比率videoDeduction 来进行计算. if videoDeduction == 0 { // 若扣量比例为0 则直接返回不扣量, 且不计入博主视频的购买数量和扣量次数 log.Info("checkVideoDeduction---不扣量", log.Any("uid", uid)) return false } // 计算是否扣量: deduct := func() bool { if videoDeduction >= 10 || videoDeductionCount == 0 { return true // 若扣量比例为100% 或视频没有扣量次数, 则扣量 } // 按比例计算扣量 deduction := (float64(videoDeductionCount) / (float64(videoDeductionPayCount + 1))) * 10 if deduction < videoDeduction { // 若当前扣量比例低于配置的扣量比例, 则扣量 log.Info("checkVideoDeduction---扣量", log.Any("videoDeduction", videoDeduction), log.Any("deduction", deduction), log.Any("uid", uid)) return true } log.Info("checkVideoDeduction---不扣量", log.Any("videoDeduction", videoDeduction), log.Any("deduction", deduction), log.Any("uid", uid)) return false }() //更新博主视频的购买数量和扣量次数 _ = usermod.IncVideoDeduction(uid, deduct) return deduct } func GetCoin(uid uint64) (data interface{}, code stderr.Code) { //获取用户信息 u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return nil, stderr.ErrNetWorkBusy } //获取金币月卡产品信息---根据产品类型获取 p, err := productmod.FindByProductType(commod.CoinMonthCard) if err != nil || p == nil { return nil, stderr.ErrParamError } w, err := walletmod.GetWallet(uid) if err != nil || w == nil { return nil, stderr.ErrNetWorkBusy } //检查vip过期时间_判断用户vip是否过期 if !u.CoinMouthExpireDate.After(time.Now()) { return nil, stderr.ExpiredVip } txnLogs := make([]txnmod.TransactionLog, 0) if err = appg.VideoDB.Trans(func(t *db.MongoTool) error { //金币月卡赠送金币 if len(p) > 0 && p[0].EveryDayGiveCoin > 0 { if _, err = walletmod.CreditAmount(t, p[0].EveryDayGiveCoin, uid); err != nil { return err } //插入购买金币月卡怎送金币流水 giveLog := txnmod.TransactionLog{UID: uid, Amount: p[0].EveryDayGiveCoin, ActualAmount: float64(p[0].EveryDayGiveCoin), TranType: txnmod.VipCardGive.Key(), TranTypeInt: int64(txnmod.VipCardGive), TransNo: p[0].ID, Desc: "金币月卡每日赠送金币", DiscDoc: u.DiscDoc, SysType: u.SysType, RealAmount: w.RealAmount(), } txnLogs = append(txnLogs, giveLog) if err = txnmod.InsertTransactionLog(t, &giveLog); err != nil { return err } } return nil }); err != nil { log.Warn(fmt.Sprintf("productser BuyModel Transaction err %s", err.Error())) return nil, stderr.BuyFailed } return "success", stderr.Success } func GetAwVipInfo(uid uint64) (data *productmod.RespVipInfoList, code stderr.Code) { var res productmod.RespVipInfoList //获取用户信息 u, err := usermod.FindUserByUID(uid) if err != nil || u == nil { return nil, stderr.ErrNetWorkBusy } str, err := appg.Redis.Get(redisconst.GetAwVipInfo) if err != nil { log.Warn(fmt.Sprintf("用户ID:%d;缓存获取会员卡列表信息异常:%v", uid, err)) } if str != nil { if err = json.Unmarshal([]byte(*str), &res); err == nil { return &res, stderr.Success } log.Warn(fmt.Sprintf("用户ID:%d;解析缓存数据异常:%v", uid, err)) } productList, err := productmod.FindByVipLevel(productmod.LevelThree) if err != nil || productList == nil { return nil, stderr.ErrParamError } if len(productList) <= 0 { return &res, stderr.Success } if len(productList) > 0 { for _, p := range productList { res.List = append(res.List, productmod.ProductList{ ID: p.ID.Hex(), Name: p.Name, }) } } common.Go(func() { if res.List != nil || len(res.List) > 0 { d, err := json.Marshal(res) if err != nil { return } if err = appg.Redis.Set(redisconst.GetAwVipInfo, d, 5*time.Minute); err != nil { log.Warn(fmt.Sprintf("用户ID:%d;保存缓存数据异常:%v", uid, err)) } } }) return &res, stderr.Success } //// GetRecommendVip 获取推荐展示的vip卡 //func GetRecommendVip() (data *productmod.Product, err error) { // _, err = cachev2.Classes().CacheTime(redisconst.RecommendVipExpire).Key(redisconst.RecommendVip).ResBind(&data).Cache(productmod.GetRecommendVip) // if err != nil { // log.Error("cachev2 productmod.FindOne fail", log.E(err)) // return // } // return //}