refactor(orders): 重构订单列表查询逻辑,支持按消耗状态筛选 feat(orders): 订单列表返回新增活动分类与玩法类型信息 fix(orders): 修复订单支付时间空指针问题 docs(swagger): 更新订单相关接口文档 test(matching): 添加对对碰奖励匹配测试用例 chore: 清理无用脚本文件
310 lines
10 KiB
Go
310 lines
10 KiB
Go
package activity
|
||
|
||
import (
|
||
"bindbox-game/internal/pkg/core"
|
||
"bindbox-game/internal/pkg/logger"
|
||
"bindbox-game/internal/repository/mysql"
|
||
"bindbox-game/internal/repository/mysql/dao"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
titlesvc "bindbox-game/internal/service/title"
|
||
usersvc "bindbox-game/internal/service/user"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
)
|
||
|
||
// ActivityOrderService 活动订单创建服务
|
||
// 统一处理一番赏和对对碰的订单创建逻辑
|
||
type ActivityOrderService interface {
|
||
// CreateActivityOrder 创建活动订单
|
||
// 统一处理优惠券、称号折扣、道具卡记录等
|
||
CreateActivityOrder(ctx core.Context, req CreateActivityOrderRequest) (*CreateActivityOrderResult, error)
|
||
}
|
||
|
||
// CreateActivityOrderRequest 订单创建请求
|
||
type CreateActivityOrderRequest struct {
|
||
UserID int64 // 用户ID
|
||
ActivityID int64 // 活动ID
|
||
IssueID int64 // 期ID
|
||
Count int64 // 数量
|
||
UnitPrice int64 // 单价(分)
|
||
SourceType int32 // 订单来源类型: 2=抽奖, 3=对对碰
|
||
CouponID *int64 // 优惠券ID(可选)
|
||
ItemCardID *int64 // 道具卡ID(可选)
|
||
ExtraRemark string // 额外备注信息
|
||
}
|
||
|
||
// CreateActivityOrderResult 订单创建结果
|
||
type CreateActivityOrderResult struct {
|
||
Order *model.Orders // 创建的订单
|
||
AppliedCouponVal int64 // 应用的优惠券抵扣金额
|
||
}
|
||
|
||
type activityOrderService struct {
|
||
logger logger.CustomLogger
|
||
readDB *dao.Query
|
||
writeDB *dao.Query
|
||
repo mysql.Repo
|
||
title titlesvc.Service
|
||
user usersvc.Service
|
||
}
|
||
|
||
// NewActivityOrderService 创建活动订单服务
|
||
func NewActivityOrderService(l logger.CustomLogger, db mysql.Repo) ActivityOrderService {
|
||
return &activityOrderService{
|
||
logger: l,
|
||
readDB: dao.Use(db.GetDbR()),
|
||
writeDB: dao.Use(db.GetDbW()),
|
||
repo: db,
|
||
title: titlesvc.New(l, db),
|
||
user: usersvc.New(l, db),
|
||
}
|
||
}
|
||
|
||
// CreateActivityOrder 创建活动订单
|
||
func (s *activityOrderService) CreateActivityOrder(ctx core.Context, req CreateActivityOrderRequest) (*CreateActivityOrderResult, error) {
|
||
userID := req.UserID
|
||
count := req.Count
|
||
if count <= 0 {
|
||
count = 1
|
||
}
|
||
total := req.UnitPrice * count
|
||
|
||
// 1. 创建订单基础信息
|
||
orderNo := fmt.Sprintf("O%s", time.Now().Format("20060102150405"))
|
||
order := &model.Orders{
|
||
UserID: userID,
|
||
OrderNo: orderNo,
|
||
SourceType: req.SourceType,
|
||
TotalAmount: total,
|
||
ActualAmount: total,
|
||
Status: 1, // Pending
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
// 设置备注
|
||
if req.ExtraRemark != "" {
|
||
order.Remark = req.ExtraRemark
|
||
} else {
|
||
order.Remark = fmt.Sprintf("activity:%d|issue:%d|count:%d", req.ActivityID, req.IssueID, count)
|
||
}
|
||
|
||
// 2. 应用称号折扣 (Title Discount)
|
||
titleEffects, _ := s.title.ResolveActiveEffects(ctx.RequestContext(), userID, titlesvc.EffectScope{
|
||
ActivityID: &req.ActivityID,
|
||
IssueID: &req.IssueID,
|
||
})
|
||
for _, ef := range titleEffects {
|
||
if ef.EffectType == 2 { // Discount effect
|
||
var p struct {
|
||
DiscountType string `json:"discount_type"`
|
||
ValueX1000 int64 `json:"value_x1000"`
|
||
MaxDiscountX1000 int64 `json:"max_discount_x1000"`
|
||
}
|
||
if jsonErr := json.Unmarshal([]byte(ef.ParamsJSON), &p); jsonErr == nil {
|
||
var discount int64
|
||
if p.DiscountType == "percentage" {
|
||
discount = order.ActualAmount * p.ValueX1000 / 1000
|
||
} else if p.DiscountType == "fixed" {
|
||
discount = p.ValueX1000
|
||
}
|
||
if p.MaxDiscountX1000 > 0 && discount > p.MaxDiscountX1000 {
|
||
discount = p.MaxDiscountX1000
|
||
}
|
||
if discount > order.ActualAmount {
|
||
discount = order.ActualAmount
|
||
}
|
||
if discount > 0 {
|
||
order.ActualAmount -= discount
|
||
order.Remark += fmt.Sprintf("|title_discount:%d:%d", ef.ID, discount)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 应用优惠券 (using applyCouponWithCap logic)
|
||
var appliedCouponVal int64
|
||
if req.CouponID != nil && *req.CouponID > 0 {
|
||
fmt.Printf("[订单服务] 尝试优惠券 用户券ID=%d 应用前实付(分)=%d\n", *req.CouponID, order.ActualAmount)
|
||
appliedCouponVal = s.applyCouponWithCap(ctx.RequestContext(), userID, order, req.ActivityID, *req.CouponID)
|
||
fmt.Printf("[订单服务] 优惠后 实付(分)=%d 累计优惠(分)=%d\n", order.ActualAmount, order.DiscountAmount)
|
||
}
|
||
|
||
// 4. 记录道具卡到备注
|
||
if req.ItemCardID != nil && *req.ItemCardID > 0 {
|
||
order.Remark += fmt.Sprintf("|itemcard:%d", *req.ItemCardID)
|
||
}
|
||
|
||
// 5. 保存订单
|
||
if err := s.writeDB.Orders.WithContext(ctx.RequestContext()).Omit(s.writeDB.Orders.PaidAt, s.writeDB.Orders.CancelledAt).Create(order); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 6. 记录优惠券使用明细
|
||
if appliedCouponVal > 0 && req.CouponID != nil && *req.CouponID > 0 {
|
||
_ = s.user.RecordOrderCouponUsage(ctx.RequestContext(), order.ID, *req.CouponID, appliedCouponVal)
|
||
}
|
||
|
||
// 7. 处理0元订单自动支付
|
||
if order.ActualAmount == 0 {
|
||
now := time.Now()
|
||
_, _ = s.writeDB.Orders.WithContext(ctx.RequestContext()).Where(s.writeDB.Orders.OrderNo.Eq(orderNo)).Updates(map[string]any{
|
||
s.writeDB.Orders.Status.ColumnName().String(): 2,
|
||
s.writeDB.Orders.PaidAt.ColumnName().String(): now,
|
||
})
|
||
order.Status = 2
|
||
|
||
// 核销优惠券
|
||
if req.CouponID != nil && *req.CouponID > 0 {
|
||
s.consumeCouponOnZeroPay(ctx.RequestContext(), userID, order.ID, *req.CouponID, appliedCouponVal, now)
|
||
}
|
||
}
|
||
|
||
fmt.Printf("[订单服务] 订单创建完成 订单号=%s 总额(分)=%d 优惠(分)=%d 实付(分)=%d 状态=%d\n",
|
||
order.OrderNo, order.TotalAmount, order.DiscountAmount, order.ActualAmount, order.Status)
|
||
|
||
return &CreateActivityOrderResult{
|
||
Order: order,
|
||
AppliedCouponVal: appliedCouponVal,
|
||
}, nil
|
||
}
|
||
|
||
// applyCouponWithCap 优惠券抵扣(含50%封顶与金额券部分使用)
|
||
func (s *activityOrderService) applyCouponWithCap(ctx context.Context, userID int64, order *model.Orders, activityID int64, userCouponID int64) int64 {
|
||
uc, _ := s.readDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.ID.Eq(userCouponID), s.readDB.UserCoupons.UserID.Eq(userID), s.readDB.UserCoupons.Status.Eq(1)).First()
|
||
if uc == nil {
|
||
return 0
|
||
}
|
||
fmt.Printf("[优惠券] 用户券ID=%d 开始=%s 结束=%s\n", userCouponID, uc.ValidStart.Format(time.RFC3339), func() string {
|
||
if uc.ValidEnd.IsZero() {
|
||
return "无截止"
|
||
}
|
||
return uc.ValidEnd.Format(time.RFC3339)
|
||
}())
|
||
sc, _ := s.readDB.SystemCoupons.WithContext(ctx).Where(s.readDB.SystemCoupons.ID.Eq(uc.CouponID), s.readDB.SystemCoupons.Status.Eq(1)).First()
|
||
now := time.Now()
|
||
if sc == nil {
|
||
fmt.Printf("[优惠券] 模板不存在 用户券ID=%d\n", userCouponID)
|
||
return 0
|
||
}
|
||
if uc.ValidStart.After(now) {
|
||
fmt.Printf("[优惠券] 未到开始时间 用户券ID=%d\n", userCouponID)
|
||
return 0
|
||
}
|
||
if !uc.ValidEnd.IsZero() && uc.ValidEnd.Before(now) {
|
||
fmt.Printf("[优惠券] 已过期 用户券ID=%d\n", userCouponID)
|
||
return 0
|
||
}
|
||
scopeOK := (sc.ScopeType == 1) || (sc.ScopeType == 2 && sc.ActivityID == activityID)
|
||
if !scopeOK {
|
||
fmt.Printf("[优惠券] 范围不匹配 用户券ID=%d scope_type=%d\n", userCouponID, sc.ScopeType)
|
||
return 0
|
||
}
|
||
if order.TotalAmount < sc.MinSpend {
|
||
fmt.Printf("[优惠券] 未达使用门槛 用户券ID=%d min_spend(分)=%d 订单总额(分)=%d\n", userCouponID, sc.MinSpend, order.TotalAmount)
|
||
return 0
|
||
}
|
||
|
||
// 50% 封顶
|
||
cap := order.TotalAmount / 2
|
||
remainingCap := cap - order.DiscountAmount
|
||
if remainingCap <= 0 {
|
||
fmt.Printf("[优惠券] 已达封顶\n")
|
||
return 0
|
||
}
|
||
|
||
applied := int64(0)
|
||
switch sc.DiscountType {
|
||
case 1: // 金额券
|
||
var bal int64
|
||
_ = s.repo.GetDbR().Raw("SELECT COALESCE(balance_amount,0) FROM user_coupons WHERE id=?", userCouponID).Scan(&bal).Error
|
||
if bal <= 0 {
|
||
bal = sc.DiscountValue
|
||
}
|
||
if bal > 0 {
|
||
if bal > remainingCap {
|
||
applied = remainingCap
|
||
} else {
|
||
applied = bal
|
||
}
|
||
}
|
||
case 2: // 满减券
|
||
applied = sc.DiscountValue
|
||
if applied > remainingCap {
|
||
applied = remainingCap
|
||
}
|
||
case 3: // 折扣券
|
||
rate := sc.DiscountValue
|
||
if rate < 0 {
|
||
rate = 0
|
||
}
|
||
if rate > 1000 {
|
||
rate = 1000
|
||
}
|
||
newAmt := order.ActualAmount * rate / 1000
|
||
d := order.ActualAmount - newAmt
|
||
if d > remainingCap {
|
||
applied = remainingCap
|
||
} else {
|
||
applied = d
|
||
}
|
||
}
|
||
|
||
if applied > order.ActualAmount {
|
||
applied = order.ActualAmount
|
||
}
|
||
if applied <= 0 {
|
||
return 0
|
||
}
|
||
|
||
order.ActualAmount -= applied
|
||
order.DiscountAmount += applied
|
||
order.Remark += fmt.Sprintf("|c:%d:%d", userCouponID, applied)
|
||
fmt.Printf("[优惠券] 本次抵扣(分)=%d\n", applied)
|
||
|
||
return applied
|
||
}
|
||
|
||
// consumeCouponOnZeroPay 0元支付时核销优惠券
|
||
func (s *activityOrderService) consumeCouponOnZeroPay(ctx context.Context, userID int64, orderID int64, userCouponID int64, applied int64, now time.Time) {
|
||
uc, _ := s.readDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.ID.Eq(userCouponID), s.readDB.UserCoupons.UserID.Eq(userID)).First()
|
||
if uc == nil {
|
||
return
|
||
}
|
||
sc, _ := s.readDB.SystemCoupons.WithContext(ctx).Where(s.readDB.SystemCoupons.ID.Eq(uc.CouponID)).First()
|
||
if sc == nil {
|
||
return
|
||
}
|
||
|
||
if sc.DiscountType == 1 { // 金额券 - 部分扣减
|
||
var bal int64
|
||
_ = s.repo.GetDbR().Raw("SELECT COALESCE(balance_amount,0) FROM user_coupons WHERE id=?", userCouponID).Scan(&bal).Error
|
||
nb := bal - applied
|
||
if nb < 0 {
|
||
nb = 0
|
||
}
|
||
if nb == 0 {
|
||
_, _ = s.writeDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.ID.Eq(userCouponID)).Updates(map[string]any{
|
||
"balance_amount": nb,
|
||
s.readDB.UserCoupons.Status.ColumnName().String(): 2,
|
||
s.readDB.UserCoupons.UsedOrderID.ColumnName().String(): orderID,
|
||
s.readDB.UserCoupons.UsedAt.ColumnName().String(): now,
|
||
})
|
||
} else {
|
||
_, _ = s.writeDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.ID.Eq(userCouponID)).Updates(map[string]any{
|
||
"balance_amount": nb,
|
||
s.readDB.UserCoupons.UsedOrderID.ColumnName().String(): orderID,
|
||
s.readDB.UserCoupons.UsedAt.ColumnName().String(): now,
|
||
})
|
||
}
|
||
} else { // 满减/折扣券 - 直接核销
|
||
_, _ = s.writeDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.ID.Eq(userCouponID)).Updates(map[string]any{
|
||
s.readDB.UserCoupons.Status.ColumnName().String(): 2,
|
||
s.readDB.UserCoupons.UsedOrderID.ColumnName().String(): orderID,
|
||
s.readDB.UserCoupons.UsedAt.ColumnName().String(): now,
|
||
})
|
||
}
|
||
}
|