Case 29: Distribution commission splitting
This case corresponds to scenario 29 “Distribution commission / upline-downline profit splitting” in the JQuick-Java repository USE-CASES, in the e-commerce distribution domain. For a distribution order, the system computes multi-level profit splitting across self-purchase commission, direct-referral upline reward, indirect-referral upline reward and group-leader service fee, and enforces risk-control constraints including promotion base reduction, a 30% aggregate cap circuit breaker, refund forfeiture, and manual review for large orders.
Industry scenario
Section titled “Industry scenario”In a social-distribution system, an order’s commission is often split among the transacting distributor, their first-level upline (direct referral), second-level upline (indirect referral), and the group leader who organized the group deal. Rates vary with product-category margin (beauty high, appliances thin), distributor level (L1/L2/L3/PARTNER), and the promotion cost ratio. Operations also grants the group leader an extra service fee based on monthly group-deal volume.
The calculation starts from the commissionable base: the order’s actual paid amount is first multiplied by the promotion-reduction coefficient (cost ratio ≥50% → base counted at 50%, ≥30% → 70%, ≥10% → 90%, no promotion → full amount), preventing commissions from being calculated on the original price after a deep merchant discount. Self-purchase commission = base × category base rate (beauty 20%, standard 10%, fresh 6%, appliances and others 3%) × level multiplier (PARTNER 1.5, L3 1.3, L2 1.1, L1 1.0); direct-referral reward = base × upline-level rate (PARTNER 5%, L3 4%, L2 3%, L1 none); indirect-referral reward = base × second-level-upline rate (PARTNER 2%, L3 1.5%, others none); the group-leader service fee is granted only to group leaders: 20+ monthly group deals → 2%, 10+ → 1%.
The Service side enforces three financial and risk-control constraints: first, the sum of the four shares must not exceed 30% of the commissionable base — anything above is capped and flagged as capped; second, if the order has been refunded, all levels are zeroed and the status is set to FORFEITED; third, a wholesale order of 50,000 yuan or more still has its commission calculated but is frozen as PENDING, to be settled only after manual verification of the transaction’s authenticity. All amounts are kept to two decimal places with BigDecimal HALF_UP.
The decision output is DistributionCommissionDecision: commissionable base, the four share amounts, total amount, whether the circuit breaker was triggered, settlement status (SETTLED/PENDING/FORFEITED), and itemized split details (breakdown), which can be used directly as settlement statements and reconciliation evidence.
Business pain points
Section titled “Business pain points”- Category rates, level multipliers, direct/indirect referral rates and group-leader thresholds adjust frequently with recruitment policies. Hard-coding rates in the settlement code means racing the settlement window for a release on every policy change.
- Multi-level stacking can easily “split through” thin-margin goods: beauty’s high rate stacked with upline and group-leader awards may exceed the merchant’s concession room, so an aggregate-cap circuit breaker with reconciliation explanation is required.
- During promotions, orders carry deep concessions; if the commission base is still calculated on the actual or original price, subsidy losses occur. An independently configurable base-reduction caliber is needed.
- Risk constraints such as refund recovery and large-wholesale-order fraud are on the same pipeline as normal splitting, requiring a clear upfront short-circuit (refund → immediate forfeiture) and freeze-and-review (50,000 yuan → PENDING).
- Profit splitting involves real money; chained double multiplication easily causes rounding disputes. Finance requires each item rounded to two decimals, and every level’s amount auditable and traceable.
- Distribution systems are mostly years-old JDK 8 systems that must also adapt to domestic-innovation JDKs, making it hard to introduce heavy commission-rule platforms that require standalone deployment.
Solution architecture
Section titled “Solution architecture”This case uses the six-part structure “input model + decision model + rule interface + XML rule script + Service orchestration + JUnit tests”: six rule methods each encapsulate one rate/multiplier type, all externalized in XML, and the Service handles base reduction, multi-level chained multiplication, BigDecimal rounding, the 30% cap circuit breaker and status short-circuiting.
| Layer | Responsibility | Files |
|---|---|---|
| Domain model layer | Carries the distribution order and identity elements of each distributor level; outputs the four shares, circuit-breaker flag and settlement status | DistributionOrder.java, DistributionCommissionDecision.java |
| Rule interface layer | Declares six methods: category rate, level multiplier, direct/indirect referral rates, base factor, group-leader rate | DistributionCommissionRules.java |
| Rule script layer | All rates, multipliers and group-deal thresholds externalized; supports hot update | distribution-commission.xml |
| Orchestration & test layer | Base reduction, multi-level chained multiplication, two-decimal rounding, 30% circuit breaker, refund/large-order short-circuit; regression over 6 profiles | DistributionCommissionService.java, DistributionCommissionServiceTest.java |
Rule method breakdown
Section titled “Rule method breakdown”| Rule method | Caliber | Score/output |
|---|---|---|
| categoryRate | Category base commission rate: BEAUTY 0.20, STANDARD 0.10, FRESH 0.06, others (appliances) 0.03 | 0.03-0.20 |
| levelMultiplier | Distributor level multiplier: PARTNER 1.5, L3 1.3, L2 1.1, L1 and others 1.0 | 1.0-1.5 |
| directReferRate | Direct-referral (first-level upline) reward rate: PARTNER 0.05, L3 0.04, L2 0.03, others 0 | 0-0.05 |
| indirectReferRate | Indirect-referral (second-level upline) reward rate: PARTNER 0.02, L3 0.015, others 0 | 0-0.02 |
| baseFactor | Promotion concession reduction: ≥50% → 0.5, ≥30% → 0.7, ≥10% → 0.9, no promotion → 1.0 | 0.5-1.0 |
| groupBonusRate | Group leader with 20+ monthly group deals → 0.02, 10+ → 0.01; non-leader or fewer than 10 → 0 | 0-0.02 |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”Place the following files into the JQuick-Java test project under their original packages: the two models into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test class into src/test/java/com/github/paohaijiao/demo/, the Service into src/test/java/com/github/paohaijiao/demo/service/, and the XML script into src/test/resources/demo/. Run mvn test -Dtest=DistributionCommissionServiceTest at the project root to execute.
DistributionOrder.java
Section titled “DistributionOrder.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 分销订单与各级分销商身份要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic class DistributionOrder {
/** * 订单号 */ private String orderId;
/** * 商品编码 */ private String sku;
/** * 实付金额(元) */ private double orderAmount;
/** * 商品类目:BEAUTY 美妆 / STANDARD 标品 / FRESH 生鲜 / APPLIANCE 家电 */ private String productCategory;
/** * 成交分销商等级:L1 初级 / L2 中级 / L3 高级 / PARTNER 合伙人 */ private String distributorLevel;
/** * 直推上级(一级上线)等级 */ private String uplineLevel;
/** * 间推上级(二级上线)等级 */ private String rootLevel;
/** * 是否团长 */ private boolean groupLeader;
/** * 近 30 天成团数 */ private int groupCount30d;
/** * 活动让利占比(整数百分比) */ private int promotionCostRate;
/** * 订单后续是否发生退款 */ private boolean refunded;}DistributionCommissionDecision.java
Section titled “DistributionCommissionDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 多级分润决策 */@Datapublic class DistributionCommissionDecision {
/** * 订单号 */ private String orderId;
/** * 可佣基数(活动让利后) */ private double baseAmount;
/** * 自购/直销佣金 */ private double selfCommission;
/** * 直推上级奖励 */ private double directCommission;
/** * 间推上级奖励 */ private double indirectCommission;
/** * 团长团队服务费 */ private double groupBonus;
/** * 分润合计(受 30% 封顶约束) */ private double totalCommission;
/** * 是否触发 30% 分润上限熔断 */ private boolean capped;
/** * 状态:SETTLED 可结算 / PENDING 待人工审核 / FORFEITED 退款没收 */ private String status;
/** * 分润明细与说明 */ private List<String> breakdown = new ArrayList<String>();
public void addBreakdown(String line) { this.breakdown.add(line); }}DistributionCommissionRules.java
Section titled “DistributionCommissionRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 29:分销多级分润的类目费率与身份系数 */public interface DistributionCommissionRules {
/** * 类目基础佣金率 */ double categoryRate(@Param("category") String category);
/** * 分销商等级系数 */ double levelMultiplier(@Param("level") String level);
/** * 直推奖励率(一级上线) */ double directReferRate(@Param("uplineLevel") String uplineLevel);
/** * 间推奖励率(二级上线) */ double indirectReferRate(@Param("rootLevel") String rootLevel);
/** * 活动订单可佣基数系数(让利折减) */ double baseFactor(@Param("promotionCostRate") int promotionCostRate);
/** * 团长服务费费率 */ double groupBonusRate(@Param("groupLeader") boolean groupLeader, @Param("groupCount") int groupCount);}distribution-commission.xml
Section titled “distribution-commission.xml”<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN" "classpath:paohaijiao/dtd/Jquick-java.dtd"><javas namespace="com.github.paohaijiao.demo.DistributionCommissionRules">
<java name="categoryRate" returnClass="double"> <![CDATA[ double def categoryRate(str:category) { if (category == "BEAUTY") { return 0.20; } else if (category == "STANDARD") { return 0.10; } else if (category == "FRESH") { return 0.06; } else { return 0.03; } } ]]> </java>
<java name="levelMultiplier" returnClass="double"> <![CDATA[ double def levelMultiplier(str:level) { if (level == "PARTNER") { return 1.5; } else if (level == "L3") { return 1.3; } else if (level == "L2") { return 1.1; } else { return 1.0; } } ]]> </java>
<java name="directReferRate" returnClass="double"> <![CDATA[ double def directReferRate(str:uplineLevel) { if (uplineLevel == "PARTNER") { return 0.05; } else if (uplineLevel == "L3") { return 0.04; } else if (uplineLevel == "L2") { return 0.03; } else { return 0.0; } } ]]> </java>
<java name="indirectReferRate" returnClass="double"> <![CDATA[ double def indirectReferRate(str:rootLevel) { if (rootLevel == "PARTNER") { return 0.02; } else if (rootLevel == "L3") { return 0.015; } else { return 0.0; } } ]]> </java>
<java name="baseFactor" returnClass="double"> <![CDATA[ double def baseFactor(int:promotionCostRate) { if (promotionCostRate >= 50) { return 0.5; } else if (promotionCostRate >= 30) { return 0.7; } else if (promotionCostRate >= 10) { return 0.9; } else { return 1.0; } } ]]> </java>
<java name="groupBonusRate" returnClass="double"> <![CDATA[ double def groupBonusRate(boolean:groupLeader, int:groupCount) { if (groupLeader) { if (groupCount >= 20) { return 0.02; } else if (groupCount >= 10) { return 0.01; } else { return 0.0; } } else { return 0.0; } } ]]> </java>
</javas>DistributionCommissionService.java
Section titled “DistributionCommissionService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.DistributionCommissionRules;import com.github.paohaijiao.demo.model.DistributionCommissionDecision;import com.github.paohaijiao.demo.model.DistributionOrder;
import java.math.BigDecimal;import java.math.RoundingMode;
/** * 场景 29:分销自购 + 直推 + 间推 + 团长服务费多级分润 */public class DistributionCommissionService {
private static final BigDecimal TOTAL_CAP_RATIO = new BigDecimal("0.30");
private final DistributionCommissionRules rules;
public DistributionCommissionService(DistributionCommissionRules rules) { this.rules = rules; }
public DistributionCommissionDecision settle(DistributionOrder order) { DistributionCommissionDecision d = new DistributionCommissionDecision(); d.setOrderId(order.getOrderId());
if (order.isRefunded()) { d.setBaseAmount(0.0); d.setSelfCommission(0.0); d.setDirectCommission(0.0); d.setIndirectCommission(0.0); d.setGroupBonus(0.0); d.setTotalCommission(0.0); d.setStatus("FORFEITED"); d.addBreakdown("订单已退款,各级分润全部不予结算"); return d; }
BigDecimal base = money(order.getOrderAmount() * rules.baseFactor(order.getPromotionCostRate())); BigDecimal self = money(base.doubleValue() * rules.categoryRate(order.getProductCategory()) * rules.levelMultiplier(order.getDistributorLevel())); BigDecimal direct = money(base.doubleValue() * rules.directReferRate(order.getUplineLevel())); BigDecimal indirect = money(base.doubleValue() * rules.indirectReferRate(order.getRootLevel())); BigDecimal group = money(base.doubleValue() * rules.groupBonusRate(order.isGroupLeader(), order.getGroupCount30d()));
BigDecimal total = self.add(direct).add(indirect).add(group); BigDecimal cap = money(base.multiply(TOTAL_CAP_RATIO).doubleValue()); boolean capped = total.compareTo(cap) > 0; if (capped) { total = cap; }
d.setBaseAmount(base.doubleValue()); d.setSelfCommission(self.doubleValue()); d.setDirectCommission(direct.doubleValue()); d.setIndirectCommission(indirect.doubleValue()); d.setGroupBonus(group.doubleValue()); d.setTotalCommission(total.doubleValue()); d.setCapped(capped);
d.addBreakdown("可佣基数: " + base.toPlainString() + " 元"); d.addBreakdown("自购佣金: " + self.toPlainString() + " 元"); d.addBreakdown("直推奖励: " + direct.toPlainString() + " 元"); d.addBreakdown("间推奖励: " + indirect.toPlainString() + " 元"); d.addBreakdown("团长服务费: " + group.toPlainString() + " 元"); if (capped) { d.addBreakdown("多级分润合计超过可佣基数 30%,已按上限 " + cap.toPlainString() + " 元熔断"); }
if (order.getOrderAmount() >= 50000) { d.setStatus("PENDING"); d.addBreakdown("单笔金额超 5 万元,转人工核验交易真实性后再行结算"); } else { d.setStatus("SETTLED"); } return d; }
private BigDecimal money(double v) { return BigDecimal.valueOf(v).setScale(2, RoundingMode.HALF_UP); }}DistributionCommissionServiceTest.java
Section titled “DistributionCommissionServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.DistributionCommissionDecision;import com.github.paohaijiao.demo.model.DistributionOrder;import com.github.paohaijiao.demo.service.DistributionCommissionService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 29:分销多级返佣与分润熔断 * <p> * 6 组形态:美妆高级分销团长触顶熔断、家电薄利两级分润、 * 活动让利基数折减、退款全额没收、大额批发人工审核、生鲜团长成团临界。 */public class DistributionCommissionServiceTest {
private DistributionCommissionService service;
@Before public void setUp() { DistributionCommissionRules rules = JQuickJava.create() .createApi(DistributionCommissionRules.class, "demo/distribution-commission.xml"); this.service = new DistributionCommissionService(rules); }
/** * 美妆 L3 + 合伙人直推 + L3 间推 + 月成团 25 团长 → 345 元触 300 元上限 */ @Test public void shouldCapTotalCommissionAtThirtyPercent() { DistributionOrder o = base("DIS001", 1000, "BEAUTY", "L3"); o.setUplineLevel("PARTNER"); o.setRootLevel("L3"); o.setGroupLeader(true); o.setGroupCount30d(25);
DistributionCommissionDecision d = service.settle(o);
Assert.assertEquals(1000.0, d.getBaseAmount(), 0.0001); Assert.assertEquals(260.0, d.getSelfCommission(), 0.0001); Assert.assertEquals(50.0, d.getDirectCommission(), 0.0001); Assert.assertEquals(15.0, d.getIndirectCommission(), 0.0001); Assert.assertEquals(20.0, d.getGroupBonus(), 0.0001); Assert.assertEquals(300.0, d.getTotalCommission(), 0.0001); Assert.assertTrue(d.isCapped()); Assert.assertEquals("SETTLED", d.getStatus()); }
/** * 家电低毛利 L1 分销 + L2 直推 → 60 + 60,无间推与团长奖 */ @Test public void shouldSettleApplianceThinMargin() { DistributionOrder o = base("DIS002", 2000, "APPLIANCE", "L1"); o.setUplineLevel("L2"); o.setRootLevel("L1");
DistributionCommissionDecision d = service.settle(o);
Assert.assertEquals(60.0, d.getSelfCommission(), 0.0001); Assert.assertEquals(60.0, d.getDirectCommission(), 0.0001); Assert.assertEquals(0.0, d.getIndirectCommission(), 0.0001); Assert.assertEquals(0.0, d.getGroupBonus(), 0.0001); Assert.assertEquals(120.0, d.getTotalCommission(), 0.0001); Assert.assertFalse(d.isCapped()); Assert.assertEquals("SETTLED", d.getStatus()); }
/** * 标品 L2 活动让利 35% → 基数 350,自购 38.5、直推 14、间推 7 */ @Test public void shouldDiscountCommissionBaseForPromotion() { DistributionOrder o = base("DIS003", 500, "STANDARD", "L2"); o.setUplineLevel("L3"); o.setRootLevel("PARTNER"); o.setPromotionCostRate(35);
DistributionCommissionDecision d = service.settle(o);
Assert.assertEquals(350.0, d.getBaseAmount(), 0.0001); Assert.assertEquals(38.5, d.getSelfCommission(), 0.0001); Assert.assertEquals(14.0, d.getDirectCommission(), 0.0001); Assert.assertEquals(7.0, d.getIndirectCommission(), 0.0001); Assert.assertEquals(59.5, d.getTotalCommission(), 0.0001); Assert.assertEquals("SETTLED", d.getStatus()); }
/** * 订单退款 → 所有分润没收 */ @Test public void shouldForfeitAllWhenRefunded() { DistributionOrder o = base("DIS004", 1000, "BEAUTY", "L3"); o.setUplineLevel("PARTNER"); o.setRefunded(true);
DistributionCommissionDecision d = service.settle(o);
Assert.assertEquals("FORFEITED", d.getStatus()); Assert.assertEquals(0.0, d.getTotalCommission(), 0.0001); Assert.assertEquals(0.0, d.getSelfCommission(), 0.0001); Assert.assertTrue(d.getBreakdown().contains("订单已退款,各级分润全部不予结算")); }
/** * 单笔 6 万元批发单 → 佣金预估照算但冻结待人工审核 */ @Test public void shouldHoldLargeWholesaleOrderForReview() { DistributionOrder o = base("DIS005", 60000, "STANDARD", "L1"); o.setUplineLevel("L1"); o.setRootLevel("L1");
DistributionCommissionDecision d = service.settle(o);
Assert.assertEquals("PENDING", d.getStatus()); Assert.assertEquals(60000.0, d.getBaseAmount(), 0.0001); Assert.assertEquals(6000.0, d.getSelfCommission(), 0.0001); Assert.assertEquals(6000.0, d.getTotalCommission(), 0.0001); Assert.assertTrue(d.getBreakdown().contains( "单笔金额超 5 万元,转人工核验交易真实性后再行结算")); }
/** * 生鲜 L1 团长:成团 10 单拿 1% 服务费(21 元),9 单无服务费(18 元) */ @Test public void shouldPayGroupBonusAtTenOrdersThreshold() { DistributionOrder o = base("DIS006", 300, "FRESH", "L1"); o.setUplineLevel("L1"); o.setRootLevel("L1"); o.setGroupLeader(true); o.setGroupCount30d(10);
DistributionCommissionDecision d1 = service.settle(o); Assert.assertEquals(18.0, d1.getSelfCommission(), 0.0001); Assert.assertEquals(3.0, d1.getGroupBonus(), 0.0001); Assert.assertEquals(21.0, d1.getTotalCommission(), 0.0001); Assert.assertEquals("SETTLED", d1.getStatus());
o.setGroupCount30d(9); DistributionCommissionDecision d2 = service.settle(o); Assert.assertEquals(0.0, d2.getGroupBonus(), 0.0001); Assert.assertEquals(18.0, d2.getTotalCommission(), 0.0001); }
private DistributionOrder base(String id, double amount, String category, String level) { DistributionOrder o = new DistributionOrder(); o.setOrderId(id); o.setSku("SKU200"); o.setOrderAmount(amount); o.setProductCategory(category); o.setDistributorLevel(level); o.setUplineLevel("L1"); o.setRootLevel("L1"); o.setGroupLeader(false); o.setGroupCount30d(0); o.setPromotionCostRate(0); o.setRefunded(false); return o; }}Run & verified results
Section titled “Run & verified results”Execute at the project root:
mvn test -Dtest=DistributionCommissionServiceTestTest coverage (6 @Test methods in total):
| Test method | Business profile | Key assertions / expected decision |
|---|---|---|
| shouldCapTotalCommissionAtThirtyPercent | Beauty 1000 yuan, L3 self-purchase, PARTNER direct referral, L3 indirect referral, group leader with 25 monthly group deals | Base 1000; self 260, direct 50, indirect 15, group 20 (sum 345); total capped at 300, capped=true, SETTLED |
| shouldSettleApplianceThinMargin | Appliance 2000 yuan thin margin, L1 self-purchase, L2 direct referral, no indirect or group-leader | Self 60, direct 60, indirect 0, group 0; total=120, capped=false, SETTLED |
| shouldDiscountCommissionBaseForPromotion | Standard 500 yuan, L2, 35% concession, L3 direct referral, PARTNER indirect referral | Base reduced to 350; self 38.5, direct 14, indirect 7; total=59.5, SETTLED |
| shouldForfeitAllWhenRefunded | Beauty L3 order later refunded | status=FORFEITED; total and self both 0; breakdown contains “订单已退款,各级分润全部不予结算” |
| shouldHoldLargeWholesaleOrderForReview | Standard 60,000-yuan wholesale order, L1 | status=PENDING; base 60000, self 6000, total=6000 (commission calculated but frozen); contains “单笔金额超 5 万元,转人工核验交易真实性后再行结算” |
| shouldPayGroupBonusAtTenOrdersThreshold | Fresh 300 yuan L1 group leader: 10 monthly group deals vs changed to 9 | At 10: self 18, group 3, total=21; at 9: group fee 0, total=18, verifying the group-deal threshold boundary |
Technical highlights
Section titled “Technical highlights”- Five rate types, one method per caliber, externalized hot update: category rate, level multiplier, direct-referral rate, indirect-referral rate and group-leader rate each occupy one rule method. Recruitment rate changes only touch the XML; the settlement main program is not released.
- Base × multi-coefficient chained multiplication: self-purchase commission chains “post-concession base × category rate × level multiplier”, while direct/indirect/group-leader each multiply the base by an independent rate — multi-level relationships are expressed through method composition rather than nested branches.
- Java-side financial-grade constraints: every item is uniformly rounded to two decimals via
BigDecimalHALF_UP before summation; if the total exceeds 30% of the commissionable base it is truncated to the cap and a circuit-breaker line is written, preventing multi-level stacking from splitting through the merchant’s margin. - Layered hard short-circuits: refund is front-most — everything zeroed to FORFEITED; large orders of 50,000 yuan have commissions estimated as usual but are frozen as PENDING for manual review, decoupling risk control from settlement while keeping amounts visible.
- Identity passed as string enums:
@Parambinds L1/L2/L3/PARTNER by name, and the XML matches identity tiers with==; adding a new level only requires adding one tier in the script. - ASM proxy, Java 8 with no heavy dependencies: the rule interface is implemented via dynamic proxy and can be reused, with no standalone settlement rule service, embeddable directly into JDK 8 legacy distribution systems and domestic-innovation environments.