订单命名-改成_下划线,拉卡拉要求的规则,否则无法分账

This commit is contained in:
Jack 2025-09-09 01:08:35 +08:00
parent ab9c68fb8f
commit d55dd93b05
2 changed files with 139 additions and 119 deletions

View File

@ -356,13 +356,104 @@ public class CommonUtil {
/** /**
* 对象去重工具 * 对象去重工具
*
* @param keyExtractor * @param keyExtractor
* @return
* @param <T> * @param <T>
* @return
*/ */
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = new HashSet<>(); Set<Object> seen = new HashSet<>();
return t -> seen.add(keyExtractor.apply(t)); return t -> seen.add(keyExtractor.apply(t));
} }
/**
* 按照指定比例计算分账金额
* 分配优先级: 商家 > 平台 > 代理商
*
* @param totalAmount 分账总金额(单位:)
* @param merchantRatio 商家分账比例
* @param platformRatioInRemaining 平台在剩余比例中的分账比例
* @param agentRatioInRemaining 代理商在剩余比例中的分账比例
* @return 包含商家平台代理商分得金额的Map
*/
public static Map<String, Integer> calculateProfitSharing(int totalAmount,
BigDecimal merchantRatio,
BigDecimal platformRatioInRemaining,
BigDecimal agentRatioInRemaining) {
Map<String, Integer> 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")));
}
} }

View File

