diff --git a/service/pricing/charge_calc.go b/service/pricing/charge_calc.go index cceeebb..f03433b 100644 --- a/service/pricing/charge_calc.go +++ b/service/pricing/charge_calc.go @@ -291,9 +291,19 @@ func (c modelTokenCalculator) validate(rulesJSON string) (interface{}, error) { if r.Unit != pricingConsts.ChargeModePer1K && r.Unit != pricingConsts.ChargeModePer1M { return nil, errors.New("model token 费率 unit 须为 per_1K/per_1M") } + // 实例基准与 JSON unit 交叉校验:per_1K 实例 base=1000,per_1M 实例 base=1e6, + // 防止 per_1M 配置配 per_1K 单位导致结算 1000 倍价差(P2)。 + if (c.base == 1000) != (r.Unit == pricingConsts.ChargeModePer1K) { + return nil, errors.New("model token 费率 unit 与实例基准不匹配") + } if len(r.Rules) == 0 { return nil, errors.New("model 费率至少一条 rules") } + for i := range r.Rules { + if r.Rules[i].Price == nil { + return nil, errors.New("model 费率规则须配置 price") + } + } if r.Tiered { if err := validateTieredBands(r.Rules); err != nil { return nil, err @@ -325,7 +335,12 @@ func (c modelTokenCalculator) charge(rules interface{}, usage *ChargeUsage) (flo return 0, errors.New("无匹配计费规则") } p := rule.Price - cost := float64(usage.PromptTokens-usage.CachedTokens)/c.base*p.Input + + // 防负成本:异常/边界用量报 cached > prompt 时钳为 0,避免结算出现负数(M3)。 + promptInput := usage.PromptTokens - usage.CachedTokens + if promptInput < 0 { + promptInput = 0 + } + cost := float64(promptInput)/c.base*p.Input + float64(usage.CachedTokens)/c.base*p.CacheHit + float64(usage.CompletionTokens)/c.base*p.Output return ceilFen(roundCost(cost)), nil @@ -353,6 +368,11 @@ func (c modelUnitCalculator) validate(rulesJSON string) (interface{}, error) { if len(r.Rules) == 0 { return nil, errors.New("model 费率至少一条 rules") } + for i := range r.Rules { + if r.Rules[i].Price == nil { + return nil, errors.New("model 费率规则须配置 price") + } + } return &r, nil } diff --git a/service/pricing/charge_calc_test.go b/service/pricing/charge_calc_test.go index 6d104b8..04dd513 100644 --- a/service/pricing/charge_calc_test.go +++ b/service/pricing/charge_calc_test.go @@ -130,3 +130,60 @@ func TestCalcChargeBySubjectType(t *testing.T) { t.Fatalf("model per_second got %v want 1.98, err %v", got, err) } } + +// P1:validate 拒绝缺 price 的规则(token 计算器) +func TestModelTokenCalculator_ValidateNilPrice(t *testing.T) { + c := modelTokenCalculator{base: 1e6} + if _, err := c.validate(`{"unit":"per_1M","tiered":false,"rules":[{"name":"无价"}]}`); err == nil { + t.Fatal("expected nil price validation error") + } +} + +// P1:validate 拒绝缺 price 的规则(单位计算器) +func TestModelUnitCalculator_ValidateNilPrice(t *testing.T) { + c := modelUnitCalculator{base: 60, usage: usageDurationSec} + if _, err := c.validate(`{"unit":"per_minute","tiered":false,"rules":[{"name":"无价"}]}`); err == nil { + t.Fatal("expected nil price validation error") + } +} + +// P2:modelTokenCalculator.validate 拒绝 unit 与实例 base 不匹配的配置 +func TestModelTokenCalculator_ValidateUnitBaseMismatch(t *testing.T) { + // per_1M 实例(base=1e6)配 per_1K 单位 → 校验失败 + c := modelTokenCalculator{base: 1e6} + if _, err := c.validate(`{"unit":"per_1K","tiered":false,"rules":[{"name":"统一","price":{"input":1}}]}`); err == nil { + t.Fatal("expected unit/base mismatch error for per_1M instance with per_1K unit") + } + // per_1K 实例(base=1000)配 per_1M 单位 → 校验失败 + c2 := modelTokenCalculator{base: 1000} + if _, err := c2.validate(`{"unit":"per_1M","tiered":false,"rules":[{"name":"统一","price":{"input":1}}]}`); err == nil { + t.Fatal("expected unit/base mismatch error for per_1K instance with per_1M unit") + } + // 匹配时不报错 + if _, err := c.validate(`{"unit":"per_1M","tiered":false,"rules":[{"name":"统一","price":{"input":1}}]}`); err != nil { + t.Fatalf("unexpected err: %v", err) + } + if _, err := c2.validate(`{"unit":"per_1K","tiered":false,"rules":[{"name":"统一","price":{"input":1}}]}`); err != nil { + t.Fatalf("unexpected err: %v", err) + } +} + +// M3:cached > prompt 时金额不为负(promptInput 钳为 0) +func TestModelTokenCalculator_CachedGreaterThanPrompt(t *testing.T) { + c := modelTokenCalculator{base: 1000} + rules, err := c.validate(`{"unit":"per_1K","tiered":false,"rules":[{"name":"统一","price":{"input":1,"output":2,"cacheHit":0.1}}]}`) + if err != nil { + t.Fatalf("validate err: %v", err) + } + // cached 3000 > prompt 1000 → promptInput 钳 0:0 + 3000/1000*0.1 + 0 = 0.3 + got, err := c.charge(rules, &ChargeUsage{PromptTokens: 1000, CompletionTokens: 0, CachedTokens: 3000}) + if err != nil { + t.Fatalf("charge err: %v", err) + } + if got < 0 { + t.Fatalf("got negative amount %v", got) + } + if got != 0.3 { + t.Fatalf("got %v want 0.3", got) + } +} diff --git a/service/pricing/pricing_service.go b/service/pricing/pricing_service.go index 228d80a..ddccfe0 100644 --- a/service/pricing/pricing_service.go +++ b/service/pricing/pricing_service.go @@ -24,6 +24,8 @@ import ( var ( // ErrOrderAlreadyHandled 计费单已被处理(并发幂等:让事务回滚,避免账务重复) ErrOrderAlreadyHandled = errors.New("计费单已被处理") + // ErrConfigNotFound 计价配置不存在(service 层业务错误) + ErrConfigNotFound = errors.New("计价配置不存在") ) // pricing 计价服务:算钱(定价+计费单生命周期+门禁)。 @@ -36,28 +38,36 @@ type pricing struct{} // ====================== 建单(不动钱) ====================== -// OpenOrder 建单:校验配置 + 门禁(余额 >= min_balance,非冻结),建 CREATED 计费单,不动钱。 -// 幂等键 biz_key + biz_order_no(工作流 execId),重复调用返回既有单。 +// OpenOrder 建单:校验配置 + 解析计费方式 + 门禁(余额 >= min_balance,非冻结),建 CREATED 计费单,不动钱。 +// 幂等键 subject_type + subject_id + biz_order_no(工作流 execId),重复调用返回既有单。 func (s *pricing) OpenOrder(ctx context.Context, req *pricingDto.OpenOrderReq) (*pricingDto.ChargeOrderInfo, error) { - cfg, err := s.getEnabledConfig(ctx, req.BizKey) + cfg, err := s.getEnabledConfig(ctx, pricingConsts.SubjectType(req.SubjectType), req.SubjectID) if err != nil { return nil, err } - // 幂等快路径:同 biz_key + biz_order_no 已建单直接返回 - if exist, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{BizKey: req.BizKey, BizOrderNo: req.BizOrderNo}); err != nil { + // 幂等快路径:同 subject + biz_order_no 已建单直接返回 + if exist, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{ + SubjectType: req.SubjectType, SubjectID: req.SubjectID, BizOrderNo: req.BizOrderNo, + }); err != nil { return nil, err } else if exist != nil { info := s.toOrderInfo(exist) return &info, nil } - // 建单前校验费率 JSON(脏配置在建单即暴露,不拖到结算) - c, err := getCalculator(pricingConsts.SubjectTypeWorkflow, cfg.ChargeMode) + // 解析计费方式与规则快照(含 chargeMode 校验) + chargeMode, ruleSnapshot, err := s.resolveChargeMode(ctx, cfg, req) if err != nil { return nil, err } - if _, err = c.validate(cfg.Rules); err != nil { + + // 建单前校验费率 JSON(脏配置在建单即暴露,不拖到结算) + c, err := getCalculator(cfg.SubjectType, chargeMode) + if err != nil { + return nil, err + } + if _, err = c.validate(ruleSnapshot); err != nil { return nil, err } @@ -78,28 +88,32 @@ func (s *pricing) OpenOrder(ctx context.Context, req *pricingDto.OpenOrderReq) ( } id, err := chargeDao.ChargeOrder.Insert(ctx, &pricingDto.CreateChargeOrderReq{ - BizKey: req.BizKey, + SubjectType: cfg.SubjectType, + SubjectID: cfg.SubjectID, BizOrderNo: req.BizOrderNo, UserId: req.UserId, - ChargeMode: cfg.ChargeMode, + ChargeMode: chargeMode, Status: pricingConsts.ChargeOrderStatusCreated, - RuleSnapshot: cfg.Rules, // 费率快照,结算据此计价,防改价影响在途单 + RuleSnapshot: ruleSnapshot, // 费率快照,结算据此计价,防改价影响在途单 }) if err != nil { // 唯一键冲突:并发已建单,幂等返回既有单 - if exist, e2 := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{BizKey: req.BizKey, BizOrderNo: req.BizOrderNo}); e2 == nil && exist != nil { + if exist, e2 := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{ + SubjectType: req.SubjectType, SubjectID: req.SubjectID, BizOrderNo: req.BizOrderNo, + }); e2 == nil && exist != nil { info := s.toOrderInfo(exist) return &info, nil } return nil, err } entity := &pricingEntity.ChargeOrder{ - BizKey: req.BizKey, + SubjectType: cfg.SubjectType, + SubjectID: cfg.SubjectID, BizOrderNo: req.BizOrderNo, UserId: req.UserId, - ChargeMode: cfg.ChargeMode, + ChargeMode: chargeMode, Status: pricingConsts.ChargeOrderStatusCreated, - RuleSnapshot: cfg.Rules, + RuleSnapshot: ruleSnapshot, } entity.Id = id info := s.toOrderInfo(entity) @@ -110,7 +124,7 @@ func (s *pricing) OpenOrder(ctx context.Context, req *pricingDto.OpenOrderReq) ( // Settle 结算:按实际用量计价实收(余额不足允许为负=欠费),幂等。 func (s *pricing) Settle(ctx context.Context, req *pricingDto.SettleReq) (*pricingDto.ChargeOrderInfo, error) { - order, err := s.resolveOrder(ctx, req.OrderId, req.BizKey, req.BizOrderNo) + order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo) if err != nil { return nil, err } @@ -123,7 +137,7 @@ func (s *pricing) Settle(ctx context.Context, req *pricingDto.SettleReq) (*prici // Cancel 取消:中途取消按已消耗用量计价实收(非退款),幂等。 func (s *pricing) Cancel(ctx context.Context, req *pricingDto.CancelReq) (*pricingDto.ChargeOrderInfo, error) { - order, err := s.resolveOrder(ctx, req.OrderId, req.BizKey, req.BizOrderNo) + order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo) if err != nil { return nil, err } @@ -149,8 +163,14 @@ func (s *pricing) settleOrder(ctx context.Context, order *pricingEntity.ChargeOr if err := json.Unmarshal([]byte(usageJSON), &usage); err != nil { return 0, fmt.Errorf("用量JSON解析失败: %v", err) } - // 用建单时费率快照计价(防改价影响在途单) - actualAmount, err := calcCharge(pricingConsts.SubjectTypeWorkflow, order.ChargeMode, order.RuleSnapshot, &usage) + // 用建单时费率快照计价(防改价影响在途单);per_token 特殊:按各模型 subject 实时价(spec §5.4) + var actualAmount float64 + var err error + if order.SubjectType == pricingConsts.SubjectTypeWorkflow && order.ChargeMode == pricingConsts.ChargeModePerToken { + actualAmount, err = s.calcPerTokenOrder(ctx, &usage) + } else { + actualAmount, err = calcCharge(order.SubjectType, order.ChargeMode, order.RuleSnapshot, &usage) + } if err != nil { return 0, err } @@ -165,7 +185,7 @@ func (s *pricing) settleOrder(ctx context.Context, order *pricingEntity.ChargeOr Type: walletConsts.WalletLogTypeExpense, Amount: actualAmount, OrderNo: fmt.Sprintf("charge:%d", order.Id), - Description: desc + order.BizKey, + Description: desc + string(order.SubjectType) + ":" + order.SubjectID, }); err != nil { return err } @@ -195,11 +215,45 @@ func (s *pricing) settleOrder(ctx context.Context, order *pricingEntity.ChargeOr return actualAmount, nil } +// calcPerTokenOrder 工作流 per_token 结算:按 usage.TokensByModel 逐模型查该模型 subject 配置计价累加。 +// 未配价/未启用模型按 0 计(不收费、不报错);费率不冻结,结算按模型实时价(spec §5.4)。 +func (s *pricing) calcPerTokenOrder(ctx context.Context, usage *ChargeUsage) (float64, error) { + var total float64 + for modelID, tokens := range usage.TokensByModel { + if tokens <= 0 { + continue + } + cfg, err := chargeDao.PricingConfig.Get(ctx, &pricingDto.GetConfigReq{ + SubjectType: string(pricingConsts.SubjectTypeModel), SubjectID: modelID, + }) + if err != nil { + return 0, err + } + if cfg == nil || cfg.Enabled != 1 { + continue // 未配价模型按 0 计 + } + c, err := getCalculator(cfg.SubjectType, cfg.Unit()) + if err != nil { + return 0, err + } + rules, err := c.validate(cfg.Rules) + if err != nil { + return 0, err + } + amount, err := c.charge(rules, &ChargeUsage{PromptTokens: tokens, CompletionTokens: 0, CachedTokens: 0}) + if err != nil { + return 0, err + } + total += amount + } + return ceilFen(total), nil +} + // ====================== 失败(不扣费) ====================== // Fail 失败处理:执行失败/无产出,不扣费、不动钱,仅状态迁移 CREATED → FAILED。幂等。 func (s *pricing) Fail(ctx context.Context, req *pricingDto.FailReq) (*pricingDto.ChargeOrderInfo, error) { - order, err := s.resolveOrder(ctx, req.OrderId, req.BizKey, req.BizOrderNo) + order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo) if err != nil { return nil, err } @@ -226,9 +280,9 @@ func (s *pricing) Fail(ctx context.Context, req *pricingDto.FailReq) (*pricingDt // ====================== 查询 ====================== -// GetOrder 查询计费单(按ID或 biz_key + biz_order_no) +// GetOrder 查询计费单(按ID或 subject_type + subject_id + biz_order_no) func (s *pricing) GetOrder(ctx context.Context, req *pricingDto.GetOrderReq) (*pricingDto.ChargeOrderInfo, error) { - order, err := s.resolveOrder(ctx, req.OrderId, req.BizKey, req.BizOrderNo) + order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo) if err != nil { return nil, err } @@ -254,14 +308,10 @@ func (s *pricing) ListOrders(ctx context.Context, req *pricingDto.ListOrdersReq) // ====================== 配置管理 ====================== -// SaveConfig 新增/更新计价配置(费率全 DB 配置;同 biz_key 唯一) +// SaveConfig 新增/更新计价配置(费率全 DB 配置;按 subject 类型校验 rules 结构,防止脏配置上线后结算报错) func (s *pricing) SaveConfig(ctx context.Context, req *pricingDto.SaveConfigReq) (*pricingDto.SaveConfigData, error) { - c, err := getCalculator(pricingConsts.SubjectTypeWorkflow, pricingConsts.ChargeMode(req.ChargeMode)) - if err != nil { - return nil, err - } - // 保存前校验费率 JSON 结构(防止脏配置上线后结算报错) - if _, err = c.validate(req.Rules); err != nil { + subjectType := pricingConsts.SubjectType(req.SubjectType) + if err := s.validateConfigRules(subjectType, req.Rules); err != nil { return nil, err } if req.Currency == "" { @@ -277,6 +327,59 @@ func (s *pricing) SaveConfig(ctx context.Context, req *pricingDto.SaveConfigReq) return &pricingDto.SaveConfigData{Id: id}, nil } +// validateConfigRules 按 subject 类型校验 rules JSON(spec §4): +// workflow:三模式容器逐个校验已配模式;model:先解析 unit 再用对应计算器;business:per_period 计算器 +func (s *pricing) validateConfigRules(subjectType pricingConsts.SubjectType, rulesJSON string) error { + switch subjectType { + case pricingConsts.SubjectTypeWorkflow: + return s.validateWorkflowRules(rulesJSON) + case pricingConsts.SubjectTypeModel: + var cfg struct { + Unit pricingConsts.ChargeMode `json:"unit"` + } + if err := json.Unmarshal([]byte(rulesJSON), &cfg); err != nil { + return fmt.Errorf("model 费率JSON解析失败: %v", err) + } + c, err := getCalculator(subjectType, cfg.Unit) + if err != nil { + return err + } + _, err = c.validate(rulesJSON) + return err + case pricingConsts.SubjectTypeBusiness: + c, err := getCalculator(subjectType, pricingConsts.ChargeModePerPeriod) + if err != nil { + return err + } + _, err = c.validate(rulesJSON) + return err + default: + return errors.New("未知计价对象类型: " + string(subjectType)) + } +} + +// validateWorkflowRules 校验三模式容器:逐个校验已配模式(spec §4.1) +func (s *pricing) validateWorkflowRules(rulesJSON string) error { + var container map[string]json.RawMessage + if err := json.Unmarshal([]byte(rulesJSON), &container); err != nil { + return fmt.Errorf("workflow 费率JSON解析失败: %v", err) + } + if len(container) == 0 { + return errors.New("workflow 费率至少配置一种模式") + } + for mode, raw := range container { + chargeMode := pricingConsts.ChargeMode(mode) + c, err := getCalculator(pricingConsts.SubjectTypeWorkflow, chargeMode) + if err != nil { + return err + } + if _, err = c.validate(string(raw)); err != nil { + return fmt.Errorf("workflow %s 模式校验失败: %v", mode, err) + } + } + return nil +} + // GetConfig 查询计价配置 func (s *pricing) GetConfig(ctx context.Context, req *pricingDto.GetConfigReq) (*pricingDto.PricingConfigInfo, error) { cfg, err := chargeDao.PricingConfig.Get(ctx, req) @@ -284,7 +387,7 @@ func (s *pricing) GetConfig(ctx context.Context, req *pricingDto.GetConfigReq) ( return nil, err } if cfg == nil { - return nil, errors.New("计价配置不存在: " + req.BizKey) + return nil, ErrConfigNotFound } info := s.toConfigInfo(cfg) return &info, nil @@ -306,22 +409,56 @@ func (s *pricing) ListConfigs(ctx context.Context, req *pricingDto.ListConfigsRe // ====================== 内部辅助 ====================== // getEnabledConfig 获取启用中的计价配置 -func (s *pricing) getEnabledConfig(ctx context.Context, bizKey string) (*pricingEntity.PricingConfig, error) { - cfg, err := chargeDao.PricingConfig.Get(ctx, &pricingDto.GetConfigReq{BizKey: bizKey}) +func (s *pricing) getEnabledConfig(ctx context.Context, subjectType pricingConsts.SubjectType, subjectID string) (*pricingEntity.PricingConfig, error) { + cfg, err := chargeDao.PricingConfig.Get(ctx, &pricingDto.GetConfigReq{SubjectType: string(subjectType), SubjectID: subjectID}) if err != nil { return nil, err } if cfg == nil { - return nil, errors.New("计价配置不存在: " + bizKey) + return nil, ErrConfigNotFound } if cfg.Enabled != 1 { - return nil, errors.New("计价配置未启用: " + bizKey) + return nil, errors.New("计价配置未启用: " + subjectID) } return cfg, nil } -// resolveOrder 按ID或 biz_key + biz_order_no 定位计费单 -func (s *pricing) resolveOrder(ctx context.Context, orderId int64, bizKey, bizOrderNo string) (*pricingEntity.ChargeOrder, error) { +// resolveChargeMode 建单时解析实际计费方式与规则快照(spec §6 OpenOrder 校验): +// workflow:chargeMode 必填 ∈ rules 已配模式;快照 = rules[chargeMode] +// model:chargeMode 可空 = config.unit;快照 = 整个 modelCfg +// business:chargeMode 可空 = per_period;快照 = 周期规则 +func (s *pricing) resolveChargeMode(ctx context.Context, cfg *pricingEntity.PricingConfig, req *pricingDto.OpenOrderReq) (pricingConsts.ChargeMode, string, error) { + switch cfg.SubjectType { + case pricingConsts.SubjectTypeWorkflow: + if req.ChargeMode == "" { + return "", "", errors.New("workflow 建单须指定 chargeMode") + } + return pricingConsts.ChargeMode(req.ChargeMode), s.workflowRuleSnapshot(cfg.Rules, req.ChargeMode), nil + case pricingConsts.SubjectTypeModel: + // model 计费方式固定取配置 unit(cfg.Unit() 见 entity,Task 3 定义) + return cfg.Unit(), cfg.Rules, nil + case pricingConsts.SubjectTypeBusiness: + return pricingConsts.ChargeModePerPeriod, cfg.Rules, nil + default: + return "", "", errors.New("未知计价对象类型: " + string(cfg.SubjectType)) + } +} + +// workflowRuleSnapshot 从三模式容器取选中模式的规则片段(spec §4.1) +func (s *pricing) workflowRuleSnapshot(rulesJSON, mode string) string { + var container map[string]json.RawMessage + if err := json.Unmarshal([]byte(rulesJSON), &container); err != nil { + return rulesJSON + } + raw, ok := container[mode] + if !ok { + return "" + } + return string(raw) +} + +// resolveOrder 按ID或 subject_type+subject_id+biz_order_no 定位计费单 +func (s *pricing) resolveOrder(ctx context.Context, orderId int64, subjectType, subjectID, bizOrderNo string) (*pricingEntity.ChargeOrder, error) { if orderId > 0 { order, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{Id: orderId}) if err != nil { @@ -332,10 +469,10 @@ func (s *pricing) resolveOrder(ctx context.Context, orderId int64, bizKey, bizOr } return order, nil } - if bizKey == "" || bizOrderNo == "" { - return nil, errors.New("orderId 与 bizKey/bizOrderNo 须二选一") + if subjectType == "" || subjectID == "" || bizOrderNo == "" { + return nil, errors.New("orderId 与 subjectType/subjectId/bizOrderNo 须二选一") } - order, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{BizKey: bizKey, BizOrderNo: bizOrderNo}) + order, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{SubjectType: subjectType, SubjectID: subjectID, BizOrderNo: bizOrderNo}) if err != nil { return nil, err } @@ -349,7 +486,8 @@ func (s *pricing) resolveOrder(ctx context.Context, orderId int64, bizKey, bizOr func (s *pricing) toOrderInfo(e *pricingEntity.ChargeOrder) pricingDto.ChargeOrderInfo { info := pricingDto.ChargeOrderInfo{ ID: e.Id, - BizKey: e.BizKey, + SubjectType: string(e.SubjectType), + SubjectID: e.SubjectID, BizOrderNo: e.BizOrderNo, UserId: e.UserId, ChargeMode: string(e.ChargeMode), @@ -370,15 +508,14 @@ func (s *pricing) toOrderInfo(e *pricingEntity.ChargeOrder) pricingDto.ChargeOrd // toConfigInfo 计价配置实体转响应 func (s *pricing) toConfigInfo(e *pricingEntity.PricingConfig) pricingDto.PricingConfigInfo { info := pricingDto.PricingConfigInfo{ - ID: e.Id, - BizKey: e.BizKey, - Name: e.Name, - ChargeMode: string(e.ChargeMode), - Rules: e.Rules, - MinBalance: e.MinBalance, - Currency: e.Currency, - Enabled: e.Enabled, - Version: e.Version, + ID: e.Id, + SubjectType: string(e.SubjectType), + SubjectID: e.SubjectID, + Rules: e.Rules, + MinBalance: e.MinBalance, + Currency: e.Currency, + Enabled: e.Enabled, + Version: e.Version, } if e.CreatedAt != nil { info.CreatedAt = e.CreatedAt.String()