From d45096d13f098733c7785b54c860eec71437b3e0 Mon Sep 17 00:00:00 2001 From: Zuncle <34310384@qq.com> Date: Sun, 8 Mar 2026 16:01:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(refund):=20=E4=BF=AE=E5=A4=8D=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E5=90=8E=E7=BF=BB=E7=89=8C=E6=B8=B8=E6=88=8F=E8=B5=84?= =?UTF-8?q?=E6=A0=BC=E6=9C=AA=E5=9B=9E=E6=94=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题描述: 用户退单后,翻牌游戏资格会重新出现(被重置),但用户已经抽过奖了。 这导致用户可以通过退款获得额外的翻牌机会。 根本原因: 退款处理逻辑 reclaimLivestreamAssets 只回收了 user_inventory 中的实物奖品, 但没有回收 user_game_tickets 中的翻牌游戏资格。 解决方案: 在 reclaimLivestreamAssets 函数后添加 reclaimFlipCardTicket 函数, 用于检测并回收翻牌游戏资格: 1. 通过 shop_order_id 查询抖店订单获取商品ID 2. 查询 douyin_product_rewards 表检查商品是否配置了翻牌游戏奖励 - 检查 reward_type = 'game_ticket' - 检查 payload.game_code = 'flip_card' 3. 如果配置了翻牌奖励,回收用户的翻牌次数 - 扣减 user_game_tickets.available - 扣减 user_game_tickets.total_earned 4. 在 game_ticket_logs 表中记录回收日志 影响范围: - 仅影响配置了翻牌游戏奖励的商品订单退款 - 退款时会同步回收翻牌游戏资格 - 已使用过的翻牌次数不会被回收(只回收 available > 0 的记录) 测试建议: 1. 购买配置了翻牌奖励的商品 2. 进行翻牌游戏 3. 申请退款 4. 验证翻牌资格是否被正确回收 --- internal/service/douyin/scheduler.go | 108 +++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/internal/service/douyin/scheduler.go b/internal/service/douyin/scheduler.go index 153123d..eda1525 100755 --- a/internal/service/douyin/scheduler.go +++ b/internal/service/douyin/scheduler.go @@ -1,6 +1,7 @@ package douyin import ( + "encoding/json" "errors" "bindbox-game/internal/pkg/logger" @@ -419,6 +420,113 @@ func (s *service) reclaimLivestreamAssets(ctx context.Context, log *model.Livest // 3. 恢复奖品库存 db.Exec("UPDATE livestream_prizes SET remaining = remaining + 1 WHERE id = ? AND remaining >= 0", log.PrizeID) s.logger.Info("[资产回收] 恢复奖品库存", zap.Int64("prize_id", log.PrizeID)) + + // 4. 回收翻牌游戏资格(如果奖品配置了翻牌游戏) + s.reclaimFlipCardTicket(ctx, log) +} + +// reclaimFlipCardTicket 回收翻牌游戏资格 +// 逻辑:如果订单对应的商品配置了翻牌游戏奖励,需要回收用户的翻牌次数 +func (s *service) reclaimFlipCardTicket(ctx context.Context, log *model.LivestreamDrawLogs) { + db := s.repo.GetDbW().WithContext(ctx) + + // 1. 查找对应的抖店订单,获取商品ID + var order model.DouyinOrders + if err := s.repo.GetDbR().Where("shop_order_id = ?", log.ShopOrderID).First(&order).Error; err != nil { + s.logger.Warn("[翻牌回收] 查询订单失败", zap.String("shop_order_id", log.ShopOrderID), zap.Error(err)) + return + } + + if order.DouyinProductID == "" { + return // 订单没有商品ID + } + + // 2. 查询该商品是否配置了翻牌游戏奖励 + var rewards []model.DouyinProductRewards + if err := s.repo.GetDbR().Where("product_id = ? AND status = 1", order.DouyinProductID).Find(&rewards).Error; err != nil { + s.logger.Warn("[翻牌回收] 查询奖励配置失败", zap.String("product_id", order.DouyinProductID), zap.Error(err)) + return + } + + // 3. 检查是否有翻牌游戏奖励 + hasFlipCard := false + for _, reward := range rewards { + if reward.RewardType == "game_ticket" && reward.RewardPayload != "" { + var payload struct { + GameCode string `json:"game_code"` + } + _ = json.Unmarshal([]byte(reward.RewardPayload), &payload) + if payload.GameCode == "flip_card" { + hasFlipCard = true + break + } + } + } + + if !hasFlipCard { + return // 没有配置翻牌游戏奖励 + } + + // 4. 回收翻牌游戏资格 + // 查找用户的翻牌游戏资格记录 + var ticket model.UserGameTickets + err := s.repo.GetDbR().Where("user_id = ? AND game_code = ?", log.LocalUserID, "flip_card").First(&ticket).Error + if err != nil { + s.logger.Warn("[翻牌回收] 用户没有翻牌资格记录", + zap.Int64("user_id", log.LocalUserID), + zap.String("shop_order_id", log.ShopOrderID)) + return + } + + // 检查是否有可用次数 + if ticket.Available <= 0 { + s.logger.Info("[翻牌回收] 用户翻牌次数已为0,无需回收", + zap.Int64("user_id", log.LocalUserID), + zap.Int32("available", ticket.Available)) + return + } + + // 扣减翻牌次数 + result := db.Model(&model.UserGameTickets{}). + Where("user_id = ? AND game_code = ? AND available > 0", log.LocalUserID, "flip_card"). + Updates(map[string]interface{}{ + "available": gorm.Expr("available - 1"), + "total_earned": gorm.Expr("total_earned - 1"), + "updated_at": time.Now(), + }) + + if result.Error != nil { + s.logger.Error("[翻牌回收] 扣减翻牌次数失败", + zap.Error(result.Error), + zap.Int64("user_id", log.LocalUserID)) + return + } + + if result.RowsAffected == 0 { + s.logger.Warn("[翻牌回收] 扣减翻牌次数失败,可能次数不足", + zap.Int64("user_id", log.LocalUserID)) + return + } + + // 记录日志 + logEntry := &model.GameTicketLogs{ + UserID: log.LocalUserID, + GameCode: "flip_card", + ChangeType: 3, // 3=扣除/回收 + Amount: -1, + Balance: ticket.Available - 1, + Source: "refund_reclaim", + SourceID: log.ID, + Remark: fmt.Sprintf("订单退款回收翻牌资格 (订单: %s)", log.ShopOrderID), + } + if err := db.Create(logEntry).Error; err != nil { + s.logger.Error("[翻牌回收] 记录日志失败", zap.Error(err)) + } + + s.logger.Info("[翻牌回收] 成功回收翻牌资格", + zap.Int64("user_id", log.LocalUserID), + zap.String("shop_order_id", log.ShopOrderID), + zap.Int32("remaining", ticket.Available-1)) } type activityAttribution struct {