取消售货机接口返回失败原路退款功能,售货机存在接口返回失败扔出货的情况

This commit is contained in:
zjf committed 2026-04-16 08:38:24 +08:00
1 parent cc4ec95819
commit fbc2cf433a
4 files changed
+13 -73

No files matched your search

@@ -18,11 +18,7 @@ public enum TradeOrderCancelTypeEnum implements ArrayValuable<Integer> {
PAY_TIMEOUT(10, "超时未支付"),
AFTER_SALE_CLOSE(20, "退款关闭"),
MEMBER_CANCEL(30, "买家取消"),
COMBINATION_CLOSE(40, "拼团关闭"),
DEVICE_FAIL(50, "设备出货失败");
/**
* 设备出货失败(例如:售货机接口返回缺货/无响应等)
*/
COMBINATION_CLOSE(40, "拼团关闭");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderCancelTypeEnum::getType).toArray(Integer[]::new);
@@ -209,14 +209,6 @@ public interface TradeOrderUpdateService {
*/
void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
/**
* 设备出货失败:取消订单并发起原路退款
*
* @param userId 用户编号
* @param orderId 订单编号
*/
void cancelPaidOrderByDeviceFail(Long userId, Long orderId);
/**
* 更新下单赠送的优惠券编号到订单
*
@@ -75,8 +75,6 @@ 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;
@@ -1017,52 +1015,6 @@ 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. 检验订单存在
@@ -13,18 +13,17 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.trade.controller.admin.devicelog.vo.DeviceLogSaveReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.devicelog.DeviceLogService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import cn.iocoder.yudao.module.trade.service.order.bo.DispenseRequestBo;
import cn.iocoder.yudao.module.trade.service.order.bo.DispenseResponseBo;
import cn.iocoder.yudao.module.trade.service.order.bo.DispenseResultStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
@@ -54,7 +53,7 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_RECEI
* 3. 校验订单是否只涉及一种通道类型(同一个 cabinetType)
* 4. 按设备协议格式组装出货指令 message,并调用 xr.cabinet.api_url 出货
* 5. 将出货结果写入“贩卖机出货日志”
* 6. 最后尝试将订单状态从当前状态更新为 COMPLETED(已完成)
* 6. 最后尝试将订单状态从当前状态更新为 COMPLETED(已完成);设备接口失败时仍会完成订单(支付不退),仅按 allocation 回补库存
*
* @author 芋道源码
*/
@@ -62,9 +61,6 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_RECEI
@Slf4j
public class TradeDeviceOrderHandler implements TradeOrderHandler {
@Resource
@Lazy // 延迟加载,避免循环依赖
private TradeOrderUpdateService orderUpdateService;
@Resource
private TradeOrderQueryService orderQueryService;
@@ -226,13 +222,17 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler {
order.getNo(), request.getDeviceId(), request.getMessage());
boolean dispenseSuccess = sendDispenseRequest(order, request, deviceLogSaveReqVO);
// 5.1 设备出货失败:取消订单并发起原路退款
// 5.1 设备出货失败:按下单时扣减的货道回补库存(不取消订单、不退款)
if (!dispenseSuccess) {
orderUpdateService.cancelPaidOrderByDeviceFail(order.getUserId(), order.getId());
return;
try {
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
log.warn("[售货机出货]订单:{} 设备出货失败,已回补库存(按 stockDeductionAllocation)", order.getNo());
} catch (Exception e) {
log.error("[售货机出货]订单:{} 设备出货失败后回补库存异常: {}", order.getNo(), e.getMessage(), e);
}
}
// 6. 出货成功:将订单状态更新为已完成
// 6. 无论出货成功/失败,尝试将订单状态更新为已完成(保持原有语义)
completeOrder(order);
}
@@ -560,8 +560,8 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler {
* 将订单状态更新为“已完成”
*
* 说明:
* - 出货成功后:将订单状态从当前状态更新为 COMPLETED
* - 如果状态不符合预期(例如已经被取消),则抛出业务异常
* - 无论设备出货成功/失败,都会尝试将订单状态从当前状态更新为 COMPLETED
* - 如果状态不符合预期(例如已经不是“待发货/待收货”),则抛出业务异常
*/
private void completeOrder(TradeOrderDO order) {
TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(order.getId());