|
|
@@ -27,6 +27,7 @@ import com.fs.common.event.TemplateEvent;
|
|
|
import com.fs.common.event.TemplateListenEnum;
|
|
|
import com.fs.common.exception.CustomException;
|
|
|
import com.fs.common.exception.ServiceException;
|
|
|
+import com.fs.common.sharding.ShardingUtil;
|
|
|
import com.fs.common.utils.CloudHostUtils;
|
|
|
import com.fs.common.utils.DateUtils;
|
|
|
import com.fs.common.utils.IpUtil;
|
|
|
@@ -71,6 +72,7 @@ import com.fs.his.mapper.FsUserIntegralLogsMapper;
|
|
|
import com.fs.his.domain.FsUserIntegralLogs;
|
|
|
import com.fs.his.enums.FsStoreOrderLogEnum;
|
|
|
import com.fs.his.enums.FsStoreOrderStatusEnum;
|
|
|
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
|
|
|
import com.fs.his.mapper.*;
|
|
|
import com.fs.his.param.FsStoreOrderSalesParam;
|
|
|
import com.fs.his.service.IFsPrescribeService;
|
|
|
@@ -112,6 +114,7 @@ import com.fs.pay.pay.dto.RefundDTO;
|
|
|
import com.fs.pay.service.IPayService;
|
|
|
import com.fs.hisStore.config.StoreConfig;
|
|
|
import com.fs.hisStore.config.StoreIntegralConfig;
|
|
|
+import com.fs.his.config.PointsGrantRuleConfig;
|
|
|
import com.fs.hisStore.constants.StoreConstants;
|
|
|
import com.fs.hisStore.domain.*;
|
|
|
import com.fs.hisStore.enums.*;
|
|
|
@@ -153,6 +156,7 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
|
|
import javax.annotation.PostConstruct;
|
|
|
import java.lang.reflect.Field;
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.net.URLEncoder;
|
|
|
import java.nio.charset.Charset;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
@@ -731,35 +735,67 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
BigDecimal deductionPrice = BigDecimal.ZERO; //积分抵扣金额
|
|
|
double usedIntegral = 0; //使用了多少积分
|
|
|
if (param.getUseIntegral() != null && param.getUseIntegral() == 1 && user.getIntegral().intValue() > 0) {
|
|
|
- //如果积分总和小于用户积分 抵扣比例 计算抵扣价格
|
|
|
- //默认配置
|
|
|
- Double integralMax = Double.valueOf(100);
|
|
|
- BigDecimal integralFull = new BigDecimal(100);
|
|
|
- Double integralRatio = Double.valueOf(1);
|
|
|
- String json = configService.selectConfigByKey("store.integral");
|
|
|
- if (StringUtils.isEmpty(json)) {
|
|
|
-
|
|
|
- } else {
|
|
|
- StoreIntegralConfig integralConfig = JSONUtil.toBean(json, StoreIntegralConfig.class);
|
|
|
- integralMax = integralConfig.getIntegralMax();
|
|
|
- integralFull = integralConfig.getIntegralFull();
|
|
|
- integralRatio = integralConfig.getIntegralRatio();
|
|
|
-
|
|
|
- }
|
|
|
- if (priceGroup.getTotalPrice().compareTo(integralFull) >= 0) {
|
|
|
- Double userIntegral = user.getIntegral().doubleValue();
|
|
|
- if (integralMax.intValue() > 0 && Double.compare(userIntegral, integralMax) >= 0) {
|
|
|
- userIntegral = integralMax;
|
|
|
+ // 新积分配置:points.grantRule
|
|
|
+ Integer payRate = 100;
|
|
|
+ Integer maxDeductPercent = 50;
|
|
|
+ BigDecimal minDeductAmount = new BigDecimal(100);
|
|
|
+ Integer minUseIntegral = 0;
|
|
|
+ Integer deductEnabled = 1;
|
|
|
+ String json = configService.selectConfigByKey("points.grantRule");
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ PointsGrantRuleConfig rule = JSONUtil.toBean(json, PointsGrantRuleConfig.class);
|
|
|
+ if (rule != null && rule.getEnabled() != null && rule.getEnabled() == 1) {
|
|
|
+ if (rule.getPayRate() != null && rule.getPayRate() > 0) {
|
|
|
+ payRate = rule.getPayRate();
|
|
|
+ }
|
|
|
+ if (rule.getMaxDeductPercent() != null && rule.getMaxDeductPercent() > 0) {
|
|
|
+ maxDeductPercent = rule.getMaxDeductPercent();
|
|
|
+ }
|
|
|
+ if (rule.getMinDeductAmount() != null) {
|
|
|
+ minDeductAmount = rule.getMinDeductAmount();
|
|
|
+ }
|
|
|
+ if (rule.getMinUseIntegral() != null && rule.getMinUseIntegral() > 0) {
|
|
|
+ minUseIntegral = rule.getMinUseIntegral();
|
|
|
+ }
|
|
|
+ if (rule.getDeductEnabled() != null) {
|
|
|
+ deductEnabled = rule.getDeductEnabled();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("积分抵扣规则配置解析失败,使用默认值", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 积分抵扣功能关闭或积分不足起用门槛,跳过
|
|
|
+ if (deductEnabled == 1
|
|
|
+ && (minUseIntegral == 0 || user.getIntegral().intValue() >= minUseIntegral)
|
|
|
+ && priceGroup.getTotalPrice().compareTo(minDeductAmount) >= 0) {
|
|
|
+ long userIntegral = user.getIntegral().longValue();
|
|
|
+ deductionPrice = BigDecimal.valueOf(userIntegral)
|
|
|
+ .divide(BigDecimal.valueOf(payRate), 2, RoundingMode.HALF_UP);
|
|
|
+ // 最高抵扣比例限制
|
|
|
+ if (maxDeductPercent > 0 && maxDeductPercent < 100) {
|
|
|
+ BigDecimal maxDeductionAmount = priceGroup.getTotalPrice()
|
|
|
+ .multiply(BigDecimal.valueOf(maxDeductPercent))
|
|
|
+ .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
|
|
+ if (deductionPrice.compareTo(maxDeductionAmount) > 0) {
|
|
|
+ deductionPrice = maxDeductionAmount;
|
|
|
+ userIntegral = deductionPrice
|
|
|
+ .multiply(BigDecimal.valueOf(payRate))
|
|
|
+ .setScale(0, RoundingMode.DOWN)
|
|
|
+ .longValue();
|
|
|
+ }
|
|
|
}
|
|
|
- deductionPrice = BigDecimal.valueOf(NumberUtil.mul(userIntegral, integralRatio));
|
|
|
if (deductionPrice.compareTo(payPrice) < 0) {
|
|
|
payPrice = NumberUtil.sub(payPrice, deductionPrice);
|
|
|
usedIntegral = userIntegral;
|
|
|
} else {
|
|
|
deductionPrice = payPrice;
|
|
|
payPrice = BigDecimal.ZERO;
|
|
|
- usedIntegral = NumberUtil.round(NumberUtil.div(deductionPrice,
|
|
|
- BigDecimal.valueOf(integralRatio)), 2).doubleValue();
|
|
|
+ usedIntegral = deductionPrice
|
|
|
+ .multiply(BigDecimal.valueOf(payRate))
|
|
|
+ .setScale(0, RoundingMode.DOWN)
|
|
|
+ .doubleValue();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -912,6 +948,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
storeOrder.setUseIntegral(BigDecimal.valueOf(dto.getUsedIntegral()));
|
|
|
storeOrder.setBackIntegral(BigDecimal.ZERO);
|
|
|
storeOrder.setGainIntegral(BigDecimal.ZERO);
|
|
|
+ if (dto.getIntegralDesc() != null) {
|
|
|
+ storeOrder.setIntegralDesc(dto.getIntegralDesc());
|
|
|
+ }
|
|
|
storeOrder.setMark(param.getMark());
|
|
|
//todo 获取成本价
|
|
|
BigDecimal costPrice = this.getOrderSumPrice(carts, "costPrice");
|
|
|
@@ -926,7 +965,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
storeOrder.setServiceFee(config.getServiceFee());
|
|
|
}
|
|
|
|
|
|
- FsStorePromotionComputeResultVO promotionResult = applyPromotionFields(storeOrder, carts, userId, param.getCouponUserId(), param.getStoreId());
|
|
|
+ boolean skipPromotion = false;
|
|
|
+ if (StringUtils.isNotEmpty(param.getCreateOrderKey())) {
|
|
|
+ Boolean isAdjusted = redisCache.getCacheObject("createOrderIsAdjusted:" + param.getCreateOrderKey());
|
|
|
+ skipPromotion = (isAdjusted != null && isAdjusted);
|
|
|
+ }
|
|
|
+ FsStorePromotionComputeResultVO promotionResult = applyPromotionFields(storeOrder, carts, userId, param.getCouponUserId(), param.getStoreId(), skipPromotion);
|
|
|
|
|
|
//后台制单处理
|
|
|
if (param.getPayPrice() != null && param.getPayPrice().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
@@ -1259,14 +1303,28 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
|
|
|
/**
|
|
|
* 积分抵扣
|
|
|
+ * 1. 原子扣减 fs_user.integral(SQL 层 integral = integral - ?,并发安全)
|
|
|
+ * 2. 写账单流水 fs_user_bill(保持原有行为)
|
|
|
+ * 3. 写积分明细流水 fs_user_integral_logs(logType=25 下单使用积分抵扣,integral 存负数表示扣减)
|
|
|
*/
|
|
|
private void decIntegral(Long uid, double usedIntegral, double deductionPrice, String busId) {
|
|
|
userService.decIntegral(uid, usedIntegral);
|
|
|
FsUserScrm user = userService.selectFsUserById(uid);
|
|
|
- //积分记录
|
|
|
+ //积分记录-账单流水
|
|
|
billService.addBill(uid, BillDetailEnum.CATEGORY_2.getValue(), 0, BillDetailEnum.TYPE_1.getDesc(), usedIntegral, user.getIntegral().doubleValue(),
|
|
|
"购买商品使用" + usedIntegral + "积分抵扣" + deductionPrice + "元", busId, 0l);
|
|
|
-
|
|
|
+ //积分记录-积分明细流水(integral 存负数,balance 记录扣减后余额)
|
|
|
+ FsUserIntegralLogs integralLog = new FsUserIntegralLogs();
|
|
|
+ integralLog.setUserId(uid);
|
|
|
+ integralLog.setLogType(FsUserIntegralLogTypeEnum.TYPE_25.getValue());
|
|
|
+ integralLog.setIntegral(-Math.round(usedIntegral));
|
|
|
+ integralLog.setBalance(user.getIntegral() == null ? 0L : user.getIntegral().longValue());
|
|
|
+ integralLog.setBusinessId(busId);
|
|
|
+ integralLog.setBusinessType(0);
|
|
|
+ integralLog.setStatus(0);
|
|
|
+ integralLog.setCreateTime(new Date());
|
|
|
+ int shard = ShardingUtil.shardOf(uid);
|
|
|
+ fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLog, shard);
|
|
|
}
|
|
|
|
|
|
public void deStockIncSales(List<FsStoreCartScrmQueryVO> cartInfo) {
|
|
|
@@ -1285,6 +1343,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
|
|
|
//未支付取消订单
|
|
|
@Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public void cancelOrder(Long orderId) {
|
|
|
FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
|
|
|
if (order.getStatus() == OrderInfoEnum.STATUS_0.getValue()) {
|
|
|
@@ -1402,11 +1461,63 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
//退回积分
|
|
|
this.refundIntegral(order);
|
|
|
//退回库存
|
|
|
+
|
|
|
this.refundStock(order);
|
|
|
fsStoreOrderMapper.cancelOrder(orderId);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public void refundIntegralForAfterSales(FsStoreOrderScrm order) {
|
|
|
+ // 委托给已有的私有 refundIntegral 方法,内部有 backIntegral 幂等校验 + 原子加回 + 写流水
|
|
|
+ this.refundIntegral(order);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 售后退款时处理积分退还/扣回(独立事务,积分异常不影响退款主事务)
|
|
|
+ * <p>
|
|
|
+ * 通过 fsStoreOrderService 代理 Bean 调用,REQUIRES_NEW 生效。
|
|
|
+ * 场景A:有 logType=25(下单抵扣)且无 logType=27(未退过)→ 退回下单抵扣积分(写 TYPE_27 退款订单退回积分)
|
|
|
+ * 场景B:有 logType=2(消费发放)且无 logType=4(未扣回)→ 扣回已发放消费积分
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
|
|
|
+ public void refundIntegralByAfterSales(FsStoreOrderScrm order) {
|
|
|
+ if (order == null || order.getId() == null || order.getUserId() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String orderId = order.getId().toString();
|
|
|
+ Long userId = order.getUserId();
|
|
|
+
|
|
|
+ // 查询该订单的所有积分明细记录(带 user_id 分片键,ShardingSphere 精确路由)
|
|
|
+ List<FsUserIntegralLogs> logs = fsUserIntegralLogsMapper.selectByUserIdAndBusinessIdAll(userId, orderId, ShardingUtil.shardOf(userId));
|
|
|
+ if (logs == null || logs.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 场景A:退回下单抵扣积分(logType=25 存在 且 logType=27 不存在)
|
|
|
+ boolean hasDeductRecord = logs.stream().anyMatch(l -> Integer.valueOf(25).equals(l.getLogType()));
|
|
|
+ boolean hasRefundRecord = logs.stream().anyMatch(l -> Integer.valueOf(27).equals(l.getLogType()));
|
|
|
+ if (hasDeductRecord && !hasRefundRecord) {
|
|
|
+ // 写 TYPE_27 退款订单退回积分(区别于 TYPE_26 取消订单退回积分)
|
|
|
+ this.refundIntegral(order, FsUserIntegralLogTypeEnum.TYPE_27.getValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 场景B:扣回已发放消费积分(logType=2 存在 且 logType=4 不存在)
|
|
|
+ boolean hasGrantRecord = logs.stream().anyMatch(l -> Integer.valueOf(2).equals(l.getLogType()));
|
|
|
+ boolean hasRevokeRecord = logs.stream().anyMatch(l -> Integer.valueOf(4).equals(l.getLogType()));
|
|
|
+ if (hasGrantRecord && !hasRevokeRecord) {
|
|
|
+ // 受 points.grantRule.refundDeduct 开关控制,内部有余额保护扣到 0
|
|
|
+ FsUserGrantPointsParam grantParam = new FsUserGrantPointsParam();
|
|
|
+ grantParam.setUserId(userId);
|
|
|
+ grantParam.setLogType(4);
|
|
|
+ grantParam.setOriginalOrderNo(orderId);
|
|
|
+ grantParam.setBusinessId(orderId);
|
|
|
+ grantParam.setRemark("售后退款扣回消费积分");
|
|
|
+ fsUserIntegralLogsService.grantPoints(grantParam);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public void deliveryOrder(String orderCode, String deliveryId, String deliverCode, String deliverName) {
|
|
|
FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(orderCode);
|
|
|
@@ -2307,7 +2418,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
try {
|
|
|
// 订单级幂等校验:同一订单不可重复发放积分
|
|
|
FsUserIntegralLogs existingLog = fsUserIntegralLogsMapper.selectByUserIdAndBusinessId(
|
|
|
- order.getUserId(), order.getId().toString(), 2);
|
|
|
+ order.getUserId(), order.getId().toString(), 2, ShardingUtil.shardOf(order.getUserId()));
|
|
|
if (existingLog != null) {
|
|
|
log.info("订单已发放过积分,跳过: orderId={}, orderCode={}", order.getId(), order.getOrderCode());
|
|
|
return;
|
|
|
@@ -2555,6 +2666,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
@Override
|
|
|
public R createOmsOrder(Long orderId) throws ParseException {
|
|
|
FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
|
|
|
+ // 【关键日志】查询订单状态,确认事务内可见性
|
|
|
+ logger.info("【createOmsOrder-查询订单】orderId={}, orderCode={}, status={}, paid={}, extendOrderId={}",
|
|
|
+ order.getId(), order.getOrderCode(), order.getStatus(), order.getPaid(), order.getExtendOrderId());
|
|
|
FsSysConfig erpConfig = configUtil.generateStructConfigByKey(SysConfigEnum.HIS_CONFIG.getKey(), FsSysConfig.class);
|
|
|
List<Long> noErpCompany = erpConfig.getNoErpCompany();
|
|
|
if (noErpCompany != null && noErpCompany.contains(order.getCompanyId())) {
|
|
|
@@ -2583,7 +2697,17 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
redisCache.setCacheObject(DELIVERY + ":" + response.getCode(), order.getOrderCode());
|
|
|
//写入外部订单号
|
|
|
order.setExtendOrderId(response.getCode());
|
|
|
- fsStoreOrderMapper.updateFsStoreOrder(order);
|
|
|
+ // 【关键修复】只更新必要字段,避免用完整 order 对象覆盖 status、paid 等字段
|
|
|
+ FsStoreOrderScrm updateOrder = new FsStoreOrderScrm();
|
|
|
+ updateOrder.setId(order.getId());
|
|
|
+ updateOrder.setExtendOrderId(response.getCode());
|
|
|
+ updateOrder.setDeliveryName(order.getDeliveryName());
|
|
|
+ updateOrder.setDeliverySn(order.getDeliverySn());
|
|
|
+ logger.info("【createOmsOrder-更新前】orderId={}, orderCode={}, status={}, paid={}, extendOrderId={}, deliveryName={}, deliverySn={}",
|
|
|
+ order.getId(), order.getOrderCode(), order.getStatus(), order.getPaid(), order.getExtendOrderId(), order.getDeliveryName(), order.getDeliverySn());
|
|
|
+ fsStoreOrderMapper.updateFsStoreOrder(updateOrder);
|
|
|
+ logger.info("【createOmsOrder-更新完成】orderId={}, orderCode={}, 已更新 extendOrderId={}, deliveryName={}, deliverySn={}",
|
|
|
+ order.getId(), order.getOrderCode(), order.getExtendOrderId(), order.getDeliveryName(), order.getDeliverySn());
|
|
|
|
|
|
// 聚水潭:如果设置了仓库编码,调用指定发货仓接口
|
|
|
if (erpOrderService == jSTOrderService && StringUtils.isNotBlank(erpOrder.getWarehouse_code())) {
|
|
|
@@ -3601,9 +3725,17 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * 退回积分
|
|
|
+ * 退回积分(默认 logType=26 取消订单退回积分)
|
|
|
*/
|
|
|
private void refundIntegral(FsStoreOrderScrm order) {
|
|
|
+ refundIntegral(order, FsUserIntegralLogTypeEnum.TYPE_26.getValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 退回积分
|
|
|
+ * @param logType 积分明细类型(26=取消订单退回积分,27=退款订单退回积分)
|
|
|
+ */
|
|
|
+ private void refundIntegral(FsStoreOrderScrm order, Integer logType) {
|
|
|
|
|
|
if (order.getPayIntegral().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
order.setUseIntegral(order.getPayIntegral());
|
|
|
@@ -3614,16 +3746,26 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
if (order.getBackIntegral().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
return;
|
|
|
}
|
|
|
+ // 原子加回积分(SQL 层 integral = integral + ?,并发安全,与下单时 decIntegral 原子扣减对称)
|
|
|
+ userService.incIntegral(order.getUserId(), order.getUseIntegral().doubleValue());
|
|
|
FsUserScrm user = userService.selectFsUserById(order.getUserId());
|
|
|
- //增加积分
|
|
|
- BigDecimal newIntegral = NumberUtil.add(order.getUseIntegral(), user.getIntegral());
|
|
|
- user.setIntegral(newIntegral.longValue());
|
|
|
- userService.updateFsUser(user);
|
|
|
- //增加流水
|
|
|
+ //增加流水-账单
|
|
|
billService.addBill(user.getUserId(), BillDetailEnum.CATEGORY_2.getValue(), 1, BillDetailEnum.TYPE_5.getDesc(),
|
|
|
order.getUseIntegral().doubleValue(),
|
|
|
- newIntegral.doubleValue(),
|
|
|
+ user.getIntegral().doubleValue(),
|
|
|
"购买商品失败,回退积分" + order.getUseIntegral(), order.getId().toString(), 0l);
|
|
|
+ //增加流水-积分明细(integral 存正数表示增加,与下单扣减 TYPE_25 对称)
|
|
|
+ FsUserIntegralLogs integralLog = new FsUserIntegralLogs();
|
|
|
+ integralLog.setUserId(order.getUserId());
|
|
|
+ integralLog.setLogType(logType);
|
|
|
+ integralLog.setIntegral(Math.round(order.getUseIntegral().doubleValue()));
|
|
|
+ integralLog.setBalance(user.getIntegral() == null ? 0L : user.getIntegral().longValue());
|
|
|
+ integralLog.setBusinessId(order.getId().toString());
|
|
|
+ integralLog.setBusinessType(0);
|
|
|
+ integralLog.setStatus(0);
|
|
|
+ integralLog.setCreateTime(new Date());
|
|
|
+ int shard = ShardingUtil.shardOf(order.getUserId());
|
|
|
+ fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLog, shard);
|
|
|
//更新订单回退积分
|
|
|
FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
|
|
|
storeOrder.setBackIntegral(order.getUseIntegral());
|
|
|
@@ -5447,18 +5589,24 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
@Transactional
|
|
|
@Synchronized
|
|
|
public String payConfirmMultiStore(Integer type, Long orderId, String payCode, String tradeNo, String bankTransactionId, String bankSerialNo) {
|
|
|
+ logger.info("【payConfirmMultiStore-开始】type={}, payCode={}, orderId={}, tradeNo={}, bankTransactionId={}, bankSerialNo={}",
|
|
|
+ type, payCode, orderId, tradeNo, bankTransactionId, bankSerialNo);
|
|
|
try {
|
|
|
List<FsStoreOrderScrm> orders = new ArrayList<>();
|
|
|
if (type.equals(1)) {
|
|
|
List<FsStorePaymentScrm> storePayments = paymentService.selectFsStorePaymentByPaymentCode(payCode);
|
|
|
if (storePayments == null || storePayments.isEmpty()) {
|
|
|
- logger.warn("payConfirmMultiStore: 未找到支付记录, payCode={}", payCode);
|
|
|
+ // 【关键日志】未找到支付记录:可能支付记录被删除或 payCode 不匹配,返回空后 Controller 返回 SUCCESS,易宝不重试
|
|
|
+ logger.error("【payConfirmMultiStore-未找到支付记录】payCode={} 返回空,事务回滚,Controller将返回SUCCESS(易宝不重试)", payCode);
|
|
|
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
|
|
return "";
|
|
|
}
|
|
|
+ logger.info("【payConfirmMultiStore-支付记录数】payCode={}, paymentCount={}", payCode, storePayments.size());
|
|
|
|
|
|
// 【关键修复】第一步:先加载并校验所有订单状态,避免 payment 已更新但 order 更新失败
|
|
|
boolean allPaymentAlreadyPaid = true;
|
|
|
+ int orderLoadCount = 0;
|
|
|
+ int orderLoadFailCount = 0;
|
|
|
for (FsStorePaymentScrm storePayment : storePayments) {
|
|
|
if (storePayment == null) {
|
|
|
continue;
|
|
|
@@ -5466,30 +5614,54 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
// 检查 payment 是否已处理(幂等判断)
|
|
|
if (storePayment.getStatus() != null && !storePayment.getStatus().equals(0)) {
|
|
|
// payment 已经是非待支付状态,不再更新 payment,但仍需校验订单
|
|
|
- logger.info("payConfirmMultiStore: payment已处理, paymentId={}, status={}, payCode={}",
|
|
|
- storePayment.getPaymentId(), storePayment.getStatus(), payCode);
|
|
|
+ logger.info("【payConfirmMultiStore-payment已处理】paymentId={}, status={}, orderId={}, businessOrderId={}, payCode={}",
|
|
|
+ storePayment.getPaymentId(), storePayment.getStatus(), storePayment.getOrderId(), storePayment.getBusinessOrderId(), payCode);
|
|
|
allPaymentAlreadyPaid = allPaymentAlreadyPaid && true;
|
|
|
} else {
|
|
|
allPaymentAlreadyPaid = false;
|
|
|
+ logger.info("【payConfirmMultiStore-payment待处理】paymentId={}, status=0, orderId={}, businessOrderId={}, payCode={}",
|
|
|
+ storePayment.getPaymentId(), storePayment.getOrderId(), storePayment.getBusinessOrderId(), payCode);
|
|
|
}
|
|
|
// 加载关联订单
|
|
|
if (storePayment.getBusinessOrderId() != null && storePayment.getBusinessOrderId().length() > 30) {
|
|
|
- orders.addAll(fsStoreOrderMapper.getStoreOrderByCombinationId(storePayment.getBusinessOrderId()));
|
|
|
+ // 组合订单ID
|
|
|
+ List<FsStoreOrderScrm> comboOrders = fsStoreOrderMapper.getStoreOrderByCombinationId(storePayment.getBusinessOrderId());
|
|
|
+ if (comboOrders == null || comboOrders.isEmpty()) {
|
|
|
+ // 【关键日志】组合订单查询返回空,可能导致后续 orders 为空
|
|
|
+ logger.error("【payConfirmMultiStore-组合订单查询空】paymentId={}, businessOrderId={}, payCode={} getStoreOrderByCombinationId返回空",
|
|
|
+ storePayment.getPaymentId(), storePayment.getBusinessOrderId(), payCode);
|
|
|
+ orderLoadFailCount++;
|
|
|
+ } else {
|
|
|
+ orders.addAll(comboOrders);
|
|
|
+ orderLoadCount += comboOrders.size();
|
|
|
+ }
|
|
|
} else {
|
|
|
FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(storePayment.getOrderId());
|
|
|
if (order != null) {
|
|
|
orders.add(order);
|
|
|
+ orderLoadCount++;
|
|
|
+ } else {
|
|
|
+ // 【关键日志】订单查询返回空
|
|
|
+ logger.error("【payConfirmMultiStore-订单查询空】paymentId={}, orderId={}, payCode={} selectFsStoreOrderById返回空",
|
|
|
+ storePayment.getPaymentId(), storePayment.getOrderId(), payCode);
|
|
|
+ orderLoadFailCount++;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ logger.info("【payConfirmMultiStore-订单加载完成】payCode={}, 加载成功={}, 加载失败={}, allPaymentAlreadyPaid={}",
|
|
|
+ payCode, orderLoadCount, orderLoadFailCount, allPaymentAlreadyPaid);
|
|
|
|
|
|
if (orders.isEmpty()) {
|
|
|
- logger.warn("payConfirmMultiStore: 未找到关联订单, payCode={}", payCode);
|
|
|
+ // 【关键日志】未找到关联订单:订单可能被删除,返回空后 Controller 返回 SUCCESS,易宝不重试
|
|
|
+ logger.error("【payConfirmMultiStore-未找到关联订单】payCode={}, paymentCount={}, 返回空,事务回滚,Controller将返回SUCCESS(易宝不重试)",
|
|
|
+ payCode, storePayments.size());
|
|
|
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
|
|
return "";
|
|
|
}
|
|
|
|
|
|
// 【关键修复】第二步:校验订单状态,跳过已支付的订单
|
|
|
+ int skipCount = 0;
|
|
|
+ int needUpdateCount = 0;
|
|
|
for (FsStoreOrderScrm order : orders) {
|
|
|
if (order == null) {
|
|
|
continue;
|
|
|
@@ -5497,20 +5669,29 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
// 如果订单已经支付完成,跳过(幂等)
|
|
|
if (order.getPaid() != null && order.getPaid().equals(OrderInfoEnum.PAY_STATUS_1.getValue())
|
|
|
&& order.getStatus() != null && order.getStatus().equals(OrderInfoEnum.STATUS_1.getValue())) {
|
|
|
- logger.info("payConfirmMultiStore: 订单已支付,跳过, orderId={}, orderCode={}", order.getId(), order.getOrderCode());
|
|
|
+ logger.info("【payConfirmMultiStore-订单已支付跳过】orderId={}, orderCode={}, status={}, paid={}",
|
|
|
+ order.getId(), order.getOrderCode(), order.getStatus(), order.getPaid());
|
|
|
+ skipCount++;
|
|
|
continue;
|
|
|
}
|
|
|
// 订单状态不是待支付且也不是已支付,说明订单状态异常
|
|
|
if (order.getStatus() != null && !order.getStatus().equals(OrderInfoEnum.STATUS_0.getValue())
|
|
|
&& !order.getStatus().equals(OrderInfoEnum.STATUS_1.getValue())) {
|
|
|
- logger.error("payConfirmMultiStore: 订单状态异常, orderId={}, status={}, orderCode={}",
|
|
|
- order.getId(), order.getStatus(), order.getOrderCode());
|
|
|
+ // 【关键日志】订单状态异常:可能被其他流程修改,返回空后 Controller 返回 SUCCESS,易宝不重试
|
|
|
+ logger.error("【payConfirmMultiStore-订单状态异常】orderId={}, orderCode={}, 当前status={}, paid={}(期望status=0待支付或1已支付),返回空,事务回滚,Controller将返回SUCCESS(易宝不重试)",
|
|
|
+ order.getId(), order.getOrderCode(), order.getStatus(), order.getPaid());
|
|
|
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
|
|
return "";
|
|
|
}
|
|
|
+ needUpdateCount++;
|
|
|
+ logger.info("【payConfirmMultiStore-订单待更新】orderId={}, orderCode={}, 当前status={}, paid={}",
|
|
|
+ order.getId(), order.getOrderCode(), order.getStatus(), order.getPaid());
|
|
|
}
|
|
|
+ logger.info("【payConfirmMultiStore-订单校验完成】payCode={}, 总数={}, 跳过已支付={}, 待更新={}",
|
|
|
+ payCode, orders.size(), skipCount, needUpdateCount);
|
|
|
|
|
|
// 【关键修复】第三步:先更新订单状态,再更新 payment(保证订单优先落库)
|
|
|
+ int orderUpdatedCount = 0;
|
|
|
for (FsStoreOrderScrm order : orders) {
|
|
|
if (order == null) {
|
|
|
continue;
|
|
|
@@ -5525,14 +5706,19 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_1.getValue());
|
|
|
storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
|
|
|
storeOrder.setPayTime(new Date());
|
|
|
- fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
|
|
|
+ int updateRows = fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
|
|
|
+ logger.info("【payConfirmMultiStore-订单状态更新】orderId={}, orderCode={}, 更新前status={}, paid={}, updateRows={}",
|
|
|
+ order.getId(), order.getOrderCode(), order.getStatus(), order.getPaid(), updateRows);
|
|
|
+ orderUpdatedCount += updateRows;
|
|
|
|
|
|
// 增加状态日志
|
|
|
orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
|
|
|
OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
|
|
|
}
|
|
|
+ logger.info("【payConfirmMultiStore-订单更新完成】payCode={}, 实际更新行数={}", payCode, orderUpdatedCount);
|
|
|
|
|
|
// 【关键修复】第四步:更新 payment 状态(订单已落库,payment 更新即使异常也有 orders 兜底)
|
|
|
+ int paymentUpdatedCount = 0;
|
|
|
for (FsStorePaymentScrm storePayment : storePayments) {
|
|
|
if (storePayment == null) {
|
|
|
continue;
|
|
|
@@ -5543,14 +5729,20 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
storePayment.setTradeNo(tradeNo);
|
|
|
storePayment.setBankSerialNo(bankSerialNo);
|
|
|
storePayment.setBankTransactionId(bankTransactionId);
|
|
|
- paymentService.updateFsStorePayment(storePayment);
|
|
|
+ int updateRows = paymentService.updateFsStorePayment(storePayment);
|
|
|
+ logger.info("【payConfirmMultiStore-payment更新】paymentId={}, orderId={}, updateRows={}",
|
|
|
+ storePayment.getPaymentId(), storePayment.getOrderId(), updateRows);
|
|
|
+ paymentUpdatedCount += updateRows;
|
|
|
}
|
|
|
}
|
|
|
+ logger.info("【payConfirmMultiStore-payment更新完成】payCode={}, 实际更新行数={}", payCode, paymentUpdatedCount);
|
|
|
|
|
|
} else if (type.equals(2)) {
|
|
|
//货到付款
|
|
|
FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
|
|
|
if (!order.getStatus().equals(OrderInfoEnum.STATUS_0.getValue())) {
|
|
|
+ logger.error("【payConfirmMultiStore-货到付款订单状态异常】orderId={}, status={}(期望0待支付),返回空,事务回滚",
|
|
|
+ orderId, order.getStatus());
|
|
|
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
|
|
return "";
|
|
|
}
|
|
|
@@ -5565,6 +5757,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
|
|
|
orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
|
|
|
OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
|
|
|
+ logger.info("【payConfirmMultiStore-货到付款处理成功】orderId={}", orderId);
|
|
|
}
|
|
|
|
|
|
//第五步:OMS推送、佣金等后置处理(非关键路径,异常不影响订单支付状态)
|
|
|
@@ -5610,11 +5803,26 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ // 【关键诊断】第五步完成后,查询订单实际状态(事务内),确认 status/paid 是否正确
|
|
|
+ for (FsStoreOrderScrm order : orders) {
|
|
|
+ if (order == null) continue;
|
|
|
+ FsStoreOrderScrm latestOrder = fsStoreOrderMapper.selectFsStoreOrderById(order.getId());
|
|
|
+ logger.info("【payConfirmMultiStore-事务内最终状态】orderId={}, orderCode={}, status={}, paid={}, extendOrderId={}, deliveryName={}, deliverySn={}",
|
|
|
+ order.getId(), order.getOrderCode(),
|
|
|
+ latestOrder != null ? latestOrder.getStatus() : "null",
|
|
|
+ latestOrder != null ? latestOrder.getPaid() : "null",
|
|
|
+ latestOrder != null ? latestOrder.getExtendOrderId() : "null",
|
|
|
+ latestOrder != null ? latestOrder.getDeliveryName() : "null",
|
|
|
+ latestOrder != null ? latestOrder.getDeliverySn() : "null");
|
|
|
+ }
|
|
|
} catch (Exception e) {
|
|
|
- logger.error("payConfirmMultiStore 异常, payCode={}, orderId={}", payCode, orderId, e);
|
|
|
+ // 【关键日志】异常场景:事务回滚,但返回空后 Controller 返回 SUCCESS,易宝不重试,订单状态可能仍为待支付
|
|
|
+ logger.error("【payConfirmMultiStore-异常】payCode={}, orderId={}, 返回空,事务回滚,Controller将返回SUCCESS(易宝不重试),订单状态可能仍为待支付",
|
|
|
+ payCode, orderId, e);
|
|
|
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
|
|
return "";
|
|
|
}
|
|
|
+ logger.info("【payConfirmMultiStore-成功返回】payCode={}, orderId={}", payCode, orderId);
|
|
|
return "success";
|
|
|
}
|
|
|
|
|
|
@@ -5845,6 +6053,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
storeOrder.setUseIntegral(BigDecimal.valueOf(dto.getUsedIntegral()));
|
|
|
storeOrder.setBackIntegral(BigDecimal.ZERO);
|
|
|
storeOrder.setGainIntegral(BigDecimal.ZERO);
|
|
|
+ // 积分抵扣说明:多店铺场景由 Controller 预分配后透传到 param,单店铺场景从 dto 取(均可能为空)
|
|
|
+ if (StringUtils.isNotEmpty(param.getIntegralDesc())) {
|
|
|
+ storeOrder.setIntegralDesc(param.getIntegralDesc());
|
|
|
+ } else if (StringUtils.isNotEmpty(dto.getIntegralDesc())) {
|
|
|
+ storeOrder.setIntegralDesc(dto.getIntegralDesc());
|
|
|
+ }
|
|
|
storeOrder.setMark(param.getMark());
|
|
|
//todo 获取成本价
|
|
|
BigDecimal costPrice = this.getOrderSumPrice(carts, "costPrice");
|
|
|
@@ -5853,7 +6067,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
storeOrder.setShippingType(1);
|
|
|
storeOrder.setCreateTime(new Date());
|
|
|
storeOrder.setStatus(0);
|
|
|
- FsStorePromotionComputeResultVO promotionResult = applyPromotionFields(storeOrder, carts, userId, param.getCouponUserId(), param.getStoreId());
|
|
|
+ boolean skipPromotion = false;
|
|
|
+ if (StringUtils.isNotEmpty(param.getCreateOrderKey())) {
|
|
|
+ Boolean isAdjusted = redisCache.getCacheObject("createOrderIsAdjusted:" + param.getCreateOrderKey());
|
|
|
+ skipPromotion = (isAdjusted != null && isAdjusted);
|
|
|
+ }
|
|
|
+ FsStorePromotionComputeResultVO promotionResult = applyPromotionFields(storeOrder, carts, userId, param.getCouponUserId(), param.getStoreId(), skipPromotion);
|
|
|
//后台制单处理
|
|
|
if (param.getPayPrice() != null && param.getPayPrice().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
storeOrder.setPayPrice(param.getPayPrice());
|
|
|
@@ -6051,7 +6270,175 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
createOrderKeys = Arrays.asList(params.getCreateOrderKey().split(","));
|
|
|
}
|
|
|
|
|
|
+ // ========== 多店铺积分合并计算 ==========
|
|
|
+ // 1. 收集各子订单购物车,计算各自的 totalPrice 和 payPrice(未扣积分前)
|
|
|
+ int storeCount = orderKeys.size();
|
|
|
+ BigDecimal[] subTotalPrices = new BigDecimal[storeCount];
|
|
|
+ BigDecimal[] subPreDeductPayPrices = new BigDecimal[storeCount];
|
|
|
+ BigDecimal combinedTotal = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (int i = 0; i < storeCount; i++) {
|
|
|
+ List<FsStoreCartQueryVO> carts = redisCache.getCacheObject("orderCarts:" + orderKeys.get(i));
|
|
|
+ if (carts == null) {
|
|
|
+ throw new CustomException("订单已过期", 501);
|
|
|
+ }
|
|
|
+ BigDecimal subPayPrice = getOrderSumPrice(carts, "truePrice");
|
|
|
+
|
|
|
+ String createOrderKey = (createOrderKeys != null && i < createOrderKeys.size()) ? createOrderKeys.get(i) : null;
|
|
|
+ if (StringUtils.isNotEmpty(createOrderKey)) {
|
|
|
+ Integer payType = redisCache.getCacheObject("createOrderPayType:" + createOrderKey);
|
|
|
+ if (payType != null && payType == 3) {
|
|
|
+ subPayPrice = redisCache.getCacheObject("createOrderAmount:" + createOrderKey);
|
|
|
+ } else {
|
|
|
+ BigDecimal money = redisCache.getCacheObject("createOrderMoney:" + createOrderKey);
|
|
|
+ if (money != null) {
|
|
|
+ subPayPrice = money;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ FsOrderMoneyDetailsVo detailsVo = redisCache.getCacheObject("createOrderMoneyDetails:" + createOrderKey);
|
|
|
+ if (detailsVo != null) {
|
|
|
+ subPayPrice = detailsVo.getPayPrice();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ subPreDeductPayPrices[i] = subPayPrice;
|
|
|
+ // 用 payPrice 作为该子订单的"实付前金额"来参与比例分摊
|
|
|
+ subTotalPrices[i] = subPayPrice;
|
|
|
+ combinedTotal = NumberUtil.add(combinedTotal, subPayPrice);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 读积分配置,计算合并抵扣
|
|
|
+ Integer[] preAllocatedIntegrals = new Integer[storeCount];
|
|
|
+ BigDecimal[] preAllocatedDeductions = new BigDecimal[storeCount];
|
|
|
+ String[] integralDescs = new String[storeCount];
|
|
|
+
|
|
|
+ if (params.getUseIntegral() != null && params.getUseIntegral() == 1) {
|
|
|
+ FsUserScrm user = userService.selectFsUserById(uid);
|
|
|
+ if (user.getIntegral().intValue() > 0) {
|
|
|
+ // 新积分配置:points.grantRule
|
|
|
+ Integer payRate = 100; // X积分 = 1元,默认100
|
|
|
+ Integer maxDeductPercent = 50; // 最高抵扣比例,默认50%
|
|
|
+ BigDecimal minDeductAmount = new BigDecimal(100); // 满X元可抵扣,默认100
|
|
|
+ Integer minUseIntegral = 0; // 最低起用积分,默认不限制
|
|
|
+ Integer deductEnabled = 1; // 积分抵扣开关,默认开启
|
|
|
+ String json = configService.selectConfigByKey("points.grantRule");
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ PointsGrantRuleConfig rule = JSONUtil.toBean(json, PointsGrantRuleConfig.class);
|
|
|
+ if (rule != null && rule.getEnabled() != null && rule.getEnabled() == 1) {
|
|
|
+ if (rule.getPayRate() != null && rule.getPayRate() > 0) {
|
|
|
+ payRate = rule.getPayRate();
|
|
|
+ }
|
|
|
+ if (rule.getMaxDeductPercent() != null && rule.getMaxDeductPercent() > 0) {
|
|
|
+ maxDeductPercent = rule.getMaxDeductPercent();
|
|
|
+ }
|
|
|
+ if (rule.getMinDeductAmount() != null) {
|
|
|
+ minDeductAmount = rule.getMinDeductAmount();
|
|
|
+ }
|
|
|
+ if (rule.getMinUseIntegral() != null && rule.getMinUseIntegral() > 0) {
|
|
|
+ minUseIntegral = rule.getMinUseIntegral();
|
|
|
+ }
|
|
|
+ if (rule.getDeductEnabled() != null) {
|
|
|
+ deductEnabled = rule.getDeductEnabled();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("积分抵扣规则配置解析失败,使用默认值", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 积分抵扣功能关闭,跳过
|
|
|
+ if (deductEnabled == 0) {
|
|
|
+ // do nothing, preAllocated arrays remain null/zero
|
|
|
+ } else if (minUseIntegral > 0 && user.getIntegral().intValue() < minUseIntegral) {
|
|
|
+ // 用户积分不足最低起用门槛,跳过
|
|
|
+ } else if (combinedTotal.compareTo(minDeductAmount) >= 0) {
|
|
|
+ // 可用积分 = 用户全部积分(maxDeduct 已废弃,改用 maxDeductPercent 按比例限制)
|
|
|
+ long totalIntegral = user.getIntegral().longValue();
|
|
|
+ // 总抵扣金额 = 总积分 / payRate(如 100积分/100 = 1.00元)
|
|
|
+ BigDecimal totalDeduction = BigDecimal.valueOf(totalIntegral)
|
|
|
+ .divide(BigDecimal.valueOf(payRate), 2, RoundingMode.HALF_UP);
|
|
|
+ // 最高抵扣比例限制:抵扣金额不超过订单总额的 maxDeductPercent%
|
|
|
+ if (maxDeductPercent > 0 && maxDeductPercent < 100) {
|
|
|
+ BigDecimal maxDeductionAmount = combinedTotal
|
|
|
+ .multiply(BigDecimal.valueOf(maxDeductPercent))
|
|
|
+ .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
|
|
+ if (totalDeduction.compareTo(maxDeductionAmount) > 0) {
|
|
|
+ totalDeduction = maxDeductionAmount;
|
|
|
+ // 反推实际可用积分(向下取整,保证不超额)
|
|
|
+ totalIntegral = totalDeduction
|
|
|
+ .multiply(BigDecimal.valueOf(payRate))
|
|
|
+ .setScale(0, RoundingMode.DOWN)
|
|
|
+ .longValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ long allocatedIntegralSum = 0;
|
|
|
+ BigDecimal allocatedDeductionSum = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (int i = 0; i < storeCount; i++) {
|
|
|
+ if (combinedTotal.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ preAllocatedDeductions[i] = BigDecimal.ZERO;
|
|
|
+ preAllocatedIntegrals[i] = 0;
|
|
|
+ integralDescs[i] = null;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal ratio = NumberUtil.div(subTotalPrices[i], combinedTotal, 6);
|
|
|
+
|
|
|
+ // 积分分配:前 N-1 个四舍五入,最后一个用减法兜底确保分文不差
|
|
|
+ int subIntegral;
|
|
|
+ if (i == storeCount - 1) {
|
|
|
+ subIntegral = (int) (totalIntegral - allocatedIntegralSum);
|
|
|
+ } else {
|
|
|
+ subIntegral = BigDecimal.valueOf(totalIntegral)
|
|
|
+ .multiply(ratio)
|
|
|
+ .setScale(0, RoundingMode.HALF_UP)
|
|
|
+ .intValue();
|
|
|
+ }
|
|
|
|
|
|
+ // 抵扣金额分配:同理,最后一个用减法兜底
|
|
|
+ BigDecimal subDeduction;
|
|
|
+ if (i == storeCount - 1) {
|
|
|
+ subDeduction = NumberUtil.sub(totalDeduction, allocatedDeductionSum);
|
|
|
+ } else {
|
|
|
+ subDeduction = NumberUtil.mul(totalDeduction, ratio);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 不超过该子订单应付金额
|
|
|
+ if (subDeduction.compareTo(subPreDeductPayPrices[i]) > 0) {
|
|
|
+ subDeduction = subPreDeductPayPrices[i];
|
|
|
+ // 积分也等比例缩减
|
|
|
+ subIntegral = subDeduction
|
|
|
+ .multiply(BigDecimal.valueOf(payRate))
|
|
|
+ .setScale(0, RoundingMode.HALF_UP)
|
|
|
+ .intValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ // cap 之后再累加,确保最后一个店铺减法兜底正确
|
|
|
+ if (i != storeCount - 1) {
|
|
|
+ allocatedIntegralSum += subIntegral;
|
|
|
+ allocatedDeductionSum = NumberUtil.add(allocatedDeductionSum, subDeduction);
|
|
|
+ }
|
|
|
+
|
|
|
+ preAllocatedDeductions[i] = subDeduction;
|
|
|
+ preAllocatedIntegrals[i] = subIntegral;
|
|
|
+ integralDescs[i] = String.format(
|
|
|
+ "组合订单%d个店铺合计%.2f元,满%s元可抵扣(比例上限%d%%),共使用%d积分抵扣%.2f元,本单分摊%.2f元(%d积分)",
|
|
|
+ storeCount,
|
|
|
+ combinedTotal.doubleValue(),
|
|
|
+ minDeductAmount.stripTrailingZeros().toPlainString(),
|
|
|
+ maxDeductPercent,
|
|
|
+ totalIntegral,
|
|
|
+ totalDeduction.doubleValue(),
|
|
|
+ subDeduction.doubleValue(),
|
|
|
+ subIntegral
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 逐子订单计算
|
|
|
for (int i = 0; i < orderKeys.size(); i++) {
|
|
|
FsStoreOrderComputedParam computedParam = new FsStoreOrderComputedParam();
|
|
|
BeanUtils.copyProperties(params, computedParam);
|
|
|
@@ -6059,8 +6446,14 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
if (!createOrderKeys.isEmpty() && createOrderKeys.get(i) != null) {
|
|
|
computedParam.setCreateOrderKey(createOrderKeys.get(i));
|
|
|
}
|
|
|
+ // 注入预分配积分抵扣
|
|
|
+ if (preAllocatedDeductions[i] != null && preAllocatedDeductions[i].compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ computedParam.setPreAllocatedDeduction(preAllocatedDeductions[i]);
|
|
|
+ computedParam.setPreAllocatedIntegral(preAllocatedIntegrals[i]);
|
|
|
+ }
|
|
|
|
|
|
FsStoreOrderComputeDTO dto = this.computedOrderMultiStore(uid, computedParam);
|
|
|
+ dto.setIntegralDesc(integralDescs[i]);
|
|
|
payPriceTotal = NumberUtil.add(payPriceTotal, dto.getPayPrice());
|
|
|
totalPriceTotal = NumberUtil.add(totalPriceTotal, dto.getTotalPrice());
|
|
|
usedIntegralTotal += dto.getUsedIntegral();
|
|
|
@@ -6373,37 +6766,82 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
payPrice = NumberUtil.add(payPrice, payPostage);
|
|
|
FsUserScrm user = userService.selectFsUserById(uid);
|
|
|
// 积分抵扣
|
|
|
- BigDecimal deductionPrice = BigDecimal.ZERO; //积分抵扣金额
|
|
|
- double usedIntegral = 0; //使用了多少积分
|
|
|
+ BigDecimal deductionPrice = BigDecimal.ZERO;
|
|
|
+ double usedIntegral = 0;
|
|
|
if (param.getUseIntegral() != null && param.getUseIntegral() == 1 && user.getIntegral().intValue() > 0) {
|
|
|
- //如果积分总和小于用户积分 抵扣比例 计算抵扣价格
|
|
|
- //默认配置
|
|
|
- Double integralMax = Double.valueOf(100);
|
|
|
- BigDecimal integralFull = new BigDecimal(100);
|
|
|
- Double integralRatio = Double.valueOf(1);
|
|
|
- String json = configService.selectConfigByKey("store.integral");
|
|
|
- if (StringUtils.isEmpty(json)) {
|
|
|
-
|
|
|
- } else {
|
|
|
- StoreIntegralConfig integralConfig = JSONUtil.toBean(json, StoreIntegralConfig.class);
|
|
|
- integralMax = integralConfig.getIntegralMax();
|
|
|
- integralFull = integralConfig.getIntegralFull();
|
|
|
- integralRatio = integralConfig.getIntegralRatio();
|
|
|
- }
|
|
|
- if (priceGroup.getTotalPrice().compareTo(integralFull) >= 0) {
|
|
|
- Double userIntegral = user.getIntegral().doubleValue();
|
|
|
- if (integralMax.intValue() > 0 && Double.compare(userIntegral, integralMax) >= 0) {
|
|
|
- userIntegral = integralMax;
|
|
|
- }
|
|
|
- deductionPrice = BigDecimal.valueOf(NumberUtil.mul(userIntegral, integralRatio));
|
|
|
- if (deductionPrice.compareTo(payPrice) < 0) {
|
|
|
- payPrice = NumberUtil.sub(payPrice, deductionPrice);
|
|
|
- usedIntegral = userIntegral;
|
|
|
- } else {
|
|
|
+ // 多店铺场景:使用预分配的抵扣金额
|
|
|
+ if (param.getPreAllocatedDeduction() != null && param.getPreAllocatedDeduction().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ deductionPrice = param.getPreAllocatedDeduction();
|
|
|
+ usedIntegral = param.getPreAllocatedIntegral() != null ? param.getPreAllocatedIntegral().doubleValue() : 0;
|
|
|
+ if (deductionPrice.compareTo(payPrice) >= 0) {
|
|
|
deductionPrice = payPrice;
|
|
|
payPrice = BigDecimal.ZERO;
|
|
|
- usedIntegral = NumberUtil.round(NumberUtil.div(deductionPrice,
|
|
|
- BigDecimal.valueOf(integralRatio)), 2).doubleValue();
|
|
|
+ } else {
|
|
|
+ payPrice = NumberUtil.sub(payPrice, deductionPrice);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 单店铺场景:使用新积分配置 points.grantRule
|
|
|
+ Integer payRate = 100;
|
|
|
+ Integer maxDeductPercent = 50;
|
|
|
+ BigDecimal minDeductAmount = new BigDecimal(100);
|
|
|
+ Integer minUseIntegral = 0;
|
|
|
+ Integer deductEnabled = 1;
|
|
|
+ String json = configService.selectConfigByKey("points.grantRule");
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ PointsGrantRuleConfig rule = JSONUtil.toBean(json, PointsGrantRuleConfig.class);
|
|
|
+ if (rule != null && rule.getEnabled() != null && rule.getEnabled() == 1) {
|
|
|
+ if (rule.getPayRate() != null && rule.getPayRate() > 0) {
|
|
|
+ payRate = rule.getPayRate();
|
|
|
+ }
|
|
|
+ if (rule.getMaxDeductPercent() != null && rule.getMaxDeductPercent() > 0) {
|
|
|
+ maxDeductPercent = rule.getMaxDeductPercent();
|
|
|
+ }
|
|
|
+ if (rule.getMinDeductAmount() != null) {
|
|
|
+ minDeductAmount = rule.getMinDeductAmount();
|
|
|
+ }
|
|
|
+ if (rule.getMinUseIntegral() != null && rule.getMinUseIntegral() > 0) {
|
|
|
+ minUseIntegral = rule.getMinUseIntegral();
|
|
|
+ }
|
|
|
+ if (rule.getDeductEnabled() != null) {
|
|
|
+ deductEnabled = rule.getDeductEnabled();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("积分抵扣规则配置解析失败,使用默认值", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 积分抵扣功能关闭或积分不足起用门槛,跳过
|
|
|
+ if (deductEnabled == 1
|
|
|
+ && (minUseIntegral == 0 || user.getIntegral().intValue() >= minUseIntegral)
|
|
|
+ && priceGroup.getTotalPrice().compareTo(minDeductAmount) >= 0) {
|
|
|
+ long userIntegral = user.getIntegral().longValue();
|
|
|
+ deductionPrice = BigDecimal.valueOf(userIntegral)
|
|
|
+ .divide(BigDecimal.valueOf(payRate), 2, RoundingMode.HALF_UP);
|
|
|
+ // 最高抵扣比例限制
|
|
|
+ if (maxDeductPercent > 0 && maxDeductPercent < 100) {
|
|
|
+ BigDecimal maxDeductionAmount = priceGroup.getTotalPrice()
|
|
|
+ .multiply(BigDecimal.valueOf(maxDeductPercent))
|
|
|
+ .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
|
|
+ if (deductionPrice.compareTo(maxDeductionAmount) > 0) {
|
|
|
+ deductionPrice = maxDeductionAmount;
|
|
|
+ userIntegral = deductionPrice
|
|
|
+ .multiply(BigDecimal.valueOf(payRate))
|
|
|
+ .setScale(0, RoundingMode.DOWN)
|
|
|
+ .longValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (deductionPrice.compareTo(payPrice) < 0) {
|
|
|
+ payPrice = NumberUtil.sub(payPrice, deductionPrice);
|
|
|
+ usedIntegral = userIntegral;
|
|
|
+ } else {
|
|
|
+ deductionPrice = payPrice;
|
|
|
+ payPrice = BigDecimal.ZERO;
|
|
|
+ usedIntegral = deductionPrice
|
|
|
+ .multiply(BigDecimal.valueOf(payRate))
|
|
|
+ .setScale(0, RoundingMode.DOWN)
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -6445,6 +6883,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
.usedIntegral(usedIntegral)
|
|
|
.payIntegral(priceGroup.getPayIntegral())
|
|
|
.promotionDiscountAmount(promotionResult.getPromotionDiscountAmount())
|
|
|
+ .integralDesc(null)
|
|
|
.build();
|
|
|
}
|
|
|
|
|
|
@@ -6501,16 +6940,21 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
/**
|
|
|
* 计算并应用满减活动优惠到订单对象(不持久化),返回计算结果供后续复用
|
|
|
*
|
|
|
+ * @param skipPromotion 制单改价场景应跳过满减计算,避免重复优惠
|
|
|
* @return 活动计算结果,无活动或无优惠时返回 null
|
|
|
*/
|
|
|
private FsStorePromotionComputeResultVO applyPromotionFields(FsStoreOrderScrm storeOrder,
|
|
|
List<FsStoreCartQueryVO> carts,
|
|
|
long userId,
|
|
|
Long couponUserId,
|
|
|
- Long storeId) {
|
|
|
+ Long storeId,
|
|
|
+ boolean skipPromotion) {
|
|
|
if (storeOrder == null) {
|
|
|
return null;
|
|
|
}
|
|
|
+ if (skipPromotion) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
FsStorePromotionComputeResultVO promotion = promotionComputeService.autoApplyBestPromotion(
|
|
|
userId, carts, storeId, couponUserId);
|
|
|
if (promotion == null || promotion.getPromotionDiscountAmount() == null
|
|
|
@@ -6790,6 +7234,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public void cancelOrderReuse(FsStoreOrderScrm order) {
|
|
|
if (order.getStatus() == OrderInfoEnum.STATUS_0.getValue()) {
|
|
|
if (!StringUtils.isEmpty(order.getExtendOrderId())) {
|