修复售货机调用响应超时导致的库存扣减没有回滚的情况,增加了设备调用失败原路退款给用户
This commit is contained in:
1 parent
ffef2845e9
commit
cc4ec95819
4 files changed
+80
-9
No files matched your search
+5
-1
@@ -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);
|
||||
|
||||
|
||||
+8
@@ -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);
|
||||
|
||||
/**
|
||||
* 更新下单赠送的优惠券编号到订单
|
||||
*
|
||||
|
||||
+48
@@ -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. 检验订单存在
|
||||
|
||||
+19
-8
@@ -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());
|
||||
|
||||
Reference in new issue
Block a user