CfCommissionService.java 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package com.etotem.cfc.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.etotem.cfc.entity.CfRateTier;
  4. import com.etotem.cfc.entity.CfTransferRecord;
  5. import com.etotem.cfc.entity.Product;
  6. import com.etotem.cfc.entity.ReferralTree;
  7. import com.etotem.cfc.entity.User;
  8. import com.etotem.cfc.mapper.CfTransferRecordMapper;
  9. import com.etotem.cfc.mapper.ProductMapper;
  10. import com.etotem.cfc.mapper.ReferralTreeMapper;
  11. import com.etotem.cfc.mapper.UserMapper;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. import org.springframework.stereotype.Service;
  15. import org.springframework.transaction.annotation.Transactional;
  16. import javax.annotation.Resource;
  17. import java.util.ArrayList;
  18. import java.util.Date;
  19. import java.util.List;
  20. /**
  21. * 统一 CF 值分佣服务。
  22. * 规则:
  23. * - 普通订单(商品/套餐/测评):当前消费人返 CF + 推荐人按团队规模阶梯比例分润
  24. * - 会员/订阅订单:只返推荐人,不返当前人
  25. * - 同家庭互推:跳过本人,上溯到第一个非同家庭引荐人
  26. */
  27. @Service
  28. public class CfCommissionService {
  29. private static final Logger log = LoggerFactory.getLogger(CfCommissionService.class);
  30. @Resource
  31. private ReferralTreeMapper referralTreeMapper;
  32. @Resource
  33. private UserMapper userMapper;
  34. @Resource
  35. private CfReferralService cfReferralService;
  36. @Resource
  37. private PlatformPointsService platformPointsService;
  38. @Resource
  39. private CfTransferRecordMapper cfTransferRecordMapper;
  40. @Resource
  41. private PpointConfigService ppointConfigService;
  42. @Resource
  43. private SysConfigService sysConfigService;
  44. @Resource
  45. private ProductMapper productMapper;
  46. /**
  47. * 通用分佣(双返):当前人 + 推荐人
  48. */
  49. @Transactional
  50. public void settle(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
  51. Integer orderAmountCent, Long productId) {
  52. // 当前消费人返 CF(个人钱包)
  53. int buyerReturn = calcBuyerReturn(orderType, orderAmountCent, productId, buyerUserId);
  54. if (buyerReturn > 0) {
  55. try {
  56. platformPointsService.earn(buyerUserId, buyerReturn, "order_consume", orderId,
  57. "消费返CF:" + orderType);
  58. record(buyerUserId, null, null, buyerReturn, "allocate", "order_consume", orderId, "消费返CF");
  59. } catch (Exception e) {
  60. log.error("当前人返CF失败: buyer={}, orderId={}, err={}", buyerUserId, orderId, e.getMessage());
  61. }
  62. }
  63. // 推荐人分润
  64. distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId, orderAmountCent, productId);
  65. }
  66. /**
  67. * 会员/订阅专用:只返推荐人
  68. */
  69. @Transactional
  70. public void settleReferrerOnly(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
  71. Integer orderAmountCent) {
  72. distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId, orderAmountCent, null);
  73. }
  74. /**
  75. * 计算当前消费人返 CF。
  76. * 商品:floor(orderAmount/100) × P点 × shareBps/10000
  77. * 非商品:floor(orderAmount/100) × serviceRateBps/10000
  78. */
  79. private int calcBuyerReturn(String orderType, Integer orderAmountCent, Long productId, Long buyerUserId) {
  80. if (orderAmountCent == null || orderAmountCent <= 0) return 0;
  81. int amountYuan = orderAmountCent / 100;
  82. if ("product".equals(orderType) && productId != null) {
  83. try {
  84. Product p = productMapper.selectById(productId);
  85. if (p == null) return 0;
  86. int effectivePpoint = ppointConfigService.getEffectivePpoint(productId, p.getCategoryId());
  87. if (effectivePpoint <= 0) return 0;
  88. int shareBps = getSysBps("product_platform_points_share", 1000);
  89. return amountYuan * effectivePpoint / 100 * shareBps / 10000;
  90. } catch (Exception e) {
  91. return 0;
  92. }
  93. }
  94. int serviceRateBps = getSysBps("commission_service_rate", 1000);
  95. return amountYuan * serviceRateBps / 10000;
  96. }
  97. /**
  98. * 推荐人分润:查全链路 → 同家庭跳过上溯 → 按阶梯比例
  99. */
  100. private void distributeToReferrers(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
  101. Integer orderAmountCent, Long productId) {
  102. if (orderAmountCent == null || orderAmountCent <= 0) return;
  103. int amountYuan = orderAmountCent / 100;
  104. // 取买家全链路推荐人(referral_tree,含全部层级)
  105. List<ReferralTree> referrals = referralTreeMapper.selectList(
  106. new LambdaQueryWrapper<ReferralTree>()
  107. .eq(ReferralTree::getChildId, buyerUserId)
  108. .orderByAsc(ReferralTree::getLevel));
  109. if (referrals == null || referrals.isEmpty()) return;
  110. List<Long> processed = new ArrayList<>();
  111. for (ReferralTree ref : referrals) {
  112. Long referrerId = ref.getParentId();
  113. if (processed.contains(referrerId)) continue;
  114. processed.add(referrerId);
  115. User referrer = userMapper.selectById(referrerId);
  116. if (referrer == null) continue;
  117. // 同家庭跳过(D5):不返,继续上溯(循环继续)
  118. if (buyerFamilyId != null && buyerFamilyId.equals(referrer.getFamilyId())) {
  119. continue;
  120. }
  121. // 按团队规模匹配阶梯比例
  122. int teamSize = cfReferralService.getTotalTeamSize(referrerId);
  123. CfRateTier tier = cfReferralService.matchRateTier(teamSize);
  124. int ratePercent = tier == null ? 0 : tier.getRatePercent();
  125. if (ratePercent <= 0) continue;
  126. // 分润基数:商品按 P点,非商品按服务费率
  127. int base = calcReferrerBase(orderType, amountYuan, productId);
  128. if (base <= 0) continue;
  129. int share = base * ratePercent / 100;
  130. if (share <= 0) continue;
  131. try {
  132. platformPointsService.earn(referrerId, share, "referral_dist", orderId,
  133. "推荐分润:" + orderType);
  134. record(referrerId, null, referrer.getFamilyId(), share, "allocate", "referral_dist", orderId, "推荐分润");
  135. } catch (Exception e) {
  136. log.error("推荐人分润失败: referrer={}, orderId={}, err={}", referrerId, orderId, e.getMessage());
  137. }
  138. }
  139. }
  140. private int calcReferrerBase(String orderType, int amountYuan, Long productId) {
  141. if ("product".equals(orderType) && productId != null) {
  142. try {
  143. Product p = productMapper.selectById(productId);
  144. if (p == null) return 0;
  145. return ppointConfigService.getEffectivePpoint(productId, p.getCategoryId());
  146. } catch (Exception e) {
  147. return 0;
  148. }
  149. }
  150. int serviceRateBps = getSysBps("commission_service_rate", 1000);
  151. return amountYuan * serviceRateBps / 10000;
  152. }
  153. private int getSysBps(String key, int def) {
  154. try {
  155. String v = sysConfigService.getValue(key);
  156. if (v != null && !v.isEmpty()) return Integer.parseInt(v);
  157. } catch (Exception e) {
  158. log.warn("读取系统配置失败: key={}, err={}", key, e.getMessage());
  159. }
  160. return def;
  161. }
  162. private void record(Long fromUserId, Long toUserId, Long familyId, int amount, String type,
  163. String refType, Long refId, String remark) {
  164. CfTransferRecord r = new CfTransferRecord();
  165. r.setFromUserId(fromUserId);
  166. r.setToUserId(toUserId);
  167. r.setFamilyId(familyId);
  168. r.setAmount(amount);
  169. r.setType(type);
  170. r.setRefType(refType);
  171. r.setRefId(refId);
  172. r.setRemark(remark);
  173. r.setCreatedAt(new Date());
  174. try { cfTransferRecordMapper.insert(r); } catch (Exception e) { log.warn("写流转记录失败: {}", e.getMessage()); }
  175. }
  176. }