mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 13:18:18 +08:00
feat(payment): 订阅套餐支持币种标注,区分 $ 符号对应币种
后台配置与用户端展示的套餐价格统一使用 $ 符号,而实际扣款币种随 支付渠道配置漂移(如 Stripe 配置 NZD 时按新西兰元扣款),用户易 误认为美元。本次为套餐新增可选的展示性币种标注: - SubscriptionPlan 新增 currency 字段(ISO 4217 三字母码,默认空) - 空值不展示任何标注,存量套餐行为完全不变 - 非空值复用 payment.NormalizePaymentCurrency 校验并规范化为大写 - 后台套餐编辑弹窗新增可选币种输入,列表价格列展示币种小字 - 用户端套餐卡片价格与划线原价旁展示币种小字 - 纯展示性质,不影响任何支付/扣款逻辑 Closes #4315 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
cf6aa1a5c3
commit
2bbdd47002
@@ -1447,6 +1447,7 @@ var (
|
||||
{Name: "description", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "price", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
|
||||
{Name: "original_price", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
|
||||
{Name: "currency", Type: field.TypeString, Size: 3, Default: ""},
|
||||
{Name: "validity_days", Type: field.TypeInt, Default: 30},
|
||||
{Name: "validity_unit", Type: field.TypeString, Size: 10, Default: "day"},
|
||||
{Name: "features", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
|
||||
@@ -1470,7 +1471,7 @@ var (
|
||||
{
|
||||
Name: "subscriptionplan_for_sale",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SubscriptionPlansColumns[10]},
|
||||
Columns: []*schema.Column{SubscriptionPlansColumns[11]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+55
-1
@@ -38152,6 +38152,7 @@ type SubscriptionPlanMutation struct {
|
||||
addprice *float64
|
||||
original_price *float64
|
||||
addoriginal_price *float64
|
||||
currency *string
|
||||
validity_days *int
|
||||
addvalidity_days *int
|
||||
validity_unit *string
|
||||
@@ -38520,6 +38521,42 @@ func (m *SubscriptionPlanMutation) ResetOriginalPrice() {
|
||||
delete(m.clearedFields, subscriptionplan.FieldOriginalPrice)
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (m *SubscriptionPlanMutation) SetCurrency(s string) {
|
||||
m.currency = &s
|
||||
}
|
||||
|
||||
// Currency returns the value of the "currency" field in the mutation.
|
||||
func (m *SubscriptionPlanMutation) Currency() (r string, exists bool) {
|
||||
v := m.currency
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldCurrency returns the old "currency" field's value of the SubscriptionPlan entity.
|
||||
// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *SubscriptionPlanMutation) OldCurrency(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldCurrency is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldCurrency requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldCurrency: %w", err)
|
||||
}
|
||||
return oldValue.Currency, nil
|
||||
}
|
||||
|
||||
// ResetCurrency resets all changes to the "currency" field.
|
||||
func (m *SubscriptionPlanMutation) ResetCurrency() {
|
||||
m.currency = nil
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (m *SubscriptionPlanMutation) SetValidityDays(i int) {
|
||||
m.validity_days = &i
|
||||
@@ -38882,7 +38919,7 @@ func (m *SubscriptionPlanMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *SubscriptionPlanMutation) Fields() []string {
|
||||
fields := make([]string, 0, 13)
|
||||
fields := make([]string, 0, 14)
|
||||
if m.group_id != nil {
|
||||
fields = append(fields, subscriptionplan.FieldGroupID)
|
||||
}
|
||||
@@ -38898,6 +38935,9 @@ func (m *SubscriptionPlanMutation) Fields() []string {
|
||||
if m.original_price != nil {
|
||||
fields = append(fields, subscriptionplan.FieldOriginalPrice)
|
||||
}
|
||||
if m.currency != nil {
|
||||
fields = append(fields, subscriptionplan.FieldCurrency)
|
||||
}
|
||||
if m.validity_days != nil {
|
||||
fields = append(fields, subscriptionplan.FieldValidityDays)
|
||||
}
|
||||
@@ -38940,6 +38980,8 @@ func (m *SubscriptionPlanMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.Price()
|
||||
case subscriptionplan.FieldOriginalPrice:
|
||||
return m.OriginalPrice()
|
||||
case subscriptionplan.FieldCurrency:
|
||||
return m.Currency()
|
||||
case subscriptionplan.FieldValidityDays:
|
||||
return m.ValidityDays()
|
||||
case subscriptionplan.FieldValidityUnit:
|
||||
@@ -38975,6 +39017,8 @@ func (m *SubscriptionPlanMutation) OldField(ctx context.Context, name string) (e
|
||||
return m.OldPrice(ctx)
|
||||
case subscriptionplan.FieldOriginalPrice:
|
||||
return m.OldOriginalPrice(ctx)
|
||||
case subscriptionplan.FieldCurrency:
|
||||
return m.OldCurrency(ctx)
|
||||
case subscriptionplan.FieldValidityDays:
|
||||
return m.OldValidityDays(ctx)
|
||||
case subscriptionplan.FieldValidityUnit:
|
||||
@@ -39035,6 +39079,13 @@ func (m *SubscriptionPlanMutation) SetField(name string, value ent.Value) error
|
||||
}
|
||||
m.SetOriginalPrice(v)
|
||||
return nil
|
||||
case subscriptionplan.FieldCurrency:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetCurrency(v)
|
||||
return nil
|
||||
case subscriptionplan.FieldValidityDays:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
@@ -39227,6 +39278,9 @@ func (m *SubscriptionPlanMutation) ResetField(name string) error {
|
||||
case subscriptionplan.FieldOriginalPrice:
|
||||
m.ResetOriginalPrice()
|
||||
return nil
|
||||
case subscriptionplan.FieldCurrency:
|
||||
m.ResetCurrency()
|
||||
return nil
|
||||
case subscriptionplan.FieldValidityDays:
|
||||
m.ResetValidityDays()
|
||||
return nil
|
||||
|
||||
@@ -1718,40 +1718,46 @@ func init() {
|
||||
subscriptionplanDescDescription := subscriptionplanFields[2].Descriptor()
|
||||
// subscriptionplan.DefaultDescription holds the default value on creation for the description field.
|
||||
subscriptionplan.DefaultDescription = subscriptionplanDescDescription.Default.(string)
|
||||
// subscriptionplanDescCurrency is the schema descriptor for currency field.
|
||||
subscriptionplanDescCurrency := subscriptionplanFields[5].Descriptor()
|
||||
// subscriptionplan.DefaultCurrency holds the default value on creation for the currency field.
|
||||
subscriptionplan.DefaultCurrency = subscriptionplanDescCurrency.Default.(string)
|
||||
// subscriptionplan.CurrencyValidator is a validator for the "currency" field. It is called by the builders before save.
|
||||
subscriptionplan.CurrencyValidator = subscriptionplanDescCurrency.Validators[0].(func(string) error)
|
||||
// subscriptionplanDescValidityDays is the schema descriptor for validity_days field.
|
||||
subscriptionplanDescValidityDays := subscriptionplanFields[5].Descriptor()
|
||||
subscriptionplanDescValidityDays := subscriptionplanFields[6].Descriptor()
|
||||
// subscriptionplan.DefaultValidityDays holds the default value on creation for the validity_days field.
|
||||
subscriptionplan.DefaultValidityDays = subscriptionplanDescValidityDays.Default.(int)
|
||||
// subscriptionplanDescValidityUnit is the schema descriptor for validity_unit field.
|
||||
subscriptionplanDescValidityUnit := subscriptionplanFields[6].Descriptor()
|
||||
subscriptionplanDescValidityUnit := subscriptionplanFields[7].Descriptor()
|
||||
// subscriptionplan.DefaultValidityUnit holds the default value on creation for the validity_unit field.
|
||||
subscriptionplan.DefaultValidityUnit = subscriptionplanDescValidityUnit.Default.(string)
|
||||
// subscriptionplan.ValidityUnitValidator is a validator for the "validity_unit" field. It is called by the builders before save.
|
||||
subscriptionplan.ValidityUnitValidator = subscriptionplanDescValidityUnit.Validators[0].(func(string) error)
|
||||
// subscriptionplanDescFeatures is the schema descriptor for features field.
|
||||
subscriptionplanDescFeatures := subscriptionplanFields[7].Descriptor()
|
||||
subscriptionplanDescFeatures := subscriptionplanFields[8].Descriptor()
|
||||
// subscriptionplan.DefaultFeatures holds the default value on creation for the features field.
|
||||
subscriptionplan.DefaultFeatures = subscriptionplanDescFeatures.Default.(string)
|
||||
// subscriptionplanDescProductName is the schema descriptor for product_name field.
|
||||
subscriptionplanDescProductName := subscriptionplanFields[8].Descriptor()
|
||||
subscriptionplanDescProductName := subscriptionplanFields[9].Descriptor()
|
||||
// subscriptionplan.DefaultProductName holds the default value on creation for the product_name field.
|
||||
subscriptionplan.DefaultProductName = subscriptionplanDescProductName.Default.(string)
|
||||
// subscriptionplan.ProductNameValidator is a validator for the "product_name" field. It is called by the builders before save.
|
||||
subscriptionplan.ProductNameValidator = subscriptionplanDescProductName.Validators[0].(func(string) error)
|
||||
// subscriptionplanDescForSale is the schema descriptor for for_sale field.
|
||||
subscriptionplanDescForSale := subscriptionplanFields[9].Descriptor()
|
||||
subscriptionplanDescForSale := subscriptionplanFields[10].Descriptor()
|
||||
// subscriptionplan.DefaultForSale holds the default value on creation for the for_sale field.
|
||||
subscriptionplan.DefaultForSale = subscriptionplanDescForSale.Default.(bool)
|
||||
// subscriptionplanDescSortOrder is the schema descriptor for sort_order field.
|
||||
subscriptionplanDescSortOrder := subscriptionplanFields[10].Descriptor()
|
||||
subscriptionplanDescSortOrder := subscriptionplanFields[11].Descriptor()
|
||||
// subscriptionplan.DefaultSortOrder holds the default value on creation for the sort_order field.
|
||||
subscriptionplan.DefaultSortOrder = subscriptionplanDescSortOrder.Default.(int)
|
||||
// subscriptionplanDescCreatedAt is the schema descriptor for created_at field.
|
||||
subscriptionplanDescCreatedAt := subscriptionplanFields[11].Descriptor()
|
||||
subscriptionplanDescCreatedAt := subscriptionplanFields[12].Descriptor()
|
||||
// subscriptionplan.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
subscriptionplan.DefaultCreatedAt = subscriptionplanDescCreatedAt.Default.(func() time.Time)
|
||||
// subscriptionplanDescUpdatedAt is the schema descriptor for updated_at field.
|
||||
subscriptionplanDescUpdatedAt := subscriptionplanFields[12].Descriptor()
|
||||
subscriptionplanDescUpdatedAt := subscriptionplanFields[13].Descriptor()
|
||||
// subscriptionplan.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||
subscriptionplan.DefaultUpdatedAt = subscriptionplanDescUpdatedAt.Default.(func() time.Time)
|
||||
// subscriptionplan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||
|
||||
@@ -43,6 +43,9 @@ func (SubscriptionPlan) Fields() []ent.Field {
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}).
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.String("currency").
|
||||
MaxLen(3).
|
||||
Default(""),
|
||||
field.Int("validity_days").
|
||||
Default(30),
|
||||
field.String("validity_unit").
|
||||
|
||||
@@ -27,6 +27,8 @@ type SubscriptionPlan struct {
|
||||
Price float64 `json:"price,omitempty"`
|
||||
// OriginalPrice holds the value of the "original_price" field.
|
||||
OriginalPrice *float64 `json:"original_price,omitempty"`
|
||||
// Currency holds the value of the "currency" field.
|
||||
Currency string `json:"currency,omitempty"`
|
||||
// ValidityDays holds the value of the "validity_days" field.
|
||||
ValidityDays int `json:"validity_days,omitempty"`
|
||||
// ValidityUnit holds the value of the "validity_unit" field.
|
||||
@@ -57,7 +59,7 @@ func (*SubscriptionPlan) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case subscriptionplan.FieldID, subscriptionplan.FieldGroupID, subscriptionplan.FieldValidityDays, subscriptionplan.FieldSortOrder:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case subscriptionplan.FieldName, subscriptionplan.FieldDescription, subscriptionplan.FieldValidityUnit, subscriptionplan.FieldFeatures, subscriptionplan.FieldProductName:
|
||||
case subscriptionplan.FieldName, subscriptionplan.FieldDescription, subscriptionplan.FieldCurrency, subscriptionplan.FieldValidityUnit, subscriptionplan.FieldFeatures, subscriptionplan.FieldProductName:
|
||||
values[i] = new(sql.NullString)
|
||||
case subscriptionplan.FieldCreatedAt, subscriptionplan.FieldUpdatedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
@@ -113,6 +115,12 @@ func (_m *SubscriptionPlan) assignValues(columns []string, values []any) error {
|
||||
_m.OriginalPrice = new(float64)
|
||||
*_m.OriginalPrice = value.Float64
|
||||
}
|
||||
case subscriptionplan.FieldCurrency:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field currency", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Currency = value.String
|
||||
}
|
||||
case subscriptionplan.FieldValidityDays:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field validity_days", values[i])
|
||||
@@ -214,6 +222,9 @@ func (_m *SubscriptionPlan) String() string {
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("currency=")
|
||||
builder.WriteString(_m.Currency)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("validity_days=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ValidityDays))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -23,6 +23,8 @@ const (
|
||||
FieldPrice = "price"
|
||||
// FieldOriginalPrice holds the string denoting the original_price field in the database.
|
||||
FieldOriginalPrice = "original_price"
|
||||
// FieldCurrency holds the string denoting the currency field in the database.
|
||||
FieldCurrency = "currency"
|
||||
// FieldValidityDays holds the string denoting the validity_days field in the database.
|
||||
FieldValidityDays = "validity_days"
|
||||
// FieldValidityUnit holds the string denoting the validity_unit field in the database.
|
||||
@@ -51,6 +53,7 @@ var Columns = []string{
|
||||
FieldDescription,
|
||||
FieldPrice,
|
||||
FieldOriginalPrice,
|
||||
FieldCurrency,
|
||||
FieldValidityDays,
|
||||
FieldValidityUnit,
|
||||
FieldFeatures,
|
||||
@@ -76,6 +79,10 @@ var (
|
||||
NameValidator func(string) error
|
||||
// DefaultDescription holds the default value on creation for the "description" field.
|
||||
DefaultDescription string
|
||||
// DefaultCurrency holds the default value on creation for the "currency" field.
|
||||
DefaultCurrency string
|
||||
// CurrencyValidator is a validator for the "currency" field. It is called by the builders before save.
|
||||
CurrencyValidator func(string) error
|
||||
// DefaultValidityDays holds the default value on creation for the "validity_days" field.
|
||||
DefaultValidityDays int
|
||||
// DefaultValidityUnit holds the default value on creation for the "validity_unit" field.
|
||||
@@ -133,6 +140,11 @@ func ByOriginalPrice(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldOriginalPrice, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCurrency orders the results by the currency field.
|
||||
func ByCurrency(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCurrency, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByValidityDays orders the results by the validity_days field.
|
||||
func ByValidityDays(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldValidityDays, opts...).ToFunc()
|
||||
|
||||
@@ -79,6 +79,11 @@ func OriginalPrice(v float64) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldEQ(FieldOriginalPrice, v))
|
||||
}
|
||||
|
||||
// Currency applies equality check predicate on the "currency" field. It's identical to CurrencyEQ.
|
||||
func Currency(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldEQ(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// ValidityDays applies equality check predicate on the "validity_days" field. It's identical to ValidityDaysEQ.
|
||||
func ValidityDays(v int) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityDays, v))
|
||||
@@ -379,6 +384,71 @@ func OriginalPriceNotNil() predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldNotNull(FieldOriginalPrice))
|
||||
}
|
||||
|
||||
// CurrencyEQ applies the EQ predicate on the "currency" field.
|
||||
func CurrencyEQ(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldEQ(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyNEQ applies the NEQ predicate on the "currency" field.
|
||||
func CurrencyNEQ(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyIn applies the In predicate on the "currency" field.
|
||||
func CurrencyIn(vs ...string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldIn(FieldCurrency, vs...))
|
||||
}
|
||||
|
||||
// CurrencyNotIn applies the NotIn predicate on the "currency" field.
|
||||
func CurrencyNotIn(vs ...string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldCurrency, vs...))
|
||||
}
|
||||
|
||||
// CurrencyGT applies the GT predicate on the "currency" field.
|
||||
func CurrencyGT(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldGT(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyGTE applies the GTE predicate on the "currency" field.
|
||||
func CurrencyGTE(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldGTE(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyLT applies the LT predicate on the "currency" field.
|
||||
func CurrencyLT(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldLT(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyLTE applies the LTE predicate on the "currency" field.
|
||||
func CurrencyLTE(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldLTE(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyContains applies the Contains predicate on the "currency" field.
|
||||
func CurrencyContains(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldContains(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyHasPrefix applies the HasPrefix predicate on the "currency" field.
|
||||
func CurrencyHasPrefix(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyHasSuffix applies the HasSuffix predicate on the "currency" field.
|
||||
func CurrencyHasSuffix(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyEqualFold applies the EqualFold predicate on the "currency" field.
|
||||
func CurrencyEqualFold(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// CurrencyContainsFold applies the ContainsFold predicate on the "currency" field.
|
||||
func CurrencyContainsFold(v string) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldCurrency, v))
|
||||
}
|
||||
|
||||
// ValidityDaysEQ applies the EQ predicate on the "validity_days" field.
|
||||
func ValidityDaysEQ(v int) predicate.SubscriptionPlan {
|
||||
return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityDays, v))
|
||||
|
||||
@@ -68,6 +68,20 @@ func (_c *SubscriptionPlanCreate) SetNillableOriginalPrice(v *float64) *Subscrip
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (_c *SubscriptionPlanCreate) SetCurrency(v string) *SubscriptionPlanCreate {
|
||||
_c.mutation.SetCurrency(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableCurrency sets the "currency" field if the given value is not nil.
|
||||
func (_c *SubscriptionPlanCreate) SetNillableCurrency(v *string) *SubscriptionPlanCreate {
|
||||
if v != nil {
|
||||
_c.SetCurrency(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (_c *SubscriptionPlanCreate) SetValidityDays(v int) *SubscriptionPlanCreate {
|
||||
_c.mutation.SetValidityDays(v)
|
||||
@@ -219,6 +233,10 @@ func (_c *SubscriptionPlanCreate) defaults() {
|
||||
v := subscriptionplan.DefaultDescription
|
||||
_c.mutation.SetDescription(v)
|
||||
}
|
||||
if _, ok := _c.mutation.Currency(); !ok {
|
||||
v := subscriptionplan.DefaultCurrency
|
||||
_c.mutation.SetCurrency(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ValidityDays(); !ok {
|
||||
v := subscriptionplan.DefaultValidityDays
|
||||
_c.mutation.SetValidityDays(v)
|
||||
@@ -272,6 +290,14 @@ func (_c *SubscriptionPlanCreate) check() error {
|
||||
if _, ok := _c.mutation.Price(); !ok {
|
||||
return &ValidationError{Name: "price", err: errors.New(`ent: missing required field "SubscriptionPlan.price"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Currency(); !ok {
|
||||
return &ValidationError{Name: "currency", err: errors.New(`ent: missing required field "SubscriptionPlan.currency"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.Currency(); ok {
|
||||
if err := subscriptionplan.CurrencyValidator(v); err != nil {
|
||||
return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.currency": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.ValidityDays(); !ok {
|
||||
return &ValidationError{Name: "validity_days", err: errors.New(`ent: missing required field "SubscriptionPlan.validity_days"`)}
|
||||
}
|
||||
@@ -353,6 +379,10 @@ func (_c *SubscriptionPlanCreate) createSpec() (*SubscriptionPlan, *sqlgraph.Cre
|
||||
_spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value)
|
||||
_node.OriginalPrice = &value
|
||||
}
|
||||
if value, ok := _c.mutation.Currency(); ok {
|
||||
_spec.SetField(subscriptionplan.FieldCurrency, field.TypeString, value)
|
||||
_node.Currency = value
|
||||
}
|
||||
if value, ok := _c.mutation.ValidityDays(); ok {
|
||||
_spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
|
||||
_node.ValidityDays = value
|
||||
@@ -521,6 +551,18 @@ func (u *SubscriptionPlanUpsert) ClearOriginalPrice() *SubscriptionPlanUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (u *SubscriptionPlanUpsert) SetCurrency(v string) *SubscriptionPlanUpsert {
|
||||
u.Set(subscriptionplan.FieldCurrency, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateCurrency sets the "currency" field to the value that was provided on create.
|
||||
func (u *SubscriptionPlanUpsert) UpdateCurrency() *SubscriptionPlanUpsert {
|
||||
u.SetExcluded(subscriptionplan.FieldCurrency)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (u *SubscriptionPlanUpsert) SetValidityDays(v int) *SubscriptionPlanUpsert {
|
||||
u.Set(subscriptionplan.FieldValidityDays, v)
|
||||
@@ -760,6 +802,20 @@ func (u *SubscriptionPlanUpsertOne) ClearOriginalPrice() *SubscriptionPlanUpsert
|
||||
})
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (u *SubscriptionPlanUpsertOne) SetCurrency(v string) *SubscriptionPlanUpsertOne {
|
||||
return u.Update(func(s *SubscriptionPlanUpsert) {
|
||||
s.SetCurrency(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCurrency sets the "currency" field to the value that was provided on create.
|
||||
func (u *SubscriptionPlanUpsertOne) UpdateCurrency() *SubscriptionPlanUpsertOne {
|
||||
return u.Update(func(s *SubscriptionPlanUpsert) {
|
||||
s.UpdateCurrency()
|
||||
})
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (u *SubscriptionPlanUpsertOne) SetValidityDays(v int) *SubscriptionPlanUpsertOne {
|
||||
return u.Update(func(s *SubscriptionPlanUpsert) {
|
||||
@@ -1181,6 +1237,20 @@ func (u *SubscriptionPlanUpsertBulk) ClearOriginalPrice() *SubscriptionPlanUpser
|
||||
})
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (u *SubscriptionPlanUpsertBulk) SetCurrency(v string) *SubscriptionPlanUpsertBulk {
|
||||
return u.Update(func(s *SubscriptionPlanUpsert) {
|
||||
s.SetCurrency(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCurrency sets the "currency" field to the value that was provided on create.
|
||||
func (u *SubscriptionPlanUpsertBulk) UpdateCurrency() *SubscriptionPlanUpsertBulk {
|
||||
return u.Update(func(s *SubscriptionPlanUpsert) {
|
||||
s.UpdateCurrency()
|
||||
})
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (u *SubscriptionPlanUpsertBulk) SetValidityDays(v int) *SubscriptionPlanUpsertBulk {
|
||||
return u.Update(func(s *SubscriptionPlanUpsert) {
|
||||
|
||||
@@ -125,6 +125,20 @@ func (_u *SubscriptionPlanUpdate) ClearOriginalPrice() *SubscriptionPlanUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (_u *SubscriptionPlanUpdate) SetCurrency(v string) *SubscriptionPlanUpdate {
|
||||
_u.mutation.SetCurrency(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableCurrency sets the "currency" field if the given value is not nil.
|
||||
func (_u *SubscriptionPlanUpdate) SetNillableCurrency(v *string) *SubscriptionPlanUpdate {
|
||||
if v != nil {
|
||||
_u.SetCurrency(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (_u *SubscriptionPlanUpdate) SetValidityDays(v int) *SubscriptionPlanUpdate {
|
||||
_u.mutation.ResetValidityDays()
|
||||
@@ -277,6 +291,11 @@ func (_u *SubscriptionPlanUpdate) check() error {
|
||||
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Currency(); ok {
|
||||
if err := subscriptionplan.CurrencyValidator(v); err != nil {
|
||||
return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.currency": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.ValidityUnit(); ok {
|
||||
if err := subscriptionplan.ValidityUnitValidator(v); err != nil {
|
||||
return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)}
|
||||
@@ -329,6 +348,9 @@ func (_u *SubscriptionPlanUpdate) sqlSave(ctx context.Context) (_node int, err e
|
||||
if _u.mutation.OriginalPriceCleared() {
|
||||
_spec.ClearField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.Currency(); ok {
|
||||
_spec.SetField(subscriptionplan.FieldCurrency, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ValidityDays(); ok {
|
||||
_spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
|
||||
}
|
||||
@@ -473,6 +495,20 @@ func (_u *SubscriptionPlanUpdateOne) ClearOriginalPrice() *SubscriptionPlanUpdat
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCurrency sets the "currency" field.
|
||||
func (_u *SubscriptionPlanUpdateOne) SetCurrency(v string) *SubscriptionPlanUpdateOne {
|
||||
_u.mutation.SetCurrency(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableCurrency sets the "currency" field if the given value is not nil.
|
||||
func (_u *SubscriptionPlanUpdateOne) SetNillableCurrency(v *string) *SubscriptionPlanUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetCurrency(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetValidityDays sets the "validity_days" field.
|
||||
func (_u *SubscriptionPlanUpdateOne) SetValidityDays(v int) *SubscriptionPlanUpdateOne {
|
||||
_u.mutation.ResetValidityDays()
|
||||
@@ -638,6 +674,11 @@ func (_u *SubscriptionPlanUpdateOne) check() error {
|
||||
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Currency(); ok {
|
||||
if err := subscriptionplan.CurrencyValidator(v); err != nil {
|
||||
return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.currency": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.ValidityUnit(); ok {
|
||||
if err := subscriptionplan.ValidityUnitValidator(v); err != nil {
|
||||
return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)}
|
||||
@@ -707,6 +748,9 @@ func (_u *SubscriptionPlanUpdateOne) sqlSave(ctx context.Context) (_node *Subscr
|
||||
if _u.mutation.OriginalPriceCleared() {
|
||||
_spec.ClearField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.Currency(); ok {
|
||||
_spec.SetField(subscriptionplan.FieldCurrency, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ValidityDays(); ok {
|
||||
_spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ func (h *PaymentHandler) GetPlans(c *gin.Context) {
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
OriginalPrice *float64 `json:"original_price,omitempty"`
|
||||
Currency string `json:"currency,omitempty"`
|
||||
ValidityDays int `json:"validity_days"`
|
||||
ValidityUnit string `json:"validity_unit"`
|
||||
Features string `json:"features"`
|
||||
@@ -81,6 +82,7 @@ func (h *PaymentHandler) GetPlans(c *gin.Context) {
|
||||
RateMultiplier: gi.RateMultiplier, PeakRateEnabled: gi.PeakRateEnabled,
|
||||
PeakStart: gi.PeakStart, PeakEnd: gi.PeakEnd, PeakRateMultiplier: gi.PeakRateMultiplier,
|
||||
Name: p.Name, Description: p.Description, Price: p.Price, OriginalPrice: p.OriginalPrice,
|
||||
Currency: p.Currency,
|
||||
ValidityDays: p.ValidityDays, ValidityUnit: p.ValidityUnit, Features: p.Features,
|
||||
ProductName: p.ProductName, ForSale: p.ForSale, SortOrder: p.SortOrder,
|
||||
})
|
||||
@@ -124,6 +126,7 @@ func (h *PaymentHandler) GetCheckoutInfo(c *gin.Context) {
|
||||
WeeklyLimitUSD: gi.WeeklyLimitUSD, MonthlyLimitUSD: gi.MonthlyLimitUSD,
|
||||
ModelScopes: gi.ModelScopes,
|
||||
Name: p.Name, Description: p.Description, Price: p.Price, OriginalPrice: p.OriginalPrice,
|
||||
Currency: p.Currency,
|
||||
ValidityDays: p.ValidityDays, ValidityUnit: p.ValidityUnit, Features: parseFeatures(p.Features),
|
||||
ProductName: p.ProductName,
|
||||
})
|
||||
@@ -178,6 +181,7 @@ type checkoutPlan struct {
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
OriginalPrice *float64 `json:"original_price,omitempty"`
|
||||
Currency string `json:"currency,omitempty"`
|
||||
ValidityDays int `json:"validity_days"`
|
||||
ValidityUnit string `json:"validity_unit"`
|
||||
Features []string `json:"features"`
|
||||
|
||||
@@ -8,9 +8,23 @@ import (
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// normalizePlanCurrency validates and normalizes the display-only currency label.
|
||||
// Empty means "no label" and is kept as-is so existing plans stay unchanged.
|
||||
func normalizePlanCurrency(raw string) (string, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return "", nil
|
||||
}
|
||||
currency, err := payment.NormalizePaymentCurrency(raw)
|
||||
if err != nil {
|
||||
return "", infraerrors.BadRequest("PLAN_CURRENCY_INVALID", "currency must be a 3-letter ISO currency code")
|
||||
}
|
||||
return currency, nil
|
||||
}
|
||||
|
||||
// validatePlanRequired checks that all required fields for a plan are provided.
|
||||
func validatePlanRequired(name string, groupID int64, price float64, validityDays int, validityUnit string, originalPrice *float64) error {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
@@ -122,9 +136,13 @@ func (s *PaymentConfigService) CreatePlan(ctx context.Context, req CreatePlanReq
|
||||
if err := validatePlanRequired(req.Name, req.GroupID, req.Price, req.ValidityDays, req.ValidityUnit, req.OriginalPrice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currency, err := normalizePlanCurrency(req.Currency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := s.entClient.SubscriptionPlan.Create().
|
||||
SetGroupID(req.GroupID).SetName(req.Name).SetDescription(req.Description).
|
||||
SetPrice(req.Price).SetValidityDays(req.ValidityDays).SetValidityUnit(req.ValidityUnit).
|
||||
SetPrice(req.Price).SetCurrency(currency).SetValidityDays(req.ValidityDays).SetValidityUnit(req.ValidityUnit).
|
||||
SetFeatures(req.Features).SetProductName(req.ProductName).
|
||||
SetForSale(req.ForSale).SetSortOrder(req.SortOrder)
|
||||
if req.OriginalPrice != nil {
|
||||
@@ -156,6 +174,13 @@ func (s *PaymentConfigService) UpdatePlan(ctx context.Context, id int64, req Upd
|
||||
if req.OriginalPrice != nil {
|
||||
u.SetOriginalPrice(*req.OriginalPrice)
|
||||
}
|
||||
if req.Currency != nil {
|
||||
currency, err := normalizePlanCurrency(*req.Currency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.SetCurrency(currency)
|
||||
}
|
||||
if req.ValidityDays != nil {
|
||||
u.SetValidityDays(*req.ValidityDays)
|
||||
}
|
||||
|
||||
@@ -191,3 +191,49 @@ func TestValidatePlanPatch_AllNil(t *testing.T) {
|
||||
err := validatePlanPatch(UpdatePlanRequest{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// --- normalizePlanCurrency tests ---
|
||||
// Empty must stay empty (not coerced to the default payment currency),
|
||||
// so existing plans keep rendering without any currency label.
|
||||
|
||||
func TestNormalizePlanCurrency_EmptyKeepsEmpty(t *testing.T) {
|
||||
currency, err := normalizePlanCurrency("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", currency)
|
||||
}
|
||||
|
||||
func TestNormalizePlanCurrency_WhitespaceKeepsEmpty(t *testing.T) {
|
||||
currency, err := normalizePlanCurrency(" ")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", currency)
|
||||
}
|
||||
|
||||
func TestNormalizePlanCurrency_LowercaseNormalized(t *testing.T) {
|
||||
currency, err := normalizePlanCurrency("nzd")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "NZD", currency)
|
||||
}
|
||||
|
||||
func TestNormalizePlanCurrency_ValidUppercase(t *testing.T) {
|
||||
currency, err := normalizePlanCurrency("USD")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "USD", currency)
|
||||
}
|
||||
|
||||
func TestNormalizePlanCurrency_TooShort(t *testing.T) {
|
||||
_, err := normalizePlanCurrency("NZ")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "currency")
|
||||
}
|
||||
|
||||
func TestNormalizePlanCurrency_TooLong(t *testing.T) {
|
||||
_, err := normalizePlanCurrency("NZDD")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "currency")
|
||||
}
|
||||
|
||||
func TestNormalizePlanCurrency_NonLetter(t *testing.T) {
|
||||
_, err := normalizePlanCurrency("N2D")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "currency")
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ type CreatePlanRequest struct {
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
OriginalPrice *float64 `json:"original_price"`
|
||||
Currency string `json:"currency"`
|
||||
ValidityDays int `json:"validity_days"`
|
||||
ValidityUnit string `json:"validity_unit"`
|
||||
Features string `json:"features"`
|
||||
@@ -176,6 +177,7 @@ type UpdatePlanRequest struct {
|
||||
Description *string `json:"description"`
|
||||
Price *float64 `json:"price"`
|
||||
OriginalPrice *float64 `json:"original_price"`
|
||||
Currency *string `json:"currency"`
|
||||
ValidityDays *int `json:"validity_days"`
|
||||
ValidityUnit *string `json:"validity_unit"`
|
||||
Features *string `json:"features"`
|
||||
|
||||
@@ -28,10 +28,11 @@
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-xs text-gray-400 dark:text-dark-500">$</span>
|
||||
<span :class="['text-2xl font-extrabold tracking-tight', textClass]">{{ plan.price }}</span>
|
||||
<span v-if="plan.currency" class="text-xs font-medium text-gray-400 dark:text-dark-500">{{ plan.currency }}</span>
|
||||
</div>
|
||||
<span class="text-[11px] text-gray-400 dark:text-dark-500">/ {{ validitySuffix }}</span>
|
||||
<div v-if="plan.original_price" class="mt-0.5 flex items-center justify-end gap-1.5">
|
||||
<span class="text-xs text-gray-400 line-through dark:text-dark-500">${{ plan.original_price }}</span>
|
||||
<span class="text-xs text-gray-400 line-through dark:text-dark-500">${{ plan.original_price }}<template v-if="plan.currency"> {{ plan.currency }}</template></span>
|
||||
<span :class="['rounded px-1 py-0.5 text-[10px] font-semibold', discountClass]">{{ discountText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -545,6 +545,9 @@ export default {
|
||||
deletePlanConfirm: 'Are you sure you want to delete this plan?',
|
||||
originalPrice: 'Original Price',
|
||||
price: 'Price',
|
||||
currency: 'Currency Label',
|
||||
currencyPlaceholder: 'e.g. USD / NZD / CNY',
|
||||
currencyHint: 'Display-only 3-letter ISO currency code shown next to the price; leave empty to hide, does not affect billing',
|
||||
subscriptionCnyPayPreview: 'CNY channel charge preview: {amount}',
|
||||
subscriptionCnyPayPreviewWithFee: '({feeRate}% fee included: {total})',
|
||||
validityDays: 'Validity (days)',
|
||||
|
||||
@@ -569,6 +569,9 @@ export default {
|
||||
deletePlanConfirm: '确定要删除此套餐吗?',
|
||||
originalPrice: '原价',
|
||||
price: '价格',
|
||||
currency: '币种标注',
|
||||
currencyPlaceholder: '如 USD / NZD / CNY',
|
||||
currencyHint: '仅用于价格展示的 ISO 三字母币种码,留空不展示,不影响实际扣款',
|
||||
subscriptionCnyPayPreview: 'CNY 通道实扣预览:{amount}',
|
||||
subscriptionCnyPayPreviewWithFee: '(含 {feeRate}% 手续费:{total})',
|
||||
validityDays: '有效期(天)',
|
||||
|
||||
@@ -124,6 +124,8 @@ export interface SubscriptionPlan {
|
||||
description: string
|
||||
price: number
|
||||
original_price?: number
|
||||
/** Display-only ISO 4217 currency label (e.g. "NZD"); empty means no label */
|
||||
currency?: string
|
||||
validity_days: number
|
||||
validity_unit: string
|
||||
/** Stored as JSON string in backend; API layer should parse before use */
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<template #cell-price="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ (value ?? 0).toFixed(2) }}</span>
|
||||
<span v-if="row.currency" class="ml-1 text-xs text-gray-400">{{ row.currency }}</span>
|
||||
<span v-if="row.original_price" class="ml-1 text-xs text-gray-400 line-through">${{ row.original_price.toFixed(2) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -53,6 +53,11 @@
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="input-label">{{ t('payment.admin.sortOrder') }}</label><input v-model.number="planForm.sort_order" type="number" min="0" class="input" /></div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('payment.admin.currency') }}</label>
|
||||
<input v-model="planForm.currency" type="text" maxlength="3" class="input uppercase" :placeholder="t('payment.admin.currencyPlaceholder')" />
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ t('payment.admin.currencyHint') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('payment.admin.features') }}</label>
|
||||
@@ -117,7 +122,7 @@ const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const saving = ref(false)
|
||||
const planForm = reactive({ name: '', group_id: null as number | null, description: '', price: 0, original_price: 0, validity_days: 30, validity_unit: 'days', sort_order: 0, for_sale: true })
|
||||
const planForm = reactive({ name: '', group_id: null as number | null, description: '', price: 0, original_price: 0, currency: '', validity_days: 30, validity_unit: 'days', sort_order: 0, for_sale: true })
|
||||
const planFeaturesText = ref('')
|
||||
|
||||
const validityUnitOptions = computed(() => [
|
||||
@@ -170,10 +175,10 @@ const subscriptionCnyPreview = computed(() => {
|
||||
watch(() => props.show, (visible) => {
|
||||
if (!visible) return
|
||||
if (props.plan) {
|
||||
Object.assign(planForm, { name: props.plan.name, group_id: props.plan.group_id, description: props.plan.description, price: props.plan.price, original_price: props.plan.original_price || 0, validity_days: props.plan.validity_days, validity_unit: props.plan.validity_unit || 'days', sort_order: props.plan.sort_order || 0, for_sale: props.plan.for_sale })
|
||||
Object.assign(planForm, { name: props.plan.name, group_id: props.plan.group_id, description: props.plan.description, price: props.plan.price, original_price: props.plan.original_price || 0, currency: props.plan.currency || '', validity_days: props.plan.validity_days, validity_unit: props.plan.validity_unit || 'days', sort_order: props.plan.sort_order || 0, for_sale: props.plan.for_sale })
|
||||
planFeaturesText.value = (props.plan.features || []).join('\n')
|
||||
} else {
|
||||
Object.assign(planForm, { name: '', group_id: null, description: '', price: 0, original_price: 0, validity_days: 30, validity_unit: 'days', sort_order: 0, for_sale: true })
|
||||
Object.assign(planForm, { name: '', group_id: null, description: '', price: 0, original_price: 0, currency: '', validity_days: 30, validity_unit: 'days', sort_order: 0, for_sale: true })
|
||||
planFeaturesText.value = ''
|
||||
}
|
||||
})
|
||||
@@ -187,6 +192,7 @@ function buildPlanPayload() {
|
||||
description: planForm.description,
|
||||
price: planForm.price,
|
||||
original_price: planForm.original_price || 0,
|
||||
currency: planForm.currency.trim().toUpperCase(),
|
||||
validity_days: planForm.validity_days,
|
||||
validity_unit: planForm.validity_unit,
|
||||
sort_order: planForm.sort_order,
|
||||
|
||||
Reference in New Issue
Block a user