package distribution import ( "errors" "math" ) const ( ModeTwoPartyDistributorReceive = "two_party_distributor_receive" ModeTwoPartyPlatformReceive = "two_party_platform_receive" ModeThreeParty = "three_party" // ModeSiteSelfSell 是分站卖本站货,不复用两方默认规则。 ModeSiteSelfSell = "site_self_sell" PartyTypeSite = "site" PartyTypePlatform = "platform" ServiceFeeUnitPercent = "percent" ServiceFeeUnitFixedAmount = "fixed_amount" ServiceFeeUnitRatio = "ratio" DefaultReceivePercent = 35.0 DefaultSellPercent = 40.0 DefaultServicePercent = 25.0 ) var ( ErrInvalidAmount = errors.New("invalid distribution amount") ErrInvalidRate = errors.New("invalid distribution rate") ErrInvalidServiceUnit = errors.New("invalid service fee unit") ErrUnsupportedMode = errors.New("unsupported distribution mode") ErrInsufficientBalance = errors.New("insufficient wallet balance") ErrSyncInProgress = errors.New("distribution site accounts sync already in progress") ) // Rule 是后台可编辑的分账规则。比例字段存百分数(35/40/25),按参与方角色命名。 type Rule struct { Mode string PlatformReceiveRatio float64 PlatformSellRatio float64 DistributorReceiveRatio float64 DistributorSellRatio float64 DistributorARatio float64 DistributorBRatio float64 ServiceFeeValue float64 ServiceFeeUnit string PaymentFeeBPS int64 TaxEnabled bool TaxRateBPS int64 } // SettlementInput 使用支付单和退款单的整数分快照计算分销费用。 type SettlementInput struct { GrossPaidAmountCent int64 RefundAmountCent int64 OwnerRentAmountCent int64 MarkupAmountCent int64 GrossProfitByParty []PartyProfitInput } type PartyProfitInput struct { PartyRole string IncomeAmountCent int64 CostAmountCent int64 } type PartyTaxResult struct { PartyRole string `json:"party_role"` GrossProfitCent int64 `json:"gross_profit_cent"` TaxAmountCent int64 `json:"tax_amount_cent"` } type SettlementResult struct { GrossPaidAmountCent int64 RefundAmountCent int64 NetPaidAmountCent int64 PaymentFeeCent int64 ServiceFeeCent int64 MarkupAmountCent int64 SplitBaseAmountCent int64 TaxAmountCent int64 TaxByParty []PartyTaxResult } // DefaultRule 返回已确认的默认百分比:收号 35%、卖出 40%、服务费 25%。 func DefaultRule(mode string) (Rule, error) { base := Rule{ Mode: mode, ServiceFeeValue: DefaultServicePercent, ServiceFeeUnit: ServiceFeeUnitPercent, PaymentFeeBPS: 50, TaxEnabled: false, TaxRateBPS: 100, } switch mode { case ModeTwoPartyDistributorReceive: base.DistributorReceiveRatio = DefaultReceivePercent base.PlatformSellRatio = DefaultSellPercent case ModeTwoPartyPlatformReceive: base.PlatformReceiveRatio = DefaultReceivePercent base.DistributorSellRatio = DefaultSellPercent case ModeThreeParty: base.DistributorARatio = DefaultReceivePercent base.DistributorBRatio = DefaultSellPercent case ModeSiteSelfSell: base.DistributorSellRatio = DefaultReceivePercent + DefaultSellPercent default: return Rule{}, ErrUnsupportedMode } return base, nil } // ValidateRule 校验模式,并在百分比服务费下要求收号+卖出+服务费合计为 100。 func ValidateRule(rule Rule) error { normalizeRulePercents(&rule) if _, err := DefaultRule(rule.Mode); err != nil { return ErrUnsupportedMode } unit := rule.ServiceFeeUnit if unit == "" || unit == ServiceFeeUnitRatio { unit = ServiceFeeUnitPercent } if unit != ServiceFeeUnitPercent && unit != ServiceFeeUnitFixedAmount { return ErrInvalidServiceUnit } if unit != ServiceFeeUnitPercent { return nil } var sum float64 switch rule.Mode { case ModeTwoPartyDistributorReceive: sum = rule.DistributorReceiveRatio + rule.PlatformSellRatio + rule.ServiceFeeValue case ModeTwoPartyPlatformReceive: sum = rule.PlatformReceiveRatio + rule.DistributorSellRatio + rule.ServiceFeeValue case ModeThreeParty: sum = rule.DistributorARatio + rule.DistributorBRatio + rule.ServiceFeeValue case ModeSiteSelfSell: sum = rule.DistributorSellRatio + rule.ServiceFeeValue } if math.Abs(sum-100) > 0.15 { return ErrInvalidRate } return nil } // NormalizeSplitPercent 把口头 3.5/4/2.5 换成 35/40/25;已经是百分数的原样保留。 func NormalizeSplitPercent(value float64) float64 { if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) { return 0 } if value <= 10 { return math.Round(value*10*10) / 10 } return math.Round(value*10) / 10 } func normalizeRulePercents(row *Rule) { if row == nil { return } row.PlatformReceiveRatio = NormalizeSplitPercent(row.PlatformReceiveRatio) row.PlatformSellRatio = NormalizeSplitPercent(row.PlatformSellRatio) row.DistributorReceiveRatio = NormalizeSplitPercent(row.DistributorReceiveRatio) row.DistributorSellRatio = NormalizeSplitPercent(row.DistributorSellRatio) row.DistributorARatio = NormalizeSplitPercent(row.DistributorARatio) row.DistributorBRatio = NormalizeSplitPercent(row.DistributorBRatio) if row.ServiceFeeUnit == ServiceFeeUnitRatio || row.ServiceFeeUnit == "" { row.ServiceFeeUnit = ServiceFeeUnitPercent } if row.ServiceFeeUnit == ServiceFeeUnitPercent { row.ServiceFeeValue = NormalizeSplitPercent(row.ServiceFeeValue) } } // CalculateSettlement 先扣号主应得与分站加价,得到利润后再按 35/40/25 切。 // 支付通道手续费由主站承担:只记录金额供对账,不再从分成基数中扣除。 func CalculateSettlement(rule Rule, input SettlementInput) (SettlementResult, error) { if input.GrossPaidAmountCent < 0 || input.RefundAmountCent < 0 || input.OwnerRentAmountCent < 0 || input.MarkupAmountCent < 0 || input.RefundAmountCent > input.GrossPaidAmountCent { return SettlementResult{}, ErrInvalidAmount } if rule.PaymentFeeBPS < 0 || rule.PaymentFeeBPS > 10000 || rule.TaxRateBPS < 0 || rule.TaxRateBPS > 10000 || rule.ServiceFeeValue < 0 { return SettlementResult{}, ErrInvalidRate } if rule.ServiceFeeUnit != ServiceFeeUnitPercent && rule.ServiceFeeUnit != ServiceFeeUnitFixedAmount && rule.ServiceFeeUnit != ServiceFeeUnitRatio { return SettlementResult{}, ErrInvalidServiceUnit } netPaid := input.GrossPaidAmountCent - input.RefundAmountCent paymentFee := roundHalfUp(float64(netPaid) * float64(rule.PaymentFeeBPS) / 10000) effectiveMarkup := EffectiveMarkupCent(netPaid, input.OwnerRentAmountCent, input.MarkupAmountCent) profit := netPaid - input.OwnerRentAmountCent - effectiveMarkup if profit < 0 { profit = 0 } serviceFee, err := serviceFeeCent(rule, profit, netPaid) if err != nil { return SettlementResult{}, err } if serviceFee > profit { serviceFee = profit } result := SettlementResult{ GrossPaidAmountCent: input.GrossPaidAmountCent, RefundAmountCent: input.RefundAmountCent, NetPaidAmountCent: netPaid, PaymentFeeCent: paymentFee, ServiceFeeCent: serviceFee, MarkupAmountCent: effectiveMarkup, SplitBaseAmountCent: profit, } if rule.TaxEnabled { result.TaxByParty = make([]PartyTaxResult, 0, len(input.GrossProfitByParty)) for _, party := range input.GrossProfitByParty { profitParty := party.IncomeAmountCent - party.CostAmountCent tax := roundHalfUp(float64(maxInt64(profitParty, 0)) * float64(rule.TaxRateBPS) / 10000) result.TaxAmountCent += tax result.TaxByParty = append(result.TaxByParty, PartyTaxResult{PartyRole: party.PartyRole, GrossProfitCent: profitParty, TaxAmountCent: tax}) } } return result, nil } func serviceFeeCent(rule Rule, profit, netPaid int64) (int64, error) { switch rule.ServiceFeeUnit { case ServiceFeeUnitPercent: return roundHalfUp(float64(profit) * rule.ServiceFeeValue / 100), nil case ServiceFeeUnitFixedAmount: return roundHalfUp(rule.ServiceFeeValue * 100), nil case ServiceFeeUnitRatio: if rule.ServiceFeeValue <= 0 { return 0, nil } return roundHalfUp(float64(netPaid) / rule.ServiceFeeValue), nil default: return 0, ErrInvalidServiceUnit } } func roundHalfUp(value float64) int64 { return int64(math.Floor(value + 0.5)) } func maxInt64(a, b int64) int64 { if a > b { return a } return b } func minInt64(a, b int64) int64 { if a < b { return a } return b } // EffectiveMarkupCent 按剩余净付款压缩加价:全额退租金后卖方不得再留加价。 func EffectiveMarkupCent(netPaid, ownerRent, markup int64) int64 { if markup <= 0 { return 0 } remain := netPaid - ownerRent if remain <= 0 { return 0 } return minInt64(markup, remain) } // PartySplit 是一次分成拆分中单个参与方的金额。 type PartySplit struct { PartyRole string Ratio float64 AmountCent int64 } // SplitSettlement 按百分数切主站提价利润(收号 35%、卖出 40%),并将分站自主加价溢价(markupCent)100% 独立全额归入卖出方。 // 平台服务费 25% 记在财务快照,不并进参与方。 func SplitSettlement(rule Rule, profitCent int64, markupCent int64) []PartySplit { roles := []struct { role string ratio float64 }{} switch rule.Mode { case ModeTwoPartyDistributorReceive: roles = []struct { role string ratio float64 }{{"distributor_receive", rule.DistributorReceiveRatio}, {"platform_sell", rule.PlatformSellRatio}} case ModeTwoPartyPlatformReceive: roles = []struct { role string ratio float64 }{{"distributor_sell", rule.DistributorSellRatio}, {"platform_receive", rule.PlatformReceiveRatio}} case ModeThreeParty: roles = []struct { role string ratio float64 }{{"distributor_a", rule.DistributorARatio}, {"distributor_b", rule.DistributorBRatio}} case ModeSiteSelfSell: roles = []struct { role string ratio float64 }{{"distributor_sell", rule.DistributorSellRatio}} default: return nil } splits := make([]PartySplit, 0, len(roles)) for _, item := range roles { amount := roundHalfUp(float64(profitCent) * item.ratio / 100) splits = append(splits, PartySplit{PartyRole: item.role, Ratio: item.ratio, AmountCent: maxInt64(amount, 0)}) } if markupCent > 0 { sellerRole := "distributor_sell" switch rule.Mode { case ModeTwoPartyDistributorReceive: sellerRole = "platform_sell" case ModeThreeParty: sellerRole = "distributor_b" } for i := range splits { if splits[i].PartyRole == sellerRole { splits[i].AmountCent += markupCent } } } return splits } // SplitRemainderCent 返回主站提价利润扣除各方提价分成后的剩余(未扣服务费)。 func SplitRemainderCent(splitBase int64, splits []PartySplit, markupCent int64) int64 { sum := int64(0) for _, item := range splits { sum += item.AmountCent } profitSplitSum := sum - markupCent return splitBase - profitSplitSum } // SplitRoundingCent 返回提价利润扣完收号、卖出和服务费后的四舍五入尾差。 func SplitRoundingCent(profitCent, serviceFeeCent, markupCent int64, splits []PartySplit) int64 { return SplitRemainderCent(profitCent, splits, markupCent) - serviceFeeCent }