diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java b/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java index 6b997e36..f5115e34 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java @@ -356,13 +356,104 @@ public class CommonUtil { /** * 对象去重工具 + * * @param keyExtractor - * @return * @param + * @return */ public static Predicate distinctByKey(Function keyExtractor) { Set seen = new HashSet<>(); return t -> seen.add(keyExtractor.apply(t)); } + /** + * 按照指定比例计算分账金额 + * 分配优先级: 商家 > 平台 > 代理商 + * + * @param totalAmount 分账总金额(单位:分) + * @param merchantRatio 商家分账比例 + * @param platformRatioInRemaining 平台在剩余比例中的分账比例 + * @param agentRatioInRemaining 代理商在剩余比例中的分账比例 + * @return 包含商家、平台、代理商分得金额的Map + */ + public static Map calculateProfitSharing(int totalAmount, + BigDecimal merchantRatio, + BigDecimal platformRatioInRemaining, + BigDecimal agentRatioInRemaining) { + Map result = new HashMap<>(); + + // 初始化各方法定金额 + int merchantAmount = 0; + int platformAmount = 0; + int agentAmount = 0; + + // 1. 计算商家按比例应得的金额(优先级最高) + if (merchantRatio != null && merchantRatio.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal merchantAmountDecimal = merchantRatio.multiply(new BigDecimal(totalAmount)); + merchantAmount = merchantAmountDecimal.setScale(0, RoundingMode.DOWN).intValue(); + + // 确保商家至少获得1分钱(如果商家参与分账但分配为0,且总金额大于0) + if (merchantAmount == 0 && totalAmount > 0) { + merchantAmount = 1; + } + } + + // 2. 计算剩余金额 + int remainingAmount = totalAmount - merchantAmount; + + // 3. 只有当有剩余金额时,才计算平台和代理商的分配 + if (remainingAmount > 0) { + // 计算剩余比例(1 - 商家比例) + BigDecimal remainingRatio = new BigDecimal("1"); + if (merchantRatio != null && merchantRatio.compareTo(BigDecimal.ZERO) > 0) { + remainingRatio = new BigDecimal("1").subtract(merchantRatio); + } + + // 如果代理商不参与分账 + if (agentRatioInRemaining == null || agentRatioInRemaining.compareTo(BigDecimal.ZERO) <= 0) { + // 所有剩余金额都给平台 + platformAmount = remainingAmount; + } else { + // 计算平台应得金额(基于剩余金额) + if (platformRatioInRemaining != null && platformRatioInRemaining.compareTo(BigDecimal.ZERO) > 0) { + // 平台在剩余部分中的分配 = 剩余金额 * 平台在剩余中的比例 + BigDecimal platformShareOfRemaining = platformRatioInRemaining.multiply(new BigDecimal(remainingAmount)); + platformAmount = platformShareOfRemaining.setScale(0, RoundingMode.DOWN).intValue(); + } + + // 计算代理商应得金额(基于剩余金额) + if (agentRatioInRemaining != null && agentRatioInRemaining.compareTo(BigDecimal.ZERO) > 0) { + // 代理商在剩余部分中的分配 = 剩余金额 * 代理商在剩余中的比例 + BigDecimal agentShareOfRemaining = agentRatioInRemaining.multiply(new BigDecimal(remainingAmount)); + agentAmount = agentShareOfRemaining.setScale(0, RoundingMode.DOWN).intValue(); + } + + // 重新计算剩余金额用于最终分配 + int finalRemainingAmount = remainingAmount - platformAmount - agentAmount; + + // 确保平台至少获得1分钱(如果平台参与分账但分配为0,且还有剩余金额) + if (platformRatioInRemaining != null && platformRatioInRemaining.compareTo(BigDecimal.ZERO) > 0 + && platformAmount == 0 && finalRemainingAmount > 0) { + platformAmount = 1; + finalRemainingAmount--; + } + + // 按优先级分配剩余金额: 商家 > 平台 > 代理商 + // 剩余金额给商家(因为商家优先级最高) + merchantAmount += finalRemainingAmount; + } + } + + result.put("merchantAmount", merchantAmount); + result.put("platformAmount", platformAmount); + result.put("agentAmount", agentAmount); + + return result; + } + + public static void main(String[] args) { + System.out.println("测试1分钱分配:"); + System.out.println(calculateProfitSharing(9800, new BigDecimal("0.94"), new BigDecimal("0.2"), new BigDecimal("0"))); + } + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java index 6084290a..c2235a75 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java @@ -48,7 +48,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; @@ -57,8 +56,8 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Map; @Slf4j @@ -1653,7 +1652,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { splitAmount = CheckUtil.isEmpty(splitAmount) ? 0 : splitAmount; // 7. 分账金额校验 - if (splitAmount <= 0) { + if (splitAmount < 1) { String errorMsg = String.format("店铺[%s]订单[%s]分账金额[%d]低于1分钱,跳过分账", shopOrderLkl.getStore_id(), orderId, splitAmount); log.error(errorMsg); @@ -1678,134 +1677,64 @@ public class LakalaApiServiceImpl implements LakalaApiService { List recvDatas = new ArrayList<>(); // 9. 获取商家分账比例并校验 - BigDecimal splitRatioMch = shopOrderLkl.getSplit_ratio(); + BigDecimal splitRatioValMch = shopOrderLkl.getSplit_ratio(); // 如:94 代表94% // 判断商家分账比例是否有效(必须在(0, 100]范围内) - boolean canSplitForMch = splitRatioMch != null - && splitRatioMch.compareTo(BigDecimal.ZERO) > 0 - && splitRatioMch.compareTo(new BigDecimal(100)) <= 0; + boolean canSplitForMch = splitRatioValMch != null + && splitRatioValMch.compareTo(BigDecimal.ZERO) > 0 + && splitRatioValMch.compareTo(new BigDecimal(100)) <= 0; if (!canSplitForMch) { String errorMsg = String.format("店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账", - shopOrderLkl.getStore_id(), splitRatioMch); + shopOrderLkl.getStore_id(), splitRatioValMch); log.error(errorMsg); errorMessages.append(errorMsg).append("; "); continue; - } else { - log.info("店铺[%s]商家分账比例[%s]在(0-100]范围内,可以分账", - shopOrderLkl.getStore_id(), splitRatioMch); - // 商家分账 - BigDecimal mchRatio = splitRatioMch.divide(new BigDecimal(100)); // 比如:94/100 - Integer mchSplitCent = new BigDecimal(splitAmount).multiply(mchRatio).intValue(); - if (mchSplitCent > 0 || splitAmount >= 1) { - // 总分账金额大于1分,商家则分账 - V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); - receiver.setRecvMerchantNo(merchantNo); - receiver.setSeparateValue(mchSplitCent.toString()); - recvDatas.add(receiver); + } + // 商家分账 + BigDecimal mchRatio = splitRatioValMch.divide(new BigDecimal(100)); // 比如:94% - log.debug("商家分账:金额={}分,商户号={}", mchSplitCent, merchantNo); - } + BigDecimal distributorRatio = BigDecimal.ZERO; + BigDecimal platformRatio = BigDecimal.ONE; + // 分账代理商接收方信息 + List distributors = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo); + if (distributors != null && distributors.size() > 0) { + distributorRatio = new BigDecimal("0.8"); + platformRatio = new BigDecimal("0.2"); } - // 10. 计算平台和代理商分账金额 - Integer pdSplitAmount = 0; + // 记录关键分账参数,便于问题排查 + log.info("分账参数信息:订单={}, 商户={}, 总金额={}分, 商家比例={}, 平台比例={}, 代理商比例={}, 是否有代理商={}", + orderId, merchantNo, splitAmount, mchRatio, platformRatio, distributorRatio, + (distributors != null && !distributors.isEmpty())); - // 计算平台+代理商的总比例=100-商家分账比例 - BigDecimal splitRatioNoMch = new BigDecimal(100).subtract(splitRatioMch); - // 判断平台和代理商分账比例是否有效(必须在[0, 100)范围内) - boolean canSplitForNoMch = splitRatioNoMch != null - && splitRatioNoMch.compareTo(BigDecimal.ZERO) > 0 - && splitRatioNoMch.compareTo(new BigDecimal(100)) < 0; - if (!canSplitForNoMch) { - String errorMsg = String.format("店铺[%s]平台和代理商分账比例[%s]不在[0-100)范围内,无法分账", - shopOrderLkl.getStore_id(), splitRatioNoMch); - log.warn(errorMsg); - } else { - log.info("店铺[%s]平台和代理商分账比例[%s]在[0-100)范围内,可以分账", - shopOrderLkl.getStore_id(), splitRatioNoMch); + // 返回值如下:{platformAmount=6, merchantAmount=94, agentAmount=0} + Map splitAmountMap = CommonUtil.calculateProfitSharing(splitAmount, mchRatio, platformRatio, distributorRatio); + Integer merchantAmount = splitAmountMap.get("merchantAmount"); + Integer platformAmount = splitAmountMap.get("platformAmount"); + Integer agentAmount = splitAmountMap.get("agentAmount"); - // 分账代理商接收方信息 - List distributors = Collections.emptyList(); + // 记录分账结果,便于问题排查 + log.info("分账金额计算结果:订单={}, 商户={}, 总金额={}分, 商家分得={}分, 平台分得={}分, 代理商分得={}分", + orderId, merchantNo, splitAmount, merchantAmount, platformAmount, agentAmount); - // 判断要不要分账给平台和代理商(分账金额太低或者没有平台、代理商,就不用分账) - // 计算平台和代理商分账金额,小于1分钱,不进行平台和代理商分账 - BigDecimal notMchRatio = splitRatioNoMch.divide(new BigDecimal(100)); // 比如:6/100 + if (merchantAmount > 0) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvMerchantNo(merchantNo); + receiver.setSeparateValue(merchantAmount.toString()); + recvDatas.add(receiver); + } - // 平台方和代理商总计分账金额 - pdSplitAmount = notMchRatio.multiply(new BigDecimal(splitAmount)).intValue(); - if (pdSplitAmount > 1) { + if (platformAmount > 0) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvNo(platform.getReceiver_no()); + receiver.setSeparateValue(platformAmount.toString()); + recvDatas.add(receiver); + } - // 11. 判断是否能分账给代理商 - // 如果有平台和代理商同时存在,平台分账剩余资金20%;代理商分账剩余资金80%; - // 能分账给代理商的条件:总分账金额*0.2要大于1分钱 - boolean canSplitForDistributors = pdSplitAmount * 0.2 > 1; - - // 12. 根据是否能分账给代理商决定分账模式 - if (canSplitForDistributors) { - // 获取分账代理商接收方信息 - distributors = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo); - // 根据是否有代理商决定分账模式 - if (!CollectionUtils.isEmpty(distributors)) { - // 平台+代理商分账模式 - log.debug("订单[{}]采用平台+代理商分账模式", orderId); - - // 平台收取剩余资金20%手续费 - Integer platformValue = pdSplitAmount * 20 / 100; - if (platformValue >= 1) { - V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); - receiver.setRecvNo(platform.getReceiver_no()); - receiver.setSeparateValue(platformValue.toString()); - recvDatas.add(receiver); - log.debug("平台分账:金额={}分,接收方={}", platformValue, platform.getReceiver_no()); - } - - // 代理商分账(扣除平台20%后的剩余比例) - Integer distributorValue = pdSplitAmount - platformValue; - if (distributorValue >= 1) { - V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); - receiver.setRecvNo(distributors.get(0).getReceiver_no()); - receiver.setSeparateValue(distributorValue.toString()); - recvDatas.add(receiver); - log.debug("代理商分账:金额={}分,接收方={}", distributorValue, distributors.get(0).getReceiver_no()); - } - } - } else { - // 仅平台分账模式 - log.debug("订单[{}]采用仅平台分账模式", orderId); - - if (pdSplitAmount > 1) { - V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); - receiver.setRecvNo(platform.getReceiver_no()); - receiver.setSeparateValue(pdSplitAmount.toString()); - recvDatas.add(receiver); - log.debug("平台分账:金额={}分,接收方={}", pdSplitAmount, platform.getReceiver_no()); - } - } - - // 13. 记录详细的分账信息,便于部署后排查问题 - if (log.isDebugEnabled()) { - StringBuilder detailLog = new StringBuilder(); - detailLog.append("详细分账信息:"); - detailLog.append("订单ID=").append(shopOrderLkl.getOrder_id()).append(", "); - detailLog.append("商户号=").append(merchantNo).append(", "); - detailLog.append("分账流水号=").append(shopOrderLkl.getLkl_split_log_no()).append(", "); - detailLog.append("外部分账单号=").append(shopOrderLkl.getOut_separate_no()).append(", "); - detailLog.append("分账总金额=").append(splitAmount).append("分, "); - detailLog.append("商家分账比例=").append(splitRatioMch).append("%, "); - detailLog.append("平台接收方=").append(platform.getReceiver_no()).append(", "); - - if (canSplitForDistributors && !CollectionUtils.isEmpty(distributors)) { - detailLog.append("代理商接收方=["); - for (int i = 0; i < distributors.size(); i++) { - if (i > 0) detailLog.append(","); - detailLog.append(distributors.get(i).getReceiver_no()); - } - detailLog.append("], "); - } - - detailLog.append("分账接收方数量=").append(recvDatas.size()); - log.debug(detailLog.toString()); - } - } + if (agentAmount > 0 && distributors != null && !distributors.isEmpty()) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvNo(distributors.get(0).getReceiver_no()); + receiver.setSeparateValue(agentAmount.toString()); + recvDatas.add(receiver); } // 14. 构建分账请求对象 @@ -1864,7 +1793,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { lklOrderSeparate.setLkl_org_no(request.getLklOrgNo()); lklOrderSeparate.setRecv_datas(JSONUtil.toJsonStr(request.getRecvDatas())); lklOrderSeparate.setStatus(respData.getStr("status")); - lklOrderSeparate.setTotal_separate_value(pdSplitAmount); + lklOrderSeparate.setTotal_separate_value(platformAmount + agentAmount); try { if (lklOrderSeparateService.addOrUpdateByReceiverNo(lklOrderSeparate)) {