|
|
@@ -0,0 +1,193 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.etotem.cfc.entity.CfRateTier;
|
|
|
+import com.etotem.cfc.entity.CfTransferRecord;
|
|
|
+import com.etotem.cfc.entity.Product;
|
|
|
+import com.etotem.cfc.entity.ReferralTree;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.CfTransferRecordMapper;
|
|
|
+import com.etotem.cfc.mapper.ProductMapper;
|
|
|
+import com.etotem.cfc.mapper.ReferralTreeMapper;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 统一 CF 值分佣服务。
|
|
|
+ * 规则:
|
|
|
+ * - 普通订单(商品/套餐/测评):当前消费人返 CF + 推荐人按团队规模阶梯比例分润
|
|
|
+ * - 会员/订阅订单:只返推荐人,不返当前人
|
|
|
+ * - 同家庭互推:跳过本人,上溯到第一个非同家庭引荐人
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class CfCommissionService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(CfCommissionService.class);
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReferralTreeMapper referralTreeMapper;
|
|
|
+ @Resource
|
|
|
+ private UserMapper userMapper;
|
|
|
+ @Resource
|
|
|
+ private CfReferralService cfReferralService;
|
|
|
+ @Resource
|
|
|
+ private PlatformPointsService platformPointsService;
|
|
|
+ @Resource
|
|
|
+ private CfTransferRecordMapper cfTransferRecordMapper;
|
|
|
+ @Resource
|
|
|
+ private PpointConfigService ppointConfigService;
|
|
|
+ @Resource
|
|
|
+ private SysConfigService sysConfigService;
|
|
|
+ @Resource
|
|
|
+ private ProductMapper productMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通用分佣(双返):当前人 + 推荐人
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public void settle(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
|
|
|
+ Integer orderAmountCent, Long productId) {
|
|
|
+ // 当前消费人返 CF(个人钱包)
|
|
|
+ int buyerReturn = calcBuyerReturn(orderType, orderAmountCent, productId, buyerUserId);
|
|
|
+ if (buyerReturn > 0) {
|
|
|
+ try {
|
|
|
+ platformPointsService.earn(buyerUserId, buyerReturn, "order_consume", orderId,
|
|
|
+ "消费返CF:" + orderType);
|
|
|
+ record(buyerUserId, null, null, buyerReturn, "allocate", "order_consume", orderId, "消费返CF");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("当前人返CF失败: buyer={}, orderId={}, err={}", buyerUserId, orderId, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 推荐人分润
|
|
|
+ distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId, orderAmountCent, productId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 会员/订阅专用:只返推荐人
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public void settleReferrerOnly(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
|
|
|
+ Integer orderAmountCent) {
|
|
|
+ distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId, orderAmountCent, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算当前消费人返 CF。
|
|
|
+ * 商品:floor(orderAmount/100) × P点 × shareBps/10000
|
|
|
+ * 非商品:floor(orderAmount/100) × serviceRateBps/10000
|
|
|
+ */
|
|
|
+ private int calcBuyerReturn(String orderType, Integer orderAmountCent, Long productId, Long buyerUserId) {
|
|
|
+ if (orderAmountCent == null || orderAmountCent <= 0) return 0;
|
|
|
+ int amountYuan = orderAmountCent / 100;
|
|
|
+ if ("product".equals(orderType) && productId != null) {
|
|
|
+ try {
|
|
|
+ Product p = productMapper.selectById(productId);
|
|
|
+ if (p == null) return 0;
|
|
|
+ int effectivePpoint = ppointConfigService.getEffectivePpoint(productId, p.getCategoryId());
|
|
|
+ if (effectivePpoint <= 0) return 0;
|
|
|
+ int shareBps = getSysBps("product_platform_points_share", 1000);
|
|
|
+ return amountYuan * effectivePpoint / 100 * shareBps / 10000;
|
|
|
+ } catch (Exception e) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ int serviceRateBps = getSysBps("commission_service_rate", 1000);
|
|
|
+ return amountYuan * serviceRateBps / 10000;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 推荐人分润:查全链路 → 同家庭跳过上溯 → 按阶梯比例
|
|
|
+ */
|
|
|
+ private void distributeToReferrers(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
|
|
|
+ Integer orderAmountCent, Long productId) {
|
|
|
+ if (orderAmountCent == null || orderAmountCent <= 0) return;
|
|
|
+ int amountYuan = orderAmountCent / 100;
|
|
|
+
|
|
|
+ // 取买家全链路推荐人(referral_tree,含全部层级)
|
|
|
+ List<ReferralTree> referrals = referralTreeMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ReferralTree>()
|
|
|
+ .eq(ReferralTree::getChildId, buyerUserId)
|
|
|
+ .orderByAsc(ReferralTree::getLevel));
|
|
|
+ if (referrals == null || referrals.isEmpty()) return;
|
|
|
+
|
|
|
+ List<Long> processed = new ArrayList<>();
|
|
|
+ for (ReferralTree ref : referrals) {
|
|
|
+ Long referrerId = ref.getParentId();
|
|
|
+ if (processed.contains(referrerId)) continue;
|
|
|
+ processed.add(referrerId);
|
|
|
+
|
|
|
+ User referrer = userMapper.selectById(referrerId);
|
|
|
+ if (referrer == null) continue;
|
|
|
+ // 同家庭跳过(D5):不返,继续上溯(循环继续)
|
|
|
+ if (buyerFamilyId != null && buyerFamilyId.equals(referrer.getFamilyId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 按团队规模匹配阶梯比例
|
|
|
+ int teamSize = cfReferralService.getTotalTeamSize(referrerId);
|
|
|
+ CfRateTier tier = cfReferralService.matchRateTier(teamSize);
|
|
|
+ int ratePercent = tier == null ? 0 : tier.getRatePercent();
|
|
|
+ if (ratePercent <= 0) continue;
|
|
|
+
|
|
|
+ // 分润基数:商品按 P点,非商品按服务费率
|
|
|
+ int base = calcReferrerBase(orderType, amountYuan, productId);
|
|
|
+ if (base <= 0) continue;
|
|
|
+ int share = base * ratePercent / 100;
|
|
|
+ if (share <= 0) continue;
|
|
|
+
|
|
|
+ try {
|
|
|
+ platformPointsService.earn(referrerId, share, "referral_dist", orderId,
|
|
|
+ "推荐分润:" + orderType);
|
|
|
+ record(referrerId, null, referrer.getFamilyId(), share, "allocate", "referral_dist", orderId, "推荐分润");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("推荐人分润失败: referrer={}, orderId={}, err={}", referrerId, orderId, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int calcReferrerBase(String orderType, int amountYuan, Long productId) {
|
|
|
+ if ("product".equals(orderType) && productId != null) {
|
|
|
+ try {
|
|
|
+ Product p = productMapper.selectById(productId);
|
|
|
+ if (p == null) return 0;
|
|
|
+ return ppointConfigService.getEffectivePpoint(productId, p.getCategoryId());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ int serviceRateBps = getSysBps("commission_service_rate", 1000);
|
|
|
+ return amountYuan * serviceRateBps / 10000;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getSysBps(String key, int def) {
|
|
|
+ try {
|
|
|
+ String v = sysConfigService.getValue(key);
|
|
|
+ if (v != null && !v.isEmpty()) return Integer.parseInt(v);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("读取系统配置失败: key={}, err={}", key, e.getMessage());
|
|
|
+ }
|
|
|
+ return def;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void record(Long fromUserId, Long toUserId, Long familyId, int amount, String type,
|
|
|
+ String refType, Long refId, String remark) {
|
|
|
+ CfTransferRecord r = new CfTransferRecord();
|
|
|
+ r.setFromUserId(fromUserId);
|
|
|
+ r.setToUserId(toUserId);
|
|
|
+ r.setFamilyId(familyId);
|
|
|
+ r.setAmount(amount);
|
|
|
+ r.setType(type);
|
|
|
+ r.setRefType(refType);
|
|
|
+ r.setRefId(refId);
|
|
|
+ r.setRemark(remark);
|
|
|
+ r.setCreatedAt(new Date());
|
|
|
+ try { cfTransferRecordMapper.insert(r); } catch (Exception e) { log.warn("写流转记录失败: {}", e.getMessage()); }
|
|
|
+ }
|
|
|
+}
|