From cc4ec95819a1fed9d2283fade3bc5f215077ec8e Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Thu, 2 Apr 2026 15:32:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=94=AE=E8=B4=A7=E6=9C=BA?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E5=93=8D=E5=BA=94=E8=B6=85=E6=97=B6=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=E5=BA=93=E5=AD=98=E6=89=A3=E5=87=8F=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=9B=9E=E6=BB=9A=E7=9A=84=E6=83=85=E5=86=B5=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E8=AE=BE=E5=A4=87=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=8E=9F=E8=B7=AF=E9=80=80=E6=AC=BE=E7=BB=99?= =?UTF-8?q?=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/order/TradeOrderCancelTypeEnum.java | 6 ++- .../order/TradeOrderUpdateService.java | 8 ++++ .../order/TradeOrderUpdateServiceImpl.java | 48 +++++++++++++++++++ .../handler/TradeDeviceOrderHandler.java | 27 +++++++---- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java index b614f78..31d17fb 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java @@ -18,7 +18,11 @@ public enum TradeOrderCancelTypeEnum implements ArrayValuable { PAY_TIMEOUT(10, "超时未支付"), AFTER_SALE_CLOSE(20, "退款关闭"), MEMBER_CANCEL(30, "买家取消"), - COMBINATION_CLOSE(40, "拼团关闭"); + COMBINATION_CLOSE(40, "拼团关闭"), + DEVICE_FAIL(50, "设备出货失败"); + /** + * 设备出货失败(例如:售货机接口返回缺货/无响应等) + */ public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderCancelTypeEnum::getType).toArray(Integer[]::new); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index a3badfb..fa70edd 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -209,6 +209,14 @@ public interface TradeOrderUpdateService { */ void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); + /** + * 设备出货失败:取消订单并发起原路退款 + * + * @param userId 用户编号 + * @param orderId 订单编号 + */ + void cancelPaidOrderByDeviceFail(Long userId, Long orderId); + /** * 更新下单赠送的优惠券编号到订单 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 4c3a595..4022377 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -75,6 +75,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.Resource; import javax.validation.constraints.NotNull; @@ -1015,6 +1017,52 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())); // 价格信息 } + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelPaidOrderByDeviceFail(Long userId, Long orderId) { + // 1. 校验订单存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + + // 2. 校验订单是否支付 + if (!order.getPayStatus()) { + throw exception(ORDER_CANCEL_PAID_FAIL, "未支付"); + } + + // 3. 校验订单是否未退款(避免重复触发退款) + if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { + return; + } + + // 4. 取消订单(会触发 TradeOrderHandler.afterCancelOrder,从而回补库存) + cancelOrder0(order, TradeOrderCancelTypeEnum.DEVICE_FAIL); + + // 5. 事务提交后发起退款,避免退款创建失败导致回滚库存 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + // 0元订单通常没有实际支付资金,跳过退款创建避免无意义的退款单 + if (order.getPayPrice() == null || order.getPayPrice() <= 0) { + return; + } + try { + payRefundApi.createRefund(new PayRefundCreateReqDTO() + .setAppKey(tradeOrderProperties.getPayAppKey()) + .setUserIp(NetUtil.getLocalhostStr()) + .setMerchantOrderId(String.valueOf(order.getId())) + .setMerchantRefundId(String.valueOf(order.getId())) + .setReason(TradeOrderCancelTypeEnum.DEVICE_FAIL.getName()) + .setPrice(order.getPayPrice())); + } catch (Exception e) { + log.error("[cancelPaidOrderByDeviceFail][order({}) 发起退款失败]: {}", order.getId(), + e.getMessage(), e); + } + } + }); + } + @Override public void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds) { // 1. 检验订单存在 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java index d2592ec..79ee2e3 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java @@ -224,9 +224,15 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { // 5. 调用设备出货接口,并记录出货日志 log.info("[售货机出货]订单:{} 校验通过,准备调用出货API deviceId={} message={}", order.getNo(), request.getDeviceId(), request.getMessage()); - sendDispenseRequest(order, request, deviceLogSaveReqVO); + boolean dispenseSuccess = sendDispenseRequest(order, request, deviceLogSaveReqVO); - // 6. 无论出货成功/失败,尝试将订单状态更新为已完成(保持原有语义) + // 5.1 设备出货失败:取消订单并发起原路退款 + if (!dispenseSuccess) { + orderUpdateService.cancelPaidOrderByDeviceFail(order.getUserId(), order.getId()); + return; + } + + // 6. 出货成功:将订单状态更新为已完成 completeOrder(order); } @@ -387,9 +393,9 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { /** * 调用自提柜出货接口,并记录设备出货日志 */ - private void sendDispenseRequest(TradeOrderDO order, - DispenseRequestBo request, - DeviceLogSaveReqVO deviceLogSaveReqVO) { + private boolean sendDispenseRequest(TradeOrderDO order, + DispenseRequestBo request, + DeviceLogSaveReqVO deviceLogSaveReqVO) { String requestJson = JsonUtils.toJsonString(request); log.info("[售货机出货]订单:{} 即将请求出货API url={} requestBody={}", order.getNo(), api_url, requestJson); @@ -419,7 +425,7 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { deviceLogSaveReqVO.setDeviceStatus(99); deviceLogSaveReqVO.setContent("[出货失败]原因:响应为空"); deviceLogService.createDeviceLog(deviceLogSaveReqVO); - return; + return false; } Integer respCode = body.getCode() != null ? body.getCode() : -1; @@ -434,6 +440,7 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { if (respCode != 0) { log.warn("[售货机出货]订单:{} 出货业务失败: {}", order.getNo(), content); } + return respCode != null && respCode == 0; } catch (HttpClientErrorException e) { String responseBody = e.getResponseBodyAsString(); log.error("[售货机出货]订单:{} API返回4xx statusCode={} responseBody={} | 异常: {}", @@ -442,6 +449,7 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { deviceLogSaveReqVO.setDeviceStatus(99); deviceLogSaveReqVO.setContent("[出货异常]API返回" + e.getStatusCode().value() + ": " + StrUtil.sub(e.getMessage(), 0, 200)); deviceLogService.createDeviceLog(deviceLogSaveReqVO); + return false; } catch (HttpServerErrorException e) { String responseBody = e.getResponseBodyAsString(); log.error("[售货机出货]订单:{} API返回5xx statusCode={} responseBody={} | 异常: {}", @@ -450,18 +458,21 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { deviceLogSaveReqVO.setDeviceStatus(99); deviceLogSaveReqVO.setContent("[出货异常]API返回" + e.getStatusCode().value() + ": " + StrUtil.sub(e.getMessage(), 0, 200)); deviceLogService.createDeviceLog(deviceLogSaveReqVO); + return false; } catch (ResourceAccessException e) { log.error("[售货机出货]订单:{} API连接异常(超时/网络不可达) url={} | 异常: {}", order.getNo(), api_url, e.getMessage(), e); deviceLogSaveReqVO.setDeviceId(request.getDeviceId()); deviceLogSaveReqVO.setDeviceStatus(99); deviceLogSaveReqVO.setContent("[出货异常]连接失败: " + e.getMessage()); deviceLogService.createDeviceLog(deviceLogSaveReqVO); + return false; } catch (Exception e) { log.error("[售货机出货]订单:{} API请求异常 异常类型={} | 异常: {}", order.getNo(), e.getClass().getName(), e.getMessage(), e); deviceLogSaveReqVO.setDeviceId(request.getDeviceId()); deviceLogSaveReqVO.setDeviceStatus(99); deviceLogSaveReqVO.setContent("[出货异常]原因:" + e.getMessage()); deviceLogService.createDeviceLog(deviceLogSaveReqVO); + return false; } } @@ -549,8 +560,8 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { * 将订单状态更新为“已完成” * * 说明: - * - 为了保持与原逻辑一致,这里无论设备出货成功或失败,都会尝试将订单状态从当前状态更新为 COMPLETED - * - 如果状态不符合预期(例如已经不是“待发货/待收货”),则抛出业务异常 + * - 出货成功后:将订单状态从当前状态更新为 COMPLETED + * - 如果状态不符合预期(例如已经被取消),则抛出业务异常 */ private void completeOrder(TradeOrderDO order) { TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(order.getId());