修复售货机调用响应超时导致的库存扣减没有回滚的情况,增加了设备调用失败原路退款给用户

This commit is contained in:
zjf committed 2026-04-02 15:32:29 +08:00
1 parent ffef2845e9
commit cc4ec95819
4 files changed
+80 -9

No files matched your search

@@ -18,7 +18,11 @@ public enum TradeOrderCancelTypeEnum implements ArrayValuable<Integer> {
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);
@@ -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);
/**
* 更新下单赠送的优惠券编号到订单
*
@@ -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<Long> giveCouponIds) {
// 1. 检验订单存在
@@ -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());