fix(order): align invite gift ownership
Build docker and publish / build (20.15.1) (push) Has been cancelled

This commit is contained in:
2026-04-28 05:19:57 -07:00
parent ab38cd4943
commit 0ec0e2b9d2
4 changed files with 1070 additions and 6 deletions
+32 -4
View File
@@ -1090,7 +1090,9 @@ func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *use
// 有佣金路径:邀请人拿佣金,被邀请用户(首单)拿天数
if orderInfo.IsNew {
giftTarget := l.resolveGiftTargetUser(ctx, userInfo, orderInfo.SubscriptionUserId)
_ = l.grantGiftDays(ctx, giftTarget, int(l.svc.Config.Invite.GiftDays), orderInfo.OrderNo, "邀请赠送")
if giftErr := l.grantGiftDays(ctx, giftTarget, int(l.svc.Config.Invite.GiftDays), orderInfo.OrderNo, "邀请赠送"); giftErr != nil {
l.logGiftDaysError(ctx, giftErr, giftTarget, orderInfo, "commission_path_referee")
}
}
}
@@ -1100,7 +1102,9 @@ func (l *ActivateOrderLogic) grantGiftDaysToBothParties(ctx context.Context, ref
return
}
refereeTarget := l.resolveGiftTargetUser(ctx, referee, orderInfo.SubscriptionUserId)
_ = l.grantGiftDays(ctx, refereeTarget, int(giftDays), orderInfo.OrderNo, "邀请赠送")
if err := l.grantGiftDays(ctx, refereeTarget, int(giftDays), orderInfo.OrderNo, "邀请赠送"); err != nil {
l.logGiftDaysError(ctx, err, refereeTarget, orderInfo, "no_commission_referee")
}
if referee.RefererId == 0 {
return
}
@@ -1109,7 +1113,9 @@ func (l *ActivateOrderLogic) grantGiftDaysToBothParties(ctx context.Context, ref
return
}
refererTarget := l.resolveGiftTargetUser(ctx, referer, 0)
_ = l.grantGiftDays(ctx, refererTarget, int(giftDays), orderInfo.OrderNo, "邀请赠送")
if err = l.grantGiftDays(ctx, refererTarget, int(giftDays), orderInfo.OrderNo, "邀请赠送"); err != nil {
l.logGiftDaysError(ctx, err, refererTarget, orderInfo, "no_commission_referer")
}
}
func (l *ActivateOrderLogic) resolveGiftTargetUser(ctx context.Context, source *user.User, forcedOwnerID int64) *user.User {
@@ -1136,6 +1142,26 @@ func (l *ActivateOrderLogic) resolveGiftTargetUser(ctx context.Context, source *
return target
}
func (l *ActivateOrderLogic) logGiftDaysError(ctx context.Context, err error, target *user.User, orderInfo *order.Order, stage string) {
if err == nil {
return
}
var targetID int64
if target != nil {
targetID = target.Id
}
var orderNo string
if orderInfo != nil {
orderNo = orderInfo.OrderNo
}
logger.WithContext(ctx).Error("Grant invite gift days failed",
logger.Field("error", err.Error()),
logger.Field("stage", stage),
logger.Field("target_user_id", targetID),
logger.Field("order_no", orderNo),
)
}
func (l *ActivateOrderLogic) grantGiftDays(ctx context.Context, u *user.User, days int, orderNo string, remark string) error {
if u == nil || days <= 0 {
return nil
@@ -1179,7 +1205,9 @@ func (l *ActivateOrderLogic) grantGiftDays(ctx context.Context, u *user.User, da
}
return err
}
activeSubscribe.ExpireTime = activeSubscribe.ExpireTime.Add(time.Duration(days) * 24 * time.Hour)
if !activeSubscribe.ExpireTime.Equal(time.UnixMilli(0)) {
activeSubscribe.ExpireTime = activeSubscribe.ExpireTime.Add(time.Duration(days) * 24 * time.Hour)
}
err = l.svc.UserModel.UpdateSubscribe(ctx, activeSubscribe)
if err != nil {
return err
@@ -312,6 +312,134 @@ func TestInviteFlow_OrderThenBind_NoGiftDays(t *testing.T) {
assertExpireIncreasedByDays(t, db, refererSub.Id, baseExpire, 0)
}
func TestHandleCommission_GiftDaysToRefereeFamilyOwnerWhenChannelFirstOrder(t *testing.T) {
logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{
ReferralPercentage: 10,
OnlyFirstPurchase: false,
GiftDays: 2,
})
defer cleanup()
refereeOwner := seedUser(t, db, 0, false)
referee := seedUser(t, db, 0, false)
referer := seedUser(t, db, 0, false)
seedFamily(t, db, refereeOwner.Id, referee.Id)
referee.RefererId = referer.Id
ownerBaseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second)
ownerSub := seedActiveSubscribe(t, db, refereeOwner.Id, ownerBaseExpire)
logic.handleCommission(context.Background(), referee, &order.Order{
OrderNo: "ORD-FAMILY-REFEREE-001",
Type: OrderTypeSubscribe,
IsNew: true,
Amount: 100,
FeeAmount: 0,
SubscriptionUserId: refereeOwner.Id,
CreatedAt: time.Now(),
})
assertExpireIncreasedByDays(t, db, ownerSub.Id, ownerBaseExpire, 2)
assertUserCommission(t, db, referer.Id, 10)
}
func TestHandleCommission_GiftDaysToRefererFamilyOwnerWhenCommissionDisabled(t *testing.T) {
logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{
ReferralPercentage: 0,
OnlyFirstPurchase: false,
GiftDays: 2,
})
defer cleanup()
referee := seedUser(t, db, 0, false)
refererOwner := seedUser(t, db, 0, false)
referer := seedUser(t, db, 0, false)
seedFamily(t, db, refererOwner.Id, referer.Id)
referee.RefererId = referer.Id
refereeBaseExpire := time.Now().Add(72 * time.Hour).Truncate(time.Second)
refererOwnerBaseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second)
refereeSub := seedActiveSubscribe(t, db, referee.Id, refereeBaseExpire)
refererOwnerSub := seedActiveSubscribe(t, db, refererOwner.Id, refererOwnerBaseExpire)
logic.handleCommission(context.Background(), referee, &order.Order{
OrderNo: "ORD-FAMILY-REFERER-001",
Type: OrderTypeSubscribe,
IsNew: true,
Amount: 100,
FeeAmount: 0,
CreatedAt: time.Now(),
})
assertExpireIncreasedByDays(t, db, refereeSub.Id, refereeBaseExpire, 2)
assertExpireIncreasedByDays(t, db, refererOwnerSub.Id, refererOwnerBaseExpire, 2)
}
func TestHandleCommission_RefererFamilyMemberCommissionBehaviorUnchanged(t *testing.T) {
logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{
ReferralPercentage: 10,
OnlyFirstPurchase: false,
GiftDays: 2,
})
defer cleanup()
referee := seedUser(t, db, 0, false)
refererOwner := seedUser(t, db, 0, false)
referer := seedUser(t, db, 0, false)
seedFamily(t, db, refererOwner.Id, referer.Id)
referee.RefererId = referer.Id
refereeBaseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second)
refereeSub := seedActiveSubscribe(t, db, referee.Id, refereeBaseExpire)
logic.handleCommission(context.Background(), referee, &order.Order{
OrderNo: "ORD-FAMILY-COMMISSION-001",
Type: OrderTypeSubscribe,
IsNew: true,
Amount: 100,
FeeAmount: 0,
CreatedAt: time.Now(),
})
assertExpireIncreasedByDays(t, db, refereeSub.Id, refereeBaseExpire, 2)
assertUserCommission(t, db, referer.Id, 10)
assertUserCommission(t, db, refererOwner.Id, 0)
}
func TestHandleCommission_GiftDaysRecognizesUnlimitedSubscription(t *testing.T) {
logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{
ReferralPercentage: 10,
OnlyFirstPurchase: false,
GiftDays: 2,
})
defer cleanup()
referee := seedUser(t, db, 0, false)
referer := seedUser(t, db, 0, false)
referee.RefererId = referer.Id
unlimitedExpire := time.UnixMilli(0)
refereeSub := seedActiveSubscribe(t, db, referee.Id, unlimitedExpire)
logic.handleCommission(context.Background(), referee, &order.Order{
OrderNo: "ORD-UNLIMITED-001",
Type: OrderTypeSubscribe,
IsNew: true,
Amount: 100,
FeeAmount: 0,
CreatedAt: time.Now(),
})
assertExpireIncreasedByDays(t, db, refereeSub.Id, unlimitedExpire, 0)
var giftCount int64
if err := db.Model(&modelLog.SystemLog{}).Where("type = ? AND object_id = ?", modelLog.TypeGift.Uint8(), referee.Id).Count(&giftCount).Error; err != nil {
t.Fatalf("count gift logs failed: %v", err)
}
if giftCount != 1 {
t.Fatalf("expected 1 gift log for unlimited subscription, got %d", giftCount)
}
}
func setupInviteTestLogic(t *testing.T, inviteCfg config.InviteConfig) (*ActivateOrderLogic, *gorm.DB, func()) {
t.Helper()
@@ -336,7 +464,7 @@ func setupInviteTestLogic(t *testing.T, inviteCfg config.InviteConfig) (*Activat
t.Fatalf("open test database failed: %v", err)
}
if err := db.AutoMigrate(&user.User{}, &user.Device{}, &user.AuthMethods{}, &user.Subscribe{}, &modelLog.SystemLog{}); err != nil {
if err := db.AutoMigrate(&user.User{}, &user.Device{}, &user.AuthMethods{}, &user.Subscribe{}, &user.UserFamily{}, &user.UserFamilyMember{}, &modelLog.SystemLog{}); err != nil {
t.Fatalf("auto migrate failed: %v", err)
}
@@ -392,6 +520,40 @@ func seedUser(t *testing.T, db *gorm.DB, referralPercentage uint8, onlyFirstPurc
return u
}
func seedFamily(t *testing.T, db *gorm.DB, ownerID int64, memberID int64) {
t.Helper()
family := &user.UserFamily{
OwnerUserId: ownerID,
MaxMembers: 3,
Status: user.FamilyStatusActive,
}
if err := db.Create(family).Error; err != nil {
t.Fatalf("seed family failed: %v", err)
}
now := time.Now()
members := []user.UserFamilyMember{
{
FamilyId: family.Id,
UserId: ownerID,
Role: user.FamilyRoleOwner,
Status: user.FamilyMemberActive,
JoinSource: "test",
JoinedAt: now,
},
{
FamilyId: family.Id,
UserId: memberID,
Role: user.FamilyRoleMember,
Status: user.FamilyMemberActive,
JoinSource: "test",
JoinedAt: now,
},
}
if err := db.Create(&members).Error; err != nil {
t.Fatalf("seed family members failed: %v", err)
}
}
func seedActiveSubscribe(t *testing.T, db *gorm.DB, userID int64, expireAt time.Time) *user.Subscribe {
t.Helper()
sub := &user.Subscribe{
@@ -423,6 +585,17 @@ func assertExpireIncreasedByDays(t *testing.T, db *gorm.DB, subscribeID int64, b
}
}
func assertUserCommission(t *testing.T, db *gorm.DB, userID int64, expected int64) {
t.Helper()
var u user.User
if err := db.First(&u, userID).Error; err != nil {
t.Fatalf("query user failed: %v", err)
}
if u.Commission != expected {
t.Fatalf("expected user %d commission=%d, got %d", userID, expected, u.Commission)
}
}
func boolPtr(v bool) *bool {
return &v
}