分账公共计算方法

This commit is contained in:
Jack 2025-09-17 09:45:58 +08:00
parent 4623650b18
commit 3e64c19f82
6 changed files with 697 additions and 21 deletions

View File

@ -98,4 +98,10 @@ public class CommonConstant {
public static final String Sep_DeliveryFee_Prefix = "DF_";
public static final String Sep_GoodsFee_Prefix = "ORD_";
// 分账状态1-已分账2-未分账3-分账已失败
public static final Integer Sta_Separate_Success = 1;
public static final Integer Sta_Separate_Undone = 2;
public static final Integer Sta_Separate_Fail = 3;
}

View File

@ -90,6 +90,8 @@ public class ShopOrderLkl implements Serializable {
private Integer receive_status;
private Integer separate_status;
private Integer status;
private Date created_at;

View File

@ -0,0 +1,579 @@
package com.suisung.mall.common.pojo.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 拉卡拉订单分账信息对象
* <p>
* 分账计算逻辑说明
* 前提条件分账总金额必须大于0配送费不能小于0拉卡拉商户平台分账比例必须大于0
* 一级二级代理商分账比例可以为null或小于等于0
* <p>
* 分账计算步骤
* 1. 计算拉卡拉分账金额 = 分账总金额 × 拉卡拉分账比例结果向上取整
* 2. 计算可分账金额 = 分账总金额 - 配送费 - 拉卡拉分账金额
* 3. 调整分账比例确保商户平台一级代理商二级代理商的有效比例之和等于1.0
* - 如果有二级代理商参与分账则调整二级代理商比例
* - 否则如果有一级代理商参与分账则调整一级代理商比例
* - 否则调整平台比例
* 4. 按优先级计算各参与方分账金额结果四舍五入
* - 商户分账金额 = 可分账金额 × 商户分账比例
* - 平台分账金额 = 可分账金额 × 平台分账比例
* - 一级代理商分账金额 = 可分账金额 × 一级代理商分账比例如果参与分账
* - 二级代理商分账金额 = 可分账金额 × 二级代理商分账比例如果参与分账
* 5. 如分配总额与可分账金额不符则调整最后一个分账方按优先级顺序以确保总额平衡
* - 优先调整二级代理商如果参与分账
* - 否则调整一级代理商如果参与分账
* - 否则调整平台
* - 最后调整商户
* 6. 保障各参与方分账比例不能低于指定属性的比例按优先级处理
* - 商户优先级最高
* - 平台优先级次之
* - 一级代理商优先级再次之
* - 二级代理商优先级最低
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "拉卡拉订单分账信息对象", description = "拉卡拉订单分账信息对象")
public class LklSeparateDTO implements java.io.Serializable {
private static final Logger logger = LoggerFactory.getLogger(LklSeparateDTO.class);
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "分账总金额(分)")
private Integer totalSeparateAmount;
@ApiModelProperty(value = "可分账金额(分)")
private Integer canSeparateAmount;
@ApiModelProperty(value = "配送费(分)")
private Integer shippingFee;
@ApiModelProperty(value = "拉卡拉分账比例(如 0.0025=0.25%)")
private BigDecimal lklRatio;
@ApiModelProperty(value = "商户分账比例(如 0.96=96%)")
private BigDecimal mchRatio;
@ApiModelProperty(value = "平台分账比例(如 0.01=1%)")
private BigDecimal platRatio;
@ApiModelProperty(value = "一级代理商分账比例(如 0.01=1%)")
private BigDecimal agent1stRatio;
@ApiModelProperty(value = "二级代理商分账比例(如 0.03=3%)")
private BigDecimal agent2ndRatio;
@ApiModelProperty(value = "拉卡拉分账金额(分)")
private Integer lklAmount;
@ApiModelProperty(value = "商户分账金额(分)")
private Integer mchAmount;
@ApiModelProperty(value = "平台分账金额(分)")
private Integer platAmount;
@ApiModelProperty(value = "一级代理商分账金额(分)")
private Integer agent1stAmount;
@ApiModelProperty(value = "二级代理商分账金额(分)")
private Integer agent2ndAmount;
/**
* 测试方法
*/
public static void main(String[] args) {
logger.info("开始测试分账计算功能...");
// 临界点测试用例1: 极小金额测试
logger.info("\n\n临界点测试用例1 - 极小金额测试:");
LklSeparateDTO dto1 = new LklSeparateDTO();
dto1.setTotalSeparateAmount(3);
dto1.setShippingFee(0);
dto1.setLklRatio(new BigDecimal("0.0025"));
dto1.setMchRatio(new BigDecimal("0.94"));
dto1.setPlatRatio(new BigDecimal("0.06"));
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto1.getTotalSeparateAmount(), dto1.getShippingFee(), dto1.getLklRatio(),
dto1.getMchRatio(), dto1.getPlatRatio());
boolean result1 = dto1.calculateProfitSharing();
logger.info("分账计算结果: {}", (result1 ? "成功" : "失败"));
if (result1) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto1.getTotalSeparateAmount(), dto1.getLklAmount(), dto1.getShippingFee(), dto1.getCanSeparateAmount(),
dto1.getMchAmount(), dto1.getPlatAmount(), dto1.getAgent1stAmount(), dto1.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto1.getLklAmount() / dto1.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto1.getMchAmount() / dto1.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto1.getPlatAmount() / dto1.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto1.getAgent1stAmount() / dto1.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto1.getAgent2ndAmount() / dto1.getTotalSeparateAmount() * 100;
double lklRatioOfAvailable = (double) dto1.getLklAmount() / dto1.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto1.getMchAmount() / dto1.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto1.getPlatAmount() / dto1.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto1.getAgent1stAmount() / dto1.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto1.getAgent2ndAmount() / dto1.getCanSeparateAmount() * 100;
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
String.format("%.4f", mchRatioOfTotal), String.format("%.4f", mchRatioOfAvailable),
String.format("%.4f", platRatioOfTotal), String.format("%.4f", platRatioOfAvailable),
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto1.getLklAmount() + dto1.getMchAmount() + dto1.getPlatAmount() + dto1.getAgent1stAmount() + dto1.getAgent2ndAmount() + dto1.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto1.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto1.getTotalSeparateAmount());
}
// 临界点测试用例2: 小金额测试
logger.info("\n\n临界点测试用例2 - 小金额测试:");
LklSeparateDTO dto2 = new LklSeparateDTO();
dto2.setTotalSeparateAmount(800);
dto2.setShippingFee(500);
dto2.setLklRatio(new BigDecimal("0.0025"));
dto2.setMchRatio(new BigDecimal("0.94"));
dto2.setPlatRatio(new BigDecimal("0.06"));
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto2.getTotalSeparateAmount(), dto2.getShippingFee(), dto2.getLklRatio(),
dto2.getMchRatio(), dto2.getPlatRatio());
boolean result2 = dto2.calculateProfitSharing();
logger.info("分账计算结果: {}", (result2 ? "成功" : "失败"));
if (result2) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto2.getTotalSeparateAmount(), dto2.getLklAmount(), dto2.getShippingFee(), dto2.getCanSeparateAmount(),
dto2.getMchAmount(), dto2.getPlatAmount(), dto2.getAgent1stAmount(), dto2.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto2.getLklAmount() / dto2.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto2.getMchAmount() / dto2.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto2.getPlatAmount() / dto2.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto2.getAgent1stAmount() / dto2.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto2.getAgent2ndAmount() / dto2.getTotalSeparateAmount() * 100;
double lklRatioOfAvailable = (double) dto2.getLklAmount() / dto2.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto2.getMchAmount() / dto2.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto2.getPlatAmount() / dto2.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto2.getAgent1stAmount() / dto2.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto2.getAgent2ndAmount() / dto2.getCanSeparateAmount() * 100;
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
String.format("%.4f", mchRatioOfTotal), String.format("%.4f", mchRatioOfAvailable),
String.format("%.4f", platRatioOfTotal), String.format("%.4f", platRatioOfAvailable),
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto2.getLklAmount() + dto2.getMchAmount() + dto2.getPlatAmount() + dto2.getAgent1stAmount() + dto2.getAgent2ndAmount() + dto2.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto2.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto2.getTotalSeparateAmount());
}
// 临界点测试用例3: 中等金额测试
logger.info("\n\n临界点测试用例3 - 中等金额测试:");
LklSeparateDTO dto3 = new LklSeparateDTO();
dto3.setTotalSeparateAmount(100);
dto3.setShippingFee(30);
dto3.setLklRatio(new BigDecimal("0.0025"));
dto3.setMchRatio(new BigDecimal("0.94"));
dto3.setPlatRatio(new BigDecimal("0.06"));
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto3.getTotalSeparateAmount(), dto3.getShippingFee(), dto3.getLklRatio(),
dto3.getMchRatio(), dto3.getPlatRatio());
boolean result3 = dto3.calculateProfitSharing();
logger.info("分账计算结果: {}", (result3 ? "成功" : "失败"));
if (result3) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto3.getTotalSeparateAmount(), dto3.getLklAmount(), dto3.getShippingFee(), dto3.getCanSeparateAmount(),
dto3.getMchAmount(), dto3.getPlatAmount(), dto3.getAgent1stAmount(), dto3.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto3.getLklAmount() / dto3.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto3.getMchAmount() / dto3.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto3.getPlatAmount() / dto3.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto3.getAgent1stAmount() / dto3.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto3.getAgent2ndAmount() / dto3.getTotalSeparateAmount() * 100;
double lklRatioOfAvailable = (double) dto3.getLklAmount() / dto3.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto3.getMchAmount() / dto3.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto3.getPlatAmount() / dto3.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto3.getAgent1stAmount() / dto3.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto3.getAgent2ndAmount() / dto3.getCanSeparateAmount() * 100;
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
String.format("%.4f", mchRatioOfTotal), String.format("%.4f", mchRatioOfAvailable),
String.format("%.4f", platRatioOfTotal), String.format("%.4f", platRatioOfAvailable),
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto3.getLklAmount() + dto3.getMchAmount() + dto3.getPlatAmount() + dto3.getAgent1stAmount() + dto3.getAgent2ndAmount() + dto3.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto3.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto3.getTotalSeparateAmount());
}
// 临界点测试用例4: 大金额测试
logger.info("\n\n临界点测试用例4 - 大金额测试:");
LklSeparateDTO dto4 = new LklSeparateDTO();
dto4.setTotalSeparateAmount(10000);
dto4.setShippingFee(500);
dto4.setLklRatio(new BigDecimal("0.0025"));
dto4.setMchRatio(new BigDecimal("0.94"));
dto4.setPlatRatio(new BigDecimal("0.06"));
logger.info("测试参数: 总分账金额={}分, 配送费={}分, 拉卡拉比例={}, 商户比例={}, 平台比例={}",
dto4.getTotalSeparateAmount(), dto4.getShippingFee(), dto4.getLklRatio(),
dto4.getMchRatio(), dto4.getPlatRatio());
boolean result4 = dto4.calculateProfitSharing();
logger.info("分账计算结果: {}", (result4 ? "成功" : "失败"));
if (result4) {
logger.info("分账结果: 总金额={}分, 拉卡拉={}分, 配送费={}分, 可分账={}分, 商户={}分, 平台={}分, 一级代理商={}分, 二级代理商={}分",
dto4.getTotalSeparateAmount(), dto4.getLklAmount(), dto4.getShippingFee(), dto4.getCanSeparateAmount(),
dto4.getMchAmount(), dto4.getPlatAmount(), dto4.getAgent1stAmount(), dto4.getAgent2ndAmount());
double lklRatioOfTotal = (double) dto4.getLklAmount() / dto4.getTotalSeparateAmount() * 100;
double mchRatioOfTotal = (double) dto4.getMchAmount() / dto4.getTotalSeparateAmount() * 100;
double platRatioOfTotal = (double) dto4.getPlatAmount() / dto4.getTotalSeparateAmount() * 100;
double agent1stRatioOfTotal = (double) dto4.getAgent1stAmount() / dto4.getTotalSeparateAmount() * 100;
double agent2ndRatioOfTotal = (double) dto4.getAgent2ndAmount() / dto4.getTotalSeparateAmount() * 100;
double lklRatioOfAvailable = (double) dto4.getLklAmount() / dto4.getCanSeparateAmount() * 100;
double mchRatioOfAvailable = (double) dto4.getMchAmount() / dto4.getCanSeparateAmount() * 100;
double platRatioOfAvailable = (double) dto4.getPlatAmount() / dto4.getCanSeparateAmount() * 100;
double agent1stRatioOfAvailable = (double) dto4.getAgent1stAmount() / dto4.getCanSeparateAmount() * 100;
double agent2ndRatioOfAvailable = (double) dto4.getAgent2ndAmount() / dto4.getCanSeparateAmount() * 100;
logger.info("占比详情 (总金额占比 / 可分账金额占比): 拉卡拉={}% / {}%, 商户={}% / {}%, 平台={}% / {}%, 一级代理商={}% / {}%, 二级代理商={}% / {}%",
String.format("%.4f", lklRatioOfTotal), String.format("%.4f", lklRatioOfAvailable),
String.format("%.4f", mchRatioOfTotal), String.format("%.4f", mchRatioOfAvailable),
String.format("%.4f", platRatioOfTotal), String.format("%.4f", platRatioOfAvailable),
String.format("%.4f", agent1stRatioOfTotal), String.format("%.4f", agent1stRatioOfAvailable),
String.format("%.4f", agent2ndRatioOfTotal), String.format("%.4f", agent2ndRatioOfAvailable));
int total = dto4.getLklAmount() + dto4.getMchAmount() + dto4.getPlatAmount() + dto4.getAgent1stAmount() + dto4.getAgent2ndAmount() + dto4.getShippingFee();
logger.info("金额校验: {} (计算总金额={}分, 原始总金额={}分)", (total == dto4.getTotalSeparateAmount() ? "通过" : "不通过"), total, dto4.getTotalSeparateAmount());
}
}
/**
* 根据实体类的属性扣除拉卡拉手续费配送费之后从可用余额中进行分账计算
*
* @return 分账结果是否成功
*/
public boolean calculateProfitSharing() {
// 检查前提条件
if (!validateInputs()) {
return false;
}
logger.info("开始分账计算,分账总金额={}分,配送费={}分", totalSeparateAmount, shippingFee);
// 1. 计算拉卡拉分账金额 = 分账总金额 × 拉卡拉分账比例向上取整
calculateLklAmount();
// 2. 计算可分账金额 = 分账总金额 - 配送费 - 拉卡拉分账金额
calculateCanSeparateAmount();
if (canSeparateAmount <= 0) {
logger.info("分账计算失败可分账金额必须大于0当前值={}", canSeparateAmount);
return false;
}
// 3. 调整分账比例确保有效值比例相加等于1.0
adjustRatios();
// 4. 按优先级分账商户 > 平台 > 一级代理商 > 二级代理商
performSeparate();
// 5. 确保总额平衡
balanceAmounts();
// 6. 保障各参与方分账比例不低于指定值
guaranteeRatios();
logger.info("分账计算完成:商户={},平台={},一级代理商={},二级代理商={}",
mchAmount, platAmount, agent1stAmount, agent2ndAmount);
return true;
}
/**
* 验证输入参数
*/
private boolean validateInputs() {
if (totalSeparateAmount == null || totalSeparateAmount <= 0) {
logger.info("分账计算失败分账总金额必须大于0当前值={}", totalSeparateAmount);
return false;
}
if (shippingFee == null || shippingFee < 0) {
logger.info("分账计算失败配送费不能小于0当前值={}", shippingFee);
return false;
}
if (lklRatio == null || lklRatio.compareTo(BigDecimal.ZERO) <= 0) {
logger.info("分账计算失败拉卡拉分账比例必须大于0当前值={}", lklRatio);
return false;
}
if (mchRatio == null || mchRatio.compareTo(BigDecimal.ZERO) <= 0) {
logger.info("分账计算失败商户分账比例必须大于0当前值={}", mchRatio);
return false;
}
if (platRatio == null || platRatio.compareTo(BigDecimal.ZERO) <= 0) {
logger.info("分账计算失败平台分账比例必须大于0当前值={}", platRatio);
return false;
}
return true;
}
/**
* 计算拉卡拉分账金额
*/
private void calculateLklAmount() {
BigDecimal lklAmountDecimal = new BigDecimal(totalSeparateAmount).multiply(lklRatio);
lklAmount = lklAmountDecimal.setScale(0, RoundingMode.UP).intValue();
logger.debug("拉卡拉分账计算:分账总金额{} × 拉卡拉比例{} = {},向上取整后={}", totalSeparateAmount, lklRatio, lklAmountDecimal, lklAmount);
}
/**
* 计算可分账金额
*/
private void calculateCanSeparateAmount() {
canSeparateAmount = totalSeparateAmount - shippingFee - lklAmount;
logger.debug("可分账金额计算:分账总金额{} - 配送费{} - 拉卡拉分账{} = {}", totalSeparateAmount, shippingFee, lklAmount, canSeparateAmount);
}
/**
* 调整分账比例确保有效值比例相加等于1.0
*/
private void adjustRatios() {
// 计算有效的分账比例总和
BigDecimal effectiveRatioSum = BigDecimal.ZERO
.add(mchRatio)
.add(platRatio)
.add(getOrDefault(agent1stRatio, BigDecimal.ZERO))
.add(getOrDefault(agent2ndRatio, BigDecimal.ZERO));
logger.debug("调整前各分账比例:商户={},平台={},一级代理商={},二级代理商={},有效比例总和={}",
mchRatio, platRatio, agent1stRatio, agent2ndRatio, effectiveRatioSum);
// 动态调整最后一个分账方的比例确保总和为1.0
BigDecimal adjustmentRatio = BigDecimal.ONE.subtract(effectiveRatioSum);
if (agent2ndRatio != null && agent2ndRatio.compareTo(BigDecimal.ZERO) > 0) {
// 调整二级代理商比例
agent2ndRatio = agent2ndRatio.add(adjustmentRatio);
} else if (agent1stRatio != null && agent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
// 调整一级代理商比例
agent1stRatio = agent1stRatio.add(adjustmentRatio);
} else {
// 调整平台比例
platRatio = platRatio.add(adjustmentRatio);
}
logger.debug("调整后各分账比例:商户={},平台={},一级代理商={},二级代理商={}",
mchRatio, platRatio, agent1stRatio, agent2ndRatio);
}
/**
* 按优先级进行分账
*/
private void performSeparate() {
// 商户分账金额 = 可分账金额 × 商户分账比例四舍五入
BigDecimal mchAmountDecimal = new BigDecimal(canSeparateAmount).multiply(mchRatio);
mchAmount = mchAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("商户分账计算:可分账金额{} × 商户比例{} = {},四舍五入后={}", canSeparateAmount, mchRatio, mchAmountDecimal, mchAmount);
// 平台分账金额 = 可分账金额 × 平台分账比例四舍五入
BigDecimal platAmountDecimal = new BigDecimal(canSeparateAmount).multiply(platRatio);
platAmount = platAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("平台分账计算:可分账金额{} × 平台比例{} = {},四舍五入后={}", canSeparateAmount, platRatio, platAmountDecimal, platAmount);
// 一级代理商分账金额 = 可分账金额 × 一级代理商分账比例四舍五入
if (agent1stRatio != null && agent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal agent1stAmountDecimal = new BigDecimal(canSeparateAmount).multiply(agent1stRatio);
agent1stAmount = agent1stAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("一级代理商分账计算:可分账金额{} × 一级代理商比例{} = {},四舍五入后={}", canSeparateAmount, agent1stRatio, agent1stAmountDecimal, agent1stAmount);
} else {
agent1stAmount = 0;
}
// 二级代理商分账金额 = 可分账金额 × 二级代理商分账比例四舍五入
if (agent2ndRatio != null && agent2ndRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal agent2ndAmountDecimal = new BigDecimal(canSeparateAmount).multiply(agent2ndRatio);
agent2ndAmount = agent2ndAmountDecimal.setScale(0, RoundingMode.HALF_UP).intValue();
logger.debug("二级代理商分账计算:可分账金额{} × 二级代理商比例{} = {},四舍五入后={}", canSeparateAmount, agent2ndRatio, agent2ndAmountDecimal, agent2ndAmount);
} else {
agent2ndAmount = 0;
}
}
/**
* 确保总额平衡
*/
private void balanceAmounts() {
int totalAmount = mchAmount + platAmount + agent1stAmount + agent2ndAmount;
if (totalAmount != canSeparateAmount) {
int diff = canSeparateAmount - totalAmount;
logger.debug("分账总额与可分账金额不符,差额={},开始调整", diff);
// 将差额分配给优先级最低的参与方确保总额平衡
if (agent2ndAmount > 0 && agent2ndAmount + diff >= 0) {
agent2ndAmount += diff;
logger.debug("调整二级代理商分账金额:{} + {} = {}", agent2ndAmount - diff, diff, agent2ndAmount);
} else if (agent1stAmount > 0 && agent1stAmount + diff >= 0) {
agent1stAmount += diff;
logger.debug("调整一级代理商分账金额:{} + {} = {}", agent1stAmount - diff, diff, agent1stAmount);
} else if (platAmount + diff >= 0) {
platAmount += diff;
logger.debug("调整平台分账金额:{} + {} = {}", platAmount - diff, diff, platAmount);
} else {
mchAmount += diff;
logger.debug("调整商户分账金额:{} + {} = {}", mchAmount - diff, diff, mchAmount);
}
}
}
/**
* 保障各参与方分账比例不低于指定值
*/
private void guaranteeRatios() {
// 商户优先级最高
guaranteeMerchantRatio();
// 平台优先级次之
guaranteePlatformRatio();
// 一级代理商优先级再次之
guaranteeAgent1stRatio();
// 最终验证和调整确保所有参与方分账比例不低于指定值
finalGuaranteeMerchantRatio();
}
/**
* 保障商户分账比例
*/
private void guaranteeMerchantRatio() {
BigDecimal actualMchRatio = new BigDecimal(mchAmount).divide(new BigDecimal(canSeparateAmount), 6, RoundingMode.HALF_UP);
if (actualMchRatio.compareTo(mchRatio) < 0) {
int requiredMchAmount = mchRatio.multiply(new BigDecimal(canSeparateAmount)).setScale(0, RoundingMode.HALF_UP).intValue();
int diff = requiredMchAmount - mchAmount;
if (diff > 0) {
logger.debug("商户分账比例不足,需要调整金额={},商户当前金额={},应得金额={}", diff, mchAmount, requiredMchAmount);
if (agent2ndAmount > 0 && agent2ndAmount >= diff) {
agent2ndAmount -= diff;
mchAmount += diff;
logger.debug("从二级代理商调整{}给商户,调整后:商户={},二级代理商={}", diff, mchAmount, agent2ndAmount);
} else if (agent1stAmount > 0 && agent1stAmount >= diff) {
agent1stAmount -= diff;
mchAmount += diff;
logger.debug("从一级代理商调整{}给商户,调整后:商户={},一级代理商={}", diff, mchAmount, agent1stAmount);
} else if (platAmount >= diff) {
platAmount -= diff;
mchAmount += diff;
logger.debug("从平台调整{}给商户,调整后:商户={},平台={}", diff, mchAmount, platAmount);
}
}
}
}
/**
* 保障平台分账比例
*/
private void guaranteePlatformRatio() {
BigDecimal actualPlatRatio = new BigDecimal(platAmount).divide(new BigDecimal(canSeparateAmount), 6, RoundingMode.HALF_UP);
if (actualPlatRatio.compareTo(platRatio) < 0) {
int requiredPlatAmount = platRatio.multiply(new BigDecimal(canSeparateAmount)).setScale(0, RoundingMode.HALF_UP).intValue();
int diff = requiredPlatAmount - platAmount;
if (diff > 0) {
logger.debug("平台分账比例不足,需要调整金额={},平台当前金额={},应得金额={}", diff, platAmount, requiredPlatAmount);
if (agent2ndAmount > 0 && agent2ndAmount >= diff) {
agent2ndAmount -= diff;
platAmount += diff;
logger.debug("从二级代理商调整{}给平台,调整后:平台={},二级代理商={}", diff, platAmount, agent2ndAmount);
} else if (agent1stAmount > 0 && agent1stAmount >= diff) {
agent1stAmount -= diff;
platAmount += diff;
logger.debug("从一级代理商调整{}给平台,调整后:平台={},一级代理商={}", diff, platAmount, agent1stAmount);
}
}
}
}
/**
* 保障一级代理商分账比例
*/
private void guaranteeAgent1stRatio() {
if (agent1stRatio != null && agent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal actualAgent1stRatio = new BigDecimal(agent1stAmount).divide(new BigDecimal(canSeparateAmount), 6, RoundingMode.HALF_UP);
if (actualAgent1stRatio.compareTo(agent1stRatio) < 0) {
int requiredAgent1stAmount = agent1stRatio.multiply(new BigDecimal(canSeparateAmount)).setScale(0, RoundingMode.HALF_UP).intValue();
int diff = requiredAgent1stAmount - agent1stAmount;
if (diff > 0) {
logger.debug("一级代理商分账比例不足,需要调整金额={},一级代理商当前金额={},应得金额={}", diff, agent1stAmount, requiredAgent1stAmount);
if (agent2ndAmount > 0 && agent2ndAmount >= diff) {
agent2ndAmount -= diff;
agent1stAmount += diff;
logger.debug("从二级代理商调整{}给一级代理商,调整后:一级代理商={},二级代理商={}", diff, agent1stAmount, agent2ndAmount);
}
}
}
}
}
/**
* 最终保障商户分账比例
*/
private void finalGuaranteeMerchantRatio() {
BigDecimal finalMchRatio = new BigDecimal(mchAmount).divide(new BigDecimal(canSeparateAmount), 6, RoundingMode.HALF_UP);
if (finalMchRatio.compareTo(mchRatio) < 0) {
int requiredMchAmount = mchRatio.multiply(new BigDecimal(canSeparateAmount)).setScale(0, RoundingMode.UP).intValue();
int diff = requiredMchAmount - mchAmount;
if (diff > 0) {
logger.debug("商户最终分账比例仍不足,需要调整金额={},商户当前金额={},应得金额={}", diff, mchAmount, requiredMchAmount);
if (agent2ndAmount > 0 && agent2ndAmount >= diff) {
agent2ndAmount -= diff;
mchAmount += diff;
logger.debug("最终调整:从二级代理商调整{}给商户,调整后:商户={},二级代理商={}", diff, mchAmount, agent2ndAmount);
} else {
int remainingDiff = diff - agent2ndAmount;
if (agent1stAmount > 0 && agent1stAmount >= remainingDiff) {
agent1stAmount -= remainingDiff;
mchAmount += diff;
logger.debug("最终调整:从一级代理商调整{}给商户,调整后:商户={},一级代理商={}", remainingDiff, mchAmount, agent1stAmount);
} else {
int remainingDiff2 = remainingDiff - agent1stAmount;
if (platAmount >= remainingDiff2) {
platAmount -= remainingDiff2;
mchAmount += diff;
logger.debug("最终调整:从平台调整{}给商户,调整后:商户={},平台={}", remainingDiff2, mchAmount, platAmount);
}
}
}
}
}
}
/**
* 获取BigDecimal值如果为null则返回默认值
*/
private BigDecimal getOrDefault(BigDecimal value, BigDecimal defaultValue) {
return value != null ? value : defaultValue;
}
}

