From 9dd5dcb9d218392f3124ce871d1efee25857691a Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Sat, 2 May 2026 16:48:45 -0700 Subject: [PATCH] fix(order): mark first renewal payments as new --- .../order/renewalInviteFirstPurchase_test.go | 130 ++++++++++++++++++ internal/logic/public/order/renewalLogic.go | 7 + 2 files changed, 137 insertions(+) create mode 100644 internal/logic/public/order/renewalInviteFirstPurchase_test.go diff --git a/internal/logic/public/order/renewalInviteFirstPurchase_test.go b/internal/logic/public/order/renewalInviteFirstPurchase_test.go new file mode 100644 index 0000000..86e4665 --- /dev/null +++ b/internal/logic/public/order/renewalInviteFirstPurchase_test.go @@ -0,0 +1,130 @@ +package order + +import ( + "testing" + "time" + + modelOrder "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/types" + "github.com/stretchr/testify/require" + "gorm.io/gorm" +) + +func ensureRenewalInviteSubscribeColumns(t *testing.T, db *gorm.DB) { + t.Helper() + for _, sql := range []string{ + `ALTER TABLE "user_subscribe" ADD COLUMN node_group_id INTEGER NOT NULL DEFAULT 0`, + `ALTER TABLE "user_subscribe" ADD COLUMN group_locked TINYINT NOT NULL DEFAULT 0`, + `ALTER TABLE "user_subscribe" ADD COLUMN expired_download INTEGER NOT NULL DEFAULT 0`, + `ALTER TABLE "user_subscribe" ADD COLUMN expired_upload INTEGER NOT NULL DEFAULT 0`, + } { + require.NoError(t, db.Exec(sql).Error) + } +} + +func insertRenewalInviteUserSubscribe(t *testing.T, db *gorm.DB, id, userID, orderID, subscribeID int64, token, uuid string, start, expire time.Time) { + t.Helper() + require.NoError(t, db.Exec(`INSERT INTO "user_subscribe" + (id, user_id, order_id, subscribe_id, start_time, expire_time, traffic, download, upload, token, uuid, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, 0, 0, 0, ?, ?, 1, ?, ?)`, + id, + userID, + orderID, + subscribeID, + start.UTC().Format("2006-01-02 15:04:05"), + expire.UTC().Format("2006-01-02 15:04:05"), + token, + uuid, + start.UTC().Format("2006-01-02 15:04:05"), + time.Now().UTC().Format("2006-01-02 15:04:05"), + ).Error) +} + +func TestRenewalFirstSuccessfulPaymentMarksOrderAsNew(t *testing.T) { + db := setupNewUserOnlyDB(t) + ensureRenewalInviteSubscribeColumns(t, db) + rds, mr := setupNewUserOnlyRedis(t) + svcCtx := buildNewUserOnlySvcCtx(db, rds, mr) + + const ( + planID = int64(1) + paymentID = int64(2) + ownerUserID = int64(31059) + memberID = int64(31057) + familyID = int64(5638) + ownerSubID = int64(14672) + ) + + insertTestSubscribe(t, db, planID, false) + insertTestPayment(t, db, paymentID) + member := insertTestUser(t, db, memberID, time.Now().Add(-2*time.Hour)) + insertTestUser(t, db, ownerUserID, time.Now().Add(-2*time.Hour)) + insertTestFamily(t, db, familyID, ownerUserID) + insertTestFamilyMember(t, db, familyID, ownerUserID, user.FamilyRoleOwner, user.FamilyMemberActive, "owner_init") + insertTestFamilyMember(t, db, familyID, memberID, user.FamilyRoleMember, user.FamilyMemberActive, "bind_email_with_verification") + + insertRenewalInviteUserSubscribe(t, db, ownerSubID, ownerUserID, 0, planID, "owner-trial-token", "owner-trial-uuid", + time.Date(2026, 5, 2, 15, 53, 39, 0, time.UTC), + time.Date(2026, 5, 9, 0, 0, 0, 0, time.UTC), + ) + + resp, err := NewRenewalLogic(buildPurchaseCtx(member), svcCtx).Renewal(&types.RenewalOrderRequest{ + UserSubscribeID: ownerSubID, + Payment: paymentID, + Quantity: 1, + }) + require.NoError(t, err) + require.NotNil(t, resp) + + var created modelOrder.Order + require.NoError(t, db.Where("order_no = ?", resp.OrderNo).First(&created).Error) + require.Equal(t, uint8(2), created.Type) + require.True(t, created.IsNew) + require.Equal(t, memberID, created.UserId) + require.Equal(t, ownerUserID, created.SubscriptionUserId) + require.Equal(t, "owner-trial-token", created.SubscribeToken) +} + +func TestRenewalExistingSuccessfulPaymentMarksOrderAsNotNew(t *testing.T) { + db := setupNewUserOnlyDB(t) + ensureRenewalInviteSubscribeColumns(t, db) + rds, mr := setupNewUserOnlyRedis(t) + svcCtx := buildNewUserOnlySvcCtx(db, rds, mr) + + const ( + planID = int64(1) + paymentID = int64(2) + ownerUserID = int64(41059) + memberID = int64(41057) + familyID = int64(6638) + ownerSubID = int64(24672) + ) + + insertTestSubscribe(t, db, planID, false) + insertTestPayment(t, db, paymentID) + member := insertTestUser(t, db, memberID, time.Now().Add(-2*time.Hour)) + insertTestUser(t, db, ownerUserID, time.Now().Add(-2*time.Hour)) + insertTestFamily(t, db, familyID, ownerUserID) + insertTestFamilyMember(t, db, familyID, ownerUserID, user.FamilyRoleOwner, user.FamilyMemberActive, "owner_init") + insertTestFamilyMember(t, db, familyID, memberID, user.FamilyRoleMember, user.FamilyMemberActive, "bind_email_with_verification") + insertScopedTestOrder(t, db, "previous-successful-order", memberID, planID, 5) + + insertRenewalInviteUserSubscribe(t, db, ownerSubID, ownerUserID, 0, planID, "owner-renewal-token", "owner-renewal-uuid", + time.Date(2026, 5, 2, 15, 53, 39, 0, time.UTC), + time.Date(2026, 5, 9, 0, 0, 0, 0, time.UTC), + ) + + resp, err := NewRenewalLogic(buildPurchaseCtx(member), svcCtx).Renewal(&types.RenewalOrderRequest{ + UserSubscribeID: ownerSubID, + Payment: paymentID, + Quantity: 1, + }) + require.NoError(t, err) + require.NotNil(t, resp) + + var created modelOrder.Order + require.NoError(t, db.Where("order_no = ?", resp.OrderNo).First(&created).Error) + require.Equal(t, uint8(2), created.Type) + require.False(t, created.IsNew) +} diff --git a/internal/logic/public/order/renewalLogic.go b/internal/logic/public/order/renewalLogic.go index 684b0a9..269d01a 100644 --- a/internal/logic/public/order/renewalLogic.go +++ b/internal/logic/public/order/renewalLogic.go @@ -251,6 +251,12 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order amount exceeds maximum limit") } + isNew, err := l.svcCtx.OrderModel.IsUserEligibleForNewOrder(l.ctx, u.Id) + if err != nil { + l.Errorw("[Renewal] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user order error: %v", err.Error()) + } + // create order orderInfo := order.Order{ UserId: u.Id, @@ -272,6 +278,7 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene SubscribeId: userSubscribe.SubscribeId, SubscribeToken: userSubscribe.Token, AppAccountToken: uuid.New().String(), + IsNew: isNew, } // Database transaction err = l.svcCtx.DB.Transaction(func(db *gorm.DB) error {