Files
yml2213andCursor 768902f2f7 feat(distribution): 补齐退款加价回退、本站自卖与开通默认规则
退款时压缩卖方加价,人工/自动批次仅完结单可入账,号主收入进主站钱包;仲裁后重算释放并支持作废未入账 DS。开通时四种模式按默认生效,对外口径合并为 35/40/25,不再单独展示通道费。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 18:13:46 +08:00

171 lines
6.5 KiB
Go

package distribution
import "testing"
func TestDefaultRules(t *testing.T) {
cases := []struct {
mode string
check func(Rule) bool
}{
{ModeTwoPartyDistributorReceive, func(r Rule) bool { return r.DistributorReceiveRatio == 35 && r.PlatformSellRatio == 40 }},
{ModeTwoPartyPlatformReceive, func(r Rule) bool { return r.PlatformReceiveRatio == 35 && r.DistributorSellRatio == 40 }},
{ModeThreeParty, func(r Rule) bool { return r.DistributorARatio == 35 && r.DistributorBRatio == 40 }},
{ModeSiteSelfSell, func(r Rule) bool { return r.DistributorSellRatio == 75 && r.ServiceFeeValue == 25 }},
}
for _, item := range cases {
rule, err := DefaultRule(item.mode)
if err != nil || !item.check(rule) || rule.ServiceFeeValue != 25 || rule.ServiceFeeUnit != ServiceFeeUnitPercent || rule.PaymentFeeBPS != 50 {
t.Fatalf("default rule %s = %#v, err=%v", item.mode, rule, err)
}
}
if _, err := DefaultRule("unknown_mode"); err != ErrUnsupportedMode {
t.Fatalf("unknown mode must be unsupported, err=%v", err)
}
}
func TestNormalizeSplitPercent(t *testing.T) {
if got := NormalizeSplitPercent(3.5); got != 35 {
t.Fatalf("3.5 -> %v, want 35", got)
}
if got := NormalizeSplitPercent(4); got != 40 {
t.Fatalf("4 -> %v, want 40", got)
}
if got := NormalizeSplitPercent(2.5); got != 25 {
t.Fatalf("2.5 -> %v, want 25", got)
}
if got := NormalizeSplitPercent(35); got != 35 {
t.Fatalf("35 stays %v", got)
}
}
func TestCalculateSettlementSplitsProfitWithoutPaymentFee(t *testing.T) {
rule := Rule{ServiceFeeValue: 25, ServiceFeeUnit: ServiceFeeUnitPercent, PaymentFeeBPS: 50}
got, err := CalculateSettlement(rule, SettlementInput{GrossPaidAmountCent: 10000, OwnerRentAmountCent: 6000})
if err != nil {
t.Fatal(err)
}
// 通道费 50 由主站承担(仅记录),利润 10000-6000=4000,服务费 25%=1000
if got.NetPaidAmountCent != 10000 || got.PaymentFeeCent != 50 || got.SplitBaseAmountCent != 4000 || got.ServiceFeeCent != 1000 {
t.Fatalf("unexpected settlement: %#v", got)
}
}
func TestCalculateSettlementTaxesEachPositivePartyProfit(t *testing.T) {
rule := Rule{ServiceFeeUnit: ServiceFeeUnitFixedAmount, PaymentFeeBPS: 0, TaxEnabled: true, TaxRateBPS: 100}
got, err := CalculateSettlement(rule, SettlementInput{
GrossPaidAmountCent: 10000,
GrossProfitByParty: []PartyProfitInput{
{PartyRole: "distributor_a", IncomeAmountCent: 1000, CostAmountCent: 400},
{PartyRole: "distributor_b", IncomeAmountCent: 500, CostAmountCent: 700},
},
})
if err != nil {
t.Fatal(err)
}
if got.TaxAmountCent != 6 || len(got.TaxByParty) != 2 || got.TaxByParty[0].TaxAmountCent != 6 || got.TaxByParty[1].TaxAmountCent != 0 {
t.Fatalf("unexpected tax result: %#v", got)
}
}
func TestCalculateSettlementRejectsRefundOverPayment(t *testing.T) {
_, err := CalculateSettlement(Rule{ServiceFeeUnit: ServiceFeeUnitFixedAmount}, SettlementInput{GrossPaidAmountCent: 1, RefundAmountCent: 2})
if err != ErrInvalidAmount {
t.Fatalf("err = %v", err)
}
}
func TestSplitSettlementTwoPartyDistributorReceive(t *testing.T) {
rule := Rule{Mode: ModeTwoPartyDistributorReceive, DistributorReceiveRatio: 35, PlatformSellRatio: 40}
splits := SplitSettlement(rule, 3950, 0)
if len(splits) != 2 {
t.Fatalf("splits = %#v", splits)
}
if splits[0].PartyRole != "distributor_receive" || splits[0].AmountCent != 1383 {
t.Fatalf("distributor split = %#v", splits[0])
}
if splits[1].PartyRole != "platform_sell" || splits[1].AmountCent != 1580 {
t.Fatalf("platform split = %#v", splits[1])
}
if got := SplitRoundingCent(3950, 988, 0, splits); got != -1 {
t.Fatalf("rounding = %d, want -1", got)
}
}
func TestSplitSettlementAddsShelfMarkupDirectlyToSeller(t *testing.T) {
rule := Rule{Mode: ModeTwoPartyPlatformReceive, DistributorSellRatio: 40, PlatformReceiveRatio: 35}
// 提价利润 10000 分,分站自主加价 2000 分
splits := SplitSettlement(rule, 10000, 2000)
// 卖方分站获得 40% (4000) + 2000 (加价) = 6000
if splits[0].PartyRole != "distributor_sell" || splits[0].AmountCent != 6000 {
t.Fatalf("distributor sell = %#v, want 6000", splits[0])
}
// 收号方获得 35% = 3500
if splits[1].AmountCent != 3500 {
t.Fatalf("platform receive = %#v, want 3500", splits[1])
}
// 尾差校验:平台服务费 2500,提价利润 10000 - (3500 + 4000) - 2500 = 0
if got := SplitRoundingCent(10000, 2500, 2000, splits); got != 0 {
t.Fatalf("rounding = %d, want 0", got)
}
}
func TestSplitSettlementThreeParty(t *testing.T) {
rule := Rule{Mode: ModeThreeParty, DistributorARatio: 35, DistributorBRatio: 40}
// 提价利润 10000,分站B自主加价 1500
splits := SplitSettlement(rule, 10000, 1500)
if len(splits) != 2 || splits[0].AmountCent != 3500 || splits[1].AmountCent != 5500 {
t.Fatalf("splits = %#v", splits)
}
}
func TestCalculateSettlementClampsMarkupToRemainingNetPaid(t *testing.T) {
rule := Rule{ServiceFeeValue: 25, ServiceFeeUnit: ServiceFeeUnitPercent, PaymentFeeBPS: 50}
got, err := CalculateSettlement(rule, SettlementInput{
GrossPaidAmountCent: 10000, RefundAmountCent: 10000, OwnerRentAmountCent: 6000, MarkupAmountCent: 3000,
})
if err != nil {
t.Fatal(err)
}
if got.MarkupAmountCent != 0 || got.SplitBaseAmountCent != 0 || got.NetPaidAmountCent != 0 {
t.Fatalf("full refund must zero markup and profit: %#v", got)
}
partial, err := CalculateSettlement(rule, SettlementInput{
GrossPaidAmountCent: 10000, RefundAmountCent: 2000, OwnerRentAmountCent: 6000, MarkupAmountCent: 3000,
})
if err != nil {
t.Fatal(err)
}
if partial.NetPaidAmountCent != 8000 || partial.MarkupAmountCent != 2000 || partial.SplitBaseAmountCent != 0 {
t.Fatalf("partial refund should clamp markup to remainder: %#v", partial)
}
}
func TestSplitSettlementSiteSelfSell(t *testing.T) {
rule, err := DefaultRule(ModeSiteSelfSell)
if err != nil {
t.Fatal(err)
}
splits := SplitSettlement(rule, 10000, 2000)
if len(splits) != 1 || splits[0].PartyRole != "distributor_sell" || splits[0].AmountCent != 9500 {
t.Fatalf("self-sell split = %#v, want 7500+2000", splits)
}
}
func TestValidateRuleRequiresPercentSum(t *testing.T) {
rule, err := DefaultRule(ModeTwoPartyDistributorReceive)
if err != nil {
t.Fatal(err)
}
if err := ValidateRule(rule); err != nil {
t.Fatalf("default rule should be valid: %v", err)
}
rule.DistributorReceiveRatio = 10
if err := ValidateRule(rule); err != ErrInvalidRate {
t.Fatalf("invalid percent sum err = %v", err)
}
if err := ValidateRule(Rule{Mode: "not_a_mode", ServiceFeeUnit: ServiceFeeUnitPercent}); err != ErrUnsupportedMode {
t.Fatalf("unsupported mode err = %v", err)
}
}