diff --git a/controller/redemption.go b/controller/redemption.go
index 76c35bc3..a5a17670 100644
--- a/controller/redemption.go
+++ b/controller/redemption.go
@@ -3,6 +3,7 @@ package controller
 import (
 	"net/http"
 	"strconv"
+	"strings"
 	"unicode/utf8"
 
 	"github.com/QuantumNous/new-api/common"
@@ -19,10 +20,10 @@ func GetAllRedemptions(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
+	enrichRedemptions(redemptions)
 	pageInfo.SetTotal(int(total))
 	pageInfo.SetItems(redemptions)
 	common.ApiSuccess(c, pageInfo)
-	return
 }
 
 func SearchRedemptions(c *gin.Context) {
@@ -33,10 +34,10 @@ func SearchRedemptions(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
+	enrichRedemptions(redemptions)
 	pageInfo.SetTotal(int(total))
 	pageInfo.SetItems(redemptions)
 	common.ApiSuccess(c, pageInfo)
-	return
 }
 
 func GetRedemption(c *gin.Context) {
@@ -50,12 +51,12 @@ func GetRedemption(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
+	enrichRedemption(redemption)
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
 		"data":    redemption,
 	})
-	return
 }
 
 func AddRedemption(c *gin.Context) {
@@ -65,8 +66,7 @@ func AddRedemption(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
-	if utf8.RuneCountInString(redemption.Name) == 0 || utf8.RuneCountInString(redemption.Name) > 20 {
-		common.ApiErrorI18n(c, i18n.MsgRedemptionNameLength)
+	if !validateRedemptionPayload(c, &redemption, true) {
 		return
 	}
 	if redemption.Count <= 0 {
@@ -77,10 +77,6 @@ func AddRedemption(c *gin.Context) {
 		common.ApiErrorI18n(c, i18n.MsgRedemptionCountMax)
 		return
 	}
-	if valid, msg := validateExpiredTime(c, redemption.ExpiredTime); !valid {
-		c.JSON(http.StatusOK, gin.H{"success": false, "message": msg})
-		return
-	}
 	var keys []string
 	for i := 0; i < redemption.Count; i++ {
 		key := common.GetUUID()
@@ -90,6 +86,9 @@ func AddRedemption(c *gin.Context) {
 			Key:         key,
 			CreatedTime: common.GetTimestamp(),
 			Quota:       redemption.Quota,
+			RedeemType:  redemption.RedeemType,
+			PlanId:      redemption.PlanId,
+			SourceNote:  redemption.SourceNote,
 			ExpiredTime: redemption.ExpiredTime,
 		}
 		err = cleanRedemption.Insert()
@@ -109,7 +108,6 @@ func AddRedemption(c *gin.Context) {
 		"message": "",
 		"data":    keys,
 	})
-	return
 }
 
 func DeleteRedemption(c *gin.Context) {
@@ -123,7 +121,6 @@ func DeleteRedemption(c *gin.Context) {
 		"success": true,
 		"message": "",
 	})
-	return
 }
 
 func UpdateRedemption(c *gin.Context) {
@@ -140,13 +137,14 @@ func UpdateRedemption(c *gin.Context) {
 		return
 	}
 	if statusOnly == "" {
-		if valid, msg := validateExpiredTime(c, redemption.ExpiredTime); !valid {
-			c.JSON(http.StatusOK, gin.H{"success": false, "message": msg})
+		if !validateRedemptionPayload(c, &redemption, true) {
 			return
 		}
-		// If you add more fields, please also update redemption.Update()
 		cleanRedemption.Name = redemption.Name
 		cleanRedemption.Quota = redemption.Quota
+		cleanRedemption.RedeemType = redemption.RedeemType
+		cleanRedemption.PlanId = redemption.PlanId
+		cleanRedemption.SourceNote = redemption.SourceNote
 		cleanRedemption.ExpiredTime = redemption.ExpiredTime
 	}
 	if statusOnly != "" {
@@ -157,12 +155,12 @@ func UpdateRedemption(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
+	enrichRedemption(cleanRedemption)
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
 		"data":    cleanRedemption,
 	})
-	return
 }
 
 func DeleteInvalidRedemption(c *gin.Context) {
@@ -176,7 +174,6 @@ func DeleteInvalidRedemption(c *gin.Context) {
 		"message": "",
 		"data":    rows,
 	})
-	return
 }
 
 func validateExpiredTime(c *gin.Context, expired int64) (bool, string) {
@@ -185,3 +182,78 @@ func validateExpiredTime(c *gin.Context, expired int64) (bool, string) {
 	}
 	return true, ""
 }
+
+func validateRedemptionPayload(c *gin.Context, redemption *model.Redemption, requireName bool) bool {
+	if redemption == nil {
+		common.ApiErrorMsg(c, "invalid redemption payload")
+		return false
+	}
+	redemption.Name = strings.TrimSpace(redemption.Name)
+	redemption.SourceNote = strings.TrimSpace(redemption.SourceNote)
+	redemption.RedeemType = model.NormalizeRedemptionType(redemption.RedeemType)
+	if redemption.RedeemType == "" {
+		common.ApiErrorMsg(c, "invalid redeem type")
+		return false
+	}
+	if requireName && (utf8.RuneCountInString(redemption.Name) == 0 || utf8.RuneCountInString(redemption.Name) > 20) {
+		common.ApiErrorI18n(c, i18n.MsgRedemptionNameLength)
+		return false
+	}
+	if valid, msg := validateExpiredTime(c, redemption.ExpiredTime); !valid {
+		c.JSON(http.StatusOK, gin.H{"success": false, "message": msg})
+		return false
+	}
+	switch redemption.RedeemType {
+	case model.RedemptionTypeQuota:
+		if redemption.Quota <= 0 {
+			common.ApiErrorMsg(c, "quota must be greater than 0")
+			return false
+		}
+		redemption.PlanId = 0
+	case model.RedemptionTypeSubscription:
+		if redemption.PlanId <= 0 {
+			common.ApiErrorMsg(c, "plan_id is required")
+			return false
+		}
+		plan, err := model.GetSubscriptionPlanById(redemption.PlanId)
+		if err != nil || plan == nil {
+			common.ApiErrorMsg(c, "subscription plan not found")
+			return false
+		}
+		redemption.Quota = 0
+	}
+	return true
+}
+
+func enrichRedemptions(redemptions []*model.Redemption) {
+	if len(redemptions) == 0 {
+		return
+	}
+	planTitles := make(map[int]string)
+	for _, redemption := range redemptions {
+		if redemption == nil || model.NormalizeRedemptionType(redemption.RedeemType) != model.RedemptionTypeSubscription || redemption.PlanId <= 0 {
+			continue
+		}
+		if _, ok := planTitles[redemption.PlanId]; ok {
+			redemption.PlanTitle = planTitles[redemption.PlanId]
+			continue
+		}
+		plan, err := model.GetSubscriptionPlanById(redemption.PlanId)
+		if err != nil || plan == nil {
+			continue
+		}
+		planTitles[redemption.PlanId] = plan.Title
+		redemption.PlanTitle = plan.Title
+	}
+}
+
+func enrichRedemption(redemption *model.Redemption) {
+	if redemption == nil || model.NormalizeRedemptionType(redemption.RedeemType) != model.RedemptionTypeSubscription || redemption.PlanId <= 0 {
+		return
+	}
+	plan, err := model.GetSubscriptionPlanById(redemption.PlanId)
+	if err != nil || plan == nil {
+		return
+	}
+	redemption.PlanTitle = plan.Title
+}
diff --git a/controller/user.go b/controller/user.go
index 8229d0d2..e2a37f3c 100644
--- a/controller/user.go
+++ b/controller/user.go
@@ -1025,7 +1025,7 @@ func TopUp(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
-	quota, err := model.Redeem(req.Key, id)
+	result, err := model.Redeem(req.Key, id)
 	if err != nil {
 		if errors.Is(err, model.ErrRedeemFailed) {
 			common.ApiErrorI18n(c, i18n.MsgRedeemFailed)
@@ -1037,7 +1037,7 @@ func TopUp(c *gin.Context) {
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
-		"data":    quota,
+		"data":    result,
 	})
 }
 
diff --git a/model/main.go b/model/main.go
index f37cb667..4a4bc6f7 100644
--- a/model/main.go
+++ b/model/main.go
@@ -284,6 +284,9 @@ func migrateDB() error {
 	if err != nil {
 		return err
 	}
+	if err := migrateRedemptionRedeemType(); err != nil {
+		return err
+	}
 	if common.UsingSQLite {
 		if err := ensureSubscriptionPlanTableSQLite(); err != nil {
 			return err
@@ -352,6 +355,9 @@ func migrateDBFast() error {
 			return err
 		}
 	}
+	if err := migrateRedemptionRedeemType(); err != nil {
+		return err
+	}
 	if common.UsingSQLite {
 		if err := ensureSubscriptionPlanTableSQLite(); err != nil {
 			return err
@@ -365,6 +371,18 @@ func migrateDBFast() error {
 	return nil
 }
 
+func migrateRedemptionRedeemType() error {
+	if !DB.Migrator().HasTable(&Redemption{}) {
+		return nil
+	}
+	if !DB.Migrator().HasColumn(&Redemption{}, "redeem_type") {
+		return nil
+	}
+	return DB.Model(&Redemption{}).
+		Where("redeem_type = '' OR redeem_type IS NULL").
+		Update("redeem_type", RedemptionTypeQuota).Error
+}
+
 func migrateLOGDB() error {
 	var err error
 	if err = LOG_DB.AutoMigrate(&Log{}); err != nil {
diff --git a/model/redemption.go b/model/redemption.go
index 378976a3..495b7cda 100644
--- a/model/redemption.go
+++ b/model/redemption.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"strconv"
+	"strings"
 
 	"github.com/QuantumNous/new-api/common"
 	"github.com/QuantumNous/new-api/logger"
@@ -14,6 +15,19 @@ import (
 // ErrRedeemFailed is returned when redemption fails due to database error
 var ErrRedeemFailed = errors.New("redeem.failed")
 
+const (
+	RedemptionTypeQuota        = "quota"
+	RedemptionTypeSubscription = "subscription"
+)
+
+type RedeemResult struct {
+	RedeemType     string `json:"redeem_type"`
+	Quota          int    `json:"quota,omitempty"`
+	PlanId         int    `json:"plan_id,omitempty"`
+	PlanTitle      string `json:"plan_title,omitempty"`
+	SubscriptionId int    `json:"subscription_id,omitempty"`
+}
+
 type Redemption struct {
 	Id           int            `json:"id"`
 	UserId       int            `json:"user_id"`
@@ -21,16 +35,30 @@ type Redemption struct {
 	Status       int            `json:"status" gorm:"default:1"`
 	Name         string         `json:"name" gorm:"index"`
 	Quota        int            `json:"quota" gorm:"default:100"`
+	RedeemType   string         `json:"redeem_type" gorm:"type:varchar(32);not null;default:'quota'"`
+	PlanId       int            `json:"plan_id" gorm:"default:0;index"`
+	SourceNote   string         `json:"source_note" gorm:"type:varchar(255);default:''"`
+	PlanTitle    string         `json:"plan_title" gorm:"-:all"`
 	CreatedTime  int64          `json:"created_time" gorm:"bigint"`
 	RedeemedTime int64          `json:"redeemed_time" gorm:"bigint"`
 	Count        int            `json:"count" gorm:"-:all"` // only for api request
 	UsedUserId   int            `json:"used_user_id"`
 	DeletedAt    gorm.DeletedAt `gorm:"index"`
-	ExpiredTime  int64          `json:"expired_time" gorm:"bigint"` // 过期时间，0 表示不过期
+	ExpiredTime  int64          `json:"expired_time" gorm:"bigint"`
+}
+
+func NormalizeRedemptionType(raw string) string {
+	switch strings.TrimSpace(strings.ToLower(raw)) {
+	case "", RedemptionTypeQuota:
+		return RedemptionTypeQuota
+	case RedemptionTypeSubscription:
+		return RedemptionTypeSubscription
+	default:
+		return ""
+	}
 }
 
 func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
-	// 开始事务
 	tx := DB.Begin()
 	if tx.Error != nil {
 		return nil, 0, tx.Error
@@ -41,21 +69,18 @@ func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total
 		}
 	}()
 
-	// 获取总数
 	err = tx.Model(&Redemption{}).Count(&total).Error
 	if err != nil {
 		tx.Rollback()
 		return nil, 0, err
 	}
 
-	// 获取分页数据
 	err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
 	if err != nil {
 		tx.Rollback()
 		return nil, 0, err
 	}
 
-	// 提交事务
 	if err = tx.Commit().Error; err != nil {
 		return nil, 0, err
 	}
@@ -74,24 +99,20 @@ func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Re
 		}
 	}()
 
-	// Build query based on keyword type
 	query := tx.Model(&Redemption{})
 
-	// Only try to convert to ID if the string represents a valid integer
 	if id, err := strconv.Atoi(keyword); err == nil {
 		query = query.Where("id = ? OR name LIKE ?", id, keyword+"%")
 	} else {
 		query = query.Where("name LIKE ?", keyword+"%")
 	}
 
-	// Get total count
 	err = query.Count(&total).Error
 	if err != nil {
 		tx.Rollback()
 		return nil, 0, err
 	}
 
-	// Get paginated data
 	err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
 	if err != nil {
 		tx.Rollback()
@@ -107,22 +128,25 @@ func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Re
 
 func GetRedemptionById(id int) (*Redemption, error) {
 	if id == 0 {
-		return nil, errors.New("id 为空！")
+		return nil, errors.New("id is empty")
 	}
 	redemption := Redemption{Id: id}
-	var err error = nil
-	err = DB.First(&redemption, "id = ?", id).Error
+	err := DB.First(&redemption, "id = ?", id).Error
 	return &redemption, err
 }
 
-func Redeem(key string, userId int) (quota int, err error) {
+func Redeem(key string, userId int) (result *RedeemResult, err error) {
 	if key == "" {
-		return 0, errors.New("未提供兑换码")
+		return nil, errors.New("redemption code is required")
 	}
 	if userId == 0 {
-		return 0, errors.New("无效的 user id")
+		return nil, errors.New("invalid user id")
 	}
+
 	redemption := &Redemption{}
+	result = &RedeemResult{}
+	logMessage := ""
+	upgradeGroup := ""
 
 	keyCol := "`key`"
 	if common.UsingPostgreSQL {
@@ -132,18 +156,50 @@ func Redeem(key string, userId int) (quota int, err error) {
 	err = DB.Transaction(func(tx *gorm.DB) error {
 		err := tx.Set("gorm:query_option", "FOR UPDATE").Where(keyCol+" = ?", key).First(redemption).Error
 		if err != nil {
-			return errors.New("无效的兑换码")
+			return errors.New("invalid redemption code")
 		}
 		if redemption.Status != common.RedemptionCodeStatusEnabled {
-			return errors.New("该兑换码已被使用")
+			return errors.New("redemption code is unavailable")
 		}
 		if redemption.ExpiredTime != 0 && redemption.ExpiredTime < common.GetTimestamp() {
-			return errors.New("该兑换码已过期")
+			return errors.New("redemption code is expired")
 		}
-		err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error
-		if err != nil {
-			return err
+
+		redeemType := NormalizeRedemptionType(redemption.RedeemType)
+		if redeemType == "" {
+			return errors.New("invalid redemption type")
+		}
+
+		switch redeemType {
+		case RedemptionTypeQuota:
+			err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error
+			if err != nil {
+				return err
+			}
+			result.RedeemType = RedemptionTypeQuota
+			result.Quota = redemption.Quota
+			logMessage = fmt.Sprintf("Redeemed quota %s via redemption code ID %d", logger.LogQuota(redemption.Quota), redemption.Id)
+		case RedemptionTypeSubscription:
+			if redemption.PlanId <= 0 {
+				return errors.New("invalid subscription plan id")
+			}
+			plan, err := getSubscriptionPlanByIdTx(tx, redemption.PlanId)
+			if err != nil {
+				return err
+			}
+			sub, err := CreateUserSubscriptionFromPlanTx(tx, userId, plan, "redeem")
+			if err != nil {
+				return err
+			}
+			result.RedeemType = RedemptionTypeSubscription
+			result.PlanId = plan.Id
+			result.PlanTitle = plan.Title
+			result.SubscriptionId = sub.Id
+			upgradeGroup = strings.TrimSpace(plan.UpgradeGroup)
+			logMessage = fmt.Sprintf("Redeemed subscription plan %s via redemption code ID %d", plan.Title, redemption.Id)
 		}
+
+		redemption.RedeemType = redeemType
 		redemption.RedeemedTime = common.GetTimestamp()
 		redemption.Status = common.RedemptionCodeStatusUsed
 		redemption.UsedUserId = userId
@@ -152,39 +208,42 @@ func Redeem(key string, userId int) (quota int, err error) {
 	})
 	if err != nil {
 		common.SysError("redemption failed: " + err.Error())
-		return 0, ErrRedeemFailed
+		return nil, ErrRedeemFailed
 	}
-	RecordLog(userId, LogTypeTopup, fmt.Sprintf("通过兑换码充值 %s，兑换码ID %d", logger.LogQuota(redemption.Quota), redemption.Id))
-	return redemption.Quota, nil
+
+	if upgradeGroup != "" {
+		_ = UpdateUserGroupCache(userId, upgradeGroup)
+	}
+	if logMessage != "" {
+		RecordLog(userId, LogTypeTopup, logMessage)
+	}
+	return result, nil
 }
 
 func (redemption *Redemption) Insert() error {
-	var err error
-	err = DB.Create(redemption).Error
-	return err
+	redemption.RedeemType = NormalizeRedemptionType(redemption.RedeemType)
+	if redemption.RedeemType == "" {
+		redemption.RedeemType = RedemptionTypeQuota
+	}
+	return DB.Create(redemption).Error
 }
 
 func (redemption *Redemption) SelectUpdate() error {
-	// This can update zero values
 	return DB.Model(redemption).Select("redeemed_time", "status").Updates(redemption).Error
 }
 
 // Update Make sure your token's fields is completed, because this will update non-zero values
 func (redemption *Redemption) Update() error {
-	var err error
-	err = DB.Model(redemption).Select("name", "status", "quota", "redeemed_time", "expired_time").Updates(redemption).Error
-	return err
+	return DB.Model(redemption).Select("name", "status", "quota", "redeem_type", "plan_id", "source_note", "redeemed_time", "expired_time").Updates(redemption).Error
 }
 
 func (redemption *Redemption) Delete() error {
-	var err error
-	err = DB.Delete(redemption).Error
-	return err
+	return DB.Delete(redemption).Error
 }
 
 func DeleteRedemptionById(id int) (err error) {
 	if id == 0 {
-		return errors.New("id 为空！")
+		return errors.New("id is empty")
 	}
 	redemption := Redemption{Id: id}
 	err = DB.Where(redemption).First(&redemption).Error
diff --git a/web/src/components/table/redemptions/RedemptionsColumnDefs.jsx b/web/src/components/table/redemptions/RedemptionsColumnDefs.jsx
index efe1114c..6dcb32fb 100644
--- a/web/src/components/table/redemptions/RedemptionsColumnDefs.jsx
+++ b/web/src/components/table/redemptions/RedemptionsColumnDefs.jsx
@@ -25,11 +25,10 @@ import {
   REDEMPTION_STATUS,
   REDEMPTION_STATUS_MAP,
   REDEMPTION_ACTIONS,
+  REDEMPTION_REDEEM_TYPE,
+  REDEMPTION_REDEEM_TYPE_MAP,
 } from '../../../constants/redemption.constants';
 
-/**
- * Check if redemption code is expired
- */
 export const isExpired = (record) => {
   return (
     record.status === REDEMPTION_STATUS.UNUSED &&
@@ -38,21 +37,15 @@ export const isExpired = (record) => {
   );
 };
 
-/**
- * Render timestamp
- */
 const renderTimestamp = (timestamp) => {
   return <>{timestamp2string(timestamp)}</>;
 };
 
-/**
- * Render redemption code status
- */
 const renderStatus = (status, record, t) => {
   if (isExpired(record)) {
     return (
       <Tag color='orange' shape='circle'>
-        {t('已过期')}
+        {t('Expired')}
       </Tag>
     );
   }
@@ -68,23 +61,50 @@ const renderStatus = (status, record, t) => {
 
   return (
     <Tag color='black' shape='circle'>
-      {t('未知状态')}
+      {t('Unknown')}
+    </Tag>
+  );
+};
+
+const renderRedeemType = (redeemType, t) => {
+  const normalizedType =
+    redeemType === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION
+      ? REDEMPTION_REDEEM_TYPE.SUBSCRIPTION
+      : REDEMPTION_REDEEM_TYPE.QUOTA;
+  const typeConfig = REDEMPTION_REDEEM_TYPE_MAP[normalizedType];
+  return (
+    <Tag color={typeConfig.color} shape='circle'>
+      {t(typeConfig.text)}
+    </Tag>
+  );
+};
+
+const renderRedeemTarget = (record, t) => {
+  const redeemType =
+    record.redeem_type === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION
+      ? REDEMPTION_REDEEM_TYPE.SUBSCRIPTION
+      : REDEMPTION_REDEEM_TYPE.QUOTA;
+  if (redeemType === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION) {
+    const label = record.plan_title || `ID ${record.plan_id || '-'}`;
+    return (
+      <Tag color='light-green' shape='circle'>
+        {`${t('Plan')}: ${label}`}
+      </Tag>
+    );
+  }
+  return (
+    <Tag color='grey' shape='circle'>
+      {renderQuota(parseInt(record.quota))}
     </Tag>
   );
 };
 
-/**
- * Get redemption code table column definitions
- */
 export const getRedemptionsColumns = ({
   t,
   manageRedemption,
   copyText,
   setEditingRedemption,
   setShowEdit,
-  refresh,
-  redemptions,
-  activePage,
   showDeleteRedemptionModal,
 }) => {
   return [
@@ -93,11 +113,16 @@ export const getRedemptionsColumns = ({
       dataIndex: 'id',
     },
     {
-      title: t('名称'),
+      title: t('Name'),
       dataIndex: 'name',
     },
     {
-      title: t('状态'),
+      title: t('Type'),
+      dataIndex: 'redeem_type',
+      render: (text) => <div>{renderRedeemType(text, t)}</div>,
+    },
+    {
+      title: t('Status'),
       dataIndex: 'status',
       key: 'status',
       render: (text, record) => {
@@ -105,37 +130,31 @@ export const getRedemptionsColumns = ({
       },
     },
     {
-      title: t('额度'),
+      title: t('Redeem target'),
       dataIndex: 'quota',
-      render: (text) => {
-        return (
-          <div>
-            <Tag color='grey' shape='circle'>
-              {renderQuota(parseInt(text))}
-            </Tag>
-          </div>
-        );
+      render: (_, record) => {
+        return <div>{renderRedeemTarget(record, t)}</div>;
       },
     },
     {
-      title: t('创建时间'),
+      title: t('Created at'),
       dataIndex: 'created_time',
       render: (text) => {
         return <div>{renderTimestamp(text)}</div>;
       },
     },
     {
-      title: t('过期时间'),
+      title: t('Expires at'),
       dataIndex: 'expired_time',
       render: (text) => {
-        return <div>{text === 0 ? t('永不过期') : renderTimestamp(text)}</div>;
+        return <div>{text === 0 ? t('Never') : renderTimestamp(text)}</div>;
       },
     },
     {
-      title: t('兑换人ID'),
+      title: t('Redeemed by'),
       dataIndex: 'used_user_id',
       render: (text) => {
-        return <div>{text === 0 ? t('无') : text}</div>;
+        return <div>{text === 0 ? t('N/A') : text}</div>;
       },
     },
     {
@@ -144,11 +163,10 @@ export const getRedemptionsColumns = ({
       fixed: 'right',
       width: 205,
       render: (text, record) => {
-        // Create dropdown menu items for more operations
         const moreMenuItems = [
           {
             node: 'item',
-            name: t('删除'),
+            name: t('Delete'),
             type: 'danger',
             onClick: () => {
               showDeleteRedemptionModal(record);
@@ -159,7 +177,7 @@ export const getRedemptionsColumns = ({
         if (record.status === REDEMPTION_STATUS.UNUSED && !isExpired(record)) {
           moreMenuItems.push({
             node: 'item',
-            name: t('禁用'),
+            name: t('Disable'),
             type: 'warning',
             onClick: () => {
               manageRedemption(record.id, REDEMPTION_ACTIONS.DISABLE, record);
@@ -168,7 +186,7 @@ export const getRedemptionsColumns = ({
         } else if (!isExpired(record)) {
           moreMenuItems.push({
             node: 'item',
-            name: t('启用'),
+            name: t('Enable'),
             type: 'secondary',
             onClick: () => {
               manageRedemption(record.id, REDEMPTION_ACTIONS.ENABLE, record);
@@ -185,7 +203,7 @@ export const getRedemptionsColumns = ({
               position='top'
             >
               <Button type='tertiary' size='small'>
-                {t('查看')}
+                {t('View')}
               </Button>
             </Popover>
             <Button
@@ -194,7 +212,7 @@ export const getRedemptionsColumns = ({
                 await copyText(record.key);
               }}
             >
-              {t('复制')}
+              {t('Copy')}
             </Button>
             <Button
               type='tertiary'
@@ -205,7 +223,7 @@ export const getRedemptionsColumns = ({
               }}
               disabled={record.status !== REDEMPTION_STATUS.UNUSED}
             >
-              {t('编辑')}
+              {t('Edit')}
             </Button>
             <Dropdown
               trigger='click'
diff --git a/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx b/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx
index bcde7260..94f5c8d4 100644
--- a/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx
+++ b/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 
-import React, { useEffect, useState, useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import {
   API,
@@ -41,6 +41,7 @@ import {
   Avatar,
   Row,
   Col,
+  Select,
 } from '@douyinfe/semi-ui';
 import {
   IconCreditCard,
@@ -48,6 +49,7 @@ import {
   IconClose,
   IconGift,
 } from '@douyinfe/semi-icons';
+import { REDEMPTION_REDEEM_TYPE } from '../../../../constants/redemption.constants';
 
 const { Text, Title } = Typography;
 
@@ -55,6 +57,8 @@ const EditRedemptionModal = (props) => {
   const { t } = useTranslation();
   const isEdit = props.editingRedemption.id !== undefined;
   const [loading, setLoading] = useState(isEdit);
+  const [plansLoading, setPlansLoading] = useState(false);
+  const [subscriptionPlans, setSubscriptionPlans] = useState([]);
   const isMobile = useIsMobile();
   const formApiRef = useRef(null);
 
@@ -63,48 +67,92 @@ const EditRedemptionModal = (props) => {
     quota: 100000,
     count: 1,
     expired_time: null,
+    redeem_type: REDEMPTION_REDEEM_TYPE.QUOTA,
+    plan_id: undefined,
   });
 
   const handleCancel = () => {
     props.handleClose();
   };
 
+  const loadSubscriptionPlans = async () => {
+    setPlansLoading(true);
+    try {
+      const res = await API.get('/api/subscription/admin/plans');
+      if (res.data?.success) {
+        const plans = (res.data?.data || [])
+          .map((item) => item?.plan)
+          .filter(Boolean);
+        setSubscriptionPlans(plans);
+      } else {
+        showError(res.data?.message || t('Failed to load subscription plans'));
+      }
+    } catch (error) {
+      showError(error.message);
+    }
+    setPlansLoading(false);
+  };
+
   const loadRedemption = async () => {
     setLoading(true);
-    let res = await API.get(`/api/redemption/${props.editingRedemption.id}`);
+    const res = await API.get(`/api/redemption/${props.editingRedemption.id}`);
     const { success, message, data } = res.data;
     if (success) {
-      if (data.expired_time === 0) {
-        data.expired_time = null;
+      const nextValues = {
+        ...getInitValues(),
+        ...data,
+      };
+      if (nextValues.expired_time === 0) {
+        nextValues.expired_time = null;
       } else {
-        data.expired_time = new Date(data.expired_time * 1000);
+        nextValues.expired_time = new Date(nextValues.expired_time * 1000);
       }
-      formApiRef.current?.setValues({ ...getInitValues(), ...data });
+      if (!nextValues.plan_id) {
+        nextValues.plan_id = undefined;
+      }
+      nextValues.redeem_type =
+        nextValues.redeem_type || REDEMPTION_REDEEM_TYPE.QUOTA;
+      formApiRef.current?.setValues(nextValues);
     } else {
       showError(message);
     }
     setLoading(false);
   };
 
+  useEffect(() => {
+    if (props.visiable) {
+      loadSubscriptionPlans();
+    }
+  }, [props.visiable]);
+
   useEffect(() => {
     if (formApiRef.current) {
       if (isEdit) {
         loadRedemption();
       } else {
         formApiRef.current.setValues(getInitValues());
+        setLoading(false);
       }
     }
   }, [props.editingRedemption.id]);
 
   const submit = async (values) => {
     let name = values.name;
-    if (!isEdit && (!name || name === '')) {
-      name = renderQuota(values.quota);
+    if (!name || name === '') {
+      if (values.redeem_type === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION) {
+        const selectedPlan = subscriptionPlans.find(
+          (plan) => plan.id === Number(values.plan_id),
+        );
+        name = selectedPlan?.title || t('Subscription redemption');
+      } else {
+        name = renderQuota(values.quota);
+      }
     }
     setLoading(true);
-    let localInputs = { ...values };
-    localInputs.count = parseInt(localInputs.count) || 0;
-    localInputs.quota = parseInt(localInputs.quota) || 0;
+    const localInputs = { ...values };
+    localInputs.count = parseInt(localInputs.count, 10) || 0;
+    localInputs.quota = parseInt(localInputs.quota, 10) || 0;
+    localInputs.plan_id = parseInt(localInputs.plan_id, 10) || 0;
     localInputs.name = name;
     if (!localInputs.expired_time) {
       localInputs.expired_time = 0;
@@ -113,11 +161,16 @@ const EditRedemptionModal = (props) => {
         localInputs.expired_time.getTime() / 1000,
       );
     }
+    if (localInputs.redeem_type === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION) {
+      localInputs.quota = 0;
+    } else {
+      localInputs.plan_id = 0;
+    }
     let res;
     if (isEdit) {
       res = await API.put(`/api/redemption/`, {
         ...localInputs,
-        id: parseInt(props.editingRedemption.id),
+        id: parseInt(props.editingRedemption.id, 10),
       });
     } else {
       res = await API.post(`/api/redemption/`, {
@@ -127,11 +180,11 @@ const EditRedemptionModal = (props) => {
     const { success, message, data } = res.data;
     if (success) {
       if (isEdit) {
-        showSuccess(t('兑换码更新成功！'));
+        showSuccess(t('Redemption updated'));
         props.refresh();
         props.handleClose();
       } else {
-        showSuccess(t('兑换码创建成功！'));
+        showSuccess(t('Redemption created'));
         props.refresh();
         formApiRef.current?.setValues(getInitValues());
         props.handleClose();
@@ -145,11 +198,11 @@ const EditRedemptionModal = (props) => {
         text += data[i] + '\n';
       }
       Modal.confirm({
-        title: t('兑换码创建成功'),
+        title: t('Redemption created'),
         content: (
           <div>
-            <p>{t('兑换码创建成功，是否下载兑换码？')}</p>
-            <p>{t('兑换码将以文本文件的形式下载，文件名为兑换码的名称。')}</p>
+            <p>{t('Download the generated redemption codes?')}</p>
+            <p>{t('Codes will be downloaded as a text file named after this redemption.')}</p>
           </div>
         ),
         onOk: () => {
@@ -168,15 +221,15 @@ const EditRedemptionModal = (props) => {
           <Space>
             {isEdit ? (
               <Tag color='blue' shape='circle'>
-                {t('更新')}
+                {t('Edit')}
               </Tag>
             ) : (
               <Tag color='green' shape='circle'>
-                {t('新建')}
+                {t('New')}
               </Tag>
             )}
             <Title heading={4} className='m-0'>
-              {isEdit ? t('更新兑换码信息') : t('创建新的兑换码')}
+              {isEdit ? t('Update redemption') : t('Create redemption')}
             </Title>
           </Space>
         }
@@ -192,7 +245,7 @@ const EditRedemptionModal = (props) => {
                 icon={<IconSave />}
                 loading={loading}
               >
-                {t('提交')}
+                {t('Submit')}
               </Button>
               <Button
                 theme='light'
@@ -200,7 +253,7 @@ const EditRedemptionModal = (props) => {
                 onClick={handleCancel}
                 icon={<IconClose />}
               >
-                {t('取消')}
+                {t('Cancel')}
               </Button>
             </Space>
           </div>
@@ -214,135 +267,188 @@ const EditRedemptionModal = (props) => {
             getFormApi={(api) => (formApiRef.current = api)}
             onSubmit={submit}
           >
-            {({ values }) => (
-              <div className='p-2'>
-                <Card className='!rounded-2xl shadow-sm border-0 mb-6'>
-                  {/* Header: Basic Info */}
-                  <div className='flex items-center mb-2'>
-                    <Avatar
-                      size='small'
-                      color='blue'
-                      className='mr-2 shadow-md'
-                    >
-                      <IconGift size={16} />
-                    </Avatar>
-                    <div>
-                      <Text className='text-lg font-medium'>
-                        {t('基本信息')}
-                      </Text>
-                      <div className='text-xs text-gray-600'>
-                        {t('设置兑换码的基本信息')}
-                      </div>
-                    </div>
-                  </div>
-
-                  <Row gutter={12}>
-                    <Col span={24}>
-                      <Form.Input
-                        field='name'
-                        label={t('名称')}
-                        placeholder={t('请输入名称')}
-                        style={{ width: '100%' }}
-                        rules={
-                          !isEdit
-                            ? []
-                            : [{ required: true, message: t('请输入名称') }]
-                        }
-                        showClear
-                      />
-                    </Col>
-                    <Col span={24}>
-                      <Form.DatePicker
-                        field='expired_time'
-                        label={t('过期时间')}
-                        type='dateTime'
-                        placeholder={t('选择过期时间（可选，留空为永久）')}
-                        style={{ width: '100%' }}
-                        showClear
-                      />
-                    </Col>
-                  </Row>
-                </Card>
-
-                <Card className='!rounded-2xl shadow-sm border-0'>
-                  {/* Header: Quota Settings */}
-                  <div className='flex items-center mb-2'>
-                    <Avatar
-                      size='small'
-                      color='green'
-                      className='mr-2 shadow-md'
-                    >
-                      <IconCreditCard size={16} />
-                    </Avatar>
-                    <div>
-                      <Text className='text-lg font-medium'>
-                        {t('额度设置')}
-                      </Text>
-                      <div className='text-xs text-gray-600'>
-                        {t('设置兑换码的额度和数量')}
+            {({ values }) => {
+              const redeemType =
+                values.redeem_type || REDEMPTION_REDEEM_TYPE.QUOTA;
+              return (
+                <div className='p-2'>
+                  <Card className='!rounded-2xl shadow-sm border-0 mb-6'>
+                    <div className='flex items-center mb-2'>
+                      <Avatar
+                        size='small'
+                        color='blue'
+                        className='mr-2 shadow-md'
+                      >
+                        <IconGift size={16} />
+                      </Avatar>
+                      <div>
+                        <Text className='text-lg font-medium'>
+                          {t('Basic info')}
+                        </Text>
+                        <div className='text-xs text-gray-600'>
+                          {t('Set the redemption name, type, and expiration')}
+                        </div>
                       </div>
                     </div>
-                  </div>
 
-                  <Row gutter={12}>
-                    <Col span={12}>
-                      <Form.AutoComplete
-                        field='quota'
-                        label={t('额度')}
-                        placeholder={t('请输入额度')}
-                        style={{ width: '100%' }}
-                        type='number'
-                        rules={[
-                          { required: true, message: t('请输入额度') },
-                          {
-                            validator: (rule, v) => {
-                              const num = parseInt(v, 10);
-                              return num > 0
-                                ? Promise.resolve()
-                                : Promise.reject(t('额度必须大于0'));
-                            },
-                          },
-                        ]}
-                        extraText={renderQuotaWithPrompt(
-                          Number(values.quota) || 0,
-                        )}
-                        data={[
-                          { value: 500000, label: '1$' },
-                          { value: 5000000, label: '10$' },
-                          { value: 25000000, label: '50$' },
-                          { value: 50000000, label: '100$' },
-                          { value: 250000000, label: '500$' },
-                          { value: 500000000, label: '1000$' },
-                        ]}
-                        showClear
-                      />
-                    </Col>
-                    {!isEdit && (
+                    <Row gutter={12}>
+                      <Col span={24}>
+                        <Form.Input
+                          field='name'
+                          label={t('Name')}
+                          placeholder={t('Enter a name')}
+                          style={{ width: '100%' }}
+                          rules={
+                            !isEdit
+                              ? []
+                              : [{ required: true, message: t('Please enter a name') }]
+                          }
+                          showClear
+                        />
+                      </Col>
                       <Col span={12}>
-                        <Form.InputNumber
-                          field='count'
-                          label={t('生成数量')}
-                          min={1}
-                          rules={[
-                            { required: true, message: t('请输入生成数量') },
-                            {
-                              validator: (rule, v) => {
-                                const num = parseInt(v, 10);
-                                return num > 0
-                                  ? Promise.resolve()
-                                  : Promise.reject(t('生成数量必须大于0'));
-                              },
-                            },
-                          ]}
+                        <Form.Select
+                          field='redeem_type'
+                          label={t('Redeem type')}
+                          style={{ width: '100%' }}
+                        >
+                          <Select.Option
+                            value={REDEMPTION_REDEEM_TYPE.QUOTA}
+                          >
+                            {t('Quota')}
+                          </Select.Option>
+                          <Select.Option
+                            value={REDEMPTION_REDEEM_TYPE.SUBSCRIPTION}
+                          >
+                            {t('Subscription')}
+                          </Select.Option>
+                        </Form.Select>
+                      </Col>
+                      <Col span={12}>
+                        <Form.DatePicker
+                          field='expired_time'
+                          label={t('Expires at')}
+                          type='dateTime'
+                          placeholder={t('Leave empty for no expiration')}
                           style={{ width: '100%' }}
                           showClear
                         />
                       </Col>
-                    )}
-                  </Row>
-                </Card>
-              </div>
-            )}
+                      {redeemType === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION && (
+                        <Col span={24}>
+                          <Form.Select
+                            field='plan_id'
+                            label={t('Subscription plan')}
+                            loading={plansLoading}
+                            placeholder={t('Select a subscription plan')}
+                            style={{ width: '100%' }}
+                            rules={[
+                              {
+                                required: true,
+                                message: t('Please select a subscription plan'),
+                              },
+                            ]}
+                          >
+                            {(subscriptionPlans || []).map((plan) => (
+                              <Select.Option key={plan.id} value={plan.id}>
+                                {plan.title}
+                              </Select.Option>
+                            ))}
+                          </Form.Select>
+                        </Col>
+                      )}
+                    </Row>
+                  </Card>
+
+                  <Card className='!rounded-2xl shadow-sm border-0'>
+                    <div className='flex items-center mb-2'>
+                      <Avatar
+                        size='small'
+                        color='green'
+                        className='mr-2 shadow-md'
+                      >
+                        <IconCreditCard size={16} />
+                      </Avatar>
+                      <div>
+                        <Text className='text-lg font-medium'>
+                          {t('Redeem settings')}
+                        </Text>
+                        <div className='text-xs text-gray-600'>
+                          {redeemType === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION
+                            ? t('Choose a plan and generation count')
+                            : t('Set quota amount and generation count')}
+                        </div>
+                      </div>
+                    </div>
+
+                    <Row gutter={12}>
+                      {redeemType === REDEMPTION_REDEEM_TYPE.QUOTA && (
+                        <Col span={!isEdit ? 12 : 24}>
+                          <Form.AutoComplete
+                            field='quota'
+                            label={t('Quota')}
+                            placeholder={t('Enter quota')}
+                            style={{ width: '100%' }}
+                            type='number'
+                            rules={[
+                              { required: true, message: t('Please enter quota') },
+                              {
+                                validator: (rule, v) => {
+                                  const num = parseInt(v, 10);
+                                  return num > 0
+                                    ? Promise.resolve()
+                                    : Promise.reject(t('Quota must be greater than 0'));
+                                },
+                              },
+                            ]}
+                            extraText={renderQuotaWithPrompt(
+                              Number(values.quota) || 0,
+                            )}
+                            data={[
+                              { value: 500000, label: '1$' },
+                              { value: 5000000, label: '10$' },
+                              { value: 25000000, label: '50$' },
+                              { value: 50000000, label: '100$' },
+                              { value: 250000000, label: '500$' },
+                              { value: 500000000, label: '1000$' },
+                            ]}
+                            showClear
+                          />
+                        </Col>
+                      )}
+                      {!isEdit && (
+                        <Col
+                          span={
+                            redeemType === REDEMPTION_REDEEM_TYPE.SUBSCRIPTION
+                              ? 24
+                              : 12
+                          }
+                        >
+                          <Form.InputNumber
+                            field='count'
+                            label={t('Count')}
+                            min={1}
+                            rules={[
+                              { required: true, message: t('Please enter count') },
+                              {
+                                validator: (rule, v) => {
+                                  const num = parseInt(v, 10);
+                                  return num > 0
+                                    ? Promise.resolve()
+                                    : Promise.reject(t('Count must be greater than 0'));
+                                },
+                              },
+                            ]}
+                            style={{ width: '100%' }}
+                            showClear
+                          />
+                        </Col>
+                      )}
+                    </Row>
+                  </Card>
+                </div>
+              );
+            }}
           </Form>
         </Spin>
       </SideSheet>
diff --git a/web/src/components/topup/index.jsx b/web/src/components/topup/index.jsx
index 0348e3c8..ed9bc79b 100644
--- a/web/src/components/topup/index.jsx
+++ b/web/src/components/topup/index.jsx
@@ -112,7 +112,7 @@ const TopUp = () => {
     discount: {},
   });
 
-  const topUp = async () => {
+  const legacyTopUp = async () => {
     if (redemptionCode === '') {
       showInfo(t('请输入兑换码！'));
       return;
@@ -148,6 +148,96 @@ const TopUp = () => {
     }
   };
 
+  const topUpLegacy2 = async () => {
+    if (redemptionCode === '') {
+      showInfo(t('请输入兑换码！'));
+      return;
+    }
+    setIsSubmitting(true);
+    try {
+      const res = await API.post('/api/user/topup', {
+        key: redemptionCode,
+      });
+      const { success, message, data } = res.data;
+      if (success) {
+        showSuccess(t('兑换成功！'));
+        if (data?.redeem_type === 'subscription') {
+          Modal.success({
+            title: t('兑换成功'),
+            content: `${t('成功兑换订阅套餐：')}${data.plan_title || data.plan_id}`,
+            centered: true,
+          });
+          await getSubscriptionSelf();
+        } else {
+          Modal.success({
+            title: t('兑换成功'),
+            content: `${t('成功兑换额度：')}${renderQuota(data?.quota || 0)}`,
+            centered: true,
+          });
+          if (userState.user) {
+            const updatedUser = {
+              ...userState.user,
+              quota: userState.user.quota + (data?.quota || 0),
+            };
+            userDispatch({ type: 'login', payload: updatedUser });
+          }
+        }
+        setRedemptionCode('');
+      } else {
+        showError(message);
+      }
+    } catch (err) {
+      showError(t('请求失败'));
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  const topUp = async () => {
+    if (redemptionCode === '') {
+      showInfo(t('Please enter a redemption code'));
+      return;
+    }
+    setIsSubmitting(true);
+    try {
+      const res = await API.post('/api/user/topup', {
+        key: redemptionCode,
+      });
+      const { success, message, data } = res.data;
+      if (success) {
+        showSuccess(t('Redemption successful'));
+        if (data?.redeem_type === 'subscription') {
+          Modal.success({
+            title: t('Redemption successful'),
+            content: `${t('Subscription plan redeemed: ')}${data.plan_title || data.plan_id}`,
+            centered: true,
+          });
+          await getSubscriptionSelf();
+        } else {
+          Modal.success({
+            title: t('Redemption successful'),
+            content: `${t('Quota redeemed: ')}${renderQuota(data?.quota || 0)}`,
+            centered: true,
+          });
+          if (userState.user) {
+            const updatedUser = {
+              ...userState.user,
+              quota: userState.user.quota + (data?.quota || 0),
+            };
+            userDispatch({ type: 'login', payload: updatedUser });
+          }
+        }
+        setRedemptionCode('');
+      } else {
+        showError(message);
+      }
+    } catch (err) {
+      showError(t('Request failed'));
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
   const openTopUpLink = () => {
     if (!topUpLink) {
       showError(t('超级管理员未设置充值链接！'));
diff --git a/web/src/constants/redemption.constants.js b/web/src/constants/redemption.constants.js
index baba96ee..a453896f 100644
--- a/web/src/constants/redemption.constants.js
+++ b/web/src/constants/redemption.constants.js
@@ -23,6 +23,22 @@ export const REDEMPTION_STATUS = {
   USED: 3, // Used
 };
 
+export const REDEMPTION_REDEEM_TYPE = {
+  QUOTA: 'quota',
+  SUBSCRIPTION: 'subscription',
+};
+
+export const REDEMPTION_REDEEM_TYPE_MAP = {
+  [REDEMPTION_REDEEM_TYPE.QUOTA]: {
+    color: 'blue',
+    text: 'Quota',
+  },
+  [REDEMPTION_REDEEM_TYPE.SUBSCRIPTION]: {
+    color: 'green',
+    text: 'Subscription',
+  },
+};
+
 // Redemption code status display mapping
 export const REDEMPTION_STATUS_MAP = {
   [REDEMPTION_STATUS.UNUSED]: {
