|
|
@@ -366,4 +366,409 @@ class OrderServiceTest {
|
|
|
inOrder.verify(userService).upgradeVip(100L, 365, "annual");
|
|
|
inOrder.verify(commissionRepository).save(any(Commission.class));
|
|
|
}
|
|
|
+
|
|
|
+ // ==================== 分佣体系补充测试 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * B端升级补差: buyer 此前已购买 annual 并支付过佣金给 inviter,
|
|
|
+ * 升级 practitioner 时 L1 = 默认 - 已支付佣金
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_bEndUpgrade_deduction() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(2L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(198600); // 标准价 ¥1,986
|
|
|
+ order.setProductType("practitioner");
|
|
|
+ order.setStatus("pending");
|
|
|
+ order.setIsUpgrade(true); // 升级订单
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setVipType("practitioner");
|
|
|
+ inviter.setDirectCount(0);
|
|
|
+ inviter.setConvertedCount(0);
|
|
|
+
|
|
|
+ when(configService.getInt("commission.practitioner.l1", 50000)).thenReturn(50000);
|
|
|
+ when(configService.getInt("commission.practitioner.l2", 10000)).thenReturn(10000);
|
|
|
+ when(commissionRepository.findByOrderId(2L)).thenReturn(Collections.emptyList());
|
|
|
+
|
|
|
+ // sumAnnualCommissionTo: buyer(100) 此前已支付 annual 佣金 5240 给 inviter(200)
|
|
|
+ Commission pastCommission = new Commission();
|
|
|
+ pastCommission.setId(10L);
|
|
|
+ pastCommission.setFromUserId(100L);
|
|
|
+ pastCommission.setToUserId(200L);
|
|
|
+ pastCommission.setAmount(5240);
|
|
|
+ pastCommission.setLevel(1);
|
|
|
+ pastCommission.setStatus("settled");
|
|
|
+ pastCommission.setOrderId(1L);
|
|
|
+
|
|
|
+ Order pastOrder = new Order();
|
|
|
+ pastOrder.setId(1L);
|
|
|
+ pastOrder.setProductType("annual");
|
|
|
+ pastOrder.setUserId(100L);
|
|
|
+
|
|
|
+ when(commissionRepository.findByFromUserId(100L)).thenReturn(Arrays.asList(pastCommission));
|
|
|
+ when(orderRepository.findById(1L)).thenReturn(Optional.of(pastOrder));
|
|
|
+
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_upgrade")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_upgrade");
|
|
|
+
|
|
|
+ // L1 = 50000 - 5240 = 44760, no L2 since inviter.invitedBy is null
|
|
|
+ verify(commissionRepository).save(argThat((Commission c) ->
|
|
|
+ 1 == c.getLevel() && 44760 == c.getAmount() && "settled".equals(c.getStatus())));
|
|
|
+ verify(commissionRepository, never()).save(argThat((Commission c) ->
|
|
|
+ 2 == c.getLevel()));
|
|
|
+ verify(userRepository).incrementConvertedCount(200L);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * B端升级补差: 此前已支付金额 ≥ 默认佣金 → l1Amount = 0, 不创建佣金
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_bEndUpgrade_zeroDeduction() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(3L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(198600);
|
|
|
+ order.setProductType("practitioner");
|
|
|
+ order.setStatus("pending");
|
|
|
+ order.setIsUpgrade(true);
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setVipType("practitioner");
|
|
|
+ inviter.setDirectCount(0);
|
|
|
+
|
|
|
+ when(configService.getInt("commission.practitioner.l1", 50000)).thenReturn(50000);
|
|
|
+ when(configService.getInt("commission.practitioner.l2", 10000)).thenReturn(10000);
|
|
|
+ when(commissionRepository.findByOrderId(3L)).thenReturn(Collections.emptyList());
|
|
|
+
|
|
|
+ // 此前已支付 60000 > 50000
|
|
|
+ Commission pastCommission = new Commission();
|
|
|
+ pastCommission.setId(20L);
|
|
|
+ pastCommission.setFromUserId(100L);
|
|
|
+ pastCommission.setToUserId(200L);
|
|
|
+ pastCommission.setAmount(60000);
|
|
|
+ pastCommission.setLevel(1);
|
|
|
+ pastCommission.setStatus("settled");
|
|
|
+ pastCommission.setOrderId(4L);
|
|
|
+
|
|
|
+ Order pastOrder = new Order();
|
|
|
+ pastOrder.setId(4L);
|
|
|
+ pastOrder.setProductType("annual");
|
|
|
+ pastOrder.setUserId(100L);
|
|
|
+
|
|
|
+ when(commissionRepository.findByFromUserId(100L)).thenReturn(Arrays.asList(pastCommission));
|
|
|
+ when(orderRepository.findById(4L)).thenReturn(Optional.of(pastOrder));
|
|
|
+
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_upgrade_zero")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_upgrade_zero");
|
|
|
+
|
|
|
+ // L1 is 60000-50000=0 → should NOT create any L1 or L2 commission
|
|
|
+ verify(commissionRepository, never()).save(argThat((Commission c) ->
|
|
|
+ c.getOrderId().equals(3L)));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * B端: 推荐人未付费 (vipType=null) → 不创建佣金
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_bEnd_noVipType_skipsCommission() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(131400);
|
|
|
+ order.setProductType("practitioner");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setVipType(null); // 未付费
|
|
|
+
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_no_vip")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_no_vip");
|
|
|
+
|
|
|
+ verify(commissionRepository, never()).save(any(Commission.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * B端: C端推荐半额 — 推荐人身份为 annual (C端) → L1 = l1_cend (20000)
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_bEnd_cendHalfAmount() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(131400);
|
|
|
+ order.setProductType("practitioner");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setVipType("annual"); // C端推荐人 → 半额
|
|
|
+ inviter.setDirectCount(0);
|
|
|
+ inviter.setConvertedCount(0);
|
|
|
+
|
|
|
+ when(configService.getInt("commission.practitioner.l1_cend", 20000)).thenReturn(20000);
|
|
|
+ when(configService.getInt("commission.practitioner.l2", 10000)).thenReturn(10000);
|
|
|
+ when(commissionRepository.findByOrderId(1L)).thenReturn(Collections.emptyList());
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_cend")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_cend");
|
|
|
+
|
|
|
+ // L1 = 20000 (半额), no L2 since inviter.invitedBy is null
|
|
|
+ verify(commissionRepository).save(argThat((Commission c) ->
|
|
|
+ 1 == c.getLevel() && 20000 == c.getAmount() && "settled".equals(c.getStatus())));
|
|
|
+ verify(commissionRepository, never()).save(argThat((Commission c) ->
|
|
|
+ 2 == c.getLevel()));
|
|
|
+ verify(userRepository).incrementConvertedCount(200L);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * B端: 推荐人身份不匹配 (如 super_annual) → 不创建佣金
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_bEnd_unknownVipType_skips() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(131400);
|
|
|
+ order.setProductType("practitioner");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setVipType("super_annual"); // 不匹配任何分支
|
|
|
+
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_unknown_vip")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_unknown_vip");
|
|
|
+
|
|
|
+ verify(commissionRepository, never()).save(any(Commission.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * C端: 推荐人无 referralCode (未付费) → 不创建佣金
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_cEnd_noReferralCode_skips() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(13100);
|
|
|
+ order.setProductType("annual");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setReferralCode(null); // 未付费
|
|
|
+
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_no_rc")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_no_rc");
|
|
|
+
|
|
|
+ verify(commissionRepository, never()).save(any(Commission.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * C端 L2: 上级无 referralCode → 只创建 L1, 跳过 L2
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_cEnd_upperNoReferralCode_skipsL2() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(13100);
|
|
|
+ order.setProductType("annual");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setReferralCode("ABC123");
|
|
|
+ inviter.setInvitedBy(300L);
|
|
|
+
|
|
|
+ User inviter2 = new User();
|
|
|
+ inviter2.setId(300L);
|
|
|
+ inviter2.setReferralCode(null); // 上级未付费
|
|
|
+
|
|
|
+ when(configService.getInt("commission.annual.direct_rate", 4000)).thenReturn(4000);
|
|
|
+ when(configService.getInt("commission.annual.upstream_rate", 500)).thenReturn(500);
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_upper_no_rc")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+ when(userRepository.findById(300L)).thenReturn(Optional.of(inviter2));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_upper_no_rc");
|
|
|
+
|
|
|
+ // L1 创建, L2 跳过
|
|
|
+ verify(commissionRepository).save(argThat((Commission c) -> 1 == c.getLevel()));
|
|
|
+ verify(commissionRepository, never()).save(argThat((Commission c) -> 2 == c.getLevel()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 超级会员佣金: L1=30%, L2=5% (参照 C 端模式)
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_superMemberCommission_l1() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(131400); // ¥1,314
|
|
|
+ order.setProductType("super_member");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setReferralCode("ABC123");
|
|
|
+
|
|
|
+ when(configService.getInt("commission.super_member.direct_rate", 3000)).thenReturn(3000);
|
|
|
+ when(configService.getInt("commission.super_member.upstream_rate", 500)).thenReturn(500);
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_super_l1")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_super_l1");
|
|
|
+
|
|
|
+ // L1 = 30% of 131400 = 39420
|
|
|
+ verify(commissionRepository).save(argThat((Commission c) ->
|
|
|
+ 1 == c.getLevel() && 39420 == c.getAmount() && "settled".equals(c.getStatus())));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_superMemberCommission_l2() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(131400);
|
|
|
+ order.setProductType("super_member");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setReferralCode("ABC123");
|
|
|
+ inviter.setInvitedBy(300L);
|
|
|
+
|
|
|
+ User inviter2 = new User();
|
|
|
+ inviter2.setId(300L);
|
|
|
+ inviter2.setReferralCode("DEF456");
|
|
|
+
|
|
|
+ when(configService.getInt("commission.super_member.direct_rate", 3000)).thenReturn(3000);
|
|
|
+ when(configService.getInt("commission.super_member.upstream_rate", 500)).thenReturn(500);
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_super_l2")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+ when(userRepository.findById(300L)).thenReturn(Optional.of(inviter2));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_super_l2");
|
|
|
+
|
|
|
+ // L1 = 30% = 39420, L2 = 5% = 6570
|
|
|
+ verify(commissionRepository).save(argThat((Commission c) ->
|
|
|
+ 1 == c.getLevel() && 39420 == c.getAmount()));
|
|
|
+ verify(commissionRepository).save(argThat((Commission c) ->
|
|
|
+ 2 == c.getLevel() && 6570 == c.getAmount()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 幂等: 同一订单已创建过 L1 佣金 → 不重复创建
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testPaySuccess_bEnd_idempotentCommission() {
|
|
|
+ Order order = new Order();
|
|
|
+ order.setId(1L);
|
|
|
+ order.setUserId(100L);
|
|
|
+ order.setTotalFee(131400);
|
|
|
+ order.setProductType("practitioner");
|
|
|
+ order.setStatus("pending");
|
|
|
+
|
|
|
+ User buyer = new User();
|
|
|
+ buyer.setId(100L);
|
|
|
+ buyer.setInvitedBy(200L);
|
|
|
+
|
|
|
+ User inviter = new User();
|
|
|
+ inviter.setId(200L);
|
|
|
+ inviter.setVipType("practitioner");
|
|
|
+ inviter.setDirectCount(0);
|
|
|
+
|
|
|
+ // Existing commission for this order already created
|
|
|
+ Commission existingComm = new Commission();
|
|
|
+ existingComm.setId(99L);
|
|
|
+ existingComm.setOrderId(1L);
|
|
|
+ existingComm.setLevel(1);
|
|
|
+ existingComm.setToUserId(200L);
|
|
|
+
|
|
|
+ when(configService.getInt("commission.practitioner.l1", 50000)).thenReturn(50000);
|
|
|
+ when(configService.getInt("commission.practitioner.l2", 10000)).thenReturn(10000);
|
|
|
+ when(commissionRepository.findByOrderId(1L)).thenReturn(Arrays.asList(existingComm));
|
|
|
+ when(orderRepository.findByOutTradeNo("trade_idem")).thenReturn(Optional.of(order));
|
|
|
+ when(orderRepository.save(any(Order.class))).thenReturn(order);
|
|
|
+ when(userRepository.findById(100L)).thenReturn(Optional.of(buyer));
|
|
|
+ when(userRepository.findById(200L)).thenReturn(Optional.of(inviter));
|
|
|
+
|
|
|
+ orderService.paySuccess("trade_idem");
|
|
|
+
|
|
|
+ // Should not create any new commissions (same orderId + level already exists)
|
|
|
+ verify(commissionRepository, never()).save(argThat((Commission c) ->
|
|
|
+ "settled".equals(c.getStatus())));
|
|
|
+ }
|
|
|
}
|