122 lines
3.5 KiB
Go
122 lines
3.5 KiB
Go
package user
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
|
||
"bindbox-game/internal/repository/mysql/dao"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
|
||
"gorm.io/gorm/clause"
|
||
)
|
||
|
||
// ConsumePointsFor 扣减用户积分(通用)
|
||
// 功能:按有效期优先扣减用户积分,并记录积分流水到 `user_points_ledger`
|
||
// 参数:
|
||
// - ctx:上下文
|
||
// - userID:用户ID
|
||
// - points:扣减积分数量
|
||
// - refTable:业务关联表名(例如 products、system_coupons)
|
||
// - refID:业务关联ID字符串(例如 product_id、coupon_id)
|
||
// - remark:备注信息
|
||
// - action:流水动作标识(例如 redeem_product、redeem_coupon)
|
||
//
|
||
// 返回:ledgerID(积分流水ID),error(不足或写入失败时)
|
||
func (s *service) ConsumePointsFor(ctx context.Context, userID int64, points int64, refTable string, refID string, remark string, action string) (int64, error) {
|
||
if points <= 0 {
|
||
return 0, nil
|
||
}
|
||
var ledgerID int64
|
||
err := s.writeDB.Transaction(func(tx *dao.Query) error {
|
||
rows, err := tx.UserPoints.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).Order(tx.UserPoints.ValidEnd.Asc()).Find()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
remain := points
|
||
now := time.Now()
|
||
for _, r := range rows {
|
||
if !r.ValidEnd.IsZero() && r.ValidEnd.Before(now) {
|
||
continue
|
||
}
|
||
if remain <= 0 {
|
||
break
|
||
}
|
||
use := r.Points
|
||
if use > remain {
|
||
use = remain
|
||
}
|
||
_, err := tx.UserPoints.WithContext(ctx).Where(tx.UserPoints.ID.Eq(r.ID)).Updates(map[string]any{"points": r.Points - use})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
remain -= use
|
||
}
|
||
if remain > 0 {
|
||
return errors.New("insufficient_points")
|
||
}
|
||
led := &model.UserPointsLedger{UserID: userID, Action: action, Points: -points, RefTable: refTable, RefID: refID, Remark: remark}
|
||
if err := tx.UserPointsLedger.WithContext(ctx).Create(led); err != nil {
|
||
return err
|
||
}
|
||
ledgerID = led.ID
|
||
return nil
|
||
})
|
||
return ledgerID, err
|
||
}
|
||
|
||
// ConsumePointsForRefund 扣减用户积分(退款回收场景,支持扣完为止)
|
||
// 返回:ledgerID(流水ID), actualConsumed(实际扣除数量), error
|
||
func (s *service) ConsumePointsForRefund(ctx context.Context, userID int64, points int64, refTable string, refID string, remark string) (int64, int64, error) {
|
||
if points <= 0 {
|
||
return 0, 0, nil
|
||
}
|
||
var ledgerID int64
|
||
var actualConsumed int64
|
||
|
||
err := s.writeDB.Transaction(func(tx *dao.Query) error {
|
||
rows, err := tx.UserPoints.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).Order(tx.UserPoints.ValidEnd.Asc()).Find()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
remain := points
|
||
now := time.Now()
|
||
for _, r := range rows {
|
||
if !r.ValidEnd.IsZero() && r.ValidEnd.Before(now) {
|
||
continue
|
||
}
|
||
if remain <= 0 {
|
||
break
|
||
}
|
||
use := r.Points
|
||
if use > remain {
|
||
use = remain
|
||
}
|
||
_, err := tx.UserPoints.WithContext(ctx).Where(tx.UserPoints.ID.Eq(r.ID)).Updates(map[string]any{"points": r.Points - use})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
remain -= use
|
||
}
|
||
|
||
actualConsumed = points - remain
|
||
if actualConsumed > 0 {
|
||
led := &model.UserPointsLedger{
|
||
UserID: userID,
|
||
Action: "refund_reclaim",
|
||
Points: -actualConsumed,
|
||
RefTable: refTable,
|
||
RefID: refID,
|
||
Remark: remark,
|
||
}
|
||
if err := tx.UserPointsLedger.WithContext(ctx).Create(led); err != nil {
|
||
return err
|
||
}
|
||
ledgerID = led.ID
|
||
}
|
||
return nil
|
||
})
|
||
|
||
return ledgerID, actualConsumed, err
|
||
}
|