package distribution import ( "errors" "math" ) const ( ModeTwoPartyDistributorReceive = "two_party_distributor_receive" ModeTwoPartyPlatformReceive = "two_party_platform_receive" ModeThreeParty = "three_party" ServiceFeeUnitPercent = "percent" ServiceFeeUnitFixedAmount = "fixed_amount" ServiceFeeUnitRatio = "ratio" ) 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") ) // Rule 是后台可编辑的分账规则。比例字段按参与方角色命名,避免方向歧义。 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 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 返回文档中约定的第一版默认值;调用方应将其写入规则表而非写死到业务流程。 func DefaultRule(mode string) (Rule, error) { base := Rule{ Mode: mode, ServiceFeeValue: 2.5, ServiceFeeUnit: ServiceFeeUnitRatio, PaymentFeeBPS: 50, TaxEnabled: false, TaxRateBPS: 100, } switch mode { case ModeTwoPartyDistributorReceive: base.DistributorReceiveRatio = 3.5 base.PlatformSellRatio = 4 case ModeTwoPartyPlatformReceive: base.PlatformReceiveRatio = 3.5 base.DistributorSellRatio = 4 case ModeThreeParty: base.DistributorARatio = 3.5 base.DistributorBRatio = 4 default: return Rule{}, ErrUnsupportedMode } return base, nil } // CalculateSettlement 先扣支付手续费,再计算服务费和分成基数;退款金额不能超过支付金额。 func CalculateSettlement(rule Rule, input SettlementInput) (SettlementResult, error) { if input.GrossPaidAmountCent < 0 || input.RefundAmountCent < 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) serviceFee, err := serviceFeeCent(rule, netPaid) if err != nil { return SettlementResult{}, err } splitBase := netPaid - paymentFee - serviceFee if splitBase < 0 { splitBase = 0 } result := SettlementResult{ GrossPaidAmountCent: input.GrossPaidAmountCent, RefundAmountCent: input.RefundAmountCent, NetPaidAmountCent: netPaid, PaymentFeeCent: paymentFee, ServiceFeeCent: serviceFee, MarkupAmountCent: input.MarkupAmountCent, SplitBaseAmountCent: splitBase, } if rule.TaxEnabled { result.TaxByParty = make([]PartyTaxResult, 0, len(input.GrossProfitByParty)) for _, party := range input.GrossProfitByParty { profit := party.IncomeAmountCent - party.CostAmountCent tax := roundHalfUp(float64(max(profit, 0)) * float64(rule.TaxRateBPS) / 10000) result.TaxAmountCent += tax result.TaxByParty = append(result.TaxByParty, PartyTaxResult{PartyRole: party.PartyRole, GrossProfitCent: profit, TaxAmountCent: tax}) } } return result, nil } func serviceFeeCent(rule Rule, netPaid int64) (int64, error) { switch rule.ServiceFeeUnit { case ServiceFeeUnitPercent: return roundHalfUp(float64(netPaid) * 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 max(a, b int64) int64 { if a > b { return a } return b }