package listing import ( "context" "encoding/json" "math" "sort" "strings" ) const salePriceConfigKey = "listing.sale_price_config" type salePriceConfig struct { FixedMarkupRules []saleFixedMarkupRule `json:"fixed_markup_rules"` RatioAdjustmentRules []saleRatioAdjustmentRule `json:"ratio_adjustment_rules"` } type saleFixedMarkupRule struct { MinM float64 `json:"min_m"` MaxM float64 `json:"max_m"` MarkupAmount float64 `json:"markup_amount"` } type saleRatioAdjustmentRule struct { MinM float64 `json:"min_m"` MaxM float64 `json:"max_m"` RatioSubtract float64 `json:"ratio_subtract"` } type externalPlatformPricing struct { BuyerCoinBasePrice float64 BuyerTotalPrice float64 BuyerRatio float64 PlatformMarkupPrice float64 RuleType string } func (s *Service) externalSalePriceConfig(ctx context.Context) (salePriceConfig, error) { config := defaultSalePriceConfig() if s.config == nil { return config, nil } value, err := s.config.FindValue(ctx, salePriceConfigKey) if err != nil { return config, err } if strings.TrimSpace(value) == "" { return config, nil } if err := json.Unmarshal([]byte(value), &config); err != nil { return defaultSalePriceConfig(), nil } return normalizeSalePriceConfig(config), nil } func defaultSalePriceConfig() salePriceConfig { return salePriceConfig{ FixedMarkupRules: []saleFixedMarkupRule{ {MinM: 10, MaxM: 30, MarkupAmount: 28}, {MinM: 30, MaxM: 50, MarkupAmount: 31}, {MinM: 50, MaxM: 70, MarkupAmount: 34}, {MinM: 70, MaxM: 90, MarkupAmount: 38}, }, RatioAdjustmentRules: []saleRatioAdjustmentRule{ {MinM: 90, MaxM: 150, RatioSubtract: 5}, {MinM: 150, MaxM: 230, RatioSubtract: 4}, {MinM: 230, MaxM: 310, RatioSubtract: 3.5}, {MinM: 310, MaxM: 390, RatioSubtract: 3}, {MinM: 390, MaxM: 470, RatioSubtract: 0}, {MinM: 470, MaxM: 550, RatioSubtract: 0}, {MinM: 550, RatioSubtract: 0}, }, } } func normalizeSalePriceConfig(config salePriceConfig) salePriceConfig { config.FixedMarkupRules = filterFixedMarkupRules(config.FixedMarkupRules) config.RatioAdjustmentRules = filterRatioAdjustmentRules(config.RatioAdjustmentRules) if len(config.FixedMarkupRules) == 0 && len(config.RatioAdjustmentRules) == 0 { return defaultSalePriceConfig() } return config } func filterFixedMarkupRules(rules []saleFixedMarkupRule) []saleFixedMarkupRule { result := make([]saleFixedMarkupRule, 0, len(rules)) for _, rule := range rules { if !isFiniteNonNegative(rule.MinM) || !isFiniteNonNegative(rule.MaxM) || !isFiniteNonNegative(rule.MarkupAmount) || (rule.MaxM > 0 && rule.MaxM < rule.MinM) { continue } result = append(result, rule) } return result } func filterRatioAdjustmentRules(rules []saleRatioAdjustmentRule) []saleRatioAdjustmentRule { result := make([]saleRatioAdjustmentRule, 0, len(rules)) for _, rule := range rules { if !isFiniteNonNegative(rule.MinM) || !isFiniteNonNegative(rule.MaxM) || !isFiniteNonNegative(rule.RatioSubtract) || (rule.MaxM > 0 && rule.MaxM < rule.MinM) { continue } result = append(result, rule) } return result } func isFiniteNonNegative(value float64) bool { return value >= 0 && !math.IsNaN(value) && !math.IsInf(value, 0) } // calculateExternalPlatformPricing 将外部回收租金视为纯币价格;额外物品与平台加价均单独计算。 func calculateExternalPlatformPricing( coinM float64, sellerRatio float64, sellerCoinBasePrice float64, consumablePrice float64, config salePriceConfig, ) externalPlatformPricing { sellerCoinBasePrice = nonNegativeMoney(sellerCoinBasePrice) consumablePrice = nonNegativeMoney(consumablePrice) sellerTotalPrice := roundMoney(sellerCoinBasePrice + consumablePrice) coinWan := coinM * 100 sellerRatio = roundRatio(sellerRatio) if sellerRatio <= 0 && coinWan > 0 && sellerCoinBasePrice > 0 { sellerRatio = roundRatio(coinWan / sellerCoinBasePrice) } if rule, ok := findRatioAdjustmentRule(config.RatioAdjustmentRules, coinM); ok && sellerRatio > 0 { buyerRatio := sellerRatio - rule.RatioSubtract if buyerRatio > 0 && coinWan > 0 { buyerCoinBasePrice := roundMoney(coinWan / buyerRatio) if buyerCoinBasePrice < sellerCoinBasePrice { buyerCoinBasePrice = sellerCoinBasePrice } return buildExternalPlatformPricing( coinWan, sellerTotalPrice, consumablePrice, buyerCoinBasePrice, "ratio_subtract", ) } } if rule, ok := findFixedMarkupRule(config.FixedMarkupRules, coinM); ok { return buildExternalPlatformPricing( coinWan, sellerTotalPrice, consumablePrice, roundMoney(sellerCoinBasePrice+rule.MarkupAmount), "fixed_markup", ) } return buildExternalPlatformPricing( coinWan, sellerTotalPrice, consumablePrice, sellerCoinBasePrice, "none", ) } func nonNegativeMoney(value float64) float64 { if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) { return 0 } return roundMoney(value) } func buildExternalPlatformPricing( coinWan float64, sellerTotalPrice float64, consumablePrice float64, buyerCoinBasePrice float64, ruleType string, ) externalPlatformPricing { buyerCoinBasePrice = nonNegativeMoney(buyerCoinBasePrice) buyerTotalPrice := roundMoney(buyerCoinBasePrice + consumablePrice) return externalPlatformPricing{ BuyerCoinBasePrice: buyerCoinBasePrice, BuyerTotalPrice: buyerTotalPrice, BuyerRatio: roundRatio(coinWan / buyerCoinBasePrice), PlatformMarkupPrice: roundMoney(buyerTotalPrice - sellerTotalPrice), RuleType: ruleType, } } func findFixedMarkupRule(rules []saleFixedMarkupRule, coinM float64) (saleFixedMarkupRule, bool) { sorted := append([]saleFixedMarkupRule(nil), rules...) sort.Slice(sorted, func(i, j int) bool { return sorted[i].MinM < sorted[j].MinM }) for index, rule := range sorted { if isCoinInPriceRuleRange(rule.MinM, rule.MaxM, index, len(sorted), coinM, true, false) { return rule, true } } return saleFixedMarkupRule{}, false } func findRatioAdjustmentRule(rules []saleRatioAdjustmentRule, coinM float64) (saleRatioAdjustmentRule, bool) { sorted := append([]saleRatioAdjustmentRule(nil), rules...) sort.Slice(sorted, func(i, j int) bool { return sorted[i].MinM < sorted[j].MinM }) for index, rule := range sorted { if isCoinInPriceRuleRange(rule.MinM, rule.MaxM, index, len(sorted), coinM, false, true) { return rule, true } } return saleRatioAdjustmentRule{}, false } func isCoinInPriceRuleRange( minM float64, maxM float64, index int, total int, coinM float64, includeLastMax bool, excludeFirstMin bool, ) bool { minMatched := coinM >= minM if excludeFirstMin && index == 0 { minMatched = coinM > minM } if !minMatched { return false } if maxM <= 0 || coinM < maxM { return true } return includeLastMax && index == total-1 && coinM <= maxM }