/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ package service import ( "context" "math" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/model" relaycommon "github.com/QuantumNous/new-api/relay/common" "github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/QuantumNous/new-api/types" ) // profitShareExtraTotalTokensKey 由 RecalculateTaskQuotaByTokens 传入,用于异步 token 补扣的加价比例推算;不入库日志。 const profitShareExtraTotalTokensKey = "_profit_share_total_tokens" // TryPostWalletProfitShareForTaskBilledQuota 任务完成侧已知「最终对用户计费额度」后入账利润分成(含预扣与实际一致、操练场等无补扣 delta 的场景)。 // 提交阶段预扣结算不调用本函数,由轮询/fetch 完成路径统一触发。 func TryPostWalletProfitShareForTaskBilledQuota(ctx context.Context, task *model.Task, billedQuota int, hintTotalTokens int) { _ = ctx if task == nil || billedQuota <= 0 { return } if !common.IsDistributorProfitShareMode() { return } bs := strings.TrimSpace(task.PrivateData.BillingSource) if bs != "" && bs != BillingSourceWallet { return } ratio, ok := taskProfitShareMarkupSliceRatio(task, hintTotalTokens) if !ok || ratio <= 0 { return } slice := int(math.Round(float64(billedQuota) * ratio)) if slice <= 0 { return } invitee, err := model.GetUserById(task.UserId, false) if err != nil || invitee == nil || invitee.InviterId <= 0 { return } inviter, err2 := model.GetUserById(invitee.InviterId, false) if err2 != nil || inviter == nil || !model.UserIsDistributor(inviter) { return } modelName := strings.TrimSpace(taskModelName(task)) bps := model.EffectiveAffiliateCommissionBps(inviter, task.UserId) if bps <= 0 { return } maxBps := 10000 if bps > maxBps { bps = maxBps } reward := int(int64(slice) * int64(bps) / int64(maxBps)) if reward <= 0 { return } if err := model.CreditDistributorProfitShare(invitee.InviterId, task.UserId, task.ChannelId, modelName, billedQuota, slice, reward, bps); err != nil { common.SysError("TryPostWalletProfitShareForTaskBilledQuota: " + err.Error()) } } func taskProfitShareMarkupSliceRatio(task *model.Task, hintTotalTokens int) (float64, bool) { if task == nil { return 0, false } ch, err := model.CacheGetChannel(task.ChannelId) if err != nil || ch == nil { return 0, false } if constant.IsVideoTaskChannel(ch.Type) { if r, ok := taskProfitShareMarkupRatioVideoComplete(task); ok { return r, true } if r, ok := taskProfitShareMarkupRatioVideoSubmitInput(task); ok { return r, true } } if hintTotalTokens > 0 { if r, ok := taskProfitShareMarkupRatioFromUpstreamTokens(task, hintTotalTokens); ok { return r, true } } if r, ok := taskProfitShareMarkupRatioPerCallModelPrice(task); ok { return r, true } return 0, false } func taskProfitShareMarkupRatioVideoComplete(task *model.Task) (float64, bool) { meta, ok := extractVideoMetadataFromTaskData(task) if !ok { return 0, false } modelName := strings.TrimSpace(taskModelName(task)) if modelName == "" { return 0, false } mode := detectTaskVideoBillingMode(task) channelPerSec := channelVideoPerSecondUSD(task.ChannelId, modelName, mode, meta.Width, meta.Height, meta.HasAudio) if channelPerSec <= 0 { return 0, false } seconds := int(math.Ceil(meta.DurationSec)) if seconds <= 0 { return 0, false } groupRatio := 1.0 if task.PrivateData.BillingContext != nil && task.PrivateData.BillingContext.GroupRatio > 0 { groupRatio = task.PrivateData.BillingContext.GroupRatio } costDisc := channelCostDiscountPercentFromTask(task) markup := model.ResolveEffectiveMarkupDiscountPercentForInviteeBilling(task.UserId, task.ChannelId, modelName) globalPerSec := globalVideoPerSecondUSD(modelName, mode, meta.Width, meta.Height, meta.HasAudio) effW := effectiveVideoPerSecondUSD(channelPerSec, globalPerSec, costDisc, markup) eff0 := effectiveVideoPerSecondUSD(channelPerSec, globalPerSec, costDisc, 0) qW := int(math.Round(float64(seconds) * effW * common.QuotaPerUnit * groupRatio)) q0 := int(math.Round(float64(seconds) * eff0 * common.QuotaPerUnit * groupRatio)) if qW <= 0 || qW <= q0 { return 0, false } return float64(qW-q0) / float64(qW), true } func taskProfitShareMarkupRatioVideoSubmitInput(task *model.Task) (float64, bool) { if task == nil || strings.TrimSpace(task.Properties.Input) == "" { return 0, false } var req relaycommon.TaskSubmitReq if err := common.UnmarshalJsonStr(task.Properties.Input, &req); err != nil { return 0, false } ri, ok := relayInfoSnapshotForProfitShare(task) if !ok { return 0, false } qW := calcVideoPerSecondQuotaFromTaskReq(ri, &req, ri.PriceData.MarkupDiscountPercent) q0 := calcVideoPerSecondQuotaFromTaskReq(ri, &req, 0) if qW <= 0 || qW <= q0 { return 0, false } return float64(qW-q0) / float64(qW), true } func taskProfitShareMarkupRatioFromUpstreamTokens(task *model.Task, totalTokens int) (float64, bool) { if task == nil || totalTokens <= 0 { return 0, false } ri, ok := relayInfoSnapshotForProfitShare(task) if !ok { return 0, false } globalMr, globalOK, _ := ratio_setting.GetModelRatio(ri.OriginModelName) if !globalOK { globalMr = ri.PriceData.GlobalModelRatio } if globalMr <= 0 { globalMr = ri.PriceData.GlobalModelRatio } pd := ri.PriceData pd.GlobalModelRatio = globalMr ri.PriceData = pd qW := calcQuotaByUpstreamTokensWithMarkup(ri, totalTokens, pd.MarkupDiscountPercent) q0 := calcQuotaByUpstreamTokensWithMarkup(ri, totalTokens, 0) if qW <= 0 || qW <= q0 { return 0, false } return float64(qW-q0) / float64(qW), true } func taskProfitShareMarkupRatioPerCallModelPrice(task *model.Task) (float64, bool) { bc := task.PrivateData.BillingContext if bc == nil || bc.ModelPrice <= 0 { return 0, false } modelName := strings.TrimSpace(taskModelName(task)) if modelName == "" { return 0, false } costDisc := channelCostDiscountPercentFromTask(task) markup := model.ResolveEffectiveMarkupDiscountPercentForInviteeBilling(task.UserId, task.ChannelId, modelName) globalPrice, _ := ratio_setting.GetModelPrice(modelName, false) effW := model.EffectiveModelPrice(bc.ModelPrice, globalPrice, costDisc, markup) eff0 := model.EffectiveModelPrice(bc.ModelPrice, globalPrice, costDisc, 0) if effW <= 0 || effW <= eff0 { return 0, false } return (effW - eff0) / effW, true } func channelCostDiscountPercentFromTask(task *model.Task) float64 { if task == nil { return 100 } if bc := task.PrivateData.BillingContext; bc != nil && bc.ChannelPriceDiscountPercent > 0 { return bc.ChannelPriceDiscountPercent } return model.ResolveChannelPriceDiscountPercent(task.ChannelId) } func relayInfoSnapshotForProfitShare(task *model.Task) (*relaycommon.RelayInfo, bool) { if task == nil { return nil, false } ch, err := model.CacheGetChannel(task.ChannelId) if err != nil || ch == nil { return nil, false } bc := task.PrivateData.BillingContext if bc == nil { return nil, false } modelName := strings.TrimSpace(taskModelName(task)) if modelName == "" { return nil, false } markup := model.ResolveEffectiveMarkupDiscountPercentForInviteeBilling(task.UserId, task.ChannelId, modelName) cost := bc.ChannelPriceDiscountPercent if cost <= 0 { cost = model.ResolveChannelPriceDiscountPercent(task.ChannelId) } gr := bc.GroupRatio if gr <= 0 { gr = 1 } globalMr, globalOK, _ := ratio_setting.GetModelRatio(modelName) if !globalOK { globalMr = 0 } ri := &relaycommon.RelayInfo{ UserId: task.UserId, OriginModelName: modelName, BillingSource: task.PrivateData.BillingSource, ChannelMeta: &relaycommon.ChannelMeta{ChannelType: ch.Type, ChannelId: task.ChannelId}, } other := bc.OtherRatios if other != nil { cp := make(map[string]float64, len(other)) for k, v := range other { cp[k] = v } other = cp } ri.PriceData = types.PriceData{ ModelPrice: bc.ModelPrice, ModelRatio: bc.ModelRatio, GlobalModelRatio: globalMr, GroupRatioInfo: types.GroupRatioInfo{GroupRatio: gr}, UsePrice: true, CostDiscountPercent: cost, MarkupDiscountPercent: markup, OtherRatios: other, VideoOutputTokens: 0, } return ri, true }