diff --git a/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java b/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java index 7a3b6e19..88e641ce 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java +++ b/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java @@ -121,4 +121,8 @@ public class CommonConstant { public static final Integer Agent_Level_2nd = 2; + // 订单配送预约状态:1-立即配送;2-预约配送 + public static final Integer Order_Booking_State_LJ = 1; + public static final Integer Order_Booking_State_YY = 2; + } diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderDraw.java b/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderDraw.java index d41b1abf..0a3afff3 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderDraw.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderDraw.java @@ -110,6 +110,9 @@ public class LklOrderDraw { @ApiModelProperty(value = "商户订单号", example = "MER2023100100001") private String mer_order_no; + @ApiModelProperty(value = "商城订单Id") + private String order_id; + /** * 结算流水号 */ diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderInfo.java b/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderInfo.java index 0b5ab844..e8e42e57 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderInfo.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderInfo.java @@ -75,7 +75,7 @@ public class ShopOrderInfo implements Serializable { @ApiModelProperty(value = "下单时间:检索使用") private Long order_time; - @ApiModelProperty(value = "当前状态的处理时间") + @ApiModelProperty(value = "当前状态的处理时间,一般是确认收货时间") private Long order_deal_time; @ApiModelProperty(value = "买家删除(BOOL): 1-是; 0-否") @@ -204,6 +204,15 @@ public class ShopOrderInfo implements Serializable { @ApiModelProperty(value = "拣货完成时间戳") private Long order_picked_time; + @ApiModelProperty(value = "订单配送预约状态:1-立即配送;2-预约配送") + private Integer booking_state; + + @ApiModelProperty(value = "预约送达起始时间,格式如:yyyy-MM-dd HH:mm:ss") + private Date booking_begin_time; + + @ApiModelProperty(value = "预约送达截止时间,格式如:yyyy-MM-dd HH:mm:ss") + private Date booking_end_time; + @ApiModelProperty(value = "新建时间") private Date created_at; diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderInfoDTO.java b/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderInfoDTO.java index 05f732dc..cdfdf29b 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderInfoDTO.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderInfoDTO.java @@ -91,6 +91,6 @@ public class MchOrderInfoDTO implements Serializable { private BigDecimal order_income_amount; @ApiModelProperty(value = "两点距离,单位米") private Integer distance; - - // 快递鸟物流信息 + @ApiModelProperty(value = "是否禁止退款:1-是;2-否;") + private Integer is_deny_return; } diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderItemDTO.java b/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderItemDTO.java index dac88fd2..a13b4649 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderItemDTO.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/order/dto/MchOrderItemDTO.java @@ -61,4 +61,7 @@ public class MchOrderItemDTO implements Serializable { @ApiModelProperty(value = "退款金额:同意额度") private BigDecimal order_item_return_agree_amount; + + @ApiModelProperty(value = "是否禁止退款:1-是;2-否;") + private Integer is_deny_return; } diff --git a/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateWithTotalAmountDTO.java b/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateWithTotalAmountDTO.java index 8721b4a1..60840f8c 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateWithTotalAmountDTO.java +++ b/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateWithTotalAmountDTO.java @@ -62,7 +62,7 @@ public class LklSeparateWithTotalAmountDTO { // 测试用例1: 所有参与方都参与分账(符合比例要求) System.out.println("=== 测试用例1: 所有参与方都参与分账 ==="); LklSeparateWithTotalAmountDTO dto1 = new LklSeparateWithTotalAmountDTO(); - dto1.setTotalSeparateAmount(5500); // 总金额100元(10000分) + dto1.setTotalSeparateAmount(1696); // 总金额100元(10000分) dto1.setShippingFee(500); // dto1.setRefCanSeparateAmount(1496); dto1.setLklRatio(new BigDecimal("0.0025")); // 拉卡拉分账比例0.25% diff --git a/mall-common/src/main/java/com/suisung/mall/common/pojo/to/AddressParseResultTO.java b/mall-common/src/main/java/com/suisung/mall/common/pojo/to/AddressParseResultTO.java index 5907b161..900eca77 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/pojo/to/AddressParseResultTO.java +++ b/mall-common/src/main/java/com/suisung/mall/common/pojo/to/AddressParseResultTO.java @@ -33,7 +33,7 @@ public class AddressParseResultTO implements java.io.Serializable { private String joinArea; /** - * 详细地址 + * (去掉省市县的)详细地址 */ private String detailAddress; diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/AddressUtil.java b/mall-common/src/main/java/com/suisung/mall/common/utils/AddressUtil.java index e4013607..e6aa08c7 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/utils/AddressUtil.java +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/AddressUtil.java @@ -112,7 +112,7 @@ public class AddressUtil { * System.out.println("详细地址: " + parsedAddress.getDetailAddress()); // 西山镇新安街粤桂花城1102号 */ public static void main(String[] args) { - String fullAddress = "广西壮族自治区贵港市桂平市西山镇新安街粤桂花城1102号"; + String fullAddress = "广西壮族自治区贵港市桂平市桂平西山镇新安街粤桂花城1102号"; AddressParseResultTO parsedAddress = AddressUtil.parseAddress(fullAddress); System.out.println("省: " + parsedAddress.getProvince()); System.out.println("市: " + parsedAddress.getCity()); diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/DateTimeUtils.java b/mall-common/src/main/java/com/suisung/mall/common/utils/DateTimeUtils.java index 6048c6f2..f6f0de8e 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/utils/DateTimeUtils.java +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/DateTimeUtils.java @@ -8,6 +8,7 @@ import java.text.SimpleDateFormat; import java.time.*; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.util.Calendar; import java.util.Date; @@ -62,6 +63,121 @@ public class DateTimeUtils { } } + /** + * 验证日期时间字符串是否符合支持的日期时间格式 + * 支持的格式包括: + * - yyyy-MM-dd HH:mm (如 2023-12-01 09:30) + * - yyyy-MM-dd HH:mm:ss (如 2023-12-01 09:30:45) + * - yyyy-MM-dd HH:mm:ss.SSS (如 2023-12-01 09:30:45.123) + * - yyyy-MM-dd HH:mm:ss.SSSSSS (如 2023-12-01 09:30:45.123456) + * - yyyy-MM-dd HH:mm:ss.SSSSSSSSS (如 2023-12-01 09:30:45.123456789) + * - yyyy/MM/dd HH:mm:ss (如 2023/12/01 09:30:45) + * - yyyy.MM.dd HH:mm:ss (如 2023.12.01 09:30:45) + * + * @param dateTimeStr 待验证的日期时间字符串 + * @return true表示符合日期时间格式,false表示不符合 + */ + public static LocalDateTime tryParseDateTime(String dateTimeStr) { + // 空值检查 + if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) { + return null; + } + + dateTimeStr = dateTimeStr.trim(); + + // 使用正则表达式验证日期时间格式 + // 支持多种日期分隔符和从分钟到纳秒的各种时间格式 + String dateTimePattern = "^\\d{4}[-/.]\\d{1,2}[-/.]\\d{1,2}\\s+([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?(\\.\\d{1,9})?$"; + + if (!dateTimeStr.matches(dateTimePattern)) { + return null; + } + + try { + // 尝试解析日期时间,确保日期时间值有效 + // 支持三种常见分隔符:-, /, . + if (dateTimeStr.contains("-")) { + return parseDateTime(dateTimeStr, "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss.SSSSSS", + "yyyy-MM-dd HH:mm:ss.SSSSSSSSS"); + } else if (dateTimeStr.contains("/")) { + return parseDateTime(dateTimeStr, "yyyy/MM/dd HH:mm", "yyyy/MM/dd HH:mm:ss", + "yyyy/MM/dd HH:mm:ss.SSS", "yyyy/MM/dd HH:mm:ss.SSSSSS", + "yyyy/MM/dd HH:mm:ss.SSSSSSSSS"); + } else if (dateTimeStr.contains(".")) { + return parseDateTime(dateTimeStr, "yyyy.MM.dd HH:mm", "yyyy.MM.dd HH:mm:ss", + "yyyy.MM.dd HH:mm:ss.SSS", "yyyy.MM.dd HH:mm:ss.SSSSSS", + "yyyy.MM.dd HH:mm:ss.SSSSSSSSS"); + } + return null; + } catch (Exception e) { + // 解析失败说明日期时间值无效 + return null; + } + } + + /** + * 验证并解析日期时间字符串为Date对象 + * 支持的格式包括: + * - yyyy-MM-dd HH:mm (如 2023-12-01 09:30) + * - yyyy-MM-dd HH:mm:ss (如 2023-12-01 09:30:45) + * - yyyy-MM-dd HH:mm:ss.SSS (如 2023-12-01 09:30:45.123) + * - yyyy-MM-dd HH:mm:ss.SSSSSS (如 2023-12-01 09:30:45.123456) + * - yyyy-MM-dd HH:mm:ss.SSSSSSSSS (如 2023-12-01 09:30:45.123456789) + * - yyyy/MM/dd HH:mm:ss (如 2023/12/01 09:30:45) + * - yyyy.MM.dd HH:mm:ss (如 2023.12.01 09:30:45) + * + * @param dateTimeStr 待解析的日期时间字符串 + * @return 解析后的Date对象,解析失败返回null + */ + public static Date tryParseDateTimeToDate(String dateTimeStr) { + LocalDateTime localDateTime = tryParseDateTime(dateTimeStr); + if (localDateTime != null) { + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + return null; + } + + /** + * 解析日期时间字符串 + * + * @param dateTimeStr 日期时间字符串 + * @param patterns 可能的日期时间格式模式 + * @return 解析后的 LocalDateTime 对象 + */ + private static LocalDateTime parseDateTime(String dateTimeStr, String... patterns) { + for (String pattern : patterns) { + try { + // 检查模式和字符串长度是否匹配 + if (isMatchingDateTimePattern(dateTimeStr, pattern)) { + return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern)); + } + } catch (Exception ignored) { + // 继续尝试下一个格式 + } + } + + // 如果所有格式都失败,则抛出异常 + throw new DateTimeParseException("无法解析日期时间字符串: " + dateTimeStr, dateTimeStr, 0); + } + + /** + * 判断日期时间字符串是否与格式模式匹配 + * + * @param dateTimeStr 日期时间字符串 + * @param pattern 格式模式字符串 + * @return 是否匹配 + */ + private static boolean isMatchingDateTimePattern(String dateTimeStr, String pattern) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + LocalDateTime.parse(dateTimeStr, formatter); + return true; + } catch (Exception e) { + return false; + } + } + /** * 将多种日期格式转换为 yyyy-MM-dd @@ -233,8 +349,8 @@ public class DateTimeUtils { /** * 判断指定时间是否在两个时间点之间(包含边界) * - * @param startTimeStr 开始时间字符串,格式为 HH:mm - * @param endTimeStr 结束时间字符串,格式为 HH:mm + * @param startTimeStr 开始时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 + * @param endTimeStr 结束时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 * @param currentTime 要判断的时间点 * @return 如果在时间段内返回true,否则返回false。出现异常时返回false,不影响主流程 */ @@ -247,11 +363,11 @@ public class DateTimeUtils { return false; } - // 解析开始时间 - LocalTime startTime = LocalTime.parse(startTimeStr, DateTimeFormatter.ofPattern("HH:mm")); + // 解析开始时间 - 支持多种时间格式 + LocalTime startTime = parseTime(startTimeStr); - // 解析结束时间 - LocalTime endTime = LocalTime.parse(endTimeStr, DateTimeFormatter.ofPattern("HH:mm")); + // 解析结束时间 - 支持多种时间格式 + LocalTime endTime = parseTime(endTimeStr); // 获取当前时间的时间部分 LocalTime nowTime = currentTime.toLocalTime(); @@ -273,18 +389,114 @@ public class DateTimeUtils { } } + + /** + * 判断指定时间是否在两个时间点之间(包含边界) + * + * @param startTimeStr 开始时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 + * @param endTimeStr 结束时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 + * @param currentTime 要判断的时间点 + * @return 如果在时间段内返回true,否则返回false。出现异常时返回false,不影响主流程 + */ + public static boolean isTimeInRange(String startTimeStr, String endTimeStr, Date currentTime) { + if (currentTime == null) { + log.warn("时间参数不能为空,startTimeStr: {}, endTimeStr: {}, currentTime: null", + startTimeStr, endTimeStr); + return false; + } + // 将Date转换为LocalDateTime并调用已有的方法 + LocalDateTime localDateTime = currentTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + return isTimeInRange(startTimeStr, endTimeStr, localDateTime); + } + /** * 判断当前时间是否在两个时间点之间(包含边界) * - * @param startTimeStr 开始时间字符串,格式为 HH:mm - * @param endTimeStr 结束时间字符串,格式为 HH:mm + * @param startTimeStr 开始时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 + * @param endTimeStr 结束时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 * @return 如果在时间段内返回true,否则返回false - * @throws IllegalArgumentException 当时间字符串格式不正确时抛出异常 */ public static boolean isCurrentTimeInRange(String startTimeStr, String endTimeStr) { return isTimeInRange(startTimeStr, endTimeStr, LocalDateTime.now()); } + /** + * 解析时间字符串,支持多种时间格式 + * + * @param timeStr 时间字符串,支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS 等 + * @return 解析后的 LocalTime 对象 + */ + private static LocalTime parseTime(String timeStr) { + if (timeStr == null || timeStr.isEmpty()) { + throw new IllegalArgumentException("时间字符串不能为空"); + } + + try { + // 尝试使用不同的格式解析时间字符串 + // 按照精度从高到低排序,避免精度损失 + DateTimeFormatter[] formatters = { + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS"), // 纳秒(9位) + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS"), // 微秒(6位) + DateTimeFormatter.ofPattern("HH:mm:ss.SSS"), // 毫秒(3位) + DateTimeFormatter.ofPattern("HH:mm:ss"), // 秒 + DateTimeFormatter.ofPattern("HH:mm") // 分钟 + }; + + // 先尝试精确匹配长度的格式 + for (DateTimeFormatter formatter : formatters) { + try { + // 根据格式长度进行预筛选 + String pattern = formatter.toString(); + if (isMatchingTimePattern(timeStr, pattern)) { + return LocalTime.parse(timeStr, formatter); + } + } catch (Exception ignored) { + // 继续尝试下一个格式 + } + } + + // 如果精确匹配失败,按原有方式尝试所有格式 + for (DateTimeFormatter formatter : formatters) { + try { + return LocalTime.parse(timeStr, formatter); + } catch (Exception ignored) { + // 继续尝试下一个格式 + } + } + + // 如果所有格式都失败,则抛出异常 + throw new DateTimeParseException("无法解析时间字符串: " + timeStr, timeStr, 0); + + } catch (Exception e) { + log.warn("时间解析失败,timeStr: {}", timeStr, e); + throw e; + } + } + + /** + * 判断时间字符串是否与格式模式匹配 + * + * @param timeStr 时间字符串 + * @param pattern 格式模式字符串 + * @return 是否匹配 + */ + private static boolean isMatchingTimePattern(String timeStr, String pattern) { + // 简单的长度匹配检查 + switch (pattern) { + case "HH:mm": + return timeStr.length() == 5 && timeStr.charAt(2) == ':'; + case "HH:mm:ss": + return timeStr.length() == 8 && timeStr.charAt(2) == ':' && timeStr.charAt(5) == ':'; + case "HH:mm:ss.SSS": + return timeStr.length() == 12 && timeStr.charAt(2) == ':' && timeStr.charAt(5) == ':' && timeStr.charAt(8) == '.'; + case "HH:mm:ss.SSSSSS": + return timeStr.length() == 15 && timeStr.charAt(2) == ':' && timeStr.charAt(5) == ':' && timeStr.charAt(8) == '.'; + case "HH:mm:ss.SSSSSSSSS": + return timeStr.length() == 18 && timeStr.charAt(2) == ':' && timeStr.charAt(5) == ':' && timeStr.charAt(8) == '.'; + default: + return false; + } + } public static void main(String[] args) { // System.out.println(convertLklDate("2021-02-19")); // 2025-01-02 @@ -310,8 +522,9 @@ public class DateTimeUtils { boolean isWorkTime = isCurrentTimeInRange("09:00", "22:36"); // 判断特定时间是否在夜间时间(22:00-06:00)内 - LocalDateTime testTime = LocalDateTime.of(2025, 1, 1, 23, 30); - boolean isNight = isTimeInRange("22:00", "06:00", testTime); +// LocalDateTime testTime = LocalDateTime.of(2025, 1, 1, 23, 30); + Date testTime = Date.from(LocalDateTime.of(2025, 10, 23, 21, 30).atZone(ZoneId.systemDefault()).toInstant()); + boolean isNight = isTimeInRange("08:30", "22:20", testTime); System.out.println("当前时间是否在工作时间内:" + isWorkTime); System.out.println("特定时间是否在夜间时间段内:" + isNight); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/admin/LakalaAdminController.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/admin/LakalaAdminController.java index f1da4113..fe9b4ca8 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/admin/LakalaAdminController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/admin/LakalaAdminController.java @@ -49,7 +49,7 @@ public class LakalaAdminController extends BaseControllerImpl { } // 执行业务逻辑 - Boolean success = lakalaPayService.ewalletWithDrawD1(paramsJSON.getStr("mercId"), paramsJSON.getStr("merOrderNo"), paramsJSON.getStr("drawAmt"), paramsJSON.getStr("summary")); + Boolean success = lakalaPayService.ewalletWithDrawD1(paramsJSON.getStr("mercId"), paramsJSON.getStr("merOrderNo"), paramsJSON.getStr("drawAmt"), paramsJSON.getStr("orderId"), paramsJSON.getStr("summary")); if (success) { return CommonResult.success("账户D1提现提交成功"); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java index f4c12f23..9f7631f3 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java @@ -208,9 +208,10 @@ public interface LakalaApiService { * @param lklMerchantNo 拉卡拉商户号 * @param receiveTradeNo 收货交易号(对应拉卡拉的trade_no) * @param receiveLogNo 收货流水号(对应拉卡拉的log_no) + * @param logDate posp日期,yyyyMMdd,查清结算用, 一般是值交易完成日期(确认收货的日期) * @return Pair 处理结果对,first为是否成功,second为结果描述信息 */ - Pair innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo); + Pair innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo, String logDate); /** * 分账结果通知 @@ -339,9 +340,10 @@ public interface LakalaApiService { * @param mercId 822商户号或receiveNo * @param merOrderNo 商户订单号 * @param drawAmt 提现金额(分) + * @param orderId 商城订单Id * @param summary */ - Boolean ewalletWithDrawD1(String mercId, String merOrderNo, String drawAmt, String summary); + Boolean ewalletWithDrawD1(String mercId, String merOrderNo, String drawAmt, String orderId, String summary); /** * 拉卡拉账户D1提现结果通知 diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java index 8d074ae6..f98153b5 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java @@ -29,4 +29,12 @@ public interface LklOrderDrawService extends IBaseService { * @return */ LklOrderDraw getByByMercIdAndMerOrderNo(String mercId, String merOrderNo); + + /** + * 判断订单是否已经提现完成 + * + * @param orderId + * @return + */ + Boolean isOrderDrawed(String orderId); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java index 9a34c6f8..ba7fe700 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java @@ -11,6 +11,7 @@ package com.suisung.mall.shop.lakala.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; @@ -823,7 +824,14 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 分账用途 String logNo = paramsJSON.getStr("log_no"); String tradeNo = paramsJSON.getStr("trade_no"); - log.debug("[确认收货通知] 获取基础交易信息: logNo={} tradeNo={}", logNo, tradeNo); + String tradeTime = paramsJSON.getStr("trade_time"); // 实际交易完成时间。yyyyMMddHHmmss + // 直接截取前8位,获取日期部分 + String logDate = tradeTime != null && tradeTime.length() >= 8 ? tradeTime.substring(0, 8) : null; + if (logDate == null) { + logDate = DateUtil.format(new Date(), "yyyyMMdd"); // 当前时间 + } + + log.debug("[确认收货通知] 获取基础交易信息: logNo={} tradeNo={} logDate={}", logNo, tradeNo, logDate); // 查询用途 String originTradeNo = paramsJSON.getStr("origin_trade_no"); @@ -900,7 +908,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 重要:准备发起分账指令 log.info("[确认收货通知] 开始发起分账指令: merchantNo={}, receiveTradeNo={}, logNo={}", merchantNo, shopOrderLkl.getLkl_receive_trade_no(), logNo); - Pair separateResult = innerDoOrderSeparateByMerchantAndLogNo(merchantNo, shopOrderLkl.getLkl_receive_trade_no(), shopOrderLkl.getLkl_receive_log_no()); + Pair separateResult = innerDoOrderSeparateByMerchantAndLogNo(merchantNo, shopOrderLkl.getLkl_receive_trade_no(), shopOrderLkl.getLkl_receive_log_no(), logDate); if (!separateResult.getFirst()) { shopOrderLkl.setSeparate_msg(separateResult.getSecond()); @@ -2451,15 +2459,16 @@ public class LakalaApiServiceImpl implements LakalaApiService { * @param lklMerchantNo 拉卡拉商户号 * @param receiveTradeNo 收货交易号(对应拉卡拉的trade_no) * @param receiveLogNo 收货流水号(对应拉卡拉的log_no) + * @param logDate posp日期,yyyyMMdd,查清结算用, 一般是值交易完成日期(确认收货的日期) * @return Pair 处理结果对,first为是否成功,second为结果描述信息 */ @Transactional @Override - public Pair innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo) { + public Pair innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo, String logDate) { // 1. 输入参数校验 - if (StrUtil.hasBlank(lklMerchantNo, receiveTradeNo, receiveLogNo)) { - log.warn("[分账操作] 参数校验失败:缺少必要参数, lklMerchantNo={}, receiveTradeNo={}, receiveLogNo={}", - lklMerchantNo, receiveTradeNo, receiveLogNo); + if (StrUtil.hasBlank(lklMerchantNo, receiveTradeNo, receiveLogNo, logDate)) { + log.warn("[分账操作] 参数校验失败:缺少必要参数, lklMerchantNo={}, receiveTradeNo={}, receiveLogNo={}, logDate={}", + lklMerchantNo, receiveTradeNo, receiveLogNo, logDate); return Pair.of(false, "缺少必要参数"); } @@ -2639,7 +2648,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { receiver.setSeparateValue(Convert.toStr(merchantAmount)); recvDatas.add(receiver); } - + // 是否有二级代理商? boolean has2ndAgent = CheckUtil.isNotEmpty(agent2ndSplitRatio) && agent2ndReceiver != null && StrUtil.isNotBlank(agent2ndReceiver.getReceiver_no()) && @@ -2695,7 +2704,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { separateRequest.setMerchantNo(merchantNo); separateRequest.setOutSeparateNo(shopOrderLkl.getOut_separate_no()); separateRequest.setLogNo(shopOrderLkl.getLkl_receive_log_no()); // 使用确认收货流水号作为分账流水号 - separateRequest.setLogDate(shopOrderLkl.getLkl_log_date()); + separateRequest.setLogDate(logDate); // 重要:确认收货后的交易时间 separateRequest.setTotalAmt(refCanSeparateAmt.toString()); separateRequest.setLklOrgNo(orgCode); separateRequest.setCalType("0"); // 0- 按照指定金额,1- 按照指定比例。默认 0 @@ -2799,11 +2808,12 @@ public class LakalaApiServiceImpl implements LakalaApiService { public JSONObject sacsSeparateNotify(HttpServletRequest request, String merchantNoParam, String separateNoParam) { String channel = ""; try { - JSONObject paramsJson = null; + JSONObject paramsJson; if (!StrUtil.hasBlank(merchantNoParam, separateNoParam)) { channel = "(补偿)"; log.info("[拉卡拉分账通知] 开始处理拉卡拉分账结果通知异步回调{},merchantNoParam={},separateNoParam={}", channel, merchantNoParam, separateNoParam); + // 主动从拉卡拉获取分账结果 paramsJson = sacsQuery(merchantNoParam, separateNoParam); } else { log.info("[拉卡拉分账通知] 开始处理拉卡拉分账结果通知异步回调"); @@ -2922,13 +2932,15 @@ public class LakalaApiServiceImpl implements LakalaApiService { continue; } + // 分账成功之后,立即申请提现 String recvNo = Convert.toStr(detailData.get("recv_no"), ""); String amt = Convert.toStr(detailData.get("amt"), ""); if (StrUtil.isNotBlank(recvNo) && StrUtil.isNotBlank(amt)) { - String outSeparateNoTemp = String.format("%s_%d", outSeparateNo, idx); - Boolean drawSuccess = ewalletWithDrawD1(recvNo, outSeparateNoTemp, amt, JSONUtil.toJsonStr(detailDatas)); + String merOrderNo = String.format("%s_%d", outSeparateNo, idx); + // 账户D1提现 + Boolean drawSuccess = ewalletWithDrawD1(recvNo, merOrderNo, amt, outSeparateNo, JSONUtil.toJsonStr(detailDatas)); log.info("[拉卡拉分账通知] 账户D1提现{},商户号={},分账单号={},金额={}分", - Boolean.TRUE.equals(drawSuccess) ? "成功" : "失败", recvNo, outSeparateNoTemp, amt); + Boolean.TRUE.equals(drawSuccess) ? "成功" : "失败", recvNo, merOrderNo, amt); idx++; } } @@ -3038,9 +3050,9 @@ public class LakalaApiServiceImpl implements LakalaApiService { public Integer fixUnSuccessSeparateStatusJob() { log.info("[分账状态修复任务] 开始执行未成功分账记录的状态修复任务"); - // 获取2天前分账状态未成功的记录 - Date now = new Date(); - Date threeDaysAgo = DateUtils.addHours(now, -48); + // 获取3天前分账状态未成功的记录 + Date endDate = new Date(); + Date beginDate = DateUtils.addDays(endDate, -3); // 3天前 // 分页参数 int pageSize = 200; @@ -3050,100 +3062,104 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 记录处理开始时间 long startTime = System.currentTimeMillis(); + String redisPrefKey = "lkl:separate:status:retry:"; - List lklOrderSeparates; - do { - // 分页获取未成功分账的记录 - lklOrderSeparates = lklOrderSeparateService.getUnSuccessSeparateList(threeDaysAgo, now, currentPage, pageSize); + try { + List lklOrderSeparates; + do { + // 分页获取未成功分账的记录 + lklOrderSeparates = lklOrderSeparateService.getUnSuccessSeparateList(beginDate, endDate, currentPage, pageSize); - if (CollectionUtil.isEmpty(lklOrderSeparates)) { - break; - } - - log.info("[分账状态修复任务] 获取到第{}页数据,共{}条记录", currentPage, lklOrderSeparates.size()); - - String redisPrefKey = "lkl:separate:status:retry:"; - - // 按处理次数排序,从未处理过的记录优先处理,处理次数越多优先级越低 - lklOrderSeparates.sort((o1, o2) -> { - String redisKey1 = redisPrefKey + o1.getSeparate_no(); - String redisKey2 = redisPrefKey + o2.getSeparate_no(); - - int retryCount1 = Convert.toInt(redisService.get(redisKey1), 0); - int retryCount2 = Convert.toInt(redisService.get(redisKey2), 0); - - return Integer.compare(retryCount1, retryCount2); - }); - - // 处理当前页中的记录 - for (LklOrderSeparate record : lklOrderSeparates) { - // 检查该记录的处理次数是否已达到上限(5次) - String redisKey = redisPrefKey + record.getSeparate_no(); - int retryCount = Convert.toInt(redisService.get(redisKey), 0); - - if (retryCount >= 5) { - log.warn("[分账状态修复任务] 记录已达到最大重试次数,跳过处理: merchantNo={}, separateNo={}", - record.getMerchant_no(), record.getSeparate_no()); - continue; + if (CollectionUtil.isEmpty(lklOrderSeparates)) { + break; } - totalProcessed++; - // 每处理10条记录才输出一次详细日志,减少日志量 - if (totalProcessed % 10 == 1) { - log.info("[分账状态修复任务] 正在处理第 {} 条记录: merchantNo={}, separateNo={}, 已重试{}次", - totalProcessed, record.getMerchant_no(), record.getSeparate_no(), retryCount); - } + log.info("[分账状态修复任务] 获取到第{}页数据,共{}条记录", currentPage, lklOrderSeparates.size()); - try { - // 增加处理次数计数并设置3天过期时间 - redisService.incr(redisKey, 1); - redisService.expire(redisKey, 3 * 24 * 60 * 60); + // 按处理次数排序,从未处理过的记录优先处理,处理次数越多优先级越低 + lklOrderSeparates.sort((o1, o2) -> { + String redisKey1 = redisPrefKey + o1.getSeparate_no(); + String redisKey2 = redisPrefKey + o2.getSeparate_no(); - // 调用分账通知回调接口进行状态补偿 - JSONObject notifyResp = sacsSeparateNotify(null, record.getMerchant_no(), record.getSeparate_no()); + int retryCount1 = Convert.toInt(redisService.get(redisKey1), 0); + int retryCount2 = Convert.toInt(redisService.get(redisKey2), 0); - // 检查处理结果 - if (notifyResp != null && "SUCCESS".equals(notifyResp.getStr("code"))) { - totalSuccessCount++; - log.debug("[分账状态修复任务] 记录处理成功: merchantNo={}, separateNo={}", + return Integer.compare(retryCount1, retryCount2); + }); + + // 处理当前页中的记录 + for (LklOrderSeparate record : lklOrderSeparates) { + // 检查该记录的处理次数是否已达到上限(5次) + String redisKey = redisPrefKey + record.getSeparate_no(); + int retryCount = Convert.toInt(redisService.get(redisKey), 0); + + if (retryCount >= 5) { + log.warn("[分账状态修复任务] 记录已达到最大重试次数,跳过处理: merchantNo={}, separateNo={}", record.getMerchant_no(), record.getSeparate_no()); - } else { - String errorMsg = notifyResp != null ? notifyResp.getStr("message") : "未知错误"; - log.warn("[分账状态修复任务] 记录处理失败: merchantNo={}, separateNo={}, errorMsg={}", - record.getMerchant_no(), record.getSeparate_no(), errorMsg); + continue; + } + + totalProcessed++; + // 每处理10条记录才输出一次详细日志,减少日志量 + if (totalProcessed % 10 == 1) { + log.info("[分账状态修复任务] 正在处理第 {} 条记录: merchantNo={}, separateNo={}, 已重试{}次", + totalProcessed, record.getMerchant_no(), record.getSeparate_no(), retryCount); + } + + try { + // 增加处理次数计数并设置3天过期时间 + redisService.incr(redisKey, 1); + redisService.expire(redisKey, 3 * 24 * 60 * 60); + + // 调用拉卡拉分账通知回调接口进行状态补偿 + JSONObject notifyResp = sacsSeparateNotify(null, record.getMerchant_no(), record.getSeparate_no()); + + // 检查处理结果 + if (notifyResp != null && "SUCCESS".equals(notifyResp.getStr("code"))) { + totalSuccessCount++; + log.debug("[分账状态修复任务] 记录处理成功: merchantNo={}, separateNo={}", + record.getMerchant_no(), record.getSeparate_no()); + } else { + String errorMsg = notifyResp != null ? notifyResp.getStr("message") : "未知错误"; + log.warn("[分账状态修复任务] 记录处理失败: merchantNo={}, separateNo={}, errorMsg={}", + record.getMerchant_no(), record.getSeparate_no(), errorMsg); + } + } catch (Exception e) { + log.error("[分账状态修复任务] 处理记录时发生异常: merchantNo={}, separateNo={}", + record.getMerchant_no(), record.getSeparate_no(), e); } - } catch (Exception e) { - log.error("[分账状态修复任务] 处理记录时发生异常: merchantNo={}, separateNo={}", - record.getMerchant_no(), record.getSeparate_no(), e); } - } - log.info("[分账状态修复任务] 第{}页处理完成,已处理 {} 条记录,总成功 {} 条", - currentPage, totalProcessed, totalSuccessCount); + log.info("[分账状态修复任务] 第{}页处理完成,已处理 {} 条记录,总成功 {} 条", + currentPage, totalProcessed, totalSuccessCount); - // 如果当前页数据少于页面大小,说明已经是最后一页 - if (lklOrderSeparates.size() < pageSize) { - break; - } + // 如果当前页数据少于页面大小,说明已经是最后一页 + if (lklOrderSeparates.size() < pageSize) { + break; + } - currentPage++; + currentPage++; - // 添加短暂延迟,避免对系统造成过大压力 - try { + // 添加短暂延迟,避免对系统造成过大压力 Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } while (!CollectionUtil.isEmpty(lklOrderSeparates)); + } while (!CollectionUtil.isEmpty(lklOrderSeparates)); + + } catch (InterruptedException e) { + log.warn("[分账状态修复任务] 任务被中断"); + Thread.currentThread().interrupt(); + } catch (Exception e) { + log.error("[分账状态修复任务] 任务执行过程中发生异常", e); + } long endTime = System.currentTimeMillis(); log.info("[分账状态修复任务] 任务执行完成,总共处理 {} 条记录,成功处理 {} 条记录,耗时 {} ms", totalProcessed, totalSuccessCount, (endTime - startTime)); + return totalSuccessCount; } + /** * 检测修复补全商户的商户分账业务信息及分账接收方绑定关系(分账业务申请异步通知的补偿机制) * @@ -3499,11 +3515,12 @@ public class LakalaApiServiceImpl implements LakalaApiService { * @param mercId 822商户号或receiveNo * @param merOrderNo 商户订单号 * @param drawAmt 提现金额(分) + * @param orderId 商城订单Id * @param summary 摘要信息 * @return 操作结果,成功返回true,失败返回false */ @Override - public Boolean ewalletWithDrawD1(String mercId, String merOrderNo, String drawAmt, String summary) { + public Boolean ewalletWithDrawD1(String mercId, String merOrderNo, String drawAmt, String orderId, String summary) { // 1. 参数校验 if (StrUtil.hasBlank(mercId, merOrderNo, drawAmt)) { log.warn("[D1提现申请] D1提现参数校验失败,关键参数为空: mercId={}, merOrderNo={}, drawAmt={}", @@ -3548,7 +3565,8 @@ public class LakalaApiServiceImpl implements LakalaApiService { if (StrUtil.isNotBlank(summary)) { request.setSummary(summary); } - request.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/ewallet/ewallet/drawNotify"); + + request.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/ewallet/drawNotify"); // 4. 发送请求 String responseStr = LKLSDK.httpPost(request); @@ -3574,6 +3592,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { LklOrderDraw lklOrderDraw = new LklOrderDraw(); lklOrderDraw.setMerc_id(mercId); lklOrderDraw.setMer_order_no(merOrderNo); + lklOrderDraw.setOrder_id(orderId); lklOrderDraw.setDraw_jnl(Convert.toStr(drawJnl)); lklOrderDraw.setDraw_amt(drawAmtYuan); lklOrderDraw.setBatch_auto_settle(payType); @@ -3618,13 +3637,13 @@ public class LakalaApiServiceImpl implements LakalaApiService { */ @Override public JSONObject ewalletWithDrawNotify(HttpServletRequest request) { - log.debug("[拉卡拉提现结果通知] 开始处理拉卡拉提现结果通知"); + log.debug("[拉卡拉D1提现结果通知] 开始处理拉卡拉提现结果通知"); try { // 1. 验签处理 - 验证通知来源的合法性 Pair signCheckResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath, false); if (!signCheckResult.getFirst()) { - log.warn("[LklOrderDraw] 验签失败: {}", signCheckResult.getSecond()); + log.warn("[拉卡拉D1提现结果通知] 验签失败: {}", signCheckResult.getSecond()); return JSONUtil.createObj() .set("code", "FAIL") .set("message", signCheckResult.getSecond()); @@ -3633,7 +3652,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 2. 解析回调参数 JSONObject paramsJSON = JSONUtil.parseObj(signCheckResult.getSecond()); if (paramsJSON == null) { - log.warn("[拉卡拉提现结果通知] 回调参数解析失败"); + log.warn("[拉卡拉D1提现结果通知] 回调参数解析失败"); return JSONUtil.createObj() .set("code", "FAIL") .set("message", "回调参数解析失败"); @@ -3643,9 +3662,9 @@ public class LakalaApiServiceImpl implements LakalaApiService { String mercId = paramsJSON.getStr("mercId"); String merOrderNo = paramsJSON.getStr("merOrderNo"); - log.info("[拉卡拉提现结果通知] 提现通知参数: drawState={}, mercId={}, merOrderNo={}", drawState, mercId, merOrderNo); + log.info("[拉卡拉D1提现结果通知] 提现通知参数: drawState={}, mercId={}, merOrderNo={}", drawState, mercId, merOrderNo); - if (StrUtil.isBlank(mercId) || StrUtil.isBlank(merOrderNo) || StrUtil.isBlank(drawState)) { + if (StrUtil.hasBlank(mercId, merOrderNo, drawState)) { log.warn("[拉卡拉提现结果通知] 回调参数缺失: drawState={}, mercId={}, merOrderNo={}", drawState, mercId, merOrderNo); return JSONUtil.createObj() .set("code", "FAIL") @@ -3654,16 +3673,16 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 只处理成功的提现状态 if (!"DRAW.SUCCESS".equals(drawState)) { - log.debug("[拉卡拉提现结果通知] 提现状态未成功,忽略处理: drawState={}", drawState); + log.debug("[拉卡拉D1提现结果通知] 提现状态未成功,忽略处理: drawState={}", drawState); return JSONUtil.createObj() - .set("code", "SUCCESS") // 返回成功,避免重复通知 + .set("code", "FAIL") // 返回成功,避免重复通知 .set("message", "状态未成功,忽略处理"); } // 3. 转换参数并更新数据 String snakeCaseJson = StringUtils.convertCamelToSnake(signCheckResult.getSecond()); if (StringUtils.isBlank(snakeCaseJson)) { - log.error("[拉卡拉提现结果通知] 回调参数转换失败,mercId={} merOrderNo={}", mercId, merOrderNo); + log.error("[拉卡拉D1提现结果通知] 回调参数转换失败,mercId={} merOrderNo={}", mercId, merOrderNo); return JSONUtil.createObj() .set("code", "FAIL") .set("message", "回调参数转换失败"); @@ -3671,7 +3690,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { LklOrderDraw lklOrderDraw = JSONUtil.toBean(snakeCaseJson, LklOrderDraw.class); if (lklOrderDraw == null) { - log.error("[拉卡拉提现结果通知] 回调参数转换为对象失败,mercId={} merOrderNo={}", mercId, merOrderNo); + log.error("[拉卡拉D1提现结果通知] 回调参数转换为对象失败,mercId={} merOrderNo={}", mercId, merOrderNo); return JSONUtil.createObj() .set("code", "FAIL") .set("message", "回调参数转换失败"); @@ -3679,13 +3698,13 @@ public class LakalaApiServiceImpl implements LakalaApiService { boolean isSuccess = lklOrderDrawService.addOrUpdateByMercIdAndMerOrderNo(lklOrderDraw); if (!isSuccess) { - log.error("[拉卡拉提现结果通知] 数据更新失败,mercId={} merOrderNo={}", mercId, merOrderNo); + log.error("[拉卡拉D1提现结果通知] 数据更新失败,mercId={} merOrderNo={}", mercId, merOrderNo); return JSONUtil.createObj() .set("code", "FAIL") .set("message", "数据更新失败"); } - log.info("[拉卡拉提现结果通知] 拉卡拉提现结果通知处理成功,mercId={} merOrderNo={}", mercId, merOrderNo); + log.info("[拉卡拉D1提现结果通知] 拉卡拉提现结果通知处理成功,mercId={} merOrderNo={}", mercId, merOrderNo); // 4. 返回成功响应 return JSONUtil.createObj() @@ -3693,7 +3712,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { .set("message", "处理成功"); } catch (Exception e) { - log.error("[拉卡拉提现结果通知] 处理拉卡拉提现结果通知异常", e); + log.error("[拉卡拉D1提现结果通知] 处理拉卡拉提现结果通知异常", e); return JSONUtil.createObj() .set("code", "FAIL") .set("message", "系统处理异常"); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java index 22e7043c..d676e913 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java @@ -38,7 +38,7 @@ public class LklOrderDrawServiceImpl extends BaseServiceImpl 0) { // 更新记录 log.info("[LklOrderDraw] 记录已存在,执行更新操作,ID={}", existsRecord.getId()); @@ -102,4 +102,39 @@ public class LklOrderDrawServiceImpl extends BaseServiceImpl() + .eq("order_id", orderId) + .eq("draw_state", "DRAW.SUCCESS") + .orderByDesc("id")); + + // 根据查询结果判断是否已提现 + if (lklOrderDraw != null) { + log.debug("订单[{}]提现已完成", orderId); + return true; + } else { + log.debug("订单[{}]提现未完成或不存在提现记录", orderId); + return false; + } + } catch (Exception e) { + log.error("查询订单[{}]提现状态异常", orderId, e); + return false; + } + } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/admin/ShopOrderBaseController.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/admin/ShopOrderBaseController.java index fd82bf33..6a7145d8 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/admin/ShopOrderBaseController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/admin/ShopOrderBaseController.java @@ -103,7 +103,7 @@ public class ShopOrderBaseController extends BaseControllerImpl { public CommonResult cancel(@RequestParam(name = "order_id") String order_id) { return shopOrderBaseService.cancel(order_id); } - + @ApiOperation(value = "确认收货", notes = "确认收货") @RequestMapping(value = "/receive", method = {RequestMethod.GET, RequestMethod.POST}) public CommonResult receive(@RequestParam(name = "order_id") String order_id) { @@ -326,9 +326,12 @@ public class ShopOrderBaseController extends BaseControllerImpl { return CommonResult.failed(ResultCode.FORBIDDEN); } - // 同城订单超时秒数 + // 初定同城订单超时的秒数 long mchOrderExpireSeconds = shopOrderBaseService.sameCityOrderExpireSeconds(2100L); // 35分钟超时,60秒*35分钟 = 2100秒 + //同城配送订单状态(delivery=1时才生效):无值 or 0-全部订单;1-进行中订单;2-异常(超时)订单;3-退款订单;9-已完成订单 + Integer status = params.getInt("status"); + // === 构建响应数据 === Map respMap = new HashMap<>(); // 订单列表数据(缓存时间25分钟) @@ -336,7 +339,7 @@ public class ShopOrderBaseController extends BaseControllerImpl { storeId, params.getStr("keyword"), params.getInt("delivery"), - params.getInt("status"), + status, params.getInt("logistics_status"), mchOrderExpireSeconds, params.getLong("begin_time"), @@ -388,6 +391,7 @@ public class ShopOrderBaseController extends BaseControllerImpl { return CommonResult.failed(ResultCode.FORBIDDEN); } + // 5. 返回结果 return CommonResult.success(mchOrderInfoDTO); } catch (Exception e) { diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserOrderController.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserOrderController.java index 18a2c529..d08db1b1 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserOrderController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserOrderController.java @@ -113,7 +113,7 @@ public class UserOrderController extends BaseControllerImpl { return CommonResult.success(shopOrderBaseService.detail(order_id)); } - @ApiOperation(value = "添加订单详细信息", notes = "添加订单详细信息") + @ApiOperation(value = "添加订单详细信息", notes = "添加订单详细信息(提交订单)") @ApiImplicitParams({ @ApiImplicitParam(name = "ud_id", value = "收货地址编号", paramType = "query", required = true, dataType = "int"), @ApiImplicitParam(name = "is_edu", value = "是教育", paramType = "query", required = false, dataType = "int"), diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserReturnController.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserReturnController.java index 95db5e8d..5fa657ae 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserReturnController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/controller/mobile/UserReturnController.java @@ -85,7 +85,8 @@ public class UserReturnController extends BaseControllerImpl { return CommonResult.failed(ResultCode.NEED_LOGIN); } - return shopOrderReturnService.addWholeItems(order_id, false, ""); + // return shopOrderReturnService.addWholeItems(order_id, false, ""); + return shopOrderReturnService.addRemainingItems(order_id, true, ""); } @ApiOperation(value = "取消退款订单", notes = "取消退款订单") diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/listener/OrderPayedListener.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/listener/OrderPayedListener.java index a506e727..6cbbe8dc 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/listener/OrderPayedListener.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/listener/OrderPayedListener.java @@ -9,6 +9,7 @@ import com.suisung.mall.common.constant.CommonConstant; import com.suisung.mall.common.constant.MqConstant; import com.suisung.mall.common.constant.RedisConstant; import com.suisung.mall.common.modules.order.ShopOrderInfo; +import com.suisung.mall.common.utils.CheckUtil; import com.suisung.mall.common.utils.DateTimeUtils; import com.suisung.mall.core.web.service.RedisService; import com.suisung.mall.shop.message.service.PushMessageService; @@ -138,10 +139,10 @@ public class OrderPayedListener { } } - logger.info("[订单支付监听] 支付异步通知回调处理结果: {}, 订单ID: {}", flag, orderId); + logger.info("[订单支付监听] 订单ID: {},支付异步通知回调处理是否成功: {} ", flag, orderId); + // 生成取单号和打印小票 if (flag) { - // TODO 以下仅处理下单打印的情况,还需要处理退单打印分支。 // 原始状态 2010-待付款;2011--待订单审核;2013-待财务审核 变成 2020-待配货;2016-已经付款 的时候 // 生成取单号,打票机打印订单,向顺丰同城下单 @@ -152,12 +153,14 @@ public class OrderPayedListener { Long orderPickupNum = shopOrderInfoService.isPaidOrderGenPickNumAndPrint(orderInfoOld.getStore_id(), orderId); // 如果配送方式是 顺丰同城下单 - if (orderInfoOld.getDelivery_type_id() != null && orderInfoOld.getDelivery_type_id().equals(StateCode.DELIVERY_TYPE_SAME_CITY) - && orderPickupNum > 0) { - // 发送顺丰同城快递 + if (CheckUtil.isNotEmpty(orderInfoOld.getDelivery_type_id()) + && orderInfoOld.getDelivery_type_id().equals(StateCode.DELIVERY_TYPE_SAME_CITY) + && CheckUtil.isNotEmpty(orderPickupNum)) { + + // 顺丰同城下单 Pair pairCreateSfOrder = sfExpressApiService.innerCreateSfExpressOrder(orderId, orderPickupNum); if (pairCreateSfOrder == null) { - logger.error("[订单支付监听] 顺丰同城下单失败!pairCreateSfOrder 返回空值. 订单ID: {}", orderId); + logger.error("[订单支付监听] 顺丰同城下单失败,无返回值 订单ID: {}", orderId); continue; } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderInfoService.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderInfoService.java index ca98676e..8f79f149 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderInfoService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderInfoService.java @@ -3,6 +3,7 @@ package com.suisung.mall.shop.order.service; import com.suisung.mall.common.api.CommonResult; import com.suisung.mall.common.modules.order.ShopOrderInfo; import com.suisung.mall.core.web.service.IBaseService; +import org.springframework.data.util.Pair; import java.util.List; import java.util.Map; @@ -91,6 +92,7 @@ public interface ShopOrderInfoService extends IBaseService { /** * 分页查询取消订单 + * * @param pageNum * @param pageSize * @return @@ -99,7 +101,19 @@ public interface ShopOrderInfoService extends IBaseService { /** * 查询取消订单总数 + * * @return */ long countgetAutoCancelOrderId(); + + /** + * 检查订单预约参数是否合法 + * + * @param storeId 店铺ID + * @param bookingState 预约配送状态 + * @param bookingBeginTime 预约开始时间 + * @param bookingEndTime 预约截止时间 + * @return + */ + Pair checkBookingOrderArgs(Integer storeId, Integer bookingState, String bookingBeginTime, String bookingEndTime); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderReturnService.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderReturnService.java index 4ae76f0f..a64e8d7c 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderReturnService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderReturnService.java @@ -159,6 +159,28 @@ public interface ShopOrderReturnService extends IBaseService { */ boolean ifDenyReturn(Long order_item_id); + /** + * 判断订单中某个商品是否禁止退货 + *

+ * 该方法用于检查指定订单中的特定商品是否允许退货,主要检查以下几种情况: + * 1. 商品是否设置了"禁止退货"的消费者保障标识 + * 2. 订单是否已超过退货期限(已收货或已完成状态,且超过可提现时间) + * 3. 拉卡拉分账订单是否已完成提现 + * + * @param orderId 订单ID + * @param productId 商品ID + * @return boolean true表示禁止退货,false表示允许退货 + */ + boolean ifOrderItemDenyReturn(String orderId, Long productId); + + /** + * 判断订单是否禁止退货 + * + * @param orderId 订单ID + * @return 如果订单禁止退货返回true,否则返回false + */ + boolean isOrderDenyReturn(String orderId); + /** * 该方法用于处理整单退货申请,具体功能如下: * 根据订单ID获取订单信息,若为空则抛异常。 @@ -201,4 +223,6 @@ public interface ShopOrderReturnService extends IBaseService { * @return */ CommonResult doRefundForMch(JSONObject params); + + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java index ea5359dd..545141eb 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java @@ -99,6 +99,7 @@ import com.suisung.mall.shop.sfexpress.service.SFExpressApiService; import com.suisung.mall.shop.store.service.*; import com.suisung.mall.shop.sync.service.SyncThirdDataService; import com.suisung.mall.shop.user.service.*; +import com.suisung.mall.shop.wechat.service.WxOrderShippingService; import io.seata.common.util.StringUtils; import io.seata.core.context.RootContext; import io.seata.core.exception.TransactionException; @@ -376,6 +377,10 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl userOrderQueryWrapper = new QueryWrapper<>(); userOrderQueryWrapper.eq("user_id", user_id).in("order_id", order_ids); ShopDistributionUserOrder distributionUserOrder = shopDistributionUserOrderService.findOne(userOrderQueryWrapper); @@ -756,11 +762,14 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl pair = shopOrderInfoService.checkBookingOrderArgs(checkedStore, bookingState, bookingBeginTime, bookingEndTime); + if (!pair.getFirst()) { + throw new ApiException(I18nUtil._(pair.getSecond())); + } + + cartData.put("booking_state", bookingState); + cartData.put("booking_begin_time", bookingBeginTime); + cartData.put("booking_end_time", bookingEndTime); + } + // 添加保存订单(关键方法) List orderIdRow = addOrder(cartData, true, false, null); @@ -5037,6 +5061,9 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl= 0) { @@ -8907,17 +8947,16 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl= StateCode.ORDER_STATE_SHIPPED - && orderStateId <= StateCode.ORDER_STATE_FINISH + } else if (CommonService.isSFExpress(deliveryTypeId) + && orderStateId.intValue() >= StateCode.ORDER_STATE_SHIPPED + && orderStateId.intValue() <= StateCode.ORDER_STATE_FINISH && order.getSf_order_info() != null - && order.getSf_order_info().getFeed() == null) { + && StrUtil.isBlank(order.getSf_order_info().getFeed())) { // 获取顺丰同城的物流轨迹 Map params = new HashMap<>(); params.put("order_id", order.getSf_order_info().getSf_order_id()); try { + ThirdApiRes feedRes = sfExpressApiService.listOrderFeed(params); logger.info("获取配送员物流轨迹:{}", feedRes); if (feedRes != null && feedRes.getError_code() != null && feedRes.getError_code().equals(0)) { @@ -8926,6 +8965,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl { + item.setIs_deny_return(isOrderDenyReturn); + }); + }); return pageList; @@ -9031,10 +9081,18 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl { + item.setIs_deny_return(isOrderDenyReturn); + }); + return orderDetail; } - /** * 商家订单各个分类和状态的订单数量 * diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderInfoServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderInfoServiceImpl.java index 3eb0f96a..81512aa7 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderInfoServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderInfoServiceImpl.java @@ -25,9 +25,11 @@ import com.suisung.mall.common.modules.order.ShopOrderStateLog; import com.suisung.mall.common.modules.pay.PayPlantformResource; import com.suisung.mall.common.modules.plantform.ShopPlantformFeedback; import com.suisung.mall.common.modules.product.ShopProductComment; +import com.suisung.mall.common.modules.store.ShopStoreInfo; import com.suisung.mall.common.pojo.res.ThirdApiRes; import com.suisung.mall.common.utils.CheckUtil; import com.suisung.mall.common.utils.CommonUtil; +import com.suisung.mall.common.utils.DateTimeUtils; import com.suisung.mall.core.web.service.impl.BaseServiceImpl; import com.suisung.mall.shop.base.service.AccountBaseConfigService; import com.suisung.mall.shop.base.service.ShopBaseStateCodeService; @@ -41,12 +43,14 @@ import com.suisung.mall.shop.product.service.ShopProductBaseService; import com.suisung.mall.shop.product.service.ShopProductCommentService; import com.suisung.mall.shop.sfexpress.service.SFExpressApiService; import com.suisung.mall.shop.store.service.ShopStoreBaseService; +import com.suisung.mall.shop.store.service.ShopStoreInfoService; import com.suisung.mall.shop.store.service.ShopStorePrinterService; import com.suisung.mall.shop.store.service.ShopStoreSfOrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.request.RequestContextHolder; @@ -84,6 +88,8 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl 3) { - return CommonResult.success("已提交,请勿重复提交"); + return CommonResult.success("已通知配送人员,请稍候"); } // 更新订单拣货时间 @@ -502,7 +506,7 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl getAutoCancelOrderIdByPage(Integer pageNum, Integer pageSize) { QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.select("order_id","order_state_id"); + queryWrapper.select("order_id", "order_state_id"); queryWrapper.eq("order_state_id", StateCode.ORDER_STATE_WAIT_PAY) .eq("order_is_paid", StateCode.ORDER_PAID_STATE_NO) .eq("payment_type_id", StateCode.PAYMENT_TYPE_ONLINE); @@ -798,7 +802,7 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl shopOrderInfos=this.lists(queryWrapper,pageNum,pageSize).getRecords(); + List shopOrderInfos = this.lists(queryWrapper, pageNum, pageSize).getRecords(); return shopOrderInfos.stream().map(ShopOrderInfo::getOrder_id).collect(Collectors.toList()); } @@ -816,5 +820,56 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl checkBookingOrderArgs(Integer storeId, Integer bookingState, String bookingBeginTimeStr, String bookingEndTimeStr) { + if (CheckUtil.isEmpty(storeId) || CheckUtil.isEmpty(bookingState)) { + return Pair.of(false, "[预约订单校验] 缺少必要参数"); + } + + if (!CommonConstant.Order_Booking_State_YY.equals(bookingState)) { + return Pair.of(true, "[预约订单校验] 非预订单,跳过验证"); + } + + if (StrUtil.hasBlank(bookingBeginTimeStr, bookingEndTimeStr)) { + return Pair.of(false, "[预约订单校验] 预约时间不能为空"); + } + + Date bookingBeginTime = DateTimeUtils.tryParseDateTimeToDate(bookingBeginTimeStr); + Date bookingEndTime = DateTimeUtils.tryParseDateTimeToDate(bookingEndTimeStr); + if (bookingBeginTime == null || bookingBeginTime == null) { + return Pair.of(false, "[预约订单校验] 预约时间格式有误"); + } + + // 验证开始时间是否早于结束时间 + if (bookingBeginTime.after(bookingEndTime)) { + return Pair.of(false, "[预约订单校验] 开始时间不能晚于截止时间"); + } + + // 判断预约订单开始时间是否在店铺的营业时间段里? + ShopStoreInfo shopStoreInfo = shopStoreInfoService.getShopStoreInfoByStoreId(storeId); + if (shopStoreInfo == null) { + return Pair.of(false, "[预约订单校验] 店铺信息有误"); + } + + if (StrUtil.isBlank(shopStoreInfo.getStore_opening_hours()) || StrUtil.isBlank(shopStoreInfo.getStore_close_hours())) { + return Pair.of(false, "[预约订单校验] 店铺营业时间未设置"); + } + + if (!DateTimeUtils.isTimeInRange(shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours(), bookingBeginTime)) { + return Pair.of(false, "[预约订单校验] 预约时间不在店铺营业时间内"); + } + + return Pair.of(true, "[预约订单校验] 成功"); + } + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderLklServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderLklServiceImpl.java index 14ebbf37..4b97d081 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderLklServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderLklServiceImpl.java @@ -519,11 +519,11 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("lkl_receive_log_no", lklReceiveLogNo); updateWrapper.set("separate_status", separateStatus); - if (StrUtil.isNotBlank(separateRemark)) { - updateWrapper.set("separate_remark", separateRemark); + if (StrUtil.isNotBlank(separateMsg)) { + updateWrapper.set("separate_msg", separateMsg); } boolean result = update(updateWrapper); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderReturnServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderReturnServiceImpl.java index cf2dbcf3..514ce668 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderReturnServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderReturnServiceImpl.java @@ -43,6 +43,7 @@ import com.suisung.mall.shop.base.service.ShopBaseStateCodeService; import com.suisung.mall.shop.distribution.service.ShopDistributionUserCommissionService; import com.suisung.mall.shop.distribution.service.ShopDistributionUserOrderItemService; import com.suisung.mall.shop.distribution.service.ShopDistributionUserOrderService; +import com.suisung.mall.shop.lakala.service.LklOrderDrawService; import com.suisung.mall.shop.lakala.service.LklOrderSeparateService; import com.suisung.mall.shop.message.service.PushMessageService; import com.suisung.mall.shop.number.service.ShopNumberSeqService; @@ -196,7 +197,7 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl stockDeltaMap = new HashMap<>(); - String item_src_id= productItem.getItem_src_id(); + String item_src_id = productItem.getItem_src_id(); stockDeltaMap.put(item_src_id + "-" + shopOrderItem.getOrder_id(), returnNum); syncThirdDataService.incrProductStockToRedis(stockDeltaMap); - logger.info("退货返回给思迅,存入redis成功,item_src_id:{},订单号:{},数量:{}",item_src_id,shopOrderReturn.getOrder_id(),returnNum); + logger.info("退货返回给思迅,存入redis成功,item_src_id:{},订单号:{},数量:{}", item_src_id, shopOrderReturn.getOrder_id(), returnNum); } else { logger.warn("退货数量为空,无法增加库存,订单项ID: {}", orderItemId); } @@ -1602,12 +1606,8 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl forbiddenStates = Arrays.asList( - StateCode.ORDER_STATE_SHIPPED, - StateCode.ORDER_STATE_RECEIVED, - StateCode.ORDER_STATE_FINISH); - if (order_state_id != null && !forbiddenStates.contains(order_state_id)) { + // 订单不是禁止退货(能退货)的情况下 + if (order_state_id != null && !isOrderDenyReturn(order_id)) { logger.info("处理运费和打包费事宜:{}", order_id); ShopOrderData order_data_row = shopOrderDataService.get(order_id); @@ -2098,56 +2098,203 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl contractTypeIds = Convert.toList(Integer.class, shopProductIndex.getContract_type_ids()); - - //start判断是否可以申请退款 - // 1、商品是否允许 - if (contractTypeIds.contains(new Integer(StateCode.CONTRACT_TYPE_DENY_RETURN))) { - is_denyreturn = true; - throw new ApiException(I18nUtil._("此商品不允许退货!")); + if (orderStateId.intValue() == StateCode.ORDER_STATE_CANCEL || + orderStateId.intValue() == StateCode.ORDER_STATE_WAIT_PAY) { + log.debug("[是否禁止退货] 订单已取消或未支付,true order_id: {}", orderId); + return true; } - // 2、是否已经可结算, 进入可结算,不允许退货。 - if (shopOrderInfo.getOrder_state_id().intValue() == StateCode.ORDER_STATE_RECEIVED || shopOrderInfo.getOrder_state_id().intValue() == StateCode.ORDER_STATE_FINISH) { - Long withdrawTime = shopOrderBaseService.getWithdrawTime(); + // 2. 先判断整个订单是否可以退货(订单级别检查) + // 2.1 检查订单状态是否为已收货或已完成 + if (orderStateId.intValue() == StateCode.ORDER_STATE_RECEIVED || + orderStateId.intValue() == StateCode.ORDER_STATE_FINISH) { + try { + // 获取可提现时间戳 + Long withdrawTime = shopOrderBaseService.getWithdrawTime(); + Long orderDealTime = shopOrderInfo.getOrder_deal_time(); - //order_qs_time - if (withdrawTime.compareTo(shopOrderInfo.getOrder_deal_time()) > 0) { - is_denyreturn = true; - throw new ApiException(I18nUtil._("此商品已过退货期,不允许退货!")); + // 检查是否超过退货期限 + if (orderDealTime != null && withdrawTime.compareTo(orderDealTime) > 0) { + log.debug("[是否禁止退货] 订单已超过退货期限 true,order_id: {}", orderId); + return true; + } + } catch (Exception e) { + log.error("[是否禁止退货] 检查订单退货期限时发生异常 true,order_id: {}", orderId, e); } } - return is_denyreturn; + // 2.2 检查拉卡拉分账订单是否已提现 + try { + if (lklOrderSeparateService != null && lklOrderDrawService != null) { + boolean isSeparated = lklOrderSeparateService.isOrderSeparated(orderId); + boolean isDrawn = lklOrderDrawService.isOrderDrawed(orderId); + + if (isSeparated && isDrawn) { + log.debug("[是否禁止退货] 拉卡拉分账订单已提现,不允许退货 true,order_id: {}", orderId); + return true; + } + } + } catch (Exception e) { + log.error("[是否禁止退货] 检查拉卡拉分账状态时发生异常,order_id: {}", orderId, e); + } + + Long productId = shopOrderItem.getProduct_id(); + if (CheckUtil.isEmpty(productId)) { + log.debug("[是否禁止退货] 商品ID为空 true"); + return true; + } + + // 3. 再判断具体商品是否可以退货(商品级别检查) + // 获取商品索引信息 + ShopProductIndex productIndex = shopProductIndex; + if (productIndex == null) { + productIndex = shopProductIndexService.get(productId); + } + + if (productIndex == null) { + log.debug("[是否禁止退货] 商品索引信息不存在 true,product_id: {}", productId); + return true; + } + + // 3.1 检查商品是否设置了禁止退货标识 + String contractTypeIdsStr = productIndex.getContract_type_ids(); + if (StrUtil.isNotBlank(contractTypeIdsStr)) { + try { + List contractTypeIds = Convert.toList(Integer.class, contractTypeIdsStr); + if (contractTypeIds != null && contractTypeIds.contains(StateCode.CONTRACT_TYPE_DENY_RETURN)) { + log.debug("[是否禁止退货] 商品设置了禁止退货标识 true,order_id: {}, product_id: {}", orderId, productId); + return true; + } + } catch (Exception e) { + log.error("[是否禁止退货] 解析商品保障类型失败 true,order_id: {}, product_id: {}", orderId, productId, e); + } + } + + // 默认允许退货 + log.debug("[是否禁止退货] false}"); + return false; } + + /** + * 判断订单中某个商品是否禁止退货 + *

