feat(pricing): OpenOrder/SaveConfig 按 subject 类型校验 + per_token 按模型实时价结算

- service 层全量切换 subject_type+subject_id 键(去除 BizKey 残留)
- OpenOrder 按 subject 类型 resolveChargeMode + 规则快照 + 门禁 + 幂等
- SaveConfig/GetConfig 按 subject 类型校验 rules 结构
- settleOrder 支持 per_token 分支(calcPerTokenOrder 按模型实时价)
- 修复 model 计算器:P1 nil price 校验、P2 unit/base 交叉校验、M3 cached>prompt 负成本钳位

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-28 17:56:00 +08:00
co-authored by Claude
parent 2e8a3663a5
commit e9da4c1d78
3 changed files with 265 additions and 51 deletions
+21 -1
View File
@@ -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=1000per_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
}
+57
View File
@@ -130,3 +130,60 @@ func TestCalcChargeBySubjectType(t *testing.T) {
t.Fatalf("model per_second got %v want 1.98, err %v", got, err)
}
}
// P1validate 拒绝缺 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")
}
}
// P1validate 拒绝缺 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")
}
}
// P2modelTokenCalculator.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)
}
}
// M3cached > 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 钳 00 + 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)
}
}
+187 -50
View File
@@ -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 JSONspec §4):
// workflow:三模式容器逐个校验已配模式;model:先解析 unit 再用对应计算器;businessper_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 校验):
// workflowchargeMode 必填 ∈ rules 已配模式;快照 = rules[chargeMode]
// modelchargeMode 可空 = config.unit;快照 = 整个 modelCfg
// businesschargeMode 可空 = 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 计费方式固定取配置 unitcfg.Unit() 见 entityTask 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()