View File

@ -10,6 +10,7 @@ package com.suisung.mall.shop.lakala.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -555,8 +556,13 @@ public class LakalaApiServiceImpl implements LakalaApiService {
shopMchEntryService.updateMerchEntryEcResultUrlByMchId(mchId, ecResultUrl);
// 发短信给商家及时签署合同 SMS_488465246
// 小发同城商家恭喜您的开店入驻申请已审核通过请尽快登录小发同城商家版APP平台签署电子合同签署链接24小时内有效逾期需重新提交申请如有疑问请联系客服感谢您的支持
shopMessageTemplateService.aliyunSmsSend(contractMobile, "SMS_493160417", null);//SMS_479760276
//
//恭喜您的开店入驻申请已审核通过请尽快登录小发商家版APP平台签署电子合同签署链接24小时内有效逾期需重新提交申请如有疑问请联系客服感谢您的支持
// shopMessageTemplateService.aliyunSmsSend(contractMobile, "SMS_493160417", null);//SMS_479760276
// 恭喜您的开店入驻申请已审核通过点击链接 https://mall.gpxscs.cn/api/mobile/shop/lakala/sign/ec/${code}
// 或前往小发商家版APP完成电子合同签署链接24小时内有效逾期需重新提交申请如有疑问可联系客服感谢您的支持
shopMessageTemplateService.aliyunSmsSend(contractMobile, "SMS_494860064", null);
JSONObject payload = new JSONObject();
payload.put("category", CommonConstant.PUSH_MSG_CATE_EC);
@ -1737,42 +1743,56 @@ public class LakalaApiServiceImpl implements LakalaApiService {
@Override
public Pair<String, String> queryMchCanSplitAmt(String merchantNo, String logNo, String logDate) {
if (StringUtils.isAnyBlank(merchantNo, logNo, logDate)) {
log.warn("[查询可分账金额] 参数校验失败:缺少必要参数, merchantNo={}, logNo={}, logDate={}", merchantNo, logNo, logDate);
return null;
}
// 1. 配置初始化
initLKLSDK();
//2. 装配数据
V3SacsQueryAmtRequest req = new V3SacsQueryAmtRequest();
req.setMerchantNo(merchantNo);
req.setLogNo(logNo);
req.setLogDate(logDate);
try {
log.info("[查询可分账金额] 开始查询商户可分账金额, merchantNo={}, logNo={}, logDate={}", merchantNo, logNo, logDate);
// 1. 配置初始化
initLKLSDK();
//2. 装配数据
V3SacsQueryAmtRequest req = new V3SacsQueryAmtRequest();
req.setMerchantNo(merchantNo);
req.setLogNo(logNo);
req.setLogDate(logDate);
//3. 发送请求
String responseStr = LKLSDK.httpPost(req);
if (StrUtil.isBlank(responseStr)) {
log.error(I18nUtil._("服务器无返回值!"));
log.error("[查询可分账金额] 服务器无返回值, merchantNo={}, logNo={}, logDate={}", merchantNo, logNo, logDate);
return null;
}
JSONObject lklRespJSON = JSONUtil.parseObj(responseStr);
if (lklRespJSON == null || !lklSacsSuccessCode.equals(lklRespJSON.getStr("code")) || lklRespJSON.get("resp_data") == null) {
log.error(I18nUtil._("返回值有误!"));
log.error("[查询可分账金额] 返回值有误, merchantNo={}, logNo={}, logDate={}, response={}",
merchantNo, logNo, logDate, responseStr);
return null;
}
JSONObject respData = (JSONObject) lklRespJSON.get("resp_data");
if (respData == null
|| StringUtils.isAnyBlank(respData.getStr("total_separate_amt"), respData.getStr("can_separate_amt"))) {
log.error(I18nUtil._("返回值有误!"));
log.error("[查询可分账金额] 返回数据字段缺失, merchantNo={}, logNo={}, logDate={}, respData={}",
merchantNo, logNo, logDate, respData);
return null;
}
return Pair.of(respData.getStr("total_separate_amt"), respData.getStr("can_separate_amt"));
String totalSeparateAmt = respData.getStr("total_separate_amt");
String canSeparateAmt = respData.getStr("can_separate_amt");
log.info("[查询可分账金额] 查询成功, merchantNo={}, logNo={}, logDate={}, totalSeparateAmt={}, canSeparateAmt={}",
merchantNo, logNo, logDate, totalSeparateAmt, canSeparateAmt);
return Pair.of(totalSeparateAmt, canSeparateAmt);
} catch (SDKException e) {
log.error("账户余额查询失败:", e);
log.error("[查询可分账金额] 账户余额查询失败, merchantNo={}, logNo={}, logDate={}", merchantNo, logNo, logDate, e);
return null;
} catch (Exception e) {
log.error("[查询可分账金额] 查询过程中发生未知异常, merchantNo={}, logNo={}, logDate={}", merchantNo, logNo, logDate, e);
return null;
}
}
@ -2077,8 +2097,6 @@ public class LakalaApiServiceImpl implements LakalaApiService {
log.info("[分账操作] 开始处理分账请求, lklMerchantNo={}, receiveTradeNo={}, receiveLogNo={}",
lklMerchantNo, receiveTradeNo, receiveLogNo);
// TODO 检查可分账余额是否足够
// 2. 查询订单信息
ShopOrderLkl shopOrderLkl = shopOrderLklService.getByLklMchNoAndReceiveTradeNoAndReceiveLogNo(lklMerchantNo, receiveTradeNo, receiveLogNo);
if (shopOrderLkl == null) {
@ -2086,6 +2104,19 @@ public class LakalaApiServiceImpl implements LakalaApiService {
return Pair.of(false, "订单不存在");
}
// TODO 检查可分账余额是否足够
Pair<String, String> balanceCheckResult = queryMchCanSplitAmt(lklMerchantNo, receiveLogNo, shopOrderLkl.getLkl_log_date());
if (balanceCheckResult == null) {
log.error("[分账操作] 查询可分账余额失败:lklMerchantNo={} logDate={} receiveLogNo={}", lklMerchantNo, shopOrderLkl.getLkl_log_date(), receiveLogNo);
}
log.warn("[分账操作] 可分账余额检查结果:分账总余额={}分 可分账金额={}分", balanceCheckResult.getFirst(), balanceCheckResult.getSecond());
Integer canSeparateAmt = Convert.toInt(balanceCheckResult.getSecond());
if (canSeparateAmt == null || canSeparateAmt <= 0) {
log.warn("[分账操作] lklMerchantNo={} receiveTradeNo={} receiveLogNo={} 可分账金额={}分 可分账余额不足,跳过处理", lklMerchantNo, receiveTradeNo, receiveLogNo, balanceCheckResult.getSecond());
return Pair.of(false, "可分账余额不足");
}
String orderId = shopOrderLkl.getOrder_id();
log.info("[分账操作] 开始处理订单[{}]的分账", orderId);
@ -2212,7 +2243,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
V3SacsSeparateRequest separateRequest = new V3SacsSeparateRequest();
separateRequest.setMerchantNo(merchantNo);
separateRequest.setOutSeparateNo(shopOrderLkl.getOut_separate_no());
separateRequest.setLogNo(shopOrderLkl.getLkl_receive_log_no()); // 使用收货流水号作为分账流水号
separateRequest.setLogNo(shopOrderLkl.getLkl_receive_log_no()); // 使用确认收货流水号作为分账流水号
separateRequest.setLogDate(shopOrderLkl.getLkl_log_date());
separateRequest.setTotalAmt(splitAmount.toString());
separateRequest.setLklOrgNo(orgCode);
@ -2233,6 +2264,8 @@ public class LakalaApiServiceImpl implements LakalaApiService {
if (StrUtil.isBlank(response)) {
String errorMsg = String.format("[分账操作] 拉卡拉无响应,订单=%s商户=%s分账流水号=%s",
orderId, merchantNo, shopOrderLkl.getLkl_receive_log_no());
// 更改分账状态分账失败
shopOrderLklService.updateSeparateStatusByReceiveLogNo(shopOrderLkl.getLkl_receive_log_no(), CommonConstant.Sta_Separate_Fail);
log.error(errorMsg);
return Pair.of(false, "拉卡拉无响应");
}
@ -2244,6 +2277,8 @@ public class LakalaApiServiceImpl implements LakalaApiService {
if (respJson == null || !lklSacsSuccessCode.equals(respJson.getStr("code")) || respJson.getJSONObject("resp_data") == null) {
String errorMsg = String.format("[分账操作] 拉卡拉返回格式异常,订单=%s商户=%s分账流水号=%s响应=%srespJson=%s",
orderId, merchantNo, shopOrderLkl.getLkl_receive_log_no(), response, respJson);
// 更改分账状态分账失败
shopOrderLklService.updateSeparateStatusByReceiveLogNo(shopOrderLkl.getLkl_receive_log_no(), CommonConstant.Sta_Separate_Fail);
log.error(errorMsg);
return Pair.of(false, "拉卡拉分账异常:[" + respJson.getStr("code") + "]" + respJson.getStr("msg"));
}
@ -2340,12 +2375,13 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
// 3. 提取关键参数并校验 - 确保必要参数完整
String logNo = paramsJson.getStr("log_no"); // 合单订单是子单流水号非合单时是主单流水号
String logNo = paramsJson.getStr("log_no"); // 合单订单是子单确认收货流水号非合单时是主单确认收货流水号
String separateNo = paramsJson.getStr("separate_no");
String outSeparateNo = paramsJson.getStr("out_separate_no");
String status = paramsJson.getStr("status");
String finalStatus = paramsJson.getStr("final_status");
// 必要参数
List<String> missingParams = new ArrayList<>();
if (StrUtil.isBlank(outSeparateNo)) missingParams.add("outSeparateNo");
if (StrUtil.isBlank(separateNo)) missingParams.add("separateNo");
@ -2404,6 +2440,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
lklOrderSeparate.setCal_type(paramsJson.getStr("cal_type"));
lklOrderSeparate.setSeparate_type(paramsJson.getStr("separate_type"));
lklOrderSeparate.setSeparate_date(paramsJson.getStr("separate_date"));
// 总计分账金额
lklOrderSeparate.setTotal_separate_value(paramsJson.getInt("total_separate_value", 0));
lklOrderSeparate.setRemark("分账已完成");
lklOrderSeparate.setFinish_date(paramsJson.getStr("finish_date"));
@ -2415,6 +2452,8 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 8. 持久化处理 - 更新分账记录到数据库
boolean updateSuccess = lklOrderSeparateService.addOrUpdateByReceiverNo(lklOrderSeparate);
if (!updateSuccess) {
// 更改分账状态分账失败
shopOrderLklService.updateSeparateStatusByReceiveLogNo(logNo, CommonConstant.Sta_Separate_Fail);
String errorMsg = String.format("分账记录更新失败, separateNo=%s", separateNo);
log.error("[拉卡拉分账通知] {}", errorMsg);
return JSONUtil.createObj()
@ -2422,6 +2461,9 @@ public class LakalaApiServiceImpl implements LakalaApiService {
.put("message", "数据更新失败");
}
// 更改分账状态分账成功
shopOrderLklService.updateSeparateStatusByReceiveLogNo(logNo, CommonConstant.Sta_Separate_Success);
// 9. 记录处理成功日志
log.info("[拉卡拉分账通知] 分账通知处理成功, separateNo={}, status={}", separateNo, status);
return JSONUtil.createObj()

View File

@ -94,4 +94,13 @@ public interface ShopOrderLklService extends IBaseService<ShopOrderLkl> {
* @return ShopOrderLkl 拉卡拉订单记录
*/
ShopOrderLkl getByLklMchNoAndReceiveTradeNoAndReceiveLogNo(String lklMerchantNo, String lklReceiveTradeNo, String lklReceiveLogNo);
/**
* 根据商户号确认收货交易流水号确认收货对账单流水号更新确认收货状态
*
* @param lklReceiveLogNo 拉卡拉确认收货对账单流水号
* @param separateStatus 分账状态1-已分账2-未分账3-分账已失败
* @return
*/
Boolean updateSeparateStatusByReceiveLogNo(String lklReceiveLogNo, Integer separateStatus);
}

View File

@ -14,6 +14,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.suisung.mall.common.modules.order.ShopOrderBase;
import com.suisung.mall.common.modules.order.ShopOrderLkl;
import com.suisung.mall.common.utils.CheckUtil;
@ -290,7 +291,7 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
record.setLkl_trade_no(tradeNo);
record.setTrade_status(tradeStatus);
record.setWx_transaction_id(accTradeNo); //账户端交易订单号 对应微信的用户交易单号
log.debug("[拉卡拉订单更新] 设置可选字段: tradeNo={} accTradeNo={} tradeStatus={}", tradeNo,accTradeNo, tradeStatus);
log.debug("[拉卡拉订单更新] 设置可选字段: tradeNo={} accTradeNo={} tradeStatus={}", tradeNo, accTradeNo, tradeStatus);
// 新增的订单字段,lkl_sub_log_no,out_separate_no,split_amt 四个字段无值就给主单的值
String outSeparateNo = JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "out_separate_no");
@ -502,4 +503,41 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
return null;
}
}
/**
* 根据确认收货对账单流水号更新分账状态
*
* @param lklReceiveLogNo 拉卡拉确认收货对账单流水号
* @param separateStatus 分账状态1-已分账2-未分账3-分账已失败
* @return 更新结果 true-成功 false-失败
*/
@Override
public Boolean updateSeparateStatusByReceiveLogNo(String lklReceiveLogNo, Integer separateStatus) {
// 检查参数是否全部为空
if (StringUtils.isBlank(lklReceiveLogNo) || separateStatus == null) {
log.warn("[更新分账状态] 参数校验失败:缺少必要参数, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
return false;
}
try {
log.info("[更新分账状态] 开始更新分账状态, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
UpdateWrapper<ShopOrderLkl> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("lkl_receive_log_no", lklReceiveLogNo);
updateWrapper.set("separate_status", separateStatus);
boolean result = update(updateWrapper);
if (result) {
log.info("[更新分账状态] 分账状态更新成功, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
} else {
log.warn("[更新分账状态] 分账状态更新失败,未找到匹配记录, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
}
return result;
} catch (Exception e) {
log.error("[更新分账状态] 分账状态更新异常, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus, e);
return false;
}
}
}