+ * 该方法用于检查指定订单中的特定商品是否允许退货,主要检查以下几种情况: + * 1. 商品是否设置了"禁止退货"的消费者保障标识 + * 2. 订单是否已超过退货期限(已收货或已完成状态,且超过可提现时间) + * 3. 拉卡拉分账订单是否已完成提现 + * + * @param orderId 订单ID + * @param productId 商品ID + * @return boolean true表示禁止退货,false表示允许退货 + */ + public boolean ifOrderItemDenyReturn(String orderId, Long productId) { + // 复用已有的方法逻辑,避免重复代码 + return ifDenyReturn( + shopOrderInfoService.get(orderId), + null, // 不需要具体的订单商品项信息 + shopProductIndexService.get(productId) + ); + } + + + /** + * 判断订单是否禁止退货 + * + * @param orderId 订单ID + * @return 如果订单禁止退货返回true,否则返回false + */ + @Override + public boolean isOrderDenyReturn(String orderId) { + // 参数校验 + if (StrUtil.isBlank(orderId)) { + log.warn("[订单是否禁止退货] 订单ID为空,无法判断退货状态"); + return true; + } + + try { + // 获取订单信息 + ShopOrderInfo shopOrderInfo = shopOrderInfoService.get(orderId); + if (shopOrderInfo == null) { + log.error("[订单是否禁止退货] 订单信息不存在,订单ID: {}", orderId); + return true; + } + + // 检查订单状态是否已收货或已完成 + Integer orderStateId = shopOrderInfo.getOrder_state_id(); + if (CheckUtil.isEmpty(orderStateId)) { + log.warn("[订单是否禁止退货] 订单状态为空"); + return true; + } + + if (orderStateId.intValue() == StateCode.ORDER_STATE_CANCEL || + orderStateId.intValue() == StateCode.ORDER_STATE_WAIT_PAY) { + log.debug("[是否禁止退货] 订单已取消或未支付,order_id: {}", orderId); + return true; + } + + if (orderStateId.intValue() == StateCode.ORDER_STATE_RECEIVED || + orderStateId.intValue() == StateCode.ORDER_STATE_FINISH) { + + // 获取可提现时间戳 + Long withdrawTime = shopOrderBaseService.getWithdrawTime(); + Long orderDealTime = shopOrderInfo.getOrder_deal_time(); + log.debug("[订单是否禁止退货] 订单:{} 可提现时间戳: {},确认收货时间:{}", orderId, withdrawTime, orderDealTime); + + // 如果订单成交时间早于可提现时间,说明已过退货期 + if (orderDealTime != null && withdrawTime.compareTo(orderDealTime) > 0) { + log.debug("[订单是否禁止退货] 订单:{} 已过退货期,不允许退货", orderId); + return true; + } + } + + // 检查拉卡拉分账和提现状态 + if (lklOrderSeparateService.isOrderSeparated(orderId) && + lklOrderDrawService.isOrderDrawed(orderId)) { + log.debug("[订单是否禁止退货] 订单[{}]已提现,不允许退货", orderId); + return true; + } + + // 默认允许退货 + return false; + + } catch (Exception e) { + log.error("[订单是否禁止退货] 查询订单{}退货状态异常", orderId, e); + return true; + } + } + + /** * 该函数用于判断指定订单商品是否禁止退货。 * 根据 order_item_id 获取订单商品信息; @@ -2210,7 +2357,7 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl orderItems = shopOrderItemService.find(new QueryWrapper().eq("order_id", orderId)); - if (CollectionUtil.isEmpty(orderItems)) return CommonResult.failed("无可退货商品"); + if (CollectionUtil.isEmpty(orderItems)) { + return CommonResult.failed("无可退货商品"); + } // === 3. 检查退货单 === ShopOrderReturn refundOrder = findOne(new QueryWrapper() @@ -2723,9 +2872,10 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl 0) { return "订单已超过退货期限,无法退货"; } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java index 2ccb8e36..072b6e86 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java @@ -26,10 +26,8 @@ import com.suisung.mall.common.modules.store.ShopStoreSameCityTransportBase; import com.suisung.mall.common.modules.store.ShopStoreSfOrder; import com.suisung.mall.common.pojo.req.*; import com.suisung.mall.common.pojo.res.ThirdApiRes; -import com.suisung.mall.common.utils.CheckUtil; -import com.suisung.mall.common.utils.CommonUtil; -import com.suisung.mall.common.utils.I18nUtil; -import com.suisung.mall.common.utils.JsonUtil; +import com.suisung.mall.common.pojo.to.AddressParseResultTO; +import com.suisung.mall.common.utils.*; import com.suisung.mall.shop.message.service.PushMessageService; import com.suisung.mall.shop.order.service.ShopOrderBaseService; import com.suisung.mall.shop.order.service.ShopOrderInfoService; @@ -172,15 +170,23 @@ public class SFExpressApiServiceImpl implements SFExpressApiService { return Pair.of(false, "联系人手机号不能为空"); } + + AddressParseResultTO addressParseResultTO = AddressUtil.parseAddress(shopMchEntry.getStore_address()); // 解析城市名称 String cityName = "桂平市"; // 默认城市 + + // 去掉省市区的详细地址 + String storeAddress = addressParseResultTO.getDetailAddress(); + if (StrUtil.isNotBlank(shopMchEntry.getStore_area())) { String[] areaNames = shopMchEntry.getStore_area().split("/"); - if (areaNames.length > 0) { + if (areaNames.length >= 3) { cityName = areaNames[areaNames.length - 1]; } else { cityName = shopMchEntry.getStore_area().replace("/", ""); } + } else { + cityName = addressParseResultTO.getCity(); } // 如果解析后城市名为空,使用默认值 @@ -199,7 +205,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService { Convert.toInt(shopMchEntry.getStore_id()), shopStoreName, cityName, - shopMchEntry.getStore_address(), + storeAddress, shopMchEntry.getContact_name(), contactMobile, shopMchEntry.getStore_longitude(), @@ -1040,7 +1046,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService { pushRemark = "已完成配送"; orderStatus = StateCode.ORDER_STATE_RECEIVED; //已签收 - // 通知微信用户确认收货(同城配送不能调用微信的确认收货) + // 顺丰送达后,发出催促微信用户确认收货通知 (同城配送不能调用微信的确认收货) wxOrderShippingService.notifyConfirmReceive(shopStoreSfOrder.getShop_order_id()); // 不要提前 订单确认收货,等微信拉卡拉确认通知 @@ -1136,13 +1142,14 @@ public class SFExpressApiServiceImpl implements SFExpressApiService { throw new ApiException(I18nUtil._("状态处理失败!")); } - // 通知微信用户确认收货(同城配送不能调用该微信接口) - // wxOrderShippingService.notifyConfirmReceive(shopStoreSfOrder.getShop_order_id()); + String orderId = shopStoreSfOrder.getShop_order_id(); + + // 顺丰送达后,发出催促微信用户确认收货通知 (同城配送不能调用微信的确认收货) + wxOrderShippingService.notifyConfirmReceive(orderId); // 订单确认收货 // shopOrderBaseService.receive(shopStoreSfOrder.getShop_order_id(), null); - String orderId = shopStoreSfOrder.getShop_order_id(); // 消息推送 // JSONObject payload = new JSONObject(); @@ -1150,7 +1157,6 @@ public class SFExpressApiServiceImpl implements SFExpressApiService { // payload.put("orderId", orderId); // pushMessageService.noticeMerchantEmployeeOrderAction(null, orderId, "", "顺丰同城订单[" + orderId + "]已完成配送。", null); - return new ThirdApiRes().success("success"); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreInfoService.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreInfoService.java index ba5a1a99..fb64de28 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreInfoService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreInfoService.java @@ -20,6 +20,14 @@ public interface ShopStoreInfoService extends IBaseService { Map getStoreInfoById(Integer store_id); + /** + * 根据店铺id获取店铺信息 + * + * @param storeId + * @return + */ + ShopStoreInfo getShopStoreInfoByStoreId(Integer storeId); + Map getInfoList(QueryWrapper queryWrapper, Integer pageNum, Integer pageSize); /** diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreBaseServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreBaseServiceImpl.java index c3d207de..5ca7fa9d 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreBaseServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreBaseServiceImpl.java @@ -80,7 +80,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Serializable; @@ -176,9 +175,6 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl shopStoreInfoUpdateWrapper=new UpdateWrapper<>(); - shopStoreInfoUpdateWrapper.eq("store_id",storeId); - shopStoreInfoUpdateWrapper.set("store_opening_hours",store_opening_hours); - shopStoreInfoUpdateWrapper.set("store_close_hours",store_close_hours); + if (StringUtils.isNotEmpty(store_close_hours)) { + UpdateWrapper shopStoreInfoUpdateWrapper = new UpdateWrapper<>(); + shopStoreInfoUpdateWrapper.eq("store_id", storeId); + shopStoreInfoUpdateWrapper.set("store_opening_hours", store_opening_hours); + shopStoreInfoUpdateWrapper.set("store_close_hours", store_close_hours); shopStoreInfoService.update(shopStoreInfoUpdateWrapper); } // 使用 UpdateWrapper 更新店铺营业状态 diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreInfoServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreInfoServiceImpl.java index bcef4bea..7cc18e27 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreInfoServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreInfoServiceImpl.java @@ -53,6 +53,13 @@ public class ShopStoreInfoServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("store_id", storeId); + return findOne(queryWrapper); + } + @Override public Map getInfoList(QueryWrapper queryWrapper, Integer pageNum, Integer pageSize) { Page lists = lists(queryWrapper, pageNum, pageSize); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/wechat/service/impl/WxOrderShippingServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/wechat/service/impl/WxOrderShippingServiceImpl.java index 36c23633..19ed7cdf 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/wechat/service/impl/WxOrderShippingServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/wechat/service/impl/WxOrderShippingServiceImpl.java @@ -128,21 +128,17 @@ public class WxOrderShippingServiceImpl implements WxOrderShippingService { // Step 4: Prepare request payload JSONArray shippingList = new JSONArray().put(shippingItem); -// String mchId = orderBaseInfo.getMch_id(); - JSONObject paramsJSON = new JSONObject() .set("order_key", new JSONObject().set("order_number_type", 2).set("transaction_id", orderBaseInfo.getTransaction_id())) -// .set("order_key", new JSONObject().set("order_number_type", 1).set("out_trade_no", orderBaseInfo.getOut_trade_no()).set("mchid", mchId)) .set("delivery_mode", 1) .set("logistics_type", logisticsType) .set("shipping_list", shippingList) .set("upload_time", DateTimeUtils.formatDateTimeRFC3339(new Date())) .set("payer", new JSONObject().set("openid", orderBaseInfo.getOpen_id())); - // Step 5: Send request to WeChat API String postUrl = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token=" + accessToken; - log.debug("发货信息录入请求: {} \n {}", postUrl, paramsJSON); + log.debug("发货信息录入请求: {} \n 响应数据 {}", postUrl, paramsJSON); JSONObject respObj = RestTemplateHttpUtil.sendPost( postUrl, @@ -161,7 +157,7 @@ public class WxOrderShippingServiceImpl implements WxOrderShippingService { return Pair.of(false, "发货信息录入失败: " + errorMsg); } - // 跳转链接设置 + // 服务号发货通知跳转链接设置 setMsgJumpPath(orderId); log.info("发货信息录入成功, 订单ID: {}", orderId); @@ -235,9 +231,9 @@ public class WxOrderShippingServiceImpl implements WxOrderShippingService { return Pair.of(false, "通知微信用户确认收货失败: " + errorMsg); } - // 跳转链接设置 + /// 服务号催促确认收货通知跳转链接设置 setMsgJumpPath(orderId); - + log.info("通知微信用户确认收货成功, 订单ID: {}", orderId); return Pair.of(true, "通知微信用户确认收货成功"); diff --git a/mall-shop/src/main/resources/mapper/order/ShopOrderBaseMapper.xml b/mall-shop/src/main/resources/mapper/order/ShopOrderBaseMapper.xml index a4b55af8..0a7ab502 100644 --- a/mall-shop/src/main/resources/mapper/order/ShopOrderBaseMapper.xml +++ b/mall-shop/src/main/resources/mapper/order/ShopOrderBaseMapper.xml @@ -655,6 +655,7 @@ + @@ -684,12 +685,12 @@ - AND ob.order_state_id IN (2011,2012,2013, 2014, 2020, 2030, 2040) + AND ob.order_state_id IN (2011,2012,2013,2014,2020,2030,2040) AND (oi.order_time + #{expireSeconds}*1000) =]]> UNIX_TIMESTAMP() * 1000 - AND ob.order_state_id IN (2011,2012,2013, 2014, 2020, 2030, 2040) + AND ob.order_state_id IN (2011,2012,2013,2014,2020,2030,2040) AND (oi.order_time + #{expireSeconds}*1000) UNIX_TIMESTAMP() * 1000 diff --git a/mall-shop/src/main/resources/mapper/order/ShopOrderInfoMapper.xml b/mall-shop/src/main/resources/mapper/order/ShopOrderInfoMapper.xml index 11d0ac88..2ebafc3f 100644 --- a/mall-shop/src/main/resources/mapper/order/ShopOrderInfoMapper.xml +++ b/mall-shop/src/main/resources/mapper/order/ShopOrderInfoMapper.xml @@ -11,7 +11,8 @@ order_is_received, chain_id, delivery_type_id, order_is_offline, cart_type_id, order_express_print, activity_id, activity_type_id, salesperson_id, order_is_sync, store_is_selfsupport, store_type, order_erp_id, distributor_user_id, order_is_cb, order_is_cb_sync, src_order_id, order_is_transfer, order_is_transfer_note, - order_fx_is_settlemented, order_fx_settlement_time, order_pickup_num,order_picked_notice_count,order_picked_time + order_fx_is_settlemented, order_fx_settlement_time, order_pickup_num,order_picked_notice_count,order_picked_time, + booking_state, booking_begin_time, booking_end_time,created_at, updated_at