fix(refund): 修复退款后翻牌游戏资格未回收的问题

问题描述:
用户退单后,翻牌游戏资格会重新出现(被重置),但用户已经抽过奖了。
这导致用户可以通过退款获得额外的翻牌机会。

根本原因:
退款处理逻辑 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. 验证翻牌资格是否被正确回收
This commit is contained in:
Zuncle 2026-03-08 16:01:39 +08:00
parent 2aa7cdbd61
commit d45096d13f

View File

@ -1,6 +1,7 @@
package douyin package douyin
import ( import (
"encoding/json"
"errors" "errors"
"bindbox-game/internal/pkg/logger" "bindbox-game/internal/pkg/logger"
@ -419,6 +420,113 @@ func (s *service) reclaimLivestreamAssets(ctx context.Context, log *model.Livest
// 3. 恢复奖品库存 // 3. 恢复奖品库存
db.Exec("UPDATE livestream_prizes SET remaining = remaining + 1 WHERE id = ? AND remaining >= 0", log.PrizeID) 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)) 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 { type activityAttribution struct {