@ -48,7 +48,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -57,8 +56,8 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
@Slf4j @Slf4j
@ -1653,7 +1652,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
splitAmount = CheckUtil.isEmpty(splitAmount) ? 0 : splitAmount; splitAmount = CheckUtil.isEmpty(splitAmount) ? 0 : splitAmount;
// 7. 分账金额校验 // 7. 分账金额校验
if (splitAmount <= 0) { if (splitAmount < 1) {
String errorMsg = String.format("店铺[%s]订单[%s]分账金额[%d]低于1分钱跳过分账", String errorMsg = String.format("店铺[%s]订单[%s]分账金额[%d]低于1分钱跳过分账",
shopOrderLkl.getStore_id(), orderId, splitAmount); shopOrderLkl.getStore_id(), orderId, splitAmount);
log.error(errorMsg); log.error(errorMsg);
@ -1678,134 +1677,64 @@ public class LakalaApiServiceImpl implements LakalaApiService {
List<V3SacsSeparateRecvDatas> recvDatas = new ArrayList<>(); List<V3SacsSeparateRecvDatas> recvDatas = new ArrayList<>();
// 9. 获取商家分账比例并校验 // 9. 获取商家分账比例并校验
BigDecimal splitRatioMch = shopOrderLkl.getSplit_ratio(); BigDecimal splitRatioValMch = shopOrderLkl.getSplit_ratio(); // 94 代表94%
// 判断商家分账比例是否有效必须在(0, 100]范围内 // 判断商家分账比例是否有效必须在(0, 100]范围内
boolean canSplitForMch = splitRatioMch != null boolean canSplitForMch = splitRatioValMch != null
&& splitRatioMch.compareTo(BigDecimal.ZERO) > 0 && splitRatioValMch.compareTo(BigDecimal.ZERO) > 0
&& splitRatioMch.compareTo(new BigDecimal(100)) <= 0; && splitRatioValMch.compareTo(new BigDecimal(100)) <= 0;
if (!canSplitForMch) { if (!canSplitForMch) {
String errorMsg = String.format("店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账", String errorMsg = String.format("店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账",
shopOrderLkl.getStore_id(), splitRatioMch); shopOrderLkl.getStore_id(), splitRatioValMch);
log.error(errorMsg); log.error(errorMsg);
errorMessages.append(errorMsg).append("; "); errorMessages.append(errorMsg).append("; ");
continue; continue;
} else { }
log.info("店铺[%s]商家分账比例[%s]在(0-100]范围内,可以分账", // 商家分账
shopOrderLkl.getStore_id(), splitRatioMch); BigDecimal mchRatio = splitRatioValMch.divide(new BigDecimal(100)); // 比如94%
// 商家分账
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);
log.debug("商家分账:金额={}分,商户号={}", mchSplitCent, merchantNo); BigDecimal distributorRatio = BigDecimal.ZERO;
} BigDecimal platformRatio = BigDecimal.ONE;
// 分账代理商接收方信息
List<LklLedgerMerReceiverBind> 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-商家分账比例 // 返回值如下{platformAmount=6, merchantAmount=94, agentAmount=0}
BigDecimal splitRatioNoMch = new BigDecimal(100).subtract(splitRatioMch); Map<String, Integer> splitAmountMap = CommonUtil.calculateProfitSharing(splitAmount, mchRatio, platformRatio, distributorRatio);
// 判断平台和代理商分账比例是否有效必须在[0, 100)范围内 Integer merchantAmount = splitAmountMap.get("merchantAmount");
boolean canSplitForNoMch = splitRatioNoMch != null Integer platformAmount = splitAmountMap.get("platformAmount");
&& splitRatioNoMch.compareTo(BigDecimal.ZERO) > 0 Integer agentAmount = splitAmountMap.get("agentAmount");
&& 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);
// 分账代理商接收方信息 // 记录分账结果便于问题排查
List<LklLedgerMerReceiverBind> distributors = Collections.emptyList(); log.info("分账金额计算结果:订单={}, 商户={}, 总金额={}分, 商家分得={}分, 平台分得={}分, 代理商分得={}分",
orderId, merchantNo, splitAmount, merchantAmount, platformAmount, agentAmount);
// 判断要不要分账给平台和代理商分账金额太低或者没有平台代理商就不用分账 if (merchantAmount > 0) {
// 计算平台和代理商分账金额小于1分钱不进行平台和代理商分账 V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
BigDecimal notMchRatio = splitRatioNoMch.divide(new BigDecimal(100)); // 比如6/100 receiver.setRecvMerchantNo(merchantNo);
receiver.setSeparateValue(merchantAmount.toString());
recvDatas.add(receiver);
}
// 平台方和代理商总计分账金额 if (platformAmount > 0) {
pdSplitAmount = notMchRatio.multiply(new BigDecimal(splitAmount)).intValue(); V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
if (pdSplitAmount > 1) { receiver.setRecvNo(platform.getReceiver_no());
receiver.setSeparateValue(platformAmount.toString());
recvDatas.add(receiver);
}
// 11. 判断是否能分账给代理商 if (agentAmount > 0 && distributors != null && !distributors.isEmpty()) {
// 如果有平台和代理商同时存在平台分账剩余资金20%代理商分账剩余资金80% V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
// 能分账给代理商的条件总分账金额*0.2要大于1分钱 receiver.setRecvNo(distributors.get(0).getReceiver_no());
boolean canSplitForDistributors = pdSplitAmount * 0.2 > 1; receiver.setSeparateValue(agentAmount.toString());
recvDatas.add(receiver);
// 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());
}
}
} }
// 14. 构建分账请求对象 // 14. 构建分账请求对象
@ -1864,7 +1793,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
lklOrderSeparate.setLkl_org_no(request.getLklOrgNo()); lklOrderSeparate.setLkl_org_no(request.getLklOrgNo());
lklOrderSeparate.setRecv_datas(JSONUtil.toJsonStr(request.getRecvDatas())); lklOrderSeparate.setRecv_datas(JSONUtil.toJsonStr(request.getRecvDatas()));
lklOrderSeparate.setStatus(respData.getStr("status")); lklOrderSeparate.setStatus(respData.getStr("status"));
lklOrderSeparate.setTotal_separate_value(pdSplitAmount); lklOrderSeparate.setTotal_separate_value(platformAmount + agentAmount);
try { try {
if (lklOrderSeparateService.addOrUpdateByReceiverNo(lklOrderSeparate)) { if (lklOrderSeparateService.addOrUpdateByReceiverNo(lklOrderSeparate